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

WIP: Add v1 data API #1138

Draft
wants to merge 64 commits into
base: develop
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 23 commits
Commits
Show all changes
64 commits
Select commit Hold shift + click to select a range
7057311
Create __init__.py
michplunkett Dec 14, 2024
549ec84
Base API files
michplunkett Dec 14, 2024
5f43d63
Update views.py
michplunkett Dec 17, 2024
bb0aab4
Update views.py
michplunkett Dec 17, 2024
eb02d88
Lint
michplunkett Dec 18, 2024
ddb8233
Remove Flask-Sitemap
michplunkett Dec 18, 2024
04976b8
Move limiter to utils
michplunkett Dec 18, 2024
b9414e8
Add back sitemap
michplunkett Dec 18, 2024
e7c02be
Update views.py
michplunkett Dec 18, 2024
cf5f7a4
Update views.py
michplunkett Dec 18, 2024
ada954b
Update views.py
michplunkett Dec 18, 2024
2742f80
Update views.py
michplunkett Dec 18, 2024
07fe24c
Modify Blueprints
michplunkett Dec 19, 2024
bc57312
Delete officers.py
michplunkett Dec 19, 2024
c1388c4
Delete incidents.py
michplunkett Dec 19, 2024
066f294
Delete departments.py
michplunkett Dec 19, 2024
ff35e1f
Create api.py
michplunkett Dec 19, 2024
5e00b0b
Create api.py
michplunkett Dec 19, 2024
fcb3dd7
Update __init__.py
michplunkett Dec 19, 2024
2ca4be7
Update api.py
michplunkett Dec 19, 2024
bcd27c2
Update api.py
michplunkett Dec 19, 2024
6485464
Update views.py
michplunkett Dec 19, 2024
659993a
Update api.py
michplunkett Dec 19, 2024
6505ce7
Create test_v1_api.py
michplunkett Dec 19, 2024
5a39961
Restructure
michplunkett Dec 19, 2024
8af766a
Delete api.py
michplunkett Dec 19, 2024
88fd11c
Update __init__.py
michplunkett Dec 19, 2024
9a17b02
Create v1.py
michplunkett Dec 19, 2024
0c9b088
Create test_api_v1.py
michplunkett Dec 19, 2024
bd2971f
Update test_api_v1.py
michplunkett Dec 19, 2024
f73b203
Update test_api_v1.py
michplunkett Dec 19, 2024
8a21eca
Update v1.py
michplunkett Dec 19, 2024
55d3d0c
Update test_api_v1.py
michplunkett Dec 19, 2024
1efde38
Update v1.py
michplunkett Dec 19, 2024
60a9e49
Update test_api_v1.py
michplunkett Dec 19, 2024
668b1b6
Update test_api_v1.py
michplunkett Dec 19, 2024
b56d070
Update v1.py
michplunkett Dec 19, 2024
8e3f44f
Update database.py
michplunkett Dec 19, 2024
cb66a22
Update db.py
michplunkett Dec 20, 2024
9a1fe2f
Update __init__.py
michplunkett Dec 20, 2024
2f50bb9
Update test_api_v1.py
michplunkett Dec 20, 2024
fd5f406
Update database.py
michplunkett Dec 20, 2024
e74eff2
Update db.py
michplunkett Dec 20, 2024
6ba9269
repr tests
michplunkett Dec 20, 2024
2fcdca8
Update db.py
michplunkett Dec 20, 2024
7c8dcb1
Update __init__.py
michplunkett Dec 20, 2024
11443db
Update database.py
michplunkett Dec 20, 2024
f610941
Update database.py
michplunkett Dec 20, 2024
74f20d2
Update v1.py
michplunkett Dec 20, 2024
e8535cb
Update database.py
michplunkett Dec 20, 2024
013f4bb
Update views.py
michplunkett Dec 20, 2024
c6b7f52
Update test_models.py
michplunkett Dec 20, 2024
0bacc74
Update database.py
michplunkett Dec 20, 2024
112fd35
Revert "Update views.py"
michplunkett Dec 20, 2024
8f6a4e3
Update database.py
michplunkett Dec 20, 2024
0ba2ec8
Add tests
michplunkett Dec 20, 2024
6d0f577
Update db.py
michplunkett Dec 20, 2024
731085b
Update database.py
michplunkett Dec 20, 2024
6ebd1de
Update views.py
michplunkett Dec 20, 2024
9b95c66
Update database.py
michplunkett Dec 21, 2024
bc618af
Merge branch 'develop' into build-api
michplunkett Jan 2, 2025
88be2c3
Update database.py
michplunkett Jan 2, 2025
42f8e6f
Merge branch 'develop' into build-api
michplunkett Jan 3, 2025
cca977a
Merge branch 'develop' into build-api
michplunkett Jan 6, 2025
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
21 changes: 7 additions & 14 deletions OpenOversight/app/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,20 @@
from flask import Flask, jsonify, render_template, request
from flask_bootstrap import Bootstrap5
from flask_compress import Compress
from flask_limiter import Limiter
from flask_limiter.util import get_remote_address
from flask_login import LoginManager
from flask_migrate import Migrate
from flask_sitemap import Sitemap
from flask_wtf.csrf import CSRFProtect

