Skip to content

Commit

Permalink
Merge pull request #300 from lucyparsons/multi-department-support
Browse files Browse the repository at this point in the history
Initial multi department support
  • Loading branch information
r4v5 authored Sep 18, 2017
2 parents 00dc245 + 564d210 commit 949f0d4
Show file tree
Hide file tree
Showing 12 changed files with 225 additions and 7 deletions.
27 changes: 27 additions & 0 deletions OpenOversight/app/db_repository/versions/003_migration.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
from sqlalchemy import *
from migrate import *


from migrate.changeset import schema
pre_meta = MetaData()
post_meta = MetaData()
departments = Table('departments', post_meta,
Column('id', Integer, primary_key=True, nullable=False),
Column('name', String(length=255), index=True, nullable=False),
Column('short_name', String(length=100), nullable=False),
)


def upgrade(migrate_engine):
# Upgrade operations go here. Don't create your own engine; bind
# migrate_engine to your metadata
pre_meta.bind = migrate_engine
post_meta.bind = migrate_engine
post_meta.tables['departments'].create()


def downgrade(migrate_engine):
# Operations to reverse the above upgrade go here.
pre_meta.bind = migrate_engine
post_meta.bind = migrate_engine
post_meta.tables['departments'].drop()
12 changes: 12 additions & 0 deletions OpenOversight/app/main/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,3 +100,15 @@ class AssignmentForm(Form):
unit = QuerySelectField('unit', validators=[Optional()],
query_factory=unit_choices)
star_date = DateField('star_date', validators=[Optional()])


class DepartmentForm(Form):
name = StringField(
'Full name of police department, e.g. Chicago Police Department',
default='', validators=[Regexp('\w*'), Length(max=255), DataRequired()]
)
short_name = StringField(
'Shortened acronym for police department, e.g. CPD',
default='', validators=[Regexp('\w*'), Length(max=100), DataRequired()]
)
submit = SubmitField(label='Add')
31 changes: 29 additions & 2 deletions OpenOversight/app/main/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@
serve_image, compute_leaderboard_stats, get_random_image,
allowed_file, add_new_assignment)
from .forms import (FindOfficerForm, FindOfficerIDForm,
FaceTag, AssignmentForm)
from ..models import db, Image, User, Face, Officer, Assignment
FaceTag, AssignmentForm, DepartmentForm)
from ..models import db, Image, User, Face, Officer, Assignment, Department

# Ensure the file is read/write by the creator only
SAVED_UMASK = os.umask(0o077)
Expand Down Expand Up @@ -191,6 +191,33 @@ def classify_submission(image_id, contains_cops):
# return redirect(url_for('main.display_submission', image_id=image_id))


@main.route('/departments')
def department_overview():
departments = Department.query.all()
return render_template('departments.html', departments=departments)


@main.route('/department/new', methods=['GET', 'POST'])
@login_required
@admin_required
def add_department():
form = DepartmentForm()
if form.validate_on_submit():
departments = [x[0] for x in db.session.query(Department.name).all()]

if form.name.data not in departments:
department = Department(name=form.name.data,
short_name=form.short_name.data)
db.session.add(department)
db.session.commit()
flash('New department {} added to OpenOversight'.format(department.name))
else:
flash('Department {} already exists'.format(form.name.data))
return redirect(url_for('main.department_overview'))
else:
return render_template('add_department.html', form=form)


@main.route('/tag/delete/<int:tag_id>', methods=['POST'])
@login_required
@admin_required
Expand Down
10 changes: 10 additions & 0 deletions OpenOversight/app/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,16 @@
db = SQLAlchemy()


class Department(db.Model):
__tablename__ = 'departments'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(255), index=True, unique=True, nullable=False)
short_name = db.Column(db.String(100), unique=False, nullable=False)

def __repr__(self):
return '<Department ID {}: {}>'.format(self.id, self.name)


class Officer(db.Model):
__tablename__ = 'officers'

Expand Down
24 changes: 24 additions & 0 deletions OpenOversight/app/templates/add_department.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{% extends "base.html" %}
{% import "bootstrap/wtf.html" as wtf %}
{% block title %}OpenOversight Admin - Add Department{% endblock %}

{% block content %}
<div class="container theme-showcase" role="main">

<div class="page-header">
<h1>Add Department</h1>
</div>
<div class="col-md-6">
<form class="form" method="post" role="form">
{{ form.hidden_tag() }}
{{ wtf.form_errors(form, hiddens="only") }}

{{ wtf.form_field(form.name, autofocus="autofocus") }}
{{ wtf.form_field(form.short_name) }}
{{ wtf.form_field(form.submit, id="submit", button_map={'submit':'primary'}) }}
</form>
<br>
</div>

</div>
{% endblock %}
3 changes: 3 additions & 0 deletions OpenOversight/app/templates/base.html
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,9 @@
<li><a href="/about">About</a></li>
</ul>
<ul class="nav navbar-nav navbar-right">
{% if current_user.is_administrator %}
<li><a href="{{ url_for('main.department_overview') }}">Departments</a></li>
{% endif %}
{% if current_user and current_user.is_authenticated %}
<li><a href="{{ url_for('main.profile', username=current_user.username) }}">Profile</a></li>
<li class="dropdown">
Expand Down
30 changes: 30 additions & 0 deletions OpenOversight/app/templates/departments.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
{% extends "base.html" %}
{% block content %}

<div class="container" role="main">

<div class="text-center">
<h1><small>Departments</small></h1>
</div>

<div class="text-center frontpage-leads">

{% for department in departments %}

<h4><small>{{ department.name }} ({{department.short_name}})</small></h4>

{% endfor %}

{% if current_user.is_administrator %}
<a href="{{ url_for('main.add_department') }}"
class="btn btn-primary" role="button">
<span class="glyphicon glyphicon-plus" aria-hidden="true"></span>
Add New Department
</a>
{% endif %}

</div>

</div>

{% endblock %}
6 changes: 6 additions & 0 deletions OpenOversight/tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,11 @@ def mockdata(session, request):
session.add_all(assignments)
session.add_all(faces)

department = models.Department(name='Springfield Police Department',
short_name='SPD')
session.add(department)
session.commit()

test_user = models.User(email='[email protected]',
username='test_user',
password='dog',
Expand Down Expand Up @@ -196,6 +201,7 @@ def teardown():
models.Image.query.delete()
models.Face.query.delete()
models.Unit.query.delete()
models.Department.query.delete()
session.commit()
session.flush()

Expand Down
7 changes: 6 additions & 1 deletion OpenOversight/tests/test_models.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
from pytest import raises
import time
from OpenOversight.app.models import (Officer, Assignment, Face, Image, Unit,
User, db)
User, db, Department)


def test_department_repr(mockdata):
department = Department.query.first()
assert department.__repr__() == '<Department ID {}: {}>'.format(department.id, department.name)


def test_officer_repr(mockdata):
Expand Down
69 changes: 67 additions & 2 deletions OpenOversight/tests/test_routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@


from OpenOversight.app.main.forms import (FindOfficerIDForm, AssignmentForm,
FaceTag)
FaceTag, DepartmentForm)
from OpenOversight.app.auth.forms import (LoginForm, RegistrationForm,
ChangePasswordForm, PasswordResetForm,
PasswordResetRequestForm,
ChangeEmailForm)
from OpenOversight.app.models import User, Face
from OpenOversight.app.models import User, Face, Department


@pytest.mark.parametrize("route", [
Expand All @@ -22,6 +22,7 @@
('/privacy'),
('/submit'),
('/label'),
('/departments'),
('/officer/3'),
('/tutorial'),
('/auth/login'),
Expand All @@ -43,6 +44,7 @@ def test_routes_ok(route, client, mockdata):
('/image/tagged/1'),
('/tag/1'),
('/leaderboard'),
('/department/new'),
('/auth/logout'),
('/auth/confirm/abcd1234'),
('/auth/confirm'),
Expand Down Expand Up @@ -606,3 +608,66 @@ def test_user_cannot_change_password_if_they_dont_match(mockdata, client,
)

assert 'Passwords must match' in rv.data


def test_admin_can_add_police_department(mockdata, client, session):
with current_app.test_request_context():
login_admin(client)

form = DepartmentForm(name='Test Police Department',
short_name='TPD')

rv = client.post(
url_for('main.add_department'),
data=form.data,
follow_redirects=True
)

assert 'New department' in rv.data

# Check the department was added to the database
department = Department.query.filter_by(
name='Test Police Department').one()
assert department.short_name == 'TPD'


def test_admin_cannot_add_duplicate_police_department(mockdata, client,
session):
with current_app.test_request_context():
login_admin(client)

form = DepartmentForm(name='Chicago Police Department',
short_name='CPD')

rv = client.post(
url_for('main.add_department'),
data=form.data,
follow_redirects=True
)

# Try to add the same police department again
rv = client.post(
url_for('main.add_department'),
data=form.data,
follow_redirects=True
)

assert 'already exists' in rv.data

# Check that only one department was added to the database
# one() method will throw exception if more than one department found
department = Department.query.filter_by(
name='Chicago Police Department').one()
assert department.short_name == 'CPD'


def test_admin_can_see_department_list(mockdata, client, session):
with current_app.test_request_context():
login_admin(client)

rv = client.get(
url_for('main.department_overview'),
follow_redirects=True
)

assert 'Departments' in rv.data
3 changes: 1 addition & 2 deletions Vagrantfile
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,7 @@ Vagrant.configure(2) do |config|
config.vm.box = "ubuntu/trusty64"

# Create a forwarded port mapping which allows access to a specific port
# within the machine from a port on the host machine. In the example below,
# accessing "localhost:8080" will access port 80 on the guest machine.
# within the machine from a port on the host machine.
config.vm.network "forwarded_port", guest: 3000, host: 3000

# Provider-specific configuration so you can fine-tune various
Expand Down
10 changes: 10 additions & 0 deletions test_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,11 @@ def populate():
db.session.add_all(test_images)
db.session.commit()

department = models.Department(name='Springfield Police Department',
short_name='SPD')
db.session.add(department)
db.session.commit()

officers = [generate_officer() for o in range(NUM_OFFICERS)]
db.session.add_all(officers)
db.session.commit()
Expand All @@ -120,6 +125,7 @@ def populate():
test_user = models.User(email='[email protected]',
username='test_user',
password='testtest',
is_administrator=True,
confirmed=True)
db.session.add(test_user)
db.session.commit()
Expand All @@ -137,6 +143,10 @@ def cleanup():
for face in faces:
db.session.delete(face)

departments = models.Department.query.all()
for dept in departments:
db.session.delete(dept)

officers = models.Officer.query.all()
for po in officers:
db.session.delete(po)
Expand Down

0 comments on commit 949f0d4

Please sign in to comment.