Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New filters, sorting, and more updates for browse view #835

Open
wants to merge 42 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
731ff5a
Add meta block to base template
dismantl Aug 14, 2020
68fb05c
Add titles and meta descriptions to public templates
dismantl Aug 14, 2020
79d746d
Add structured data to officer profiles: schema.org, Open Graph, Twit…
dismantl Aug 14, 2020
120ebfa
Update structured data for incident templates
dismantl Aug 14, 2020
eb88463
flake8 fix
dismantl Aug 14, 2020
42f0a64
jinjaaaaaaaaaaaaaa fix
dismantl Aug 14, 2020
5c32849
Differentiate between local and S3-hosted images for officer structur…
dismantl Aug 15, 2020
1db45bb
Remove stray comma in officer JSON-LD
dismantl Aug 15, 2020
7abac1c
Fix officer full name display when middle_initial longer than 1 char
dismantl Aug 15, 2020
d5b4d0e
Fix double periods in middle initials
dismantl Aug 19, 2020
b52a1a4
Merge branch 'develop' into seo
dismantl Aug 21, 2020
2cec579
Allow multiple comma-separated values for UII and badge number form f…
dismantl Sep 25, 2020
67adfd9
Move logic from list_officers.html template to view; Remove officer_n…
dismantl Sep 25, 2020
ef9889e
Add field to filter by officer photo availability. Fixes #798
dismantl Sep 25, 2020
36fe52f
flake8 fixes
dismantl Sep 25, 2020
91e5ed7
Merge branch 'develop' into seo
dismantl Sep 26, 2020
f6d4bde
Add job_title() and badge_number() methods to Officer class; move log…
dismantl Sep 26, 2020
6863335
Removed tests for last name capitalization; names should be correctly…
dismantl Sep 27, 2020
6878780
Merge branch 'develop' into seo
dismantl Sep 27, 2020
bd9c357
Simplify incident list title/description
dismantl Sep 27, 2020
c7ba317
Merge branch 'develop' into seo
dismantl Sep 27, 2020
eb415f4
Switch to using job_title() and badge_number() methods in templates
dismantl Sep 27, 2020
2dc8680
Changed AddOfficerForm field job_title -> job_id to be consistent.
dismantl Sep 27, 2020
b6a0ed4
Merge branch 'seo' into list-officer-updates
dismantl Sep 27, 2020
a159bf6
Fixes related to list officer updates. Moved photo filter above unit …
dismantl Sep 27, 2020
07641f2
Added total pay filter to list_officers view
dismantl Sep 27, 2020
62a4b07
Added dropdown menu for sorting list_officers view by: last name, ran…
dismantl Sep 27, 2020
73d5d79
Forgot to change field name in Add Officer form javascript
dismantl Sep 27, 2020
fdc5dfe
Merge branch 'seo' into list-officer-updates
dismantl Sep 27, 2020
e5611a9
Fix bugs in add_officer_profile()
dismantl Sep 27, 2020
3bbd76e
Added tests for new list_officer filters and sorting
dismantl Sep 27, 2020
ff10608
Added First and Last buttons to list_officer pagination in addition t…
dismantl Sep 27, 2020
2f85fa7
minor testing fixes
dismantl Sep 27, 2020
f6528d3
Merge branch 'develop' into list-officer-updates
dismantl Sep 27, 2020
cc0b3dc
Merge branch 'develop' into list-officer-updates
dismantl Sep 28, 2020
2abefcc
Fix to latest db migration, since it would fail if DB had salaries wi…
dismantl Sep 29, 2020
3ecc2bd
Removed unncessary Bootstrap CSS include
dismantl Sep 29, 2020
4911a14
Merge branch 'list-officer-updates' of github.com:lucyparsons/OpenOve…
dismantl Sep 29, 2020
1a8b054
Generated prev/next urls didn't include the new filters
dismantl Sep 29, 2020
6768304
When sorting browse results, nulls last
dismantl Sep 29, 2020
f818509
Bump libsqlite3 version from 3.27.2 -> 3.33.0 in Dockerfile so it sup…
dismantl Sep 29, 2020
9fae02b
Merge branch 'develop' into list-officer-updates
abandoned-prototype Nov 13, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 8 additions & 10 deletions OpenOversight/app/main/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,9 @@ class HumintContribution(Form):


