Skip to content

Commit 8b00aec

Browse files
Update "Find an Officer" page (#171)
* Update "Find an Officer" page * Update OpenOversight/app/templates/list_officer.html Co-authored-by: Madison Swain-Bowden <bowdenm@spu.edu> Co-authored-by: Madison Swain-Bowden <bowdenm@spu.edu>
1 parent 35e30b3 commit 8b00aec

File tree

6 files changed

+136
-35
lines changed

6 files changed

+136
-35
lines changed

OpenOversight/app/main/forms.py

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -80,8 +80,13 @@ class HumintContribution(Form):
8080

8181
class FindOfficerForm(Form):
8282
# Any fields added to this form should generally also be added to BrowseForm
83-
name = StringField(
84-
"name", default="", validators=[Regexp(r"\w*"), Length(max=50), Optional()]
83+
first_name = StringField(
84+
"first_name",
85+
default="",
86+
validators=[Regexp(r"\w*"), Length(max=50), Optional()],
87+
)
88+
last_name = StringField(
89+
"last_name", default="", validators=[Regexp(r"\w*"), Length(max=50), Optional()]
8590
)
8691
badge = StringField(
8792
"badge", default="", validators=[Regexp(r"\w*"), Length(max=10)]
@@ -98,7 +103,7 @@ class FindOfficerForm(Form):
98103
get_label="name",
99104
)
100105
unit = StringField("unit", default="Not Sure", validators=[Optional()])
101-
current_job = BooleanField("current_job", default=False, validators=[Optional()])
106+
current_job = BooleanField("current_job", default=None, validators=[Optional()])
102107
rank = StringField(
103108
"rank", default="Not Sure", validators=[Optional()]
104109
) # Gets rewritten by Javascript
@@ -120,12 +125,6 @@ class FindOfficerForm(Form):
120125
max_age = IntegerField(
121126
"max_age", default=85, validators=[NumberRange(min=16, max=100)]
122127
)
123-
latitude = DecimalField(
124-
"latitude", default=False, validators=[NumberRange(min=-90, max=90)]
125-
)
126-
longitude = DecimalField(
127-
"longitude", default=False, validators=[NumberRange(min=-180, max=180)]
128-
)
129128

130129

131130
class FindOfficerIDForm(Form):
@@ -572,7 +571,7 @@ class BrowseForm(Form):
572571
get_label="descrip",
573572
get_pk=lambda unit: unit.descrip,
574573
) # query set in view function
575-
current_job = BooleanField("current_job", default=False, validators=[Optional()])
574+
current_job = BooleanField("current_job", default=None, validators=[Optional()])
576575
name = StringField("Last name")
577576
badge = StringField("Badge number")
578577
unique_internal_identifier = StringField("Unique ID")

OpenOversight/app/main/views.py

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,9 +145,11 @@ def get_officer():
145145
else None,
146146
rank=form.data["rank"] if form.data["rank"] != "Not Sure" else None,
147147
unit=form.data["unit"] if form.data["unit"] != "Not Sure" else None,
148+
current_job=form.data["current_job"] or None, # set to None if False
148149
min_age=form.data["min_age"],
149150
max_age=form.data["max_age"],
150-
name=form.data["name"],
151+
first_name=form.data["first_name"],
152+
last_name=form.data["last_name"],
151153
badge=form.data["badge"],
152154
unique_internal_identifier=form.data["unique_internal_identifier"],
153155
),
@@ -799,6 +801,27 @@ def get_dept_ranks(department_id=None, is_sworn_officer=None):
799801
return jsonify(rank_list)
800802

801803

804+
@main.route("/department/<int:department_id>/units")
805+
@main.route("/units")
806+
def get_dept_units(department_id=None):
807+
if not department_id:
808+
department_id = request.args.get("department_id")
809+
810+
if department_id:
811+
units = Unit.query.filter_by(department_id=department_id)
812+
units = units.order_by(Unit.descrip).all()
813+
unit_list = [(unit.id, unit.descrip) for unit in units]
814+
else:
815+
units = Unit.query.all()
816+
# Prevent duplicate units
817+
unit_list = sorted(
818+
set((unit.id, unit.descrip) for unit in units),
819+
key=lambda x: x[1],
820+
)
821+
822+
return jsonify(unit_list)
823+
824+
802825
@main.route("/officer/new", methods=["GET", "POST"])
803826
@login_required
804827
@ac_or_admin_required