from OpenOversight.app.api.api import api as api_blueprint
from OpenOversight.app.auth.views import auth as auth_blueprint
from OpenOversight.app.email_client import EmailClient
from OpenOversight.app.filters import instantiate_filters
from OpenOversight.app.main.views import main as main_blueprint
from OpenOversight.app.models.config import config
from OpenOversight.app.models.database import db
from OpenOversight.app.models.users import AnonymousUser
from OpenOversight.app.utils.constants import MEGABYTE
from OpenOversight.app.utils.flask import limiter, sitemap


bootstrap = Bootstrap5()
Expand All @@ -29,11 +30,6 @@
login_manager.anonymous_user = AnonymousUser
login_manager.login_view = "auth.login"

limiter = Limiter(
key_func=get_remote_address, default_limits=["100 per minute", "5 per second"]
)

sitemap = Sitemap()
csrf = CSRFProtect()


Expand All @@ -52,14 +48,11 @@ def create_app(config_name="default"):
sitemap.init_app(app)
compress.init_app(app)

from OpenOversight.app.main import main as main_blueprint

# Add Blueprints
app.register_blueprint(api_blueprint)
app.register_blueprint(auth_blueprint)
app.register_blueprint(main_blueprint)

from OpenOversight.app.auth import auth as auth_blueprint

app.register_blueprint(auth_blueprint, url_prefix="/auth")