class FindOfficerForm(Form):
name = StringField(
'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 @@ -64,12 +64,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 @@ -391,7 +385,8 @@ class IncidentForm(DateFieldForm):
class BrowseForm(Form):
rank = QuerySelectField('rank', validators=[Optional()], get_label='job_title',
get_pk=lambda job: job.job_title) # query set in view function
name = StringField('Last name')
last_name = StringField('Last name')
first_name = StringField('First name')
badge = StringField('Badge number')
unique_internal_identifier = StringField('Unique ID')
race = SelectField('race', default='Not Sure', choices=RACE_CHOICES,
Expand All @@ -402,4 +397,7 @@ class BrowseForm(Form):
validators=[AnyOf(allowed_values(AGE_CHOICES))])
max_age = SelectField('maximum age', default=100, choices=AGE_CHOICES,
validators=[AnyOf(allowed_values(AGE_CHOICES))])
photo = SelectField('photo', validators=[Optional(), AnyOf(['0', '1'])])
min_pay = DecimalField('min_pay', validators=[Optional(), NumberRange(min=0, max=1000000), validate_money])
max_pay = DecimalField('min_pay', validators=[Optional(), NumberRange(min=0, max=1000000), validate_money])
submit = SubmitField(label='Submit')
56 changes: 39 additions & 17 deletions OpenOversight/app/main/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@
from .forms import (FindOfficerForm, FindOfficerIDForm, AddUnitForm,
FaceTag, AssignmentForm, DepartmentForm, AddOfficerForm,
EditOfficerForm, IncidentForm, TextForm, EditTextForm,
AddImageForm, EditDepartmentForm, BrowseForm, SalaryForm, OfficerLinkForm)
AddImageForm, EditDepartmentForm, BrowseForm, SalaryForm,
OfficerLinkForm)
from .model_view import ModelView
from .choices import GENDER_CHOICES, RACE_CHOICES, AGE_CHOICES
from ..models import (db, Image, User, Face, Officer, Assignment, Department,
Expand Down Expand Up @@ -93,7 +94,7 @@ def get_officer():
unit=form.data['unit'] if form.data['unit'] != 'Not Sure' else None,
min_age=form.data['min_age'],
max_age=form.data['max_age'],
name=form.data['name'],
last_name=form.data['last_name'],
badge=form.data['badge'],
unique_internal_identifier=form.data['unique_internal_identifier']),
code=302)
Expand Down Expand Up @@ -499,8 +500,8 @@ def edit_department(department_id):


@main.route('/department/<int:department_id>')
def list_officer(department_id, page=1, race=[], gender=[], rank=[], min_age='16', max_age='100', name=None,
badge=None, unique_internal_identifier=None, unit=None):
def list_officer(department_id, page=1, order=0, race=[], gender=[], rank=[], min_age='16', max_age='100', last_name=None,
first_name=None, badge=None, unique_internal_identifier=None, unit=None, photo=[], min_pay=None, max_pay=None):
form = BrowseForm()
form.rank.query = Job.query.filter_by(department_id=department_id, is_sworn_officer=True).order_by(Job.order.asc()).all()
form_data = form.data
Expand All @@ -509,10 +510,14 @@ def list_officer(department_id, page=1, race=[], gender=[], rank=[], min_age='16
form_data['rank'] = rank
form_data['min_age'] = min_age
form_data['max_age'] = max_age
form_data['name'] = name
form_data['last_name'] = last_name
form_data['first_name'] = first_name
form_data['badge'] = badge
form_data['unit'] = unit
form_data['unique_internal_identifier'] = unique_internal_identifier
form_data['photo'] = photo
form_data['min_pay'] = min_pay
form_data['max_pay'] = max_pay

OFFICERS_PER_PAGE = int(current_app.config['OFFICERS_PER_PAGE'])
department = Department.query.filter_by(id=department_id).first()
Expand All @@ -526,8 +531,13 @@ def list_officer(department_id, page=1, race=[], gender=[], rank=[], min_age='16
form_data['max_age'] = request.args.get('max_age')
if request.args.get('page'):
page = int(request.args.get('page'))
if request.args.get('name'):
form_data['name'] = request.args.get('name')
if request.args.get('order'):
order = int(request.args.get('order'))
form_data['order'] = order
if request.args.get('last_name'):
form_data['last_name'] = request.args.get('last_name')
if request.args.get('first_name'):
form_data['first_name'] = request.args.get('first_name')
if request.args.get('badge'):
form_data['badge'] = request.args.get('badge')
if request.args.get('unit') and request.args.get('unit') != 'Not Sure':
Expand All @@ -538,13 +548,20 @@ def list_officer(department_id, page=1, race=[], gender=[], rank=[], min_age='16
form_data['race'] = request.args.getlist('race')
if request.args.get('gender') and all(gender in [gc[0] for gc in GENDER_CHOICES] for gender in request.args.getlist('gender')):
form_data['gender'] = request.args.getlist('gender')
if request.args.get('photo') and all(photo in ['0', '1'] for photo in request.args.getlist('photo')):
form_data['photo'] = request.args.getlist('photo')
if request.args.get('min_pay') and re.fullmatch(r'\d+(\.\d\d)?', request.args.get('min_pay')):
form_data['min_pay'] = request.args.get('min_pay')
if request.args.get('max_pay') and re.fullmatch(r'\d+(\.\d\d)?', request.args.get('max_pay')):
form_data['max_pay'] = request.args.get('max_pay')

unit_choices = [(unit.id, unit.descrip) for unit in Unit.query.filter_by(department_id=department_id).order_by(Unit.descrip.asc()).all()]
rank_choices = [jc[0] for jc in db.session.query(Job.job_title, Job.order).filter_by(department_id=department_id, is_sworn_officer=True).order_by(Job.order).all()]
if request.args.get('rank') and all(rank in rank_choices for rank in request.args.getlist('rank')):
form_data['rank'] = request.args.getlist('rank')

officers = filter_by_form(form_data, Officer.query, department_id).filter(Officer.department_id == department_id).order_by(Officer.last_name, Officer.first_name, Officer.id).paginate(page, OFFICERS_PER_PAGE, False)
officers = filter_by_form(form_data, Officer.query, department_id, order).filter(Officer.department_id == department_id)
officers = officers.paginate(page, OFFICERS_PER_PAGE, False)
for officer in officers.items:
officer_face = officer.face.order_by(Face.featured.desc()).first()
if officer_face:
Expand All @@ -557,14 +574,17 @@ def list_officer(department_id, page=1, race=[], gender=[], rank=[], min_age='16
'unit': [('Not Sure', 'Not Sure')] + unit_choices
}

next_url = url_for('main.list_officer', department_id=department.id,
page=officers.next_num, race=form_data['race'], gender=form_data['gender'], rank=form_data['rank'],
min_age=form_data['min_age'], max_age=form_data['max_age'], name=form_data['name'], badge=form_data['badge'],
unique_internal_identifier=form_data['unique_internal_identifier'], unit=form_data['unit'])
prev_url = url_for('main.list_officer', department_id=department.id,
page=officers.prev_num, race=form_data['race'], gender=form_data['gender'], rank=form_data['rank'],
min_age=form_data['min_age'], max_age=form_data['max_age'], name=form_data['name'], badge=form_data['badge'],
unique_internal_identifier=form_data['unique_internal_identifier'], unit=form_data['unit'])
def gen_pagination_url(page):
return url_for('main.list_officer', department_id=department.id,
page=officers.next_num, order=order, race=form_data['race'], gender=form_data['gender'],
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pretty sure this should be page=page instead of page=officers.next_num

rank=form_data['rank'], min_age=form_data['min_age'], max_age=form_data['max_age'],
last_name=form_data['last_name'], first_name=form_data['first_name'], badge=form_data['badge'],
unique_internal_identifier=form_data['unique_internal_identifier'], unit=form_data['unit'],
photo=form_data['photo'], min_pay=form_data['min_pay'], max_pay=form_data['max_pay'])
prev_url = gen_pagination_url(page=officers.prev_num)
next_url = gen_pagination_url(page=officers.next_num)
first_url = gen_pagination_url(page=1)
last_url = gen_pagination_url(page=officers.pages)

return render_template(
'list_officer.html',
Expand All @@ -574,7 +594,9 @@ def list_officer(department_id, page=1, race=[], gender=[], rank=[], min_age='16
form_data=form_data,
choices=choices,
next_url=next_url,
prev_url=prev_url)
prev_url=prev_url,
first_url=first_url,
last_url=last_url)


@main.route('/department/<int:department_id>/ranks')
Expand Down
6 changes: 3 additions & 3 deletions OpenOversight/app/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -176,12 +176,12 @@ class Salary(BaseModel):
officer_id = db.Column(db.Integer, db.ForeignKey('officers.id', ondelete='CASCADE'))
officer = db.relationship('Officer', back_populates='salaries')
salary = db.Column(db.Numeric, index=True, unique=False, nullable=False)
overtime_pay = db.Column(db.Numeric, index=True, unique=False, nullable=True)
overtime_pay = db.Column(db.Numeric, index=True, unique=False, nullable=False, server_default='0')
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Generally NULL represents missing data so I don't think we should disallow it, because there is definitely a chance that we have salary data without overtime payment information for some departments. Was this needed to make any lookups easier/better defined?

year = db.Column(db.Integer, index=True, unique=False, nullable=False)
is_fiscal_year = db.Column(db.Boolean, index=False, unique=False, nullable=False)

def __repr__(self):
return '<Salary: ID {} : {}'.format(self.officer_id, self.salary)
return '<Salary: ID {} : {} {} {}>'.format(self.officer_id, self.salary, self.overtime_pay, self.year)


class Assignment(BaseModel):
Expand Down Expand Up @@ -212,7 +212,7 @@ class Unit(BaseModel):
department = db.relationship('Department', backref='unit_types', order_by='Unit.descrip.asc()')

def __repr__(self):
return 'Unit: {}'.format(self.descrip)
return '<Unit: {}>'.format(self.descrip)


class Face(BaseModel):
Expand Down
37 changes: 37 additions & 0 deletions OpenOversight/app/static/css/openoversight.css
Original file line number Diff line number Diff line change
Expand Up @@ -491,10 +491,39 @@ tr:hover .row-actions {
content: "\e253";
}

.filter-sidebar #filter-pay .form-group {
padding-left: 5px;
padding-right: 5px;
}

.filter-sidebar #filter-pay .input-group-prefix {
position: absolute;
z-index: 4;
line-height: 34px;
left: 9px;
color: #666;
}

.filter-sidebar #filter-pay input.form-control {
padding-left: 21px;
border-top-left-radius: 4px;
border-bottom-left-radius: 4px;
}

.search-results .list-group-item {
border: 0;
}

.search-results .top-paginate > nav {
width: 100%;
}

.search-results .top-sort .sort.form-group {
max-width: 250px;
margin: 0 0 0 auto;
display: block;
}


.console .button-explanation {
height:35px;
Expand Down Expand Up @@ -560,3 +589,11 @@ tr:hover .row-actions {
display:block;
}


.pager .previous.first>a {
margin-right: 7px;
}

.pager .next.last>a {
margin-left: 7px;
}
6 changes: 3 additions & 3 deletions OpenOversight/app/templates/input_find_officer.html
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{% extends "base.html" %}
{% block content %}
{% block title %}Find an officer - OpenOversight{% endblock %}
{% block content %}
{% block meta %}<meta name="description" content="Find an officer you interacted with using this form.">{% endblock %}
<div role="main">
<div class="hero-section no-sub">
Expand Down Expand Up @@ -67,8 +67,8 @@ <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>
Expand Down
Loading