Skip to content

Commit

Permalink
Update "Find an Officer" page (#171)
Browse files Browse the repository at this point in the history
* Update "Find an Officer" page

* Update OpenOversight/app/templates/list_officer.html

Co-authored-by: Madison Swain-Bowden <[email protected]>

Co-authored-by: Madison Swain-Bowden <[email protected]>
  • Loading branch information
sea-kelp and AetherUnbound authored Aug 16, 2022
1 parent 35e30b3 commit 8b00aec
Show file tree
Hide file tree
Showing 6 changed files with 136 additions and 35 deletions.
19 changes: 9 additions & 10 deletions OpenOversight/app/main/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,13 @@ class HumintContribution(Form):

class FindOfficerForm(Form):
# Any fields added to this form should generally also be added to BrowseForm
name = StringField(
"name", default="", validators=[Regexp(r"\w*"), Length(max=50), Optional()]
first_name = StringField(
"first_name",
default="",
validators=[Regexp(r"\w*"), Length(max=50), Optional()],
)
last_name = StringField(
"last_name", default="", validators=[Regexp(r"\w*"), Length(max=50), Optional()]
)
badge = StringField(
"badge", default="", validators=[Regexp(r"\w*"), Length(max=10)]
Expand All @@ -98,7 +103,7 @@ class FindOfficerForm(Form):
get_label="name",
)
unit = StringField("unit", default="Not Sure", validators=[Optional()])
current_job = BooleanField("current_job", default=False, validators=[Optional()])
current_job = BooleanField("current_job", default=None, validators=[Optional()])
rank = StringField(
"rank", default="Not Sure", validators=[Optional()]
) # Gets rewritten by Javascript
Expand All @@ -120,12 +125,6 @@ class FindOfficerForm(Form):
max_age = IntegerField(
"max_age", default=85, validators=[NumberRange(min=16, max=100)]
)
latitude = DecimalField(
"latitude", default=False, validators=[NumberRange(min=-90, max=90)]
)
longitude = DecimalField(
"longitude", default=False, validators=[NumberRange(min=-180, max=180)]
)


class FindOfficerIDForm(Form):
Expand Down Expand Up @@ -572,7 +571,7 @@ class BrowseForm(Form):
get_label="descrip",
get_pk=lambda unit: unit.descrip,
) # query set in view function
current_job = BooleanField("current_job", default=False, validators=[Optional()])
current_job = BooleanField("current_job", default=None, validators=[Optional()])
name = StringField("Last name")
badge = StringField("Badge number")
unique_internal_identifier = StringField("Unique ID")
Expand Down
25 changes: 24 additions & 1 deletion OpenOversight/app/main/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -145,9 +145,11 @@ def get_officer():
else None,
rank=form.data["rank"] if form.data["rank"] != "Not Sure" else None,
unit=form.data["unit"] if form.data["unit"] != "Not Sure" else None,
current_job=form.data["current_job"] or None, # set to None if False
min_age=form.data["min_age"],
max_age=form.data["max_age"],
name=form.data["name"],
first_name=form.data["first_name"],
last_name=form.data["last_name"],
badge=form.data["badge"],
unique_internal_identifier=form.data["unique_internal_identifier"],
),
Expand Down Expand Up @@ -799,6 +801,27 @@ def get_dept_ranks(department_id=None, is_sworn_officer=None):
return jsonify(rank_list)


@main.route("/department/<int:department_id>/units")
@main.route("/units")
def get_dept_units(department_id=None):
if not department_id:
department_id = request.args.get("department_id")

if department_id:
units = Unit.query.filter_by(department_id=department_id)
units = units.order_by(Unit.descrip).all()
unit_list = [(unit.id, unit.descrip) for unit in units]
else:
units = Unit.query.all()
# Prevent duplicate units
unit_list = sorted(
set((unit.id, unit.descrip) for unit in units),
key=lambda x: x[1],
)

return jsonify(unit_list)


@main.route("/officer/new", methods=["GET", "POST"])
@login_required
@ac_or_admin_required
Expand Down
38 changes: 22 additions & 16 deletions OpenOversight/app/static/js/find_officer.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,22 @@
function buildSelect(name, data_url, dept_id) {
return $.ajax({
url: data_url,
data: {department_id: dept_id}
}).done(function(data) {
$('input#' + name).replaceWith('<select class="form-control" id="' + name + '" name="' + name + '">');
const dropdown = $('select#' + name);
// Add the null case first
dropdown.append(
$('<option value="Not Sure">Not Sure</option>')
);
for (i = 0; i < data.length; i++) {
dropdown.append(
$('<option></option>').attr("value", data[i][1]).text(data[i][1])
);
}
});
}

$(document).ready(function() {

var navListItems = $('ul.setup-panel li a'),
Expand Down Expand Up @@ -32,22 +51,9 @@ $(document).ready(function() {
var dept_id = $('#dept').val();
// fetch ranks for dept_id and modify #rank <select>
var ranks_url = $(this).data('ranks-url');
var ranks = $.ajax({
url: ranks_url,
data: {department_id: dept_id}
}).done(function(ranks) {
$('input#rank').replaceWith('<select class="form-control" id="rank" name="rank">');
const rank_box = $('select#rank')
// Add the null case first
rank_box.append(
$('<option value="Not Sure">Not Sure</option>')
);
for (i = 0; i < ranks.length; i++) {
rank_box.append(
$('<option></option>').attr("value", ranks[i][1]).text(ranks[i][1])
);
}
});
var units_url = $(this).data('units-url');
buildSelect('rank', ranks_url, dept_id);
buildSelect('unit', units_url, dept_id);

$('ul.setup-panel li:eq(1)').removeClass('disabled');
$('ul.setup-panel li a[href="#step-2"]').trigger('click');
Expand Down
35 changes: 30 additions & 5 deletions OpenOversight/app/templates/input_find_officer.html
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
</div>
<form action="{{ url_for('main.get_officer') }}" method="post" class="form">
{{ form.hidden_tag() }}
<div class="row form-group">
<div class="row form-group">
<div class="col-xs-12">
<ul class="nav nav-pills nav-justified thumbnail setup-panel">
<li class="active"><a href="#step-1">
Expand All @@ -42,7 +42,7 @@ <h4 class="list-group-item-heading">Step 4</h4>
</a></li>
</ul>
</div>
</div>
</div>
<div class="row setup-content" id="step-1">
<div class="col-xs-12">
<div class="col-md-12 well text-center">
Expand All @@ -58,7 +58,7 @@ <h2><small>Select Department</small></h2>
{% endfor %}

<br>
<button id="activate-step-2" data-ranks-url="{{ url_for('main.get_dept_ranks') }}" class="btn btn-primary btn-lg">Next Step</button>
<button id="activate-step-2" data-ranks-url="{{ url_for('main.get_dept_ranks') }}" data-units-url="{{ url_for('main.get_dept_units') }}" class="btn btn-primary btn-lg">Next Step</button>
</div>
</div>
</div>
Expand All @@ -67,8 +67,16 @@ <h2><small>Select Department</small></h2>
<div class="col-md-12 well text-center">
<h2><small>Do you remember any part of the Officer's last name?</small></h2>
<div class="input-group input-group-lg col-md-4 col-md-offset-4">
{{ form.name(class="form-control") }}
{% for error in form.name.errors %}
{{ form.last_name(class="form-control") }}
{% for error in form.last_name.errors %}
<p><span style="color: red;">[{{ error }}]</span></p>
{% endfor %}
</div>

<h2><small>Do you remember any part of the Officer's first name?</small></h2>
<div class="input-group input-group-lg col-md-4 col-md-offset-4">
{{ form.first_name(class="form-control") }}
{% for error in form.first_name.errors %}
<p><span style="color: red;">[{{ error }}]</span></p>
{% endfor %}
</div>
Expand Down Expand Up @@ -113,6 +121,23 @@ <h2><small>Officer Rank</small></h2>
<img src="{{url_for('static', filename='images/OfficerRank.png')}}" width="50%" height="50%">
</div>

<h2><small>Officer Unit</small></h2>
<div class="input-group input-group-lg col-md-4 col-md-offset-4">
{{ form.unit(class="form-control") }}
{% for error in form.unit.errors %}
<p><span style="color: red;">[{{ error }}]</span></p>
{% endfor %}
</div>

<h2><small>Currently Employed</small></h2>
<span class="text-muted" style="position: relative; top: -0.8em">(in this unit and/or rank, if specified)</span>
<div class="input-group input-group-sm col-md-4 col-md-offset-4">
{{ form.current_job(class="form-control") }}
{% for error in form.current_job.errors %}
<p><span style="color: red;">[{{ error }}]</span></p>
{% endfor %}
</div>

<br>
<button id="activate-step-4" class="btn btn-primary btn-lg">Next Step</button>
</div>
Expand Down
6 changes: 3 additions & 3 deletions OpenOversight/app/templates/list_officer.html
Original file line number Diff line number Diff line change
Expand Up @@ -87,10 +87,10 @@ <h3 class="panel-title accordion-toggle">Job</h3>
<div class="form-row">
<div class="form-group">
<label for="current_job">
<input id="current_job" name="current_job" type="checkbox" {% if form_data.get("current_job") %}checked{% endif %}>
Currently employed
<input id="current_job" name="current_job" type="checkbox" value="True" {% if form_data.get("current_job") %}checked{% endif %}>
Currently employed
</label>
<span>(in this rank and/or unit, if specified)</span>
<span><i>(in this rank and/or unit, if specified)</i></span>
</div>
</div>
<br />
Expand Down
48 changes: 48 additions & 0 deletions OpenOversight/tests/routes/test_officer_and_department.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
DepartmentForm,
EditDepartmentForm,
EditOfficerForm,
FindOfficerForm,
IncidentForm,
LicensePlateForm,
LinkForm,
Expand Down Expand Up @@ -1716,6 +1717,53 @@ def test_browse_filtering_allows_good(client, mockdata, session):
assert any("<dd>Male</dd>" in token for token in filter_list)


def test_find_officer_redirect(client, mockdata, session):
with current_app.test_request_context():
department_id = Department.query.first().id
rank = "Officer"
unit_id = 1234
min_age = datetime.now().year - 1991
max_age = datetime.now().year - 1989

# Check that added officer appears when filtering for this race, gender, rank and age
form = FindOfficerForm(
dept=department_id,
first_name="A",
last_name="B",
race="WHITE",
gender="M",
rank=rank,
unit=unit_id,
current_job=True,
min_age=min_age,
max_age=max_age,
)

data = process_form_data(form.data)

rv = client.post(
url_for("main.get_officer"),
data=data,
follow_redirects=True,
)

# Check that the parameters are added correctly to the response url
assert "department/{}".format(department_id) in rv.request.full_path
parameters = [
("first_name", "A"),
("last_name", "B"),
("race", "WHITE"),
("gender", "M"),
("rank", rank),
("unit", unit_id),
("current_job", True),
("min_age", min_age),
("max_age", max_age),
]
for name, value in parameters:
assert "{}={}".format(name, value) in rv.request.full_path


def test_admin_can_upload_photos_of_dept_officers(
mockdata, client, session, test_jpg_BytesIO
):
Expand Down

0 comments on commit 8b00aec

Please sign in to comment.