Skip to content

Commit

Permalink
Add created_at and created_by columns (#1002)
Browse files Browse the repository at this point in the history
## Fixes issue
#928

## Description of Changes
Add `created_by` and `created_at` columns to tables so that we can
better audit things happening in the application.

## 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.

<details><summary>DB migration output</summary>

```console
$ flask db stamp head
[2023-08-01 18:18:34,782] INFO in __init__: OpenOversight startup
INFO  [alembic.runtime.migration] Context impl PostgresqlImpl.
INFO  [alembic.runtime.migration] Will assume transactional DDL.
INFO  [alembic.runtime.migration] Running stamp_revision  -> 18f43ac4622f
$ flask db upgrade
[2023-08-01 18:18:49,052] INFO in __init__: OpenOversight startup
INFO  [alembic.runtime.migration] Context impl PostgresqlImpl.
INFO  [alembic.runtime.migration] Will assume transactional DDL.
INFO  [alembic.runtime.migration] Running upgrade 18f43ac4622f -> b429700e2dd2, add created_by and created_at columns
$ flask db downgrade
[2023-08-01 18:18:54,540] INFO in __init__: OpenOversight startup
INFO  [alembic.runtime.migration] Context impl PostgresqlImpl.
INFO  [alembic.runtime.migration] Will assume transactional DDL.
INFO  [alembic.runtime.migration] Running downgrade b429700e2dd2 -> 18f43ac4622f, add created_by and created_at columns
$ flask db upgrade
[2023-08-01 18:19:04,407] INFO in __init__: OpenOversight startup
INFO  [alembic.runtime.migration] Context impl PostgresqlImpl.
INFO  [alembic.runtime.migration] Will assume transactional DDL.
INFO  [alembic.runtime.migration] Running upgrade 18f43ac4622f -> b429700e2dd2, add created_by and created_at columns
$ 
```
</details>
  • Loading branch information
michplunkett authored Aug 3, 2023
1 parent 48199b7 commit 6a27393
Show file tree
Hide file tree
Showing 29 changed files with 1,518 additions and 470 deletions.
8 changes: 4 additions & 4 deletions OpenOversight/app/csv_imports.py
Original file line number Diff line number Diff line change
Expand Up @@ -234,7 +234,7 @@ def _handle_assignments_csv(
csv_reader = rows
else:
existing_assignments = (
Assignment.query.join(Assignment.baseofficer)
Assignment.query.join(Assignment.base_officer)
.filter(Officer.department_id == department_id)
.all()
)
Expand Down Expand Up @@ -376,8 +376,8 @@ def _handle_incidents_csv(
"city",
"state",
"zip_code",
"creator_id",
"last_updated_id",
"created_by",
"last_updated_by",
"officer_ids",
"license_plates",
],
Expand Down Expand Up @@ -442,7 +442,7 @@ def _handle_links_csv(
"link_type",
"description",
"author",
"creator_id",
"created_by",
"officer_ids",
"incident_ids",
],
Expand Down
4 changes: 2 additions & 2 deletions OpenOversight/app/main/downloads.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ def officer_record_maker(officer: Officer) -> _Record:


def assignment_record_maker(assignment: Assignment) -> _Record:
officer = assignment.baseofficer
officer = assignment.base_officer
return {
"id": assignment.id,
"officer id": assignment.officer_id,
Expand Down Expand Up @@ -159,7 +159,7 @@ def descriptions_record_maker(description: Description) -> _Record:
return {
"id": description.id,
"text_contents": description.text_contents,
"creator_id": description.creator_id,
"created_by": description.created_by,
"officer_id": description.officer_id,
"created_at": description.created_at,
"updated_at": description.updated_at,
Expand Down
67 changes: 61 additions & 6 deletions OpenOversight/app/main/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,11 @@ class FaceTag(Form):
dataY = IntegerField("dataY", validators=[InputRequired()])
dataWidth = IntegerField("dataWidth", validators=[InputRequired()])
dataHeight = IntegerField("dataHeight", validators=[InputRequired()])
created_by = HiddenField(
validators=[
DataRequired(message="Face Tags must have a valid user ID for creating.")
]
)


class AssignmentForm(Form):
Expand All @@ -162,6 +167,11 @@ class AssignmentForm(Form):
resign_date = DateField(
"Assignment end date", validators=[Optional(), validate_end_date]
)
created_by = HiddenField(
validators=[
DataRequired(message="Assignments must have a valid user ID for creating.")
]
)


class SalaryForm(Form):
Expand All @@ -177,6 +187,11 @@ class SalaryForm(Form):
validators=[NumberRange(min=1900, max=2100)],
)
is_fiscal_year = BooleanField("Is fiscal year?", default=False)
created_by = HiddenField(
validators=[
DataRequired(message="Salaries must have a valid user ID for creating.")
]
)

def validate(self, extra_validators=None):
if not self.data.get("salary") and not self.data.get("overtime_pay"):
Expand Down Expand Up @@ -207,6 +222,11 @@ class DepartmentForm(Form):
jobs = FieldList(
StringField("Job", default="", validators=[Regexp(r"\w*")]), label="Ranks"
)
created_by = HiddenField(
validators=[
DataRequired(message="Departments must have a valid user ID for creating.")
]
)
submit = SubmitField(label="Add")


Expand Down Expand Up @@ -236,7 +256,11 @@ class LinkForm(Form):
default="",
validators=[AnyOf(allowed_values(LINK_CHOICES))],
)
creator_id = HiddenField(validators=[DataRequired(message="Not a valid user ID")])
created_by = HiddenField(
validators=[
DataRequired(message="Links must have a valid user ID for creating.")
]
)

def validate(self, extra_validators=None):
success = super(LinkForm, self).validate(extra_validators=extra_validators)
Expand Down Expand Up @@ -270,7 +294,11 @@ class TextForm(EditTextForm):
officer_id = HiddenField(
validators=[DataRequired(message="Not a valid officer ID")]
)
creator_id = HiddenField(validators=[DataRequired(message="Not a valid user ID")])
created_by = HiddenField(
validators=[
DataRequired(message="Text fields must have a valid user ID for creating.")
]
)


class AddOfficerForm(Form):
Expand Down Expand Up @@ -356,6 +384,11 @@ class AddOfficerForm(Form):
min_entries=1,
widget=BootstrapListWidget(),
)
created_by = HiddenField(
validators=[
DataRequired(message="Officers must have a valid user ID for creating.")
]
)

submit = SubmitField(label="Add")

Expand Down Expand Up @@ -417,6 +450,11 @@ class AddUnitForm(Form):
query_factory=dept_choices,
get_label="name",
)
created_by = HiddenField(
validators=[
DataRequired(message="Units must have a valid user ID for creating.")
]
)
submit = SubmitField(label="Add")


Expand Down Expand Up @@ -467,6 +505,11 @@ class LocationForm(Form):
Regexp(r"^\d{5}$", message="Zip codes must have 5 digits."),
],
)
created_by = HiddenField(
validators=[
DataRequired(message="Locations must have a valid user ID for creating.")
]
)


class LicensePlateForm(Form):
Expand All @@ -476,6 +519,13 @@ class LicensePlateForm(Form):
choices=STATE_CHOICES,
validators=[AnyOf(allowed_values(STATE_CHOICES))],
)
created_by = HiddenField(
validators=[
DataRequired(
message="License Plates must have a valid user ID for creating."
)
]
)

def validate_state(self, field):
if self.number.data != "" and field.data == "":
Expand Down Expand Up @@ -545,11 +595,16 @@ class IncidentForm(DateFieldForm):
min_entries=1,
widget=BootstrapListWidget(),
)
creator_id = HiddenField(
validators=[DataRequired(message="Incidents must have a creator id.")]
created_by = HiddenField(
validators=[DataRequired(message="Incidents must have a user id for creating.")]
)
last_updated_id = HiddenField(
validators=[DataRequired(message="Incidents must have a user id for editing.")]
last_updated_by = HiddenField(
validators=[DataRequired(message="Incidents must have a user ID for editing.")]
)
last_updated_at = HiddenField(
validators=[
DataRequired(message="Incidents must have a timestamp for editing.")
]
)

submit = SubmitField(label="Submit")
Expand Down
29 changes: 16 additions & 13 deletions OpenOversight/app/main/model_view.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,10 +63,12 @@ def new(self, form=None):
add_department_query(form, current_user)
if getattr(current_user, "dept_pref_rel", None):
set_dynamic_default(form.department, current_user.dept_pref_rel)
if hasattr(form, "creator_id") and not form.creator_id.data:
form.creator_id.data = current_user.get_id()
if hasattr(form, "last_updated_id"):
form.last_updated_id.data = current_user.get_id()
if hasattr(form, "created_by") and not form.created_by.data:
form.created_by.data = current_user.get_id()
# TODO: Determine whether creating counts as updating, seems redundant
if hasattr(form, "last_updated_by"):
form.last_updated_by.data = current_user.get_id()
form.last_updated_at.data = datetime.datetime.now()

if form.validate_on_submit():
new_obj = self.create_function(form)
Expand All @@ -92,19 +94,20 @@ def edit(self, obj_id, form=None):

if not form:
form = self.get_edit_form(obj)
# if the object doesn't have a creator id set, st it to current user
# if the object doesn't have a creator id set it to current user
if (
hasattr(obj, "creator_id")
and hasattr(form, "creator_id")
and getattr(obj, "creator_id")
hasattr(obj, "created_by")
and hasattr(form, "created_by")
and getattr(obj, "created_by")
):
form.creator_id.data = obj.creator_id
elif hasattr(form, "creator_id"):
form.creator_id.data = current_user.get_id()
form.created_by.data = obj.created_by
elif hasattr(form, "created_by"):
form.created_by.data = current_user.get_id()

# if the object keeps track of who updated it last, set to current user
if hasattr(form, "last_updated_id"):
form.last_updated_id.data = current_user.get_id()
if hasattr(obj, "last_updated_by") and hasattr(form, "last_updated_by"):
form.last_updated_by.data = current_user.get_id()
form.last_updated_at.data = datetime.datetime.now()

if hasattr(form, "department"):
add_department_query(form, current_user)
Expand Down
Loading

0 comments on commit 6a27393

Please sign in to comment.