OpenOversight/app/static/js/find_officer.js

Lines changed: 22 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,22 @@
1+
function buildSelect(name, data_url, dept_id) {
2+
return $.ajax({
3+
url: data_url,
4+
data: {department_id: dept_id}
5+
}).done(function(data) {
6+
$('input#' + name).replaceWith('<select class="form-control" id="' + name + '" name="' + name + '">');
7+
const dropdown = $('select#' + name);
8+
// Add the null case first
9+
dropdown.append(
10+
$('<option value="Not Sure">Not Sure</option>')
11+
);
12+
for (i = 0; i < data.length; i++) {
13+
dropdown.append(
14+
$('<option></option>').attr("value", data[i][1]).text(data[i][1])
15+
);
16+
}
17+
});
18+
}
19+
120
$(document).ready(function() {
221

322
var navListItems = $('ul.setup-panel li a'),
@@ -32,22 +51,9 @@ $(document).ready(function() {
3251
var dept_id = $('#dept').val();
3352
// fetch ranks for dept_id and modify #rank <select>
3453
var ranks_url = $(this).data('ranks-url');
35-
var ranks = $.ajax({
36-
url: ranks_url,
37-
data: {department_id: dept_id}
38-
}).done(function(ranks) {
39-
$('input#rank').replaceWith('<select class="form-control" id="rank" name="rank">');
40-
const rank_box = $('select#rank')
41-
// Add the null case first
42-
rank_box.append(
43-
$('<option value="Not Sure">Not Sure</option>')
44-
);
45-
for (i = 0; i < ranks.length; i++) {
46-
rank_box.append(
47-
$('<option></option>').attr("value", ranks[i][1]).text(ranks[i][1])
48-
);
49-
}
50-
});
54+
var units_url = $(this).data('units-url');
55+
buildSelect('rank', ranks_url, dept_id);
56+
buildSelect('unit', units_url, dept_id);
5157

5258
$('ul.setup-panel li:eq(1)').removeClass('disabled');
5359
$('ul.setup-panel li a[href="#step-2"]').trigger('click');

OpenOversight/app/templates/input_find_officer.html

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
</div>
2222
<form action="{{ url_for('main.get_officer') }}" method="post" class="form">
2323
{{ form.hidden_tag() }}
24-
<div class="row form-group">
24+
<div class="row form-group">
2525
<div class="col-xs-12">
2626
<ul class="nav nav-pills nav-justified thumbnail setup-panel">
2727
<li class="active"><a href="#step-1">
@@ -42,7 +42,7 @@ <h4 class="list-group-item-heading">Step 4</h4>
4242
</a></li>
4343
</ul>
4444
</div>
45-
</div>
45+
</div>
4646
<div class="row setup-content" id="step-1">
4747
<div class="col-xs-12">
4848
<div class="col-md-12 well text-center">
@@ -58,7 +58,7 @@ <h2><small>Select Department</small></h2>
5858
{% endfor %}
5959

6060
<br>
61-
<button id="activate-step-2" data-ranks-url="{{ url_for('main.get_dept_ranks') }}" class="btn btn-primary btn-lg">Next Step</button>
61+
<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>
6262
</div>
6363
</div>
6464
</div>
@@ -67,8 +67,16 @@ <h2><small>Select Department</small></h2>
6767
<div class="col-md-12 well text-center">
6868
<h2><small>Do you remember any part of the Officer's last name?</small></h2>
6969
<div class="input-group input-group-lg col-md-4 col-md-offset-4">
70-
{{ form.name(class="form-control") }}
71-
{% for error in form.name.errors %}
70+
{{ form.last_name(class="form-control") }}
71+
{% for error in form.last_name.errors %}
72+
<p><span style="color: red;">[{{ error }}]</span></p>
73+
{% endfor %}
74+
</div>
75+
76+
<h2><small>Do you remember any part of the Officer's first name?</small></h2>
77+
<div class="input-group input-group-lg col-md-4 col-md-offset-4">
78+
{{ form.first_name(class="form-control") }}
79+
{% for error in form.first_name.errors %}
7280
<p><span style="color: red;">[{{ error }}]</span></p>
7381
{% endfor %}
7482
</div>
@@ -113,6 +121,23 @@ <h2><small>Officer Rank</small></h2>
113121
<img src="{{url_for('static', filename='images/OfficerRank.png')}}" width="50%" height="50%">
114122
</div>
115123

124+
<h2><small>Officer Unit</small></h2>
125+
<div class="input-group input-group-lg col-md-4 col-md-offset-4">
126+
{{ form.unit(class="form-control") }}
127+
{% for error in form.unit.errors %}
128+
<p><span style="color: red;">[{{ error }}]</span></p>
129+
{% endfor %}
130+
</div>
131+
132+
<h2><small>Currently Employed</small></h2>
133+
<span class="text-muted" style="position: relative; top: -0.8em">(in this unit and/or rank, if specified)</span>
134+
<div class="input-group input-group-sm col-md-4 col-md-offset-4">
135+
{{ form.current_job(class="form-control") }}
136+
{% for error in form.current_job.errors %}
137+
<p><span style="color: red;">[{{ error }}]</span></p>
138+
{% endfor %}
139+
</div>
140+
116141
<br>
117142
<button id="activate-step-4" class="btn btn-primary btn-lg">Next Step</button>
118143
</div>

OpenOversight/app/templates/list_officer.html

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -87,10 +87,10 @@ <h3 class="panel-title accordion-toggle">Job</h3>
8787
<div class="form-row">
8888
<div class="form-group">
8989
<label for="current_job">
90-
<input id="current_job" name="current_job" type="checkbox" {% if form_data.get("current_job") %}checked{% endif %}>
91-
Currently employed
90+
<input id="current_job" name="current_job" type="checkbox" value="True" {% if form_data.get("current_job") %}checked{% endif %}>
91+
Currently employed
9292
</label>
93-
<span>(in this rank and/or unit, if specified)</span>
93+
<span><i>(in this rank and/or unit, if specified)</i></span>
9494
</div>
9595
</div>
9696
<br />

OpenOversight/tests/routes/test_officer_and_department.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
DepartmentForm,
2222
EditDepartmentForm,
2323
EditOfficerForm,
24+
FindOfficerForm,
2425
IncidentForm,
2526
LicensePlateForm,
2627
LinkForm,
@@ -1716,6 +1717,53 @@ def test_browse_filtering_allows_good(client, mockdata, session):
17161717
assert any("<dd>Male</dd>" in token for token in filter_list)
17171718

17181719

1720+
def test_find_officer_redirect(client, mockdata, session):
1721+
with current_app.test_request_context():
1722+
department_id = Department.query.first().id
1723+
rank = "Officer"
1724+
unit_id = 1234
1725+
min_age = datetime.now().year - 1991
1726+
max_age = datetime.now().year - 1989
1727+
1728+
# Check that added officer appears when filtering for this race, gender, rank and age
1729+
form = FindOfficerForm(
1730+
dept=department_id,
1731+
first_name="A",
1732+
last_name="B",
1733+
race="WHITE",
1734+
gender="M",
1735+
rank=rank,
1736+
unit=unit_id,
1737+
current_job=True,
1738+
min_age=min_age,
1739+
max_age=max_age,
1740+
)
1741+
1742+
data = process_form_data(form.data)
1743+
1744+
rv = client.post(
1745+
url_for("main.get_officer"),
1746+
data=data,
1747+
follow_redirects=True,
1748+
)
1749+
1750+
# Check that the parameters are added correctly to the response url
1751+
assert "department/{}".format(department_id) in rv.request.full_path
1752+
parameters = [
1753+
("first_name", "A"),
1754+
("last_name", "B"),
1755+
("race", "WHITE"),
1756+
("gender", "M"),
1757+
("rank", rank),
1758+
("unit", unit_id),
1759+
("current_job", True),
1760+
("min_age", min_age),
1761+
("max_age", max_age),
1762+
]
1763+
for name, value in parameters:
1764+
assert "{}={}".format(name, value) in rv.request.full_path
1765+
1766+
17191767
def test_admin_can_upload_photos_of_dept_officers(
17201768
mockdata, client, session, test_jpg_BytesIO
17211769
):

0 commit comments

Comments
 (0)