max_log_size = 10 * MEGABYTE # start new log file after 10 MB
num_logs_to_keep = 5
file_handler = RotatingFileHandler(
Expand Down
Empty file.
8 changes: 8 additions & 0 deletions OpenOversight/app/api/api.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
from flask import Blueprint

from OpenOversight.app.api.v1.api import v1 as v1_blueprint


api = Blueprint("api", __name__, url_prefix="/api")

api.register_blueprint(v1_blueprint)
Empty file.
38 changes: 38 additions & 0 deletions OpenOversight/app/api/v1/api.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import json
from http import HTTPMethod, HTTPStatus

from flask import Blueprint, current_app
from sqlalchemy.orm import joinedload

from OpenOversight.app.models.database import Assignment, Department, Officer, db
from OpenOversight.app.models.database_cache import (
get_database_cache_entry,
put_database_cache_entry,
)
from OpenOversight.app.utils.constants import KEY_DEPT_ALL_OFFICERS
from OpenOversight.app.utils.flask import limiter


v1 = Blueprint("v1", __name__, url_prefix="/v1")


@v1.route("/departments/<int:department_id>/officers", methods=[HTTPMethod.GET])
@limiter.limit("5/minute")
def download_dept_officers(department_id: int):
cache_params = (Department(id=department_id), KEY_DEPT_ALL_OFFICERS)
officers = get_database_cache_entry(*cache_params)
if officers is None:
officers = (
db.session.query(Officer)
.options(joinedload(Officer.assignments).joinedload(Assignment.job))
.options(joinedload(Officer.salaries))
.filter_by(department_id=department_id)
.all()
)
put_database_cache_entry(*cache_params, officers)

return current_app.response_class(
mimetype="application/json",
response=json.dumps(officers),
status=HTTPStatus.OK,
)
6 changes: 0 additions & 6 deletions OpenOversight/app/auth/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +0,0 @@
from flask import Blueprint


auth = Blueprint("auth", __name__)

from . import views # noqa: E402,F401
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Moved this to ./auth/views.py.

5 changes: 3 additions & 2 deletions OpenOversight/app/auth/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from http import HTTPMethod, HTTPStatus

from flask import (
Blueprint,
current_app,
flash,
redirect,
Expand All @@ -12,8 +13,6 @@
)
from flask_login import current_user, login_required, login_user, logout_user

from OpenOversight.app import sitemap
from OpenOversight.app.auth import auth
from OpenOversight.app.auth.forms import (
ChangeDefaultDepartmentForm,
ChangeEmailForm,
Expand All @@ -36,10 +35,12 @@
)
from OpenOversight.app.utils.auth import admin_required
from OpenOversight.app.utils.constants import KEY_APPROVE_REGISTRATIONS
from OpenOversight.app.utils.flask import sitemap
from OpenOversight.app.utils.forms import set_dynamic_default
from OpenOversight.app.utils.general import validate_redirect_url


auth = Blueprint("auth", __name__, url_prefix="/auth")
js_loads = ["js/zxcvbn.js", "js/password.js"]
sitemap_endpoints = []

Expand Down
6 changes: 0 additions & 6 deletions OpenOversight/app/main/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +0,0 @@
from flask import Blueprint


main = Blueprint("main", __name__)

from OpenOversight.app.main import views # noqa: E402,F401
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Moved this to ./main/views.py.

31 changes: 20 additions & 11 deletions OpenOversight/app/main/views.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import os
import re
import sys
from datetime import datetime
Expand All @@ -7,6 +6,7 @@
from typing import Optional

from flask import (
Blueprint,
Response,
abort,
current_app,
Expand All @@ -24,9 +24,7 @@
from sqlalchemy.orm import contains_eager, joinedload, selectinload
from sqlalchemy.orm.exc import NoResultFound

from OpenOversight.app import limiter, sitemap
from OpenOversight.app.auth.forms import LoginForm
from OpenOversight.app.main import main
from OpenOversight.app.main.downloads import (
assignment_record_maker,
descriptions_record_maker,
Expand Down Expand Up @@ -101,6 +99,7 @@
dept_choices,
unit_choices,
)
from OpenOversight.app.utils.flask import limiter, sitemap
from OpenOversight.app.utils.forms import (
add_new_assignment,
add_officer_profile,
Expand All @@ -124,9 +123,7 @@
)


# Ensure the file is read/write by the creator only
SAVED_UMASK = os.umask(0o077)

main = Blueprint("main", __name__)
sitemap_endpoints = []


Expand Down Expand Up @@ -220,7 +217,8 @@ def get_officer():
def redirect_get_started_labeling():
flash(FLASH_MSG_PERMANENT_REDIRECT)
return redirect(
url_for("main.get_started_labeling"), code=HTTPStatus.PERMANENT_REDIRECT
url_for("main.get_started_labeling"),
code=HTTPStatus.PERMANENT_REDIRECT,
)


Expand Down Expand Up @@ -425,7 +423,9 @@ def redirect_edit_assignment(officer_id: int, assignment_id: int):
flash(FLASH_MSG_PERMANENT_REDIRECT)
return redirect(
url_for(
"main.edit_assignment", officer_id=officer_id, assignment_id=assignment_id
"main.edit_assignment",
officer_id=officer_id,
assignment_id=assignment_id,
),
code=HTTPStatus.PERMANENT_REDIRECT,
)
Expand Down Expand Up @@ -635,7 +635,9 @@ def display_tag(tag_id: int):
def redirect_classify_submission(image_id: int, contains_cops: int):
return redirect(
url_for(
"main.classify_submission", image_id=image_id, contains_cops=contains_cops
"main.classify_submission",
image_id=image_id,
contains_cops=contains_cops,
),
code=HTTPStatus.PERMANENT_REDIRECT,
)
Expand Down Expand Up @@ -819,7 +821,10 @@ def edit_department(department_id: int):
f"You attempted to delete a rank, {rank}, that is still in use"
)
return redirect(
url_for("main.edit_department", department_id=department_id)
url_for(
"main.edit_department",
department_id=department_id,
)
)

for new_rank, order in new_ranks:
Expand Down Expand Up @@ -2305,7 +2310,11 @@ def redirect_edit_description(officer_id: int, obj_id=None):
def redirect_delete_description(officer_id: int, obj_id=None):
flash(FLASH_MSG_PERMANENT_REDIRECT)
return redirect(
url_for("main.description_api_delete", officer_id=officer_id, obj_id=obj_id),
url_for(
"main.description_api_delete",
officer_id=officer_id,
obj_id=obj_id,
),
code=HTTPStatus.PERMANENT_REDIRECT,
)

Expand Down
4 changes: 0 additions & 4 deletions OpenOversight/app/utils/constants.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
import os


# Cache Key Constants
KEY_DEPT_ALL_ASSIGNMENTS = "all_department_assignments"
KEY_DEPT_ALL_INCIDENTS = "all_department_incidents"
Expand Down Expand Up @@ -44,7 +41,6 @@
ENCODING_UTF_8 = "utf-8"
FILE_TYPE_HTML = "html"
FILE_TYPE_PLAIN = "plain"
SAVED_UMASK = os.umask(0o077) # Ensure the file is read/write by the creator only

# File Name Constants
SERVICE_ACCOUNT_FILE = "service_account_key.json"
Expand Down
10 changes: 10 additions & 0 deletions OpenOversight/app/utils/flask.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
from flask_limiter import Limiter
from flask_limiter.util import get_remote_address
from flask_sitemap import Sitemap


limiter: Limiter = Limiter(
key_func=get_remote_address, default_limits=["100 per minute", "5 per second"]
)

sitemap = Sitemap()
Loading