From 5713521652d060caf663c60b6dceaa44af308b6f Mon Sep 17 00:00:00 2001 From: michplunkett <5885605+michplunkett@users.noreply.github.com> Date: Thu, 27 Jul 2023 15:25:31 -0500 Subject: [PATCH 01/55] Update database.py --- OpenOversight/app/models/database.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/OpenOversight/app/models/database.py b/OpenOversight/app/models/database.py index cba57f362..759224072 100644 --- a/OpenOversight/app/models/database.py +++ b/OpenOversight/app/models/database.py @@ -468,8 +468,7 @@ class Incident(BaseModel): __tablename__ = "incidents" id = db.Column(db.Integer, primary_key=True) - date = db.Column(db.Date, unique=False, index=True) - time = db.Column(db.Time, unique=False, index=True) + occurred_at = db.Column(db.DateTime(timezone=True), unique=False, nullable=False) report_number = db.Column(db.String(50), index=True) description = db.Column(db.Text(), nullable=True) address_id = db.Column(db.Integer, db.ForeignKey("locations.id")) From 71bdb39a4a3fed3ed4242948f3c897927b7c5721 Mon Sep 17 00:00:00 2001 From: michplunkett <5885605+michplunkett@users.noreply.github.com> Date: Thu, 27 Jul 2023 15:38:47 -0500 Subject: [PATCH 02/55] Create 2023-07-27-1848_9fa948bcea25_change_incident_date_and_time_to_.py --- ...cea25_change_incident_date_and_time_to_.py | 51 +++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 OpenOversight/migrations/versions/2023-07-27-1848_9fa948bcea25_change_incident_date_and_time_to_.py diff --git a/OpenOversight/migrations/versions/2023-07-27-1848_9fa948bcea25_change_incident_date_and_time_to_.py b/OpenOversight/migrations/versions/2023-07-27-1848_9fa948bcea25_change_incident_date_and_time_to_.py new file mode 100644 index 000000000..157469a79 --- /dev/null +++ b/OpenOversight/migrations/versions/2023-07-27-1848_9fa948bcea25_change_incident_date_and_time_to_.py @@ -0,0 +1,51 @@ +"""change incident date and time to timestamptz + +Revision ID: 9fa948bcea25 +Revises: 18f43ac4622f +Create Date: 2023-07-27 18:48:58.819477 + +""" +import os + +from alembic import op +import sqlalchemy as sa +from sqlalchemy.dialects import postgresql + +# revision identifiers, used by Alembic. +revision = '9fa948bcea25' +down_revision = '18f43ac4622f' +branch_labels = None +depends_on = None + +TIMEZONE = os.getenv("TIMEZONE", "America/Chicago") + + +def upgrade(): + op.add_column('incidents', sa.Column('occurred_at', sa.DateTime(timezone=True), nullable=True)) + op.execute( + f""" + UPDATE incidents + SET occurred_at = (date::date || ' ' || COALESCE(time, + '00:00:00')::timetz)::timestamp AT TIME ZONE '{TIMEZONE}' + WHERE occurred_at IS NULL + """ + ) + op.alter_column('incidents', 'occurred_at', nullable=False) + op.drop_index('ix_incidents_date') + op.drop_index('ix_incidents_time') + op.drop_column('incidents', 'time') + op.drop_column('incidents', 'date') + + +def downgrade(): + op.add_column('incidents', sa.Column('date', sa.DATE(), nullable=True)) + op.add_column('incidents', sa.Column('time', postgresql.TIME(), nullable=True)) + op.execute( + f""" + UPDATE incidents + SET (date, time) = (occurred_at::date, (occurred_at::timestamptz AT TIME ZONE '{TIMEZONE}')::time) + """) + + op.create_index(op.f('ix_incidents_time'), 'incidents', ['time'], unique=False) + op.create_index(op.f('ix_incidents_date'), 'incidents', ['date'], unique=False) + op.drop_column('incidents', 'occurred_at') From d8b8b6f3e804f7092a31a44e67b3040ae61f3714 Mon Sep 17 00:00:00 2001 From: michplunkett <5885605+michplunkett@users.noreply.github.com> Date: Thu, 27 Jul 2023 15:39:09 -0500 Subject: [PATCH 03/55] Update 2023-07-27-1848_9fa948bcea25_change_incident_date_and_time_to_.py --- ...cea25_change_incident_date_and_time_to_.py | 36 ++++++++++--------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/OpenOversight/migrations/versions/2023-07-27-1848_9fa948bcea25_change_incident_date_and_time_to_.py b/OpenOversight/migrations/versions/2023-07-27-1848_9fa948bcea25_change_incident_date_and_time_to_.py index 157469a79..9804b89ee 100644 --- a/OpenOversight/migrations/versions/2023-07-27-1848_9fa948bcea25_change_incident_date_and_time_to_.py +++ b/OpenOversight/migrations/versions/2023-07-27-1848_9fa948bcea25_change_incident_date_and_time_to_.py @@ -7,13 +7,14 @@ """ import os -from alembic import op import sqlalchemy as sa +from alembic import op from sqlalchemy.dialects import postgresql + # revision identifiers, used by Alembic. -revision = '9fa948bcea25' -down_revision = '18f43ac4622f' +revision = "9fa948bcea25" +down_revision = "18f43ac4622f" branch_labels = None depends_on = None @@ -21,31 +22,34 @@ def upgrade(): - op.add_column('incidents', sa.Column('occurred_at', sa.DateTime(timezone=True), nullable=True)) + op.add_column( + "incidents", sa.Column("occurred_at", sa.DateTime(timezone=True), nullable=True) + ) op.execute( f""" UPDATE incidents - SET occurred_at = (date::date || ' ' || COALESCE(time, + SET occurred_at = (date::date || ' ' || COALESCE(time, '00:00:00')::timetz)::timestamp AT TIME ZONE '{TIMEZONE}' WHERE occurred_at IS NULL """ ) - op.alter_column('incidents', 'occurred_at', nullable=False) - op.drop_index('ix_incidents_date') - op.drop_index('ix_incidents_time') - op.drop_column('incidents', 'time') - op.drop_column('incidents', 'date') + op.alter_column("incidents", "occurred_at", nullable=False) + op.drop_index("ix_incidents_date") + op.drop_index("ix_incidents_time") + op.drop_column("incidents", "time") + op.drop_column("incidents", "date") def downgrade(): - op.add_column('incidents', sa.Column('date', sa.DATE(), nullable=True)) - op.add_column('incidents', sa.Column('time', postgresql.TIME(), nullable=True)) + op.add_column("incidents", sa.Column("date", sa.DATE(), nullable=True)) + op.add_column("incidents", sa.Column("time", postgresql.TIME(), nullable=True)) op.execute( f""" UPDATE incidents SET (date, time) = (occurred_at::date, (occurred_at::timestamptz AT TIME ZONE '{TIMEZONE}')::time) - """) + """ + ) - op.create_index(op.f('ix_incidents_time'), 'incidents', ['time'], unique=False) - op.create_index(op.f('ix_incidents_date'), 'incidents', ['date'], unique=False) - op.drop_column('incidents', 'occurred_at') + op.create_index(op.f("ix_incidents_time"), "incidents", ["time"], unique=False) + op.create_index(op.f("ix_incidents_date"), "incidents", ["date"], unique=False) + op.drop_column("incidents", "occurred_at") From ac784952c93f925139358fe5f0aa098249696791 Mon Sep 17 00:00:00 2001 From: michplunkett <5885605+michplunkett@users.noreply.github.com> Date: Thu, 27 Jul 2023 15:52:06 -0500 Subject: [PATCH 04/55] Update views.py --- OpenOversight/app/main/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OpenOversight/app/main/views.py b/OpenOversight/app/main/views.py index d97ae434e..7aa72da0b 100644 --- a/OpenOversight/app/main/views.py +++ b/OpenOversight/app/main/views.py @@ -1489,7 +1489,7 @@ def server_shutdown(): # pragma: no cover class IncidentApi(ModelView): model = Incident model_name = "incident" - order_by = "date" + order_by = "occurred_at" descending = True form = IncidentForm create_function = create_incident From f65b4ecfc28c77f36a624210c9e4605c006e31fc Mon Sep 17 00:00:00 2001 From: michplunkett <5885605+michplunkett@users.noreply.github.com> Date: Thu, 27 Jul 2023 15:52:11 -0500 Subject: [PATCH 05/55] Update incident_fields.html --- .../app/templates/partials/incident_fields.html | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/OpenOversight/app/templates/partials/incident_fields.html b/OpenOversight/app/templates/partials/incident_fields.html index 9444f9000..152b099b8 100644 --- a/OpenOversight/app/templates/partials/incident_fields.html +++ b/OpenOversight/app/templates/partials/incident_fields.html @@ -2,16 +2,14 @@ Date - {{ incident.date.strftime("%b %d, %Y") }} + {{ incident.occurred_at | local_date }} + + + + Time + + {{ incident.occurred_at | local_time }} -{% if incident.time %} - - - Time - - {{ incident.time.strftime("%l:%M %p") }} - -{% endif %} {% if incident.report_number %} From 50e43f533c8bc8842590e45c27dc53a89e282e9b Mon Sep 17 00:00:00 2001 From: michplunkett <5885605+michplunkett@users.noreply.github.com> Date: Thu, 27 Jul 2023 15:59:28 -0500 Subject: [PATCH 06/55] Update officer_incidents.html --- OpenOversight/app/templates/partials/officer_incidents.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OpenOversight/app/templates/partials/officer_incidents.html b/OpenOversight/app/templates/partials/officer_incidents.html index f6a6f185f..4a79b5c6e 100644 --- a/OpenOversight/app/templates/partials/officer_incidents.html +++ b/OpenOversight/app/templates/partials/officer_incidents.html @@ -2,7 +2,7 @@

Incidents

{% if officer.incidents %} - {% for incident in officer.incidents | sort(attribute='date') | reverse %} + {% for incident in officer.incidents | sort(attribute='occurred_at') | reverse %} {% if not loop.first %} From 30fd480ec62c3ceea7c1c5240c628294b8cb69f6 Mon Sep 17 00:00:00 2001 From: michplunkett <5885605+michplunkett@users.noreply.github.com> Date: Thu, 27 Jul 2023 16:04:12 -0500 Subject: [PATCH 07/55] Update incident_detail.html --- .../app/templates/incident_detail.html | 36 ++++++++++--------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/OpenOversight/app/templates/incident_detail.html b/OpenOversight/app/templates/incident_detail.html index 4c855611b..e4b27608e 100644 --- a/OpenOversight/app/templates/incident_detail.html +++ b/OpenOversight/app/templates/incident_detail.html @@ -41,24 +41,26 @@ incident.department.name }}

{% endif %} -
-

- Incident - {% if incident.report_number %}{{ incident.report_number }}{% endif %} -

-
-
 
- - {% with detail=True %} - {% include "partials/incident_fields.html" %} - {% endwith %} - -
+
+
+

+ Incident + {% if incident.report_number %}{{ incident.report_number }}{% endif %} +

+
+ + + {% with detail=True %} + {% include "partials/incident_fields.html" %} + {% endwith %} + +
+
+
+
+

Incident Description

+ {{ incident.description | markdown }}
-
-
-

Incident Description

- {{ incident.description | markdown }}
{% include "partials/links_and_videos_row.html" %} {% if current_user.is_administrator From d5130692139351a0ceeb85eaa467f8b1a57bfcca Mon Sep 17 00:00:00 2001 From: michplunkett <5885605+michplunkett@users.noreply.github.com> Date: Thu, 27 Jul 2023 16:38:15 -0500 Subject: [PATCH 08/55] Update database_imports.py --- OpenOversight/app/models/database_imports.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/OpenOversight/app/models/database_imports.py b/OpenOversight/app/models/database_imports.py index 99db37e76..ac38ecba7 100644 --- a/OpenOversight/app/models/database_imports.py +++ b/OpenOversight/app/models/database_imports.py @@ -274,8 +274,7 @@ def get_or_create_location_from_dict( def create_incident_from_dict(data: Dict[str, Any], force_id: bool = False) -> Incident: incident = Incident( - date=parse_date(data.get("date")), - time=parse_time(data.get("time")), + occurred_at=data, report_number=parse_str(data.get("report_number"), None), description=parse_str(data.get("description"), None), address_id=data.get("address_id"), From 928d813c158d95639bbbfcfa5e9fabe0545166f4 Mon Sep 17 00:00:00 2001 From: michplunkett <5885605+michplunkett@users.noreply.github.com> Date: Thu, 27 Jul 2023 16:38:19 -0500 Subject: [PATCH 09/55] Update conftest.py --- OpenOversight/tests/conftest.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/OpenOversight/tests/conftest.py b/OpenOversight/tests/conftest.py index ccd114692..15127a72a 100644 --- a/OpenOversight/tests/conftest.py +++ b/OpenOversight/tests/conftest.py @@ -540,8 +540,7 @@ def add_mockdata(session): test_incidents = [ Incident( - date=datetime.date(2016, 3, 16), - time=datetime.time(4, 20), + occurred_at=datetime.datetime(2016, 3, 16, 4, 20), report_number="42", description="### A thing happened\n **Markup** description", department_id=1, @@ -553,8 +552,7 @@ def add_mockdata(session): last_updated_id=1, ), Incident( - date=datetime.date(2017, 12, 11), - time=datetime.time(2, 40), + occurred_at=datetime.datetime(2017, 12, 11, 2, 40), report_number="38", description="A thing happened", department_id=2, @@ -566,7 +564,7 @@ def add_mockdata(session): last_updated_id=1, ), Incident( - date=datetime.datetime(2019, 1, 15), + occurred_at=datetime.datetime(2019, 1, 15), report_number="39", description=( Path(__file__).parent / "description_overflow.txt" From 242ffe4d116e2c21c751884310aa8976b160941d Mon Sep 17 00:00:00 2001 From: michplunkett <5885605+michplunkett@users.noreply.github.com> Date: Thu, 27 Jul 2023 16:44:09 -0500 Subject: [PATCH 10/55] Update forms.py --- OpenOversight/app/utils/forms.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/OpenOversight/app/utils/forms.py b/OpenOversight/app/utils/forms.py index d48ed5775..ffafa262a 100644 --- a/OpenOversight/app/utils/forms.py +++ b/OpenOversight/app/utils/forms.py @@ -134,8 +134,13 @@ def create_description(self, form): def create_incident(self, form): fields = { - "date": form.date_field.data, - "time": form.time_field.data, + "occurred_at": datetime.datetime( + form.date_field.data.year, + form.date_field.data.month, + form.date_field.data.day, + form.time_field.data.hour, + form.time_field.data.minute, + ), "officers": [], "license_plates": [], "links": [], @@ -171,8 +176,7 @@ def create_incident(self, form): fields["links"].append(li) return Incident( - date=fields["date"], - time=fields["time"], + occurred_at=fields["occurred_at"], description=form.data["description"], department=form.data["department"], address=fields["address"], From 0885c30d907adb0fba9ccaa50d9e43e8a0aae564 Mon Sep 17 00:00:00 2001 From: michplunkett <5885605+michplunkett@users.noreply.github.com> Date: Thu, 27 Jul 2023 16:44:11 -0500 Subject: [PATCH 11/55] Update test_commands.py --- OpenOversight/tests/test_commands.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OpenOversight/tests/test_commands.py b/OpenOversight/tests/test_commands.py index 82ee7a12c..3de08cb4c 100644 --- a/OpenOversight/tests/test_commands.py +++ b/OpenOversight/tests/test_commands.py @@ -868,7 +868,7 @@ def test_advanced_csv_import__success(session, department, test_csv_dir): report_number="Old_Report_Number", department_id=1, description="description", - time=datetime.time(23, 45, 16), + occurred_at=datetime.datetime(2019, 3, 15, 23, 45, 16), ) incident.officers = [officer] session.add(incident) From 1d6d776f68d3c4d58174bf1831dcba788f32d1fd Mon Sep 17 00:00:00 2001 From: michplunkett <5885605+michplunkett@users.noreply.github.com> Date: Thu, 27 Jul 2023 17:00:37 -0500 Subject: [PATCH 12/55] Update test_commands.py --- OpenOversight/tests/test_commands.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OpenOversight/tests/test_commands.py b/OpenOversight/tests/test_commands.py index 3de08cb4c..7a960fa74 100644 --- a/OpenOversight/tests/test_commands.py +++ b/OpenOversight/tests/test_commands.py @@ -991,7 +991,7 @@ def test_advanced_csv_import__success(session, department, test_csv_dir): assert incident3.report_number == "CR-39283" assert incident3.description == "Don't know where it happened" assert incident3.officers == [cop1] - assert incident3.date == datetime.date(2020, 7, 26) + assert incident3.occurred_at == datetime.date(2020, 7, 26) lp = incident3.license_plates[0] assert lp.number == "XYZ11" assert lp.state is None From e06cf85c11c41b6a075bcb51520be7f782b2c24e Mon Sep 17 00:00:00 2001 From: michplunkett <5885605+michplunkett@users.noreply.github.com> Date: Thu, 27 Jul 2023 17:00:41 -0500 Subject: [PATCH 13/55] Update test_incidents.py --- OpenOversight/tests/routes/test_incidents.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/OpenOversight/tests/routes/test_incidents.py b/OpenOversight/tests/routes/test_incidents.py index 9df9f62d3..96680683d 100644 --- a/OpenOversight/tests/routes/test_incidents.py +++ b/OpenOversight/tests/routes/test_incidents.py @@ -94,7 +94,7 @@ def test_admins_can_create_basic_incidents(report_number, mockdata, client, sess assert rv.status_code == HTTPStatus.OK assert "created" in rv.data.decode(ENCODING_UTF_8) - inc = Incident.query.filter_by(date=test_date.date()).first() + inc = Incident.query.filter_by(occurred_at=test_date).first() assert inc is not None @@ -223,8 +223,8 @@ def test_admins_can_edit_incident_links_and_licenses(mockdata, client, session, ooid_forms = [OOIdForm(ooid=officer.id) for officer in inc.officers] form = IncidentForm( - date_field=str(inc.date), - time_field=str(inc.time), + date_field=str(inc.occurred_at.date()), + time_field=str(inc.occurred_at.time()), report_number=inc.report_number, description=inc.description, department="1", @@ -273,7 +273,7 @@ def test_admins_cannot_make_ancient_incidents(mockdata, client, session): form = IncidentForm( date_field=date(1899, 12, 5), - time_field=str(inc.time), + time_field=str(inc.occurred_at.time()), report_number=inc.report_number, description=inc.description, department="1", @@ -408,8 +408,8 @@ def test_admins_can_edit_incident_officers(mockdata, client, session): new_ooid_form = OOIdForm(oo_id=new_officer.id) form = IncidentForm( - date_field=str(inc.date), - time_field=str(inc.time), + date_field=str(inc.occurred_at.date()), + time_field=str(inc.occurred_at.time()), report_number=inc.report_number, description=inc.description, department="1", @@ -464,8 +464,8 @@ def test_admins_cannot_edit_nonexisting_officers(mockdata, client, session): new_ooid_form = OOIdForm(oo_id="99999999999999999") form = IncidentForm( - date_field=str(inc.date), - time_field=str(inc.time), + date_field=str(inc.occurred_at.date()), + time_field=str(inc.occurred_at.time()), report_number=inc.report_number, description=inc.description, department="1", @@ -692,7 +692,7 @@ def test_users_cannot_see_who_created_incidents(mockdata, client, session): assert "Creator" not in rv.data.decode(ENCODING_UTF_8) -def test_form_with_officer_id_prepopulates(mockdata, client, session): +def test_form_with_officer_id_pre_populate(mockdata, client, session): with current_app.test_request_context(): login_admin(client) officer_id = "1234" From 81fa0ab18e34d19bbb18421d96cd078a16e65611 Mon Sep 17 00:00:00 2001 From: michplunkett <5885605+michplunkett@users.noreply.github.com> Date: Thu, 27 Jul 2023 17:00:44 -0500 Subject: [PATCH 14/55] Update views.py --- OpenOversight/app/main/views.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/OpenOversight/app/main/views.py b/OpenOversight/app/main/views.py index 7aa72da0b..25709a334 100644 --- a/OpenOversight/app/main/views.py +++ b/OpenOversight/app/main/views.py @@ -1591,10 +1591,10 @@ def get_edit_form(self, obj): form.license_plates.min_entries = no_license_plates form.links.min_entries = no_links form.officers.min_entries = no_officers - if not form.date_field.data and obj.date: - form.date_field.data = obj.date - if not form.time_field.data and obj.time: - form.time_field.data = obj.time + if not form.date_field.data and obj.occurred_at: + form.date_field.data = obj.occurred_at.date() + if not form.time_field.data and obj.occurred_at: + form.time_field.data = obj.occurred_at.time() return form def populate_obj(self, form, obj): From 285a2548452e72e080a980a69396f3ab373f735a Mon Sep 17 00:00:00 2001 From: michplunkett <5885605+michplunkett@users.noreply.github.com> Date: Thu, 27 Jul 2023 17:20:03 -0500 Subject: [PATCH 15/55] Update database_imports.py --- OpenOversight/app/models/database_imports.py | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/OpenOversight/app/models/database_imports.py b/OpenOversight/app/models/database_imports.py index ac38ecba7..02a74c1dc 100644 --- a/OpenOversight/app/models/database_imports.py +++ b/OpenOversight/app/models/database_imports.py @@ -40,6 +40,12 @@ def parse_date(date_str: Optional[str]) -> Optional["datetime.date"]: return None +def parse_date_time(date_time_str: Optional[str]) -> Optional["datetime.datetime"]: + if date_time_str: + return dateutil.parser.parse(date_time_str) + return None + + def parse_time(time_str: Optional[str]) -> Optional["datetime.time"]: if time_str: return dateutil.parser.parse(time_str).time() @@ -274,7 +280,7 @@ def get_or_create_location_from_dict( def create_incident_from_dict(data: Dict[str, Any], force_id: bool = False) -> Incident: incident = Incident( - occurred_at=data, + occurred_at=parse_date_time(" ".join([data.get("date"), data.get("time")])), report_number=parse_str(data.get("report_number"), None), description=parse_str(data.get("description"), None), address_id=data.get("address_id"), @@ -296,9 +302,12 @@ def create_incident_from_dict(data: Dict[str, Any], force_id: bool = False) -> I def update_incident_from_dict(data: Dict[str, Any], incident: Incident) -> Incident: if "date" in data: - incident.date = parse_date(data.get("date")) - if "time" in data: - incident.time = parse_time(data.get("time")) + if "time" in data: + incident.occurred_at = parse_date_time( + " ".join([data.get("date"), data.get("time")]) + ) + else: + incident.occurred_at = parse_date_time(" ".join([data.get("date"), "00:00"])) if "report_number" in data: incident.report_number = parse_str(data.get("report_number"), None) if "description" in data: From 1a24998633b2a3b934fb9d5013deadfc5d2a658d Mon Sep 17 00:00:00 2001 From: michplunkett <5885605+michplunkett@users.noreply.github.com> Date: Thu, 27 Jul 2023 17:30:46 -0500 Subject: [PATCH 16/55] Update views.py --- OpenOversight/app/main/views.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/OpenOversight/app/main/views.py b/OpenOversight/app/main/views.py index 25709a334..e20df1735 100644 --- a/OpenOversight/app/main/views.py +++ b/OpenOversight/app/main/views.py @@ -1524,12 +1524,12 @@ def get(self, obj_id): if occurred_before := request.args.get("occurred_before"): before_date = datetime.datetime.strptime(occurred_before, "%Y-%m-%d").date() form.occurred_before.data = before_date - incidents = incidents.filter(self.model.date < before_date) + incidents = incidents.filter(self.model.occurred_at < before_date) if occurred_after := request.args.get("occurred_after"): after_date = datetime.datetime.strptime(occurred_after, "%Y-%m-%d").date() form.occurred_after.data = after_date - incidents = incidents.filter(self.model.date > after_date) + incidents = incidents.filter(self.model.occurred_at > after_date) incidents = incidents.order_by( getattr(self.model, self.order_by).desc() @@ -1632,11 +1632,14 @@ def populate_obj(self, form, obj): if license_plates and license_plates[0]["number"]: replace_list(license_plates, obj, "license_plates", LicensePlate, db) - obj.date = form.date_field.data if form.time_field.raw_data and form.time_field.raw_data != [""]: - obj.time = form.time_field.data + obj.occurred_at = datetime.datetime( + form.date_field.data, form.time_field.data + ) else: - obj.time = None + obj.occurred_at = datetime.datetime( + form.date_field.data, datetime.time(0, 0) + ) super(IncidentApi, self).populate_obj(form, obj) From a4ad73cf0a27a47b82300b18b2f7219ee8db1f9b Mon Sep 17 00:00:00 2001 From: michplunkett <5885605+michplunkett@users.noreply.github.com> Date: Thu, 27 Jul 2023 17:36:37 -0500 Subject: [PATCH 17/55] Update views.py --- OpenOversight/app/main/views.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/OpenOversight/app/main/views.py b/OpenOversight/app/main/views.py index e20df1735..57096ea3d 100644 --- a/OpenOversight/app/main/views.py +++ b/OpenOversight/app/main/views.py @@ -1633,12 +1633,12 @@ def populate_obj(self, form, obj): replace_list(license_plates, obj, "license_plates", LicensePlate, db) if form.time_field.raw_data and form.time_field.raw_data != [""]: - obj.occurred_at = datetime.datetime( + obj.occurred_at = datetime.datetime.combine( form.date_field.data, form.time_field.data ) else: - obj.occurred_at = datetime.datetime( - form.date_field.data, datetime.time(0, 0) + obj.occurred_at = datetime.datetime.combine( + form.date_field.data.date(), datetime.time(0, 0) ) super(IncidentApi, self).populate_obj(form, obj) From 5f5710cfb9a0148c831ed1a6535a309f48313aa4 Mon Sep 17 00:00:00 2001 From: michplunkett <5885605+michplunkett@users.noreply.github.com> Date: Thu, 27 Jul 2023 17:41:25 -0500 Subject: [PATCH 18/55] Update views.py --- OpenOversight/app/main/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OpenOversight/app/main/views.py b/OpenOversight/app/main/views.py index 57096ea3d..8aef4a26d 100644 --- a/OpenOversight/app/main/views.py +++ b/OpenOversight/app/main/views.py @@ -1638,7 +1638,7 @@ def populate_obj(self, form, obj): ) else: obj.occurred_at = datetime.datetime.combine( - form.date_field.data.date(), datetime.time(0, 0) + form.date_field.data, datetime.time(0, 0) ) super(IncidentApi, self).populate_obj(form, obj) From ed1c1f963fb319b53d09ccd6760b8157d3d84d3e Mon Sep 17 00:00:00 2001 From: michplunkett <5885605+michplunkett@users.noreply.github.com> Date: Thu, 27 Jul 2023 17:42:58 -0500 Subject: [PATCH 19/55] Update test_incidents.py --- OpenOversight/tests/routes/test_incidents.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/OpenOversight/tests/routes/test_incidents.py b/OpenOversight/tests/routes/test_incidents.py index 96680683d..bc8b40c19 100644 --- a/OpenOversight/tests/routes/test_incidents.py +++ b/OpenOversight/tests/routes/test_incidents.py @@ -189,8 +189,7 @@ def test_admins_can_edit_incident_date_and_address(mockdata, client, session): assert rv.status_code == HTTPStatus.OK assert "successfully updated" in rv.data.decode(ENCODING_UTF_8) updated = Incident.query.get(inc_id) - assert updated.date == new_date - assert updated.time == new_time + assert updated.occurred_at == datetime.combine(new_date, new_time) assert updated.address.street_name == street_name @@ -529,8 +528,7 @@ def test_ac_can_edit_incidents_in_their_department(mockdata, client, session): ) assert rv.status_code == HTTPStatus.OK assert "successfully updated" in rv.data.decode(ENCODING_UTF_8) - assert inc.date == new_date.date() - assert inc.time == new_date.time() + assert inc.occurred_at == new_date assert inc.address.street_name == street_name From de0936ce47f35518f2d1b80561a7be4d9fea5f67 Mon Sep 17 00:00:00 2001 From: michplunkett <5885605+michplunkett@users.noreply.github.com> Date: Thu, 27 Jul 2023 17:51:59 -0500 Subject: [PATCH 20/55] Update test_commands.py --- OpenOversight/tests/test_commands.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/OpenOversight/tests/test_commands.py b/OpenOversight/tests/test_commands.py index 7a960fa74..bbf24d08b 100644 --- a/OpenOversight/tests/test_commands.py +++ b/OpenOversight/tests/test_commands.py @@ -868,7 +868,7 @@ def test_advanced_csv_import__success(session, department, test_csv_dir): report_number="Old_Report_Number", department_id=1, description="description", - occurred_at=datetime.datetime(2019, 3, 15, 23, 45, 16), + occurred_at=datetime.datetime(2020, 7, 26, 23, 45, 16), ) incident.officers = [officer] session.add(incident) @@ -991,7 +991,7 @@ def test_advanced_csv_import__success(session, department, test_csv_dir): assert incident3.report_number == "CR-39283" assert incident3.description == "Don't know where it happened" assert incident3.officers == [cop1] - assert incident3.occurred_at == datetime.date(2020, 7, 26) + assert incident3.occurred_at == datetime.datetime(2020, 7, 26, 0, 0) lp = incident3.license_plates[0] assert lp.number == "XYZ11" assert lp.state is None From 2424c0dfee03794222b7a23b054bb46fba18f84e Mon Sep 17 00:00:00 2001 From: michplunkett <5885605+michplunkett@users.noreply.github.com> Date: Thu, 27 Jul 2023 17:52:06 -0500 Subject: [PATCH 21/55] Update forms.py --- OpenOversight/app/utils/forms.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/OpenOversight/app/utils/forms.py b/OpenOversight/app/utils/forms.py index ffafa262a..0446d6fbd 100644 --- a/OpenOversight/app/utils/forms.py +++ b/OpenOversight/app/utils/forms.py @@ -134,12 +134,8 @@ def create_description(self, form): def create_incident(self, form): fields = { - "occurred_at": datetime.datetime( - form.date_field.data.year, - form.date_field.data.month, - form.date_field.data.day, - form.time_field.data.hour, - form.time_field.data.minute, + "occurred_at": datetime.datetime.combine( + form.date_field.data, form.time_field.data ), "officers": [], "license_plates": [], From 9c061dc89e1a180fcc6cf11fc60eceaab0b4af86 Mon Sep 17 00:00:00 2001 From: michplunkett <5885605+michplunkett@users.noreply.github.com> Date: Thu, 27 Jul 2023 18:00:25 -0500 Subject: [PATCH 22/55] Update downloads.py --- OpenOversight/app/main/downloads.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/OpenOversight/app/main/downloads.py b/OpenOversight/app/main/downloads.py index 599ebfe28..47d63fef6 100644 --- a/OpenOversight/app/main/downloads.py +++ b/OpenOversight/app/main/downloads.py @@ -132,8 +132,7 @@ def incidents_record_maker(incident: Incident) -> _Record: return { "id": incident.id, "report_num": incident.report_number, - "date": incident.date, - "time": incident.time, + "occurred_at": incident.occurred_at, "description": incident.description, "location": incident.address, "licenses": " ".join(map(str, incident.license_plates)), From 55d949cd652841511d79fd4ceb16dcb2baf26c8a Mon Sep 17 00:00:00 2001 From: michplunkett <5885605+michplunkett@users.noreply.github.com> Date: Thu, 27 Jul 2023 18:00:28 -0500 Subject: [PATCH 23/55] Update views.py --- OpenOversight/app/main/views.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/OpenOversight/app/main/views.py b/OpenOversight/app/main/views.py index 8aef4a26d..ab798f956 100644 --- a/OpenOversight/app/main/views.py +++ b/OpenOversight/app/main/views.py @@ -1289,8 +1289,7 @@ def download_incidents_csv(department_id): field_names = [ "id", "report_num", - "date", - "time", + "occurred_at", "description", "location", "licenses", From 893fb36414a37daf515e3caa1c6e29768da43199 Mon Sep 17 00:00:00 2001 From: michplunkett <5885605+michplunkett@users.noreply.github.com> Date: Thu, 27 Jul 2023 18:03:59 -0500 Subject: [PATCH 24/55] Update database_imports.py --- OpenOversight/app/models/database_imports.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/OpenOversight/app/models/database_imports.py b/OpenOversight/app/models/database_imports.py index 02a74c1dc..ccba60ef1 100644 --- a/OpenOversight/app/models/database_imports.py +++ b/OpenOversight/app/models/database_imports.py @@ -42,7 +42,9 @@ def parse_date(date_str: Optional[str]) -> Optional["datetime.date"]: def parse_date_time(date_time_str: Optional[str]) -> Optional["datetime.datetime"]: if date_time_str: - return dateutil.parser.parse(date_time_str) + return datetime.datetime.combine( + parse_date(date_time_str), parse_time(date_time_str) + ) return None @@ -279,6 +281,9 @@ def get_or_create_location_from_dict( def create_incident_from_dict(data: Dict[str, Any], force_id: bool = False) -> Incident: + print('!!!!!"') + print(data.get("date")) + print(data.get("time")) incident = Incident( occurred_at=parse_date_time(" ".join([data.get("date"), data.get("time")])), report_number=parse_str(data.get("report_number"), None), @@ -302,12 +307,17 @@ def create_incident_from_dict(data: Dict[str, Any], force_id: bool = False) -> I def update_incident_from_dict(data: Dict[str, Any], incident: Incident) -> Incident: if "date" in data: + print("!!!!!") if "time" in data: + print(" ".join([data.get("date"), data.get("time")])) incident.occurred_at = parse_date_time( " ".join([data.get("date"), data.get("time")]) ) else: - incident.occurred_at = parse_date_time(" ".join([data.get("date"), "00:00"])) + print(" ".join([data.get("date"), "00:00"])) + incident.occurred_at = parse_date_time( + " ".join([data.get("date"), "00:00"]) + ) if "report_number" in data: incident.report_number = parse_str(data.get("report_number"), None) if "description" in data: From 7856910adb0af3265228b7db131474c759b65c0f Mon Sep 17 00:00:00 2001 From: michplunkett <5885605+michplunkett@users.noreply.github.com> Date: Thu, 27 Jul 2023 22:30:29 -0500 Subject: [PATCH 25/55] Update database_imports.py --- OpenOversight/app/models/database_imports.py | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/OpenOversight/app/models/database_imports.py b/OpenOversight/app/models/database_imports.py index ccba60ef1..72d593872 100644 --- a/OpenOversight/app/models/database_imports.py +++ b/OpenOversight/app/models/database_imports.py @@ -308,16 +308,9 @@ def create_incident_from_dict(data: Dict[str, Any], force_id: bool = False) -> I def update_incident_from_dict(data: Dict[str, Any], incident: Incident) -> Incident: if "date" in data: print("!!!!!") - if "time" in data: - print(" ".join([data.get("date"), data.get("time")])) - incident.occurred_at = parse_date_time( - " ".join([data.get("date"), data.get("time")]) - ) - else: - print(" ".join([data.get("date"), "00:00"])) - incident.occurred_at = parse_date_time( - " ".join([data.get("date"), "00:00"]) - ) + incident.occurred_at = parse_date_time( + " ".join([data.get("date"), data.get("time", "00:00")]) + ) if "report_number" in data: incident.report_number = parse_str(data.get("report_number"), None) if "description" in data: From d7247ecf8d802bb6b17a71fc712c524d32876cc7 Mon Sep 17 00:00:00 2001 From: michplunkett <5885605+michplunkett@users.noreply.github.com> Date: Fri, 28 Jul 2023 11:46:21 -0500 Subject: [PATCH 26/55] Update database_imports.py --- OpenOversight/app/models/database_imports.py | 28 +++++++++----------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/OpenOversight/app/models/database_imports.py b/OpenOversight/app/models/database_imports.py index 72d593872..e4fd6ca21 100644 --- a/OpenOversight/app/models/database_imports.py +++ b/OpenOversight/app/models/database_imports.py @@ -1,4 +1,5 @@ -from typing import TYPE_CHECKING, Any, Dict, Optional, Sequence, Tuple, Union +from datetime import date, datetime, time +from typing import Any, Dict, Optional, Sequence, Tuple, Union import dateutil.parser @@ -19,10 +20,6 @@ from OpenOversight.app.validators import state_validator, url_validator -if TYPE_CHECKING: - import datetime - - def validate_choice( value: Optional[str], given_choices: Sequence[Tuple[str, str]] ) -> Optional[str]: @@ -34,21 +31,19 @@ def validate_choice( return None -def parse_date(date_str: Optional[str]) -> Optional["datetime.date"]: +def parse_date(date_str: Optional[str]) -> Optional[date]: if date_str: return dateutil.parser.parse(date_str).date() return None -def parse_date_time(date_time_str: Optional[str]) -> Optional["datetime.datetime"]: +def parse_date_time(date_time_str: Optional[str]) -> Optional[datetime]: if date_time_str: - return datetime.datetime.combine( - parse_date(date_time_str), parse_time(date_time_str) - ) + return datetime.combine(parse_date(date_time_str), parse_time(date_time_str)) return None -def parse_time(time_str: Optional[str]) -> Optional["datetime.time"]: +def parse_time(time_str: Optional[str]) -> Optional[time]: if time_str: return dateutil.parser.parse(time_str).time() return None @@ -281,11 +276,10 @@ def get_or_create_location_from_dict( def create_incident_from_dict(data: Dict[str, Any], force_id: bool = False) -> Incident: - print('!!!!!"') + print("+++++++") print(data.get("date")) - print(data.get("time")) + print(data.get("time", "00:00")) incident = Incident( - occurred_at=parse_date_time(" ".join([data.get("date"), data.get("time")])), report_number=parse_str(data.get("report_number"), None), description=parse_str(data.get("description"), None), address_id=data.get("address_id"), @@ -294,6 +288,11 @@ def create_incident_from_dict(data: Dict[str, Any], force_id: bool = False) -> I last_updated_id=parse_int(data.get("last_updated_id")), ) + if "date" in data: + incident.occurred_at = parse_date_time( + " ".join([data.get("date"), data.get("time", "00:00")]) + ) + incident.officers = data.get("officers", []) incident.license_plates = data.get("license_plate_objects", []) @@ -307,7 +306,6 @@ def create_incident_from_dict(data: Dict[str, Any], force_id: bool = False) -> I def update_incident_from_dict(data: Dict[str, Any], incident: Incident) -> Incident: if "date" in data: - print("!!!!!") incident.occurred_at = parse_date_time( " ".join([data.get("date"), data.get("time", "00:00")]) ) From 8c1ee4a793e65b647d0f13e1ea079e343545a1d0 Mon Sep 17 00:00:00 2001 From: michplunkett <5885605+michplunkett@users.noreply.github.com> Date: Fri, 28 Jul 2023 11:58:21 -0500 Subject: [PATCH 27/55] Update test_commands.py --- OpenOversight/tests/test_commands.py | 1 + 1 file changed, 1 insertion(+) diff --git a/OpenOversight/tests/test_commands.py b/OpenOversight/tests/test_commands.py index bbf24d08b..4f039e556 100644 --- a/OpenOversight/tests/test_commands.py +++ b/OpenOversight/tests/test_commands.py @@ -1087,6 +1087,7 @@ def test_advanced_csv_import__force_create(session, department, tmp_path): incidents_data = [ { "id": 66001, + "date": "2021-08-12", "officer_ids": "99002|99001", "department_name": department.name, "department_state": department.state, From 4c42df1510f952b360c0b9d0db0cbbac579e191e Mon Sep 17 00:00:00 2001 From: michplunkett <5885605+michplunkett@users.noreply.github.com> Date: Fri, 28 Jul 2023 11:58:45 -0500 Subject: [PATCH 28/55] Update csv_imports.py --- OpenOversight/app/csv_imports.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/OpenOversight/app/csv_imports.py b/OpenOversight/app/csv_imports.py index 7fc86a828..93ac6a808 100644 --- a/OpenOversight/app/csv_imports.py +++ b/OpenOversight/app/csv_imports.py @@ -364,9 +364,8 @@ def _handle_incidents_csv( with _csv_reader(incidents_csv) as csv_reader: _check_provided_fields( csv_reader, - required_fields=["id", "department_name", "department_state"], + required_fields=["id", "department_name", "department_state", "date"], optional_fields=[ - "date", "time", "report_number", "description", From 213f94f76862dafa71a39dc00223ef6197f8529e Mon Sep 17 00:00:00 2001 From: michplunkett <5885605+michplunkett@users.noreply.github.com> Date: Fri, 28 Jul 2023 11:58:50 -0500 Subject: [PATCH 29/55] Update incidents.csv --- OpenOversight/tests/test_csvs/incidents.csv | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OpenOversight/tests/test_csvs/incidents.csv b/OpenOversight/tests/test_csvs/incidents.csv index 45c9e803a..db81769b5 100644 --- a/OpenOversight/tests/test_csvs/incidents.csv +++ b/OpenOversight/tests/test_csvs/incidents.csv @@ -1,4 +1,4 @@ id,department name,department state,date,time,report number,description,street name,cross street1,cross street2,city,state,zip code,license plates,officer_ids,creator id,last updated id ,Springfield Police Department,IL,2020-07-20,06:30,CR-1234,Something happened,,East Ave,Main St,Chicago,IL,60603,ABC123_NY|98UMC_IL,#1|49483,, -#I1,Springfield Police Department,IL,,,CR-9912,Something happened,Fake Street,Main Street,,Chicago,IL,60603,,#1,, +#I1,Springfield Police Department,IL,2019-08-12,,CR-9912,Something happened,Fake Street,Main Street,,Chicago,IL,60603,,#1,, 123456,Springfield Police Department,IL,2020-07-26,,CR-39283,Don't know where it happened,,,,,,,XYZ11,#1,, From 26bec47eccde29ad64e8f3418be12d8100bbd431 Mon Sep 17 00:00:00 2001 From: michplunkett <5885605+michplunkett@users.noreply.github.com> Date: Fri, 28 Jul 2023 11:58:55 -0500 Subject: [PATCH 30/55] Update database_imports.py --- OpenOversight/app/models/database_imports.py | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/OpenOversight/app/models/database_imports.py b/OpenOversight/app/models/database_imports.py index e4fd6ca21..f9d0f38db 100644 --- a/OpenOversight/app/models/database_imports.py +++ b/OpenOversight/app/models/database_imports.py @@ -276,10 +276,10 @@ def get_or_create_location_from_dict( def create_incident_from_dict(data: Dict[str, Any], force_id: bool = False) -> Incident: - print("+++++++") - print(data.get("date")) - print(data.get("time", "00:00")) incident = Incident( + occurred_at=parse_date_time( + " ".join([data.get("date"), data.get("time", "00:00")]) + ), report_number=parse_str(data.get("report_number"), None), description=parse_str(data.get("description"), None), address_id=data.get("address_id"), @@ -288,11 +288,6 @@ def create_incident_from_dict(data: Dict[str, Any], force_id: bool = False) -> I last_updated_id=parse_int(data.get("last_updated_id")), ) - if "date" in data: - incident.occurred_at = parse_date_time( - " ".join([data.get("date"), data.get("time", "00:00")]) - ) - incident.officers = data.get("officers", []) incident.license_plates = data.get("license_plate_objects", []) From 23cebb55d983885f9f70c8d80ef24d5d3d4ad976 Mon Sep 17 00:00:00 2001 From: michplunkett <5885605+michplunkett@users.noreply.github.com> Date: Fri, 28 Jul 2023 12:12:01 -0500 Subject: [PATCH 31/55] Update route_helpers.py --- OpenOversight/tests/routes/route_helpers.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/OpenOversight/tests/routes/route_helpers.py b/OpenOversight/tests/routes/route_helpers.py index b6f6f08be..7459a0d71 100644 --- a/OpenOversight/tests/routes/route_helpers.py +++ b/OpenOversight/tests/routes/route_helpers.py @@ -45,17 +45,15 @@ def login_ac(client): def process_form_data(form_dict): - """Takes the dict from a form with embedded formd and flattens it - - in the way that it is flattened in the browser""" + """Mock the browser-flattening of a form containing embedded data.""" new_dict = {} for key, value in form_dict.items(): if type(value) == list: if value[0]: if type(value[0]) is dict: for idx, item in enumerate(value): - for subkey, subvalue in item.items(): - new_dict[f"{key}-{idx}-{subkey}"] = subvalue + for sub_key, sub_value in item.items(): + new_dict[f"{key}-{idx}-{sub_key}"] = sub_value elif type(value[0]) is str or type(value[0]) is int: for idx, item in enumerate(value): new_dict[f"{key}-{idx}"] = item @@ -66,8 +64,8 @@ def process_form_data(form_dict): ) ) elif type(value) == dict: - for subkey, subvalue in value.items(): - new_dict[f"{key}-{subkey}"] = subvalue + for sub_key, sub_value in value.items(): + new_dict[f"{key}-{sub_key}"] = sub_value else: new_dict[key] = value From 63024a5e17e78c09fdc790070c9dede0cf59b4c9 Mon Sep 17 00:00:00 2001 From: michplunkett <5885605+michplunkett@users.noreply.github.com> Date: Fri, 28 Jul 2023 13:05:21 -0500 Subject: [PATCH 32/55] Update advanced_csv_import.rst --- docs/advanced_csv_import.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/advanced_csv_import.rst b/docs/advanced_csv_import.rst index 665b941c5..df26a0a93 100644 --- a/docs/advanced_csv_import.rst +++ b/docs/advanced_csv_import.rst @@ -148,8 +148,8 @@ Details: Incidents csv ^^^^^^^^^^^^^ -- Required: ``id, department_name, department_state`` -- Optional: ``date, time, report_number, description, street_name, cross_street1, cross_street2, city, state, zip_code, +- Required: ``id, department_name, department_state, date`` +- Optional: ``time, report_number, description, street_name, cross_street1, cross_street2, city, state, zip_code, creator_id, last_updated_id, officer_ids, license_plates`` Details: @@ -159,7 +159,7 @@ Details: - ``department_state`` Name of department state exactly as it is in the server database, which will be the `standard two-letter abbreviation `_ for the department's respective location. - ``date`` :ref:`Date ` of the incident -- ``time`` :ref:`Time ` of the incident +- ``time`` :ref:`Time ` of the incident. If this field is left blank, it will be defaulted to midnight of that day. - ``report_number`` String representing any kind of number assigned to complaints or incidents by the police department. - ``description`` Text description of the incident. - ``street_name`` Name of the street the incident occurred, but should not include the street number. From e6063460410d6e755e9433a2f132111a8e5bdaaa Mon Sep 17 00:00:00 2001 From: michplunkett <5885605+michplunkett@users.noreply.github.com> Date: Fri, 28 Jul 2023 13:13:58 -0500 Subject: [PATCH 33/55] Update test_commands.py --- OpenOversight/tests/test_commands.py | 1 - 1 file changed, 1 deletion(-) diff --git a/OpenOversight/tests/test_commands.py b/OpenOversight/tests/test_commands.py index 4f039e556..9aeb15044 100644 --- a/OpenOversight/tests/test_commands.py +++ b/OpenOversight/tests/test_commands.py @@ -996,7 +996,6 @@ def test_advanced_csv_import__success(session, department, test_csv_dir): assert lp.number == "XYZ11" assert lp.state is None assert incident3.address is None - assert incident3.time is None link_new = cop4.links[0] assert [link_new] == list(cop1.links) From eeb9e560e4f0ab41e073de722e48c6c719098fd2 Mon Sep 17 00:00:00 2001 From: michplunkett <5885605+michplunkett@users.noreply.github.com> Date: Fri, 28 Jul 2023 13:19:57 -0500 Subject: [PATCH 34/55] Update test_incidents.py --- OpenOversight/tests/routes/test_incidents.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/OpenOversight/tests/routes/test_incidents.py b/OpenOversight/tests/routes/test_incidents.py index bc8b40c19..4733376aa 100644 --- a/OpenOversight/tests/routes/test_incidents.py +++ b/OpenOversight/tests/routes/test_incidents.py @@ -735,8 +735,8 @@ def test_admins_cannot_inject_unsafe_html(mockdata, client, session): ooid_forms = [OOIdForm(oo_id=the_id) for the_id in old_officer_ids] form = IncidentForm( - date_field=str(inc.date), - time_field=str(inc.time), + date_field=str(inc.occurred_at.date()), + time_field=str(inc.occurred_at.time()), report_number=inc.report_number, description="", department="1", From e2ef3d1a5398953aa9e0805139cb5ffbb511c2d2 Mon Sep 17 00:00:00 2001 From: michplunkett <5885605+michplunkett@users.noreply.github.com> Date: Sun, 6 Aug 2023 16:00:44 -0500 Subject: [PATCH 35/55] Update test_incidents.py --- OpenOversight/tests/routes/test_incidents.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/OpenOversight/tests/routes/test_incidents.py b/OpenOversight/tests/routes/test_incidents.py index 37bd385dc..147fc67a3 100644 --- a/OpenOversight/tests/routes/test_incidents.py +++ b/OpenOversight/tests/routes/test_incidents.py @@ -200,7 +200,7 @@ def test_admins_can_edit_incident_date_and_address(mockdata, client, session): assert rv.status_code == HTTPStatus.OK assert "successfully updated" in rv.data.decode(ENCODING_UTF_8) updated = Incident.query.get(inc_id) - assert updated.occurred_at == datetime.combine(new_date, new_time) + assert updated.occurred_at == datetime.combine(test_date, test_time) assert updated.address.street_name == street_name @@ -566,7 +566,7 @@ def test_ac_can_edit_incidents_in_their_department(mockdata, client, session): ) assert rv.status_code == HTTPStatus.OK assert "successfully updated" in rv.data.decode(ENCODING_UTF_8) - assert inc.occurred_at == new_date + assert inc.occurred_at == test_date assert inc.address.street_name == street_name From 9fb83a561f5b583ad27a5aaa3a51d6b4b6a25d95 Mon Sep 17 00:00:00 2001 From: michplunkett <5885605+michplunkett@users.noreply.github.com> Date: Sun, 6 Aug 2023 23:18:50 -0500 Subject: [PATCH 36/55] Update 2023-08-06-2316_9fa948bcea25_change_incident_date_and_time_to_.py --- ...-2316_9fa948bcea25_change_incident_date_and_time_to_.py} | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) rename OpenOversight/migrations/versions/{2023-07-27-1848_9fa948bcea25_change_incident_date_and_time_to_.py => 2023-08-06-2316_9fa948bcea25_change_incident_date_and_time_to_.py} (94%) diff --git a/OpenOversight/migrations/versions/2023-07-27-1848_9fa948bcea25_change_incident_date_and_time_to_.py b/OpenOversight/migrations/versions/2023-08-06-2316_9fa948bcea25_change_incident_date_and_time_to_.py similarity index 94% rename from OpenOversight/migrations/versions/2023-07-27-1848_9fa948bcea25_change_incident_date_and_time_to_.py rename to OpenOversight/migrations/versions/2023-08-06-2316_9fa948bcea25_change_incident_date_and_time_to_.py index 9804b89ee..6e9046fea 100644 --- a/OpenOversight/migrations/versions/2023-07-27-1848_9fa948bcea25_change_incident_date_and_time_to_.py +++ b/OpenOversight/migrations/versions/2023-08-06-2316_9fa948bcea25_change_incident_date_and_time_to_.py @@ -1,8 +1,8 @@ """change incident date and time to timestamptz Revision ID: 9fa948bcea25 -Revises: 18f43ac4622f -Create Date: 2023-07-27 18:48:58.819477 +Revises: b38c133bed3c +Create Date: 2023-08-06 23:16:00.819477 """ import os @@ -14,7 +14,7 @@ # revision identifiers, used by Alembic. revision = "9fa948bcea25" -down_revision = "18f43ac4622f" +down_revision = "b38c133bed3c" branch_labels = None depends_on = None From 4e5588a3ac3dc32a3e6edf7558c535563e8e7179 Mon Sep 17 00:00:00 2001 From: michplunkett <5885605+michplunkett@users.noreply.github.com> Date: Sun, 6 Aug 2023 23:24:24 -0500 Subject: [PATCH 37/55] Update general.py --- OpenOversight/app/utils/general.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/OpenOversight/app/utils/general.py b/OpenOversight/app/utils/general.py index 1743ca930..75565bdcf 100644 --- a/OpenOversight/app/utils/general.py +++ b/OpenOversight/app/utils/general.py @@ -1,5 +1,6 @@ import random import sys +from datetime import datetime, timezone from distutils.util import strtobool from typing import Optional from urllib.parse import urlparse @@ -70,6 +71,14 @@ def get_random_image(image_query): return None +def get_utc_datetime(dt: datetime = None) -> datetime: + """Return the current datetime in UTC or the converted given datetime to UTC.""" + if dt: + return datetime.now(tz=timezone.utc) + else: + return dt.replace(tzinfo=timezone.utc) + + def merge_dicts(*dict_args): """ Given any number of dicts, shallow copy and merge into a new dict, From 8d68b8f8167f043d2dd16781fed062395d63a9cb Mon Sep 17 00:00:00 2001 From: michplunkett <5885605+michplunkett@users.noreply.github.com> Date: Sun, 6 Aug 2023 23:24:30 -0500 Subject: [PATCH 38/55] Update views.py --- OpenOversight/app/main/views.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/OpenOversight/app/main/views.py b/OpenOversight/app/main/views.py index ed87c4a85..ffc77e772 100644 --- a/OpenOversight/app/main/views.py +++ b/OpenOversight/app/main/views.py @@ -102,6 +102,7 @@ allowed_file, get_or_create, get_random_image, + get_utc_datetime, replace_list, serve_image, validate_redirect_url, @@ -1634,12 +1635,12 @@ def populate_obj(self, form, obj): replace_list(license_plates, obj, "license_plates", LicensePlate, db) if form.time_field.raw_data and form.time_field.raw_data != [""]: - obj.occurred_at = datetime.datetime.combine( - form.date_field.data, form.time_field.data + obj.occurred_at = get_utc_datetime( + datetime.datetime.combine(form.date_field.data, form.time_field.data) ) else: - obj.occurred_at = datetime.datetime.combine( - form.date_field.data, datetime.time(0, 0) + obj.occurred_at = get_utc_datetime( + datetime.datetime.combine(form.date_field.data, datetime.time(0, 0)) ) super(IncidentApi, self).populate_obj(form, obj) From d12759dc6263ca19666c0d63b053e1340728595d Mon Sep 17 00:00:00 2001 From: michplunkett <5885605+michplunkett@users.noreply.github.com> Date: Mon, 7 Aug 2023 00:07:36 -0500 Subject: [PATCH 39/55] Update test_commands.py --- OpenOversight/tests/test_commands.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/OpenOversight/tests/test_commands.py b/OpenOversight/tests/test_commands.py index 9ed578a1c..207d6258c 100644 --- a/OpenOversight/tests/test_commands.py +++ b/OpenOversight/tests/test_commands.py @@ -1,10 +1,10 @@ import csv -import datetime import operator import os import random import traceback import uuid +from datetime import date, datetime import pandas as pd import pytest @@ -857,7 +857,7 @@ def test_advanced_csv_import__success(session, department, test_csv_dir): id=77021, officer_id=officer.id, star_no="4567", - start_date=datetime.date(2020, 1, 1), + start_date=date(2020, 1, 1), job_id=department.jobs[0].id, created_by=user.id, ) @@ -878,7 +878,7 @@ def test_advanced_csv_import__success(session, department, test_csv_dir): report_number="Old_Report_Number", department_id=1, description="description", - occurred_at=datetime.datetime(2020, 7, 26, 23, 45, 16), + occurred_at=datetime(2020, 7, 26, 23, 45, 16), created_by=user.id, ) incident.officers = [officer] @@ -1006,7 +1006,7 @@ def test_advanced_csv_import__success(session, department, test_csv_dir): assert incident3.report_number == "CR-39283" assert incident3.description == "Don't know where it happened" assert incident3.officers == [cop1] - assert incident3.occurred_at == datetime.datetime(2020, 7, 26, 0, 0) + assert incident3.occurred_at == datetime(2020, 7, 26, 0, 0) lp = incident3.license_plates[0] assert lp.number == "XYZ11" assert lp.state is None From d138618f907d9f5a342ac7ddc84dbc482f1ed296 Mon Sep 17 00:00:00 2001 From: michplunkett <5885605+michplunkett@users.noreply.github.com> Date: Mon, 7 Aug 2023 00:22:12 -0500 Subject: [PATCH 40/55] Update database_imports.py --- OpenOversight/app/models/database_imports.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/OpenOversight/app/models/database_imports.py b/OpenOversight/app/models/database_imports.py index 6801fbb89..abb26cacf 100644 --- a/OpenOversight/app/models/database_imports.py +++ b/OpenOversight/app/models/database_imports.py @@ -21,7 +21,7 @@ RACE_CHOICES, SUFFIX_CHOICES, ) -from OpenOversight.app.utils.general import get_or_create, str_is_true +from OpenOversight.app.utils.general import get_or_create, get_utc_datetime, str_is_true from OpenOversight.app.validators import state_validator, url_validator @@ -282,8 +282,8 @@ def get_or_create_location_from_dict( def create_incident_from_dict(data: Dict[str, Any], force_id: bool = False) -> Incident: incident = Incident( - occurred_at=parse_date_time( - " ".join([data.get("date"), data.get("time", "00:00")]) + occurred_at=get_utc_datetime( + parse_date_time(" ".join([data.get("date"), data.get("time", "00:00")])) ), report_number=parse_str(data.get("report_number"), None), description=parse_str(data.get("description"), None), @@ -291,7 +291,7 @@ def create_incident_from_dict(data: Dict[str, Any], force_id: bool = False) -> I department_id=parse_int(data.get("department_id")), created_by=parse_int(data.get("created_by")), last_updated_by=parse_int(data.get("last_updated_by")), - last_updated_at=datetime.datetime.now(), + last_updated_at=datetime.now(), ) incident.officers = data.get("officers", []) @@ -307,8 +307,8 @@ def create_incident_from_dict(data: Dict[str, Any], force_id: bool = False) -> I def update_incident_from_dict(data: Dict[str, Any], incident: Incident) -> Incident: if "date" in data: - incident.occurred_at = parse_date_time( - " ".join([data.get("date"), data.get("time", "00:00")]) + incident.occurred_at = get_utc_datetime( + parse_date_time(" ".join([data.get("date"), data.get("time", "00:00")])) ) if "report_number" in data: incident.report_number = parse_str(data.get("report_number"), None) @@ -322,7 +322,7 @@ def update_incident_from_dict(data: Dict[str, Any], incident: Incident) -> Incid incident.created_by = parse_int(data.get("created_by")) if "last_updated_by" in data: incident.last_updated_by = parse_int(data.get("last_updated_by")) - incident.last_updated_at = datetime.datetime.now() + incident.last_updated_at = datetime.now() if "officers" in data: incident.officers = data["officers"] or [] if "license_plate_objects" in data: From 1f8417030be04e080fead307faae0b41105eeb1d Mon Sep 17 00:00:00 2001 From: michplunkett <5885605+michplunkett@users.noreply.github.com> Date: Mon, 7 Aug 2023 00:22:18 -0500 Subject: [PATCH 41/55] Update forms.py --- OpenOversight/app/utils/forms.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/OpenOversight/app/utils/forms.py b/OpenOversight/app/utils/forms.py index 4ca4bf6ee..49b1a29ad 100644 --- a/OpenOversight/app/utils/forms.py +++ b/OpenOversight/app/utils/forms.py @@ -20,7 +20,7 @@ db, ) from OpenOversight.app.utils.choices import GENDER_CHOICES, RACE_CHOICES -from OpenOversight.app.utils.general import get_or_create +from OpenOversight.app.utils.general import get_or_create, get_utc_datetime def add_new_assignment(officer_id, form): @@ -134,8 +134,8 @@ def create_description(self, form): def create_incident(self, form): fields = { - "occurred_at": datetime.datetime.combine( - form.date_field.data, form.time_field.data + "occurred_at": get_utc_datetime( + datetime.datetime.combine(form.date_field.data, form.time_field.data) ), "officers": [], "license_plates": [], From 4502e2895c3da02a75060c81de388c84df3b5303 Mon Sep 17 00:00:00 2001 From: michplunkett <5885605+michplunkett@users.noreply.github.com> Date: Mon, 7 Aug 2023 00:22:24 -0500 Subject: [PATCH 42/55] Update test_commands.py --- OpenOversight/tests/test_commands.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/OpenOversight/tests/test_commands.py b/OpenOversight/tests/test_commands.py index 207d6258c..0fda8c993 100644 --- a/OpenOversight/tests/test_commands.py +++ b/OpenOversight/tests/test_commands.py @@ -926,7 +926,7 @@ def test_advanced_csv_import__success(session, department, test_csv_dir): assert cop1.last_name == "Smith" assert cop1.gender == "M" assert cop1.race == "WHITE" - assert cop1.employment_date == datetime.date(2019, 7, 12) + assert cop1.employment_date == date(2019, 7, 12) assert cop1.birth_year == 1984 assert cop1.middle_initial == "O" assert cop1.suffix is None @@ -942,8 +942,8 @@ def test_advanced_csv_import__success(session, department, test_csv_dir): cop1.assignments, key=operator.attrgetter("start_date") ) assert assignment_po.star_no == "1234" - assert assignment_po.start_date == datetime.date(2019, 7, 12) - assert assignment_po.resign_date == datetime.date(2020, 1, 1) + assert assignment_po.start_date == date(2019, 7, 12) + assert assignment_po.resign_date == date(2020, 1, 1) assert assignment_po.job.job_title == "Police Officer" assert assignment_po.unit_id is None @@ -980,10 +980,10 @@ def test_advanced_csv_import__success(session, department, test_csv_dir): cop4.assignments, key=operator.attrgetter("start_date") ) assert updated_assignment.job.job_title == "Police Officer" - assert updated_assignment.resign_date == datetime.date(2020, 7, 10) + assert updated_assignment.resign_date == date(2020, 7, 10) assert updated_assignment.star_no == "4567" assert new_assignment.job.job_title == "Captain" - assert new_assignment.start_date == datetime.date(2020, 7, 10) + assert new_assignment.start_date == date(2020, 7, 10) assert new_assignment.star_no == "54321" incident = cop4.incidents[0] From b2c8b9deabd7a5f858e17402c876dc696585b6e4 Mon Sep 17 00:00:00 2001 From: michplunkett <5885605+michplunkett@users.noreply.github.com> Date: Mon, 7 Aug 2023 00:31:11 -0500 Subject: [PATCH 43/55] Update general.py --- OpenOversight/app/utils/general.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/OpenOversight/app/utils/general.py b/OpenOversight/app/utils/general.py index 75565bdcf..6982bbe38 100644 --- a/OpenOversight/app/utils/general.py +++ b/OpenOversight/app/utils/general.py @@ -73,10 +73,10 @@ def get_random_image(image_query): def get_utc_datetime(dt: datetime = None) -> datetime: """Return the current datetime in UTC or the converted given datetime to UTC.""" - if dt: - return datetime.now(tz=timezone.utc) - else: + if dt is not None: return dt.replace(tzinfo=timezone.utc) + else: + return datetime.utcnow() def merge_dicts(*dict_args): From 50deaaf1df118c4dabf87fc0a612e7fe6309df4e Mon Sep 17 00:00:00 2001 From: michplunkett <5885605+michplunkett@users.noreply.github.com> Date: Mon, 7 Aug 2023 00:31:13 -0500 Subject: [PATCH 44/55] Update test_utils.py --- OpenOversight/tests/test_utils.py | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/OpenOversight/tests/test_utils.py b/OpenOversight/tests/test_utils.py index 5f70242c2..b7fa0c669 100644 --- a/OpenOversight/tests/test_utils.py +++ b/OpenOversight/tests/test_utils.py @@ -1,6 +1,8 @@ +from datetime import datetime, timedelta, timezone from io import BytesIO import pytest +import pytz from flask import current_app from flask_login import current_user from mock import MagicMock, Mock, patch @@ -12,9 +14,14 @@ upload_image_to_s3_and_store_in_db, upload_obj_to_s3, ) +from OpenOversight.app.utils.constants import KEY_TIMEZONE from OpenOversight.app.utils.db import unit_choices from OpenOversight.app.utils.forms import filter_by_form, grab_officers -from OpenOversight.app.utils.general import allowed_file, validate_redirect_url +from OpenOversight.app.utils.general import ( + allowed_file, + get_utc_datetime, + validate_redirect_url, +) from OpenOversight.tests.routes.route_helpers import login_user @@ -74,6 +81,22 @@ def test_gender_filter_include_all_genders_if_not_sure(mockdata): assert results.count() == len(department.officers) +def test_get_utc_datetime(): + utc_now = datetime.utcnow() + test_utc_now = get_utc_datetime() + assert (test_utc_now - utc_now).total_seconds() < 0.5 + + with current_app.test_request_context(): + server_timezone = pytz.timezone(current_app.config[KEY_TIMEZONE]) + local = server_timezone.localize(datetime.now() + timedelta(days=10, hours=3)) + test_local_to_utc = get_utc_datetime(local) + test = (local - test_local_to_utc).total_seconds() + correct = ( + datetime.now(tz=timezone.utc) - datetime.now().astimezone(tz=server_timezone) + ).total_seconds() + assert test == correct + + def test_rank_filter_select_all_commanders(mockdata): department = Department.query.first() results = grab_officers({"rank": ["Commander"], "dept": department}) From 39229d4c9f6256ade053398b629da0f32b781f60 Mon Sep 17 00:00:00 2001 From: michplunkett <5885605+michplunkett@users.noreply.github.com> Date: Mon, 7 Aug 2023 15:52:35 -0500 Subject: [PATCH 45/55] Delete 2023-08-06-2316_9fa948bcea25_change_incident_date_and_time_to_.py --- ...cea25_change_incident_date_and_time_to_.py | 55 ------------------- 1 file changed, 55 deletions(-) delete mode 100644 OpenOversight/migrations/versions/2023-08-06-2316_9fa948bcea25_change_incident_date_and_time_to_.py diff --git a/OpenOversight/migrations/versions/2023-08-06-2316_9fa948bcea25_change_incident_date_and_time_to_.py b/OpenOversight/migrations/versions/2023-08-06-2316_9fa948bcea25_change_incident_date_and_time_to_.py deleted file mode 100644 index 6e9046fea..000000000 --- a/OpenOversight/migrations/versions/2023-08-06-2316_9fa948bcea25_change_incident_date_and_time_to_.py +++ /dev/null @@ -1,55 +0,0 @@ -"""change incident date and time to timestamptz - -Revision ID: 9fa948bcea25 -Revises: b38c133bed3c -Create Date: 2023-08-06 23:16:00.819477 - -""" -import os - -import sqlalchemy as sa -from alembic import op -from sqlalchemy.dialects import postgresql - - -# revision identifiers, used by Alembic. -revision = "9fa948bcea25" -down_revision = "b38c133bed3c" -branch_labels = None -depends_on = None - -TIMEZONE = os.getenv("TIMEZONE", "America/Chicago") - - -def upgrade(): - op.add_column( - "incidents", sa.Column("occurred_at", sa.DateTime(timezone=True), nullable=True) - ) - op.execute( - f""" - UPDATE incidents - SET occurred_at = (date::date || ' ' || COALESCE(time, - '00:00:00')::timetz)::timestamp AT TIME ZONE '{TIMEZONE}' - WHERE occurred_at IS NULL - """ - ) - op.alter_column("incidents", "occurred_at", nullable=False) - op.drop_index("ix_incidents_date") - op.drop_index("ix_incidents_time") - op.drop_column("incidents", "time") - op.drop_column("incidents", "date") - - -def downgrade(): - op.add_column("incidents", sa.Column("date", sa.DATE(), nullable=True)) - op.add_column("incidents", sa.Column("time", postgresql.TIME(), nullable=True)) - op.execute( - f""" - UPDATE incidents - SET (date, time) = (occurred_at::date, (occurred_at::timestamptz AT TIME ZONE '{TIMEZONE}')::time) - """ - ) - - op.create_index(op.f("ix_incidents_time"), "incidents", ["time"], unique=False) - op.create_index(op.f("ix_incidents_date"), "incidents", ["date"], unique=False) - op.drop_column("incidents", "occurred_at") From 0d8f0bb123dddeedc6ecd5567b22d92bc7c02b5f Mon Sep 17 00:00:00 2001 From: michplunkett <5885605+michplunkett@users.noreply.github.com> Date: Mon, 7 Aug 2023 16:24:21 -0500 Subject: [PATCH 46/55] Update database.py --- OpenOversight/app/models/database.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/OpenOversight/app/models/database.py b/OpenOversight/app/models/database.py index 571176d4c..1d3b32989 100644 --- a/OpenOversight/app/models/database.py +++ b/OpenOversight/app/models/database.py @@ -633,7 +633,8 @@ class Incident(BaseModel): __tablename__ = "incidents" id = db.Column(db.Integer, primary_key=True) - occurred_at = db.Column(db.DateTime(timezone=True), unique=False, nullable=False) + date = db.Column(db.Date, unique=False, index=True) + occurred_at = db.Column(db.DateTime(timezone=True), unique=False, nullable=True, index=True) report_number = db.Column(db.String(50), index=True) description = db.Column(db.Text(), nullable=True) address_id = db.Column(db.Integer, db.ForeignKey("locations.id")) From 593b0b990d4778326ae26ab113b107eef7f11193 Mon Sep 17 00:00:00 2001 From: michplunkett <5885605+michplunkett@users.noreply.github.com> Date: Mon, 7 Aug 2023 16:24:24 -0500 Subject: [PATCH 47/55] Create 2023-08-07-2114_2b99be2696a9_add_occurred_at_to_incidents.py --- ...99be2696a9_add_occurred_at_to_incidents.py | 64 +++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 OpenOversight/migrations/versions/2023-08-07-2114_2b99be2696a9_add_occurred_at_to_incidents.py diff --git a/OpenOversight/migrations/versions/2023-08-07-2114_2b99be2696a9_add_occurred_at_to_incidents.py b/OpenOversight/migrations/versions/2023-08-07-2114_2b99be2696a9_add_occurred_at_to_incidents.py new file mode 100644 index 000000000..bfdac0996 --- /dev/null +++ b/OpenOversight/migrations/versions/2023-08-07-2114_2b99be2696a9_add_occurred_at_to_incidents.py @@ -0,0 +1,64 @@ +"""add occurred_at to incidents + +Revision ID: 2b99be2696a9 +Revises: b38c133bed3c +Create Date: 2023-08-07 21:14:31.711553 + +""" +import os + +from alembic import op +import sqlalchemy as sa +from sqlalchemy.dialects import postgresql + +# revision identifiers, used by Alembic. +revision = '2b99be2696a9' +down_revision = 'b38c133bed3c' +branch_labels = None +depends_on = None + + +TIMEZONE = os.getenv("TIMEZONE", "America/Chicago") + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + with op.batch_alter_table('incidents', schema=None) as batch_op: + batch_op.add_column(sa.Column('occurred_at', sa.DateTime(timezone=True), nullable=True)) + batch_op.create_index(batch_op.f('ix_incidents_occurred_at'), ['occurred_at'], unique=False) + + op.execute( + f""" + UPDATE incidents + SET occurred_at = (date::date || ' ' || time::timetz)::timestamp AT TIME ZONE '{TIMEZONE}' + WHERE occurred_at IS NULL + AND time IS NOT NULL + AND date IS NOT NULL + """ + ) + + with op.batch_alter_table('incidents', schema=None) as batch_op: + batch_op.drop_column('time') + + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + with op.batch_alter_table('incidents', schema=None) as batch_op: + batch_op.add_column(sa.Column('time', postgresql.TIME(), autoincrement=False, nullable=True)) + batch_op.drop_index(batch_op.f('ix_incidents_occurred_at')) + batch_op.create_index('ix_incidents_time', ['time'], unique=False) + + op.execute( + f""" + UPDATE incidents + SET (date, time) = (occurred_at::date, (occurred_at::timestamptz AT TIME ZONE '{TIMEZONE}')::time) + WHERE occurred_at IS NOT NULL + """ + ) + + with op.batch_alter_table('incidents', schema=None) as batch_op: + batch_op.drop_column('occurred_at') + + # ### end Alembic commands ### From bb95a43c42ece0a33b11f1f65a8b0cc8fdb15c72 Mon Sep 17 00:00:00 2001 From: michplunkett <5885605+michplunkett@users.noreply.github.com> Date: Mon, 7 Aug 2023 16:24:57 -0500 Subject: [PATCH 48/55] Update 2023-08-07-2114_2b99be2696a9_add_occurred_at_to_incidents.py --- ...99be2696a9_add_occurred_at_to_incidents.py | 35 +++++++++++-------- 1 file changed, 21 insertions(+), 14 deletions(-) diff --git a/OpenOversight/migrations/versions/2023-08-07-2114_2b99be2696a9_add_occurred_at_to_incidents.py b/OpenOversight/migrations/versions/2023-08-07-2114_2b99be2696a9_add_occurred_at_to_incidents.py index bfdac0996..8d447458a 100644 --- a/OpenOversight/migrations/versions/2023-08-07-2114_2b99be2696a9_add_occurred_at_to_incidents.py +++ b/OpenOversight/migrations/versions/2023-08-07-2114_2b99be2696a9_add_occurred_at_to_incidents.py @@ -7,13 +7,14 @@ """ import os -from alembic import op import sqlalchemy as sa +from alembic import op from sqlalchemy.dialects import postgresql + # revision identifiers, used by Alembic. -revision = '2b99be2696a9' -down_revision = 'b38c133bed3c' +revision = "2b99be2696a9" +down_revision = "b38c133bed3c" branch_labels = None depends_on = None @@ -23,9 +24,13 @@ def upgrade(): # ### commands auto generated by Alembic - please adjust! ### - with op.batch_alter_table('incidents', schema=None) as batch_op: - batch_op.add_column(sa.Column('occurred_at', sa.DateTime(timezone=True), nullable=True)) - batch_op.create_index(batch_op.f('ix_incidents_occurred_at'), ['occurred_at'], unique=False) + with op.batch_alter_table("incidents", schema=None) as batch_op: + batch_op.add_column( + sa.Column("occurred_at", sa.DateTime(timezone=True), nullable=True) + ) + batch_op.create_index( + batch_op.f("ix_incidents_occurred_at"), ["occurred_at"], unique=False + ) op.execute( f""" @@ -37,18 +42,20 @@ def upgrade(): """ ) - with op.batch_alter_table('incidents', schema=None) as batch_op: - batch_op.drop_column('time') + with op.batch_alter_table("incidents", schema=None) as batch_op: + batch_op.drop_column("time") # ### end Alembic commands ### def downgrade(): # ### commands auto generated by Alembic - please adjust! ### - with op.batch_alter_table('incidents', schema=None) as batch_op: - batch_op.add_column(sa.Column('time', postgresql.TIME(), autoincrement=False, nullable=True)) - batch_op.drop_index(batch_op.f('ix_incidents_occurred_at')) - batch_op.create_index('ix_incidents_time', ['time'], unique=False) + with op.batch_alter_table("incidents", schema=None) as batch_op: + batch_op.add_column( + sa.Column("time", postgresql.TIME(), autoincrement=False, nullable=True) + ) + batch_op.drop_index(batch_op.f("ix_incidents_occurred_at")) + batch_op.create_index("ix_incidents_time", ["time"], unique=False) op.execute( f""" @@ -58,7 +65,7 @@ def downgrade(): """ ) - with op.batch_alter_table('incidents', schema=None) as batch_op: - batch_op.drop_column('occurred_at') + with op.batch_alter_table("incidents", schema=None) as batch_op: + batch_op.drop_column("occurred_at") # ### end Alembic commands ### From 47c8f3c5dfb2694d874b49969d3aec388c9394ab Mon Sep 17 00:00:00 2001 From: michplunkett <5885605+michplunkett@users.noreply.github.com> Date: Mon, 7 Aug 2023 16:24:59 -0500 Subject: [PATCH 49/55] Update database.py --- OpenOversight/app/models/database.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/OpenOversight/app/models/database.py b/OpenOversight/app/models/database.py index 1d3b32989..1a4516dad 100644 --- a/OpenOversight/app/models/database.py +++ b/OpenOversight/app/models/database.py @@ -634,7 +634,9 @@ class Incident(BaseModel): id = db.Column(db.Integer, primary_key=True) date = db.Column(db.Date, unique=False, index=True) - occurred_at = db.Column(db.DateTime(timezone=True), unique=False, nullable=True, index=True) + occurred_at = db.Column( + db.DateTime(timezone=True), unique=False, nullable=True, index=True + ) report_number = db.Column(db.String(50), index=True) description = db.Column(db.Text(), nullable=True) address_id = db.Column(db.Integer, db.ForeignKey("locations.id")) From 662b36b558b5c950568e01c0c8b5b5628d50ff4d Mon Sep 17 00:00:00 2001 From: michplunkett <5885605+michplunkett@users.noreply.github.com> Date: Mon, 7 Aug 2023 16:25:02 -0500 Subject: [PATCH 50/55] Update test_utils.py --- OpenOversight/tests/test_utils.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/OpenOversight/tests/test_utils.py b/OpenOversight/tests/test_utils.py index b7fa0c669..93bf2a8f1 100644 --- a/OpenOversight/tests/test_utils.py +++ b/OpenOversight/tests/test_utils.py @@ -92,7 +92,8 @@ def test_get_utc_datetime(): test_local_to_utc = get_utc_datetime(local) test = (local - test_local_to_utc).total_seconds() correct = ( - datetime.now(tz=timezone.utc) - datetime.now().astimezone(tz=server_timezone) + datetime.now(tz=timezone.utc) + - datetime.now().astimezone(tz=server_timezone) ).total_seconds() assert test == correct From db81b9989174e00f50bf93555456280a59ee7bb5 Mon Sep 17 00:00:00 2001 From: michplunkett <5885605+michplunkett@users.noreply.github.com> Date: Tue, 8 Aug 2023 16:23:48 -0500 Subject: [PATCH 51/55] Update incident_detail.html --- OpenOversight/app/templates/incident_detail.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OpenOversight/app/templates/incident_detail.html b/OpenOversight/app/templates/incident_detail.html index e4b27608e..56c0ab492 100644 --- a/OpenOversight/app/templates/incident_detail.html +++ b/OpenOversight/app/templates/incident_detail.html @@ -41,7 +41,7 @@ incident.department.name }}

{% endif %} -
+

Incident From c68554fbe1b51f3a99b323919e8fdce8ca5c6808 Mon Sep 17 00:00:00 2001 From: michplunkett <5885605+michplunkett@users.noreply.github.com> Date: Wed, 16 Aug 2023 12:37:11 -0500 Subject: [PATCH 52/55] Update 2023-08-07-2114_2b99be2696a9_add_occurred_at_to_incidents.py --- ...114_2b99be2696a9_add_occurred_at_to_incidents.py | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/OpenOversight/migrations/versions/2023-08-07-2114_2b99be2696a9_add_occurred_at_to_incidents.py b/OpenOversight/migrations/versions/2023-08-07-2114_2b99be2696a9_add_occurred_at_to_incidents.py index 8d447458a..f4241c23a 100644 --- a/OpenOversight/migrations/versions/2023-08-07-2114_2b99be2696a9_add_occurred_at_to_incidents.py +++ b/OpenOversight/migrations/versions/2023-08-07-2114_2b99be2696a9_add_occurred_at_to_incidents.py @@ -9,7 +9,6 @@ import sqlalchemy as sa from alembic import op -from sqlalchemy.dialects import postgresql # revision identifiers, used by Alembic. @@ -35,27 +34,23 @@ def upgrade(): op.execute( f""" UPDATE incidents - SET occurred_at = (date::date || ' ' || time::timetz)::timestamp AT TIME ZONE '{TIMEZONE}' + SET + occurred_at = (date::date || ' ' || time::timetz)::timestamp AT TIME ZONE '{TIMEZONE}', + time = NULL, + date = NULL WHERE occurred_at IS NULL AND time IS NOT NULL AND date IS NOT NULL """ ) - with op.batch_alter_table("incidents", schema=None) as batch_op: - batch_op.drop_column("time") - # ### end Alembic commands ### def downgrade(): # ### commands auto generated by Alembic - please adjust! ### with op.batch_alter_table("incidents", schema=None) as batch_op: - batch_op.add_column( - sa.Column("time", postgresql.TIME(), autoincrement=False, nullable=True) - ) batch_op.drop_index(batch_op.f("ix_incidents_occurred_at")) - batch_op.create_index("ix_incidents_time", ["time"], unique=False) op.execute( f""" From c59cca8e180a7b308ebe3078cf5216f919f1270a Mon Sep 17 00:00:00 2001 From: michplunkett <5885605+michplunkett@users.noreply.github.com> Date: Wed, 16 Aug 2023 12:58:25 -0500 Subject: [PATCH 53/55] Update general.py --- OpenOversight/app/utils/general.py | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/OpenOversight/app/utils/general.py b/OpenOversight/app/utils/general.py index c516c9173..d5cc760e9 100644 --- a/OpenOversight/app/utils/general.py +++ b/OpenOversight/app/utils/general.py @@ -14,7 +14,7 @@ def ac_can_edit_officer(officer, ac): return False -def allowed_file(filename): +def allowed_file(filename: str): return ( "." in filename and filename.rsplit(".", 1)[1].lower() @@ -65,12 +65,9 @@ def get_random_image(image_query): return None -def get_utc_datetime(dt: datetime = None) -> datetime: +def get_utc_datetime(dt: datetime) -> datetime: """Return the current datetime in UTC or the converted given datetime to UTC.""" - if dt is not None: - return dt.replace(tzinfo=timezone.utc) - else: - return datetime.utcnow() + return dt.replace(tzinfo=timezone.utc) def merge_dicts(*dict_args): @@ -84,7 +81,7 @@ def merge_dicts(*dict_args): return result -def normalize_gender(input_gender): +def normalize_gender(input_gender: str): if input_gender is None: return None normalized_genders = { @@ -149,8 +146,8 @@ def serve_image(filepath): return url_for("static", filename=filepath.replace("static/", "").lstrip("/")) -def str_is_true(str_): - return strtobool(str_.lower()) +def str_is_true(string: str): + return strtobool(string.lower()) def validate_redirect_url(url: Optional[str]) -> Optional[str]: From 592e37b4939303b163f0c47d927c8b39694df1d1 Mon Sep 17 00:00:00 2001 From: michplunkett <5885605+michplunkett@users.noreply.github.com> Date: Wed, 16 Aug 2023 12:58:28 -0500 Subject: [PATCH 54/55] Update database_imports.py --- OpenOversight/app/models/database_imports.py | 30 ++++++++++++++------ 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/OpenOversight/app/models/database_imports.py b/OpenOversight/app/models/database_imports.py index abb26cacf..e18f8a2ef 100644 --- a/OpenOversight/app/models/database_imports.py +++ b/OpenOversight/app/models/database_imports.py @@ -36,22 +36,22 @@ def validate_choice( return None -def parse_date(date_str: Optional[str]) -> Optional[date]: +def parse_date(date_str: Optional[str]) -> date: if date_str: return dateutil.parser.parse(date_str).date() - return None + return datetime.now().date() -def parse_date_time(date_time_str: Optional[str]) -> Optional[datetime]: +def parse_date_time(date_time_str: str) -> datetime: if date_time_str: return datetime.combine(parse_date(date_time_str), parse_time(date_time_str)) - return None + return datetime.now() -def parse_time(time_str: Optional[str]) -> Optional[time]: +def parse_time(time_str: str) -> time: if time_str: return dateutil.parser.parse(time_str).time() - return None + return datetime.now().time() def parse_int(value: Optional[Union[str, int]]) -> Optional[int]: @@ -283,7 +283,14 @@ def get_or_create_location_from_dict( def create_incident_from_dict(data: Dict[str, Any], force_id: bool = False) -> Incident: incident = Incident( occurred_at=get_utc_datetime( - parse_date_time(" ".join([data.get("date"), data.get("time", "00:00")])) + parse_date_time( + " ".join( + [ + data.get("date", datetime.now().date().strftime("%x")), + data.get("time", "00:00"), + ] + ) + ) ), report_number=parse_str(data.get("report_number"), None), description=parse_str(data.get("description"), None), @@ -308,7 +315,14 @@ def create_incident_from_dict(data: Dict[str, Any], force_id: bool = False) -> I def update_incident_from_dict(data: Dict[str, Any], incident: Incident) -> Incident: if "date" in data: incident.occurred_at = get_utc_datetime( - parse_date_time(" ".join([data.get("date"), data.get("time", "00:00")])) + parse_date_time( + " ".join( + [ + data.get("date", datetime.now().date().strftime("%x")), + data.get("time", "00:00"), + ] + ) + ) ) if "report_number" in data: incident.report_number = parse_str(data.get("report_number"), None) From 157bb3a906359eb833e898d4a1b2f7deafccb427 Mon Sep 17 00:00:00 2001 From: michplunkett <5885605+michplunkett@users.noreply.github.com> Date: Wed, 16 Aug 2023 15:52:52 -0500 Subject: [PATCH 55/55] Update database.py --- OpenOversight/app/models/database.py | 1 + 1 file changed, 1 insertion(+) diff --git a/OpenOversight/app/models/database.py b/OpenOversight/app/models/database.py index 6ca741055..885d9eeb4 100644 --- a/OpenOversight/app/models/database.py +++ b/OpenOversight/app/models/database.py @@ -636,6 +636,7 @@ class Incident(BaseModel): id = db.Column(db.Integer, primary_key=True) date = db.Column(db.Date, unique=False, index=True) + time = db.Column(db.Time, unique=False, index=True) occurred_at = db.Column( db.DateTime(timezone=True), unique=False, nullable=True, index=True )