From 0287352f8b0baa76a2220ed9108058f7d759e996 Mon Sep 17 00:00:00 2001 From: Michael Plunkett <5885605+michplunkett@users.noreply.github.com> Date: Tue, 1 Aug 2023 16:18:03 -0500 Subject: [PATCH] Add 'Federal Agency' to list of accepted states (#998) ## Fixes issue https://github.com/lucyparsons/OpenOversight/issues/797 ## Description of Changes Added 'Federal Agency' to list of accepted states, corrected formatting of numbers on browse screen, moved choice lists to `utils` folder, and modified formatter definitions. ## Screenshots (if appropriate) Browse: Update: View officer: Header for listing officers: ## Tests and linting - [x] This branch is up-to-date with the `develop` branch. - [x] `pytest` passes on my local development environment. - [x] `pre-commit` passes on my local development environment. --- OpenOversight/app/filters.py | 25 +++++---- OpenOversight/app/main/forms.py | 9 ++-- OpenOversight/app/main/views.py | 12 +---- OpenOversight/app/models/database.py | 3 +- OpenOversight/app/models/database_imports.py | 23 ++++---- OpenOversight/app/templates/browse.html | 6 +-- OpenOversight/app/templates/list_officer.html | 6 ++- .../partials/officer_general_information.html | 6 ++- OpenOversight/app/{main => utils}/choices.py | 50 ++++++++--------- OpenOversight/app/utils/forms.py | 2 +- OpenOversight/tests/conftest.py | 20 ++++++- .../routes/test_officer_and_department.py | 53 +++++++++---------- OpenOversight/tests/test_commands.py | 20 ++++--- OpenOversight/tests/test_utils.py | 17 ------ 14 files changed, 131 insertions(+), 121 deletions(-) rename OpenOversight/app/{main => utils}/choices.py (76%) diff --git a/OpenOversight/app/filters.py b/OpenOversight/app/filters.py index e8f1098a8..fdf6140b6 100644 --- a/OpenOversight/app/filters.py +++ b/OpenOversight/app/filters.py @@ -1,5 +1,5 @@ """Contains all templates filters.""" -import datetime +from datetime import datetime import bleach import markdown as _markdown @@ -14,7 +14,7 @@ def instantiate_filters(app: Flask): """Instantiate all template filters""" - def get_timezone(): + def get_timezone() -> str: """Return the applicable timezone for the filter.""" return ( session[KEY_TIMEZONE] @@ -23,15 +23,13 @@ def get_timezone(): ) @app.template_filter("capfirst") - def capfirst_filter(s): + def capfirst_filter(s: str) -> str: return s[0].capitalize() + s[1:] # only change 1st letter @app.template_filter("get_age") - def get_age_from_birth_year(birth_year): + def get_age_from_birth_year(birth_year) -> int: if birth_year: - return int( - datetime.datetime.now(pytz.timezone(get_timezone())).year - birth_year - ) + return int(datetime.now(pytz.timezone(get_timezone())).year - birth_year) @app.template_filter("field_in_query") def field_in_query(form_data, field): @@ -42,24 +40,29 @@ class which will render the field accordion open. return " in " if form_data.get(field) else "" @app.template_filter("markdown") - def markdown(text): + def markdown(text: str) -> Markup: text = text.replace("\n", " \n") # make markdown not ignore new lines. html = bleach.clean(_markdown.markdown(text), markdown_tags, markdown_attrs) return Markup(html) @app.template_filter("local_date") - def local_date(value): + def local_date(value: datetime) -> str: """Convert UTC datetime.datetime into a localized date string.""" return value.astimezone(pytz.timezone(get_timezone())).strftime("%b %d, %Y") @app.template_filter("local_date_time") - def local_date_time(value): + def local_date_time(value: datetime) -> str: """Convert UTC datetime.datetime into a localized date time string.""" return value.astimezone(pytz.timezone(get_timezone())).strftime( "%I:%M %p on %b %d, %Y" ) @app.template_filter("local_time") - def local_time(value): + def local_time(value: datetime) -> str: """Convert UTC datetime.datetime into a localized time string.""" return value.astimezone(pytz.timezone(get_timezone())).strftime("%I:%M %p") + + @app.template_filter("thousands_seperator") + def thousands_seperator(value: int) -> str: + """Convert int to string with the appropriately applied commas.""" + return f"{value:,}" diff --git a/OpenOversight/app/main/forms.py b/OpenOversight/app/main/forms.py index a0a266165..10a9648ee 100644 --- a/OpenOversight/app/main/forms.py +++ b/OpenOversight/app/main/forms.py @@ -30,15 +30,16 @@ from wtforms_sqlalchemy.fields import QuerySelectField from OpenOversight.app.formfields import TimeField -from OpenOversight.app.main.choices import ( +from OpenOversight.app.models.database import Officer +from OpenOversight.app.utils.choices import ( AGE_CHOICES, + DEPARTMENT_STATE_CHOICES, GENDER_CHOICES, LINK_CHOICES, RACE_CHOICES, STATE_CHOICES, SUFFIX_CHOICES, ) -from OpenOversight.app.models.database import Officer from OpenOversight.app.utils.db import dept_choices, unit_choices from OpenOversight.app.widgets import BootstrapListWidget, FormFieldWidget @@ -199,9 +200,9 @@ class DepartmentForm(Form): ) state = SelectField( "The law enforcement agency's home state", - choices=STATE_CHOICES, + choices=[("", "Please Select a State")] + DEPARTMENT_STATE_CHOICES, default="", - validators=[AnyOf(allowed_values(STATE_CHOICES))], + validators=[AnyOf(allowed_values(DEPARTMENT_STATE_CHOICES))], ) jobs = FieldList( StringField("Job", default="", validators=[Regexp(r"\w*")]), label="Ranks" diff --git a/OpenOversight/app/main/views.py b/OpenOversight/app/main/views.py index 8f2b4c4f0..6ab07df34 100644 --- a/OpenOversight/app/main/views.py +++ b/OpenOversight/app/main/views.py @@ -25,7 +25,6 @@ from OpenOversight.app import limiter, sitemap from OpenOversight.app.auth.forms import LoginForm from OpenOversight.app.main import main -from OpenOversight.app.main.choices import AGE_CHOICES, GENDER_CHOICES, RACE_CHOICES from OpenOversight.app.main.downloads import ( assignment_record_maker, descriptions_record_maker, @@ -73,6 +72,7 @@ db, ) from OpenOversight.app.utils.auth import ac_or_admin_required, admin_required +from OpenOversight.app.utils.choices import AGE_CHOICES, GENDER_CHOICES, RACE_CHOICES from OpenOversight.app.utils.cloud import crop_image, upload_image_to_s3_and_store_in_db from OpenOversight.app.utils.constants import ( ENCODING_UTF_8, @@ -520,10 +520,6 @@ def classify_submission(image_id, contains_cops): def add_department(): form = DepartmentForm() if form.validate_on_submit(): - if not form.state.data: - flash(f"You must select a valid state for {form.name.data}.") - return redirect(url_for("main.add_department")) - department_does_not_exist = ( Department.query.filter_by( name=form.name.data, state=form.state.data @@ -583,12 +579,6 @@ def edit_department(department_id): form = EditDepartmentForm(obj=department) original_ranks = department.jobs if form.validate_on_submit(): - if not form.state.data: - flash(f"You must select a valid state for {form.name.data}.") - return redirect( - url_for("main.edit_department", department_id=department_id) - ) - if form.name.data != previous_name: does_already_department_exist = ( Department.query.filter_by( diff --git a/OpenOversight/app/models/database.py b/OpenOversight/app/models/database.py index 9853525ec..c775f8177 100644 --- a/OpenOversight/app/models/database.py +++ b/OpenOversight/app/models/database.py @@ -13,6 +13,7 @@ from sqlalchemy.sql import func as sql_func from werkzeug.security import check_password_hash, generate_password_hash +from OpenOversight.app.utils.choices import GENDER_CHOICES, RACE_CHOICES from OpenOversight.app.utils.constants import ( ENCODING_UTF_8, HOUR, @@ -233,7 +234,6 @@ def full_name(self): def race_label(self): if self.race is None: return "Data Missing" - from OpenOversight.app.main.choices import RACE_CHOICES for race, label in RACE_CHOICES: if self.race == race: @@ -242,7 +242,6 @@ def race_label(self): def gender_label(self): if self.gender is None: return "Data Missing" - from OpenOversight.app.main.choices import GENDER_CHOICES for gender, label in GENDER_CHOICES: if self.gender == gender: diff --git a/OpenOversight/app/models/database_imports.py b/OpenOversight/app/models/database_imports.py index 99db37e76..4d2b6987f 100644 --- a/OpenOversight/app/models/database_imports.py +++ b/OpenOversight/app/models/database_imports.py @@ -3,7 +3,6 @@ import dateutil.parser from OpenOversight.app import login_manager -from OpenOversight.app.main import choices from OpenOversight.app.models.database import ( Assignment, Incident, @@ -15,6 +14,12 @@ User, db, ) +from OpenOversight.app.utils.choices import ( + GENDER_CHOICES, + LINK_CHOICES, + RACE_CHOICES, + SUFFIX_CHOICES, +) from OpenOversight.app.utils.general import get_or_create, str_is_true from OpenOversight.app.validators import state_validator, url_validator @@ -76,9 +81,9 @@ def create_officer_from_dict(data: Dict[str, Any], force_id: bool = False) -> Of last_name=parse_str(data.get("last_name", "")), first_name=parse_str(data.get("first_name", "")), middle_initial=parse_str(data.get("middle_initial", "")), - suffix=validate_choice(data.get("suffix", ""), choices.SUFFIX_CHOICES), - race=validate_choice(data.get("race"), choices.RACE_CHOICES), - gender=validate_choice(data.get("gender"), choices.GENDER_CHOICES), + suffix=validate_choice(data.get("suffix", ""), SUFFIX_CHOICES), + race=validate_choice(data.get("race"), RACE_CHOICES), + gender=validate_choice(data.get("gender"), GENDER_CHOICES), employment_date=parse_date(data.get("employment_date")), birth_year=parse_int(data.get("birth_year")), unique_internal_identifier=parse_str( @@ -103,11 +108,11 @@ def update_officer_from_dict(data: Dict[str, Any], officer: Officer) -> Officer: if "middle_initial" in data.keys(): officer.middle_initial = parse_str(data.get("middle_initial", "")) if "suffix" in data.keys(): - officer.suffix = validate_choice(data.get("suffix", ""), choices.SUFFIX_CHOICES) + officer.suffix = validate_choice(data.get("suffix", ""), SUFFIX_CHOICES) if "race" in data.keys(): - officer.race = validate_choice(data.get("race"), choices.RACE_CHOICES) + officer.race = validate_choice(data.get("race"), RACE_CHOICES) if "gender" in data.keys(): - officer.gender = validate_choice(data.get("gender"), choices.GENDER_CHOICES) + officer.gender = validate_choice(data.get("gender"), GENDER_CHOICES) if "employment_date" in data.keys(): officer.employment_date = parse_date(data.get("employment_date")) if "birth_year" in data.keys(): @@ -193,7 +198,7 @@ def create_link_from_dict(data: Dict[str, Any], force_id: bool = False) -> Link: link = Link( title=data.get("title", ""), url=url_validator(data["url"]), - link_type=validate_choice(data.get("link_type"), choices.LINK_CHOICES), + link_type=validate_choice(data.get("link_type"), LINK_CHOICES), description=parse_str(data.get("description"), None), author=parse_str(data.get("author"), None), creator_id=parse_int(data.get("creator_id")), @@ -216,7 +221,7 @@ def update_link_from_dict(data: Dict[str, Any], link: Link) -> Link: if "url" in data: link.url = url_validator(data["url"]) if "link_type" in data: - link.link_type = validate_choice(data.get("link_type"), choices.LINK_CHOICES) + link.link_type = validate_choice(data.get("link_type"), LINK_CHOICES) if "description" in data: link.description = parse_str(data.get("description"), None) if "author" in data: diff --git a/OpenOversight/app/templates/browse.html b/OpenOversight/app/templates/browse.html index 264ece8de..9097306cd 100644 --- a/OpenOversight/app/templates/browse.html +++ b/OpenOversight/app/templates/browse.html @@ -27,11 +27,11 @@