Skip to content

Commit

Permalink
Added Simple Get Route for Incidents
Browse files Browse the repository at this point in the history
  • Loading branch information
RyEggGit committed Feb 3, 2024
1 parent e99bcaa commit d5c6f8d
Show file tree
Hide file tree
Showing 5 changed files with 217 additions and 47 deletions.
51 changes: 30 additions & 21 deletions backend/database/models/incident.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
"""Define the SQL classes for Users."""
from __future__ import annotations # allows type hinting of class itself
import enum
from datetime import datetime

from ..core import CrudMixin, db
from backend.database.models._assoc_tables import incident_agency, incident_tag
from sqlalchemy.orm import RelationshipProperty


class RecordType(enum.Enum):
Expand Down Expand Up @@ -57,13 +58,20 @@ class VictimStatus(enum.Enum):


class Incident(db.Model, CrudMixin):

"""The incident table is the fact table."""

# Just a note: this only explicitly used for type hinting
def __init__(self, **kwargs):
super().__init__(**kwargs)

id = db.Column(db.Integer, primary_key=True, autoincrement=True)
source_id = db.Column(
db.Integer, db.ForeignKey("partner.id"))
source_id: RelationshipProperty[int] = db.Column(
db.Integer, db.ForeignKey("partner.id")
)
source_details = db.relationship(
"SourceDetails", backref="incident", uselist=False)
"SourceDetails", backref="incident", uselist=False
)
date_record_created = db.Column(db.DateTime)
time_of_incident = db.Column(db.DateTime)
time_confidence = db.Column(db.Integer)
Expand All @@ -89,7 +97,8 @@ class Incident(db.Model, CrudMixin):
# descriptions = db.relationship("Description", backref="incident")
tags = db.relationship("Tag", secondary=incident_tag, backref="incidents")
agencies_present = db.relationship(
"Agency", secondary=incident_agency, backref="recorded_incidents")
"Agency", secondary=incident_agency, backref="recorded_incidents"
)
participants = db.relationship("Participant", backref="incident")
attachments = db.relationship("Attachment", backref="incident")
investigations = db.relationship("Investigation", backref="incident")
Expand Down Expand Up @@ -119,22 +128,22 @@ def create(self, refresh: bool = True):
# )
# text = db.Column(db.Text)
# type = db.Column(db.Text) # TODO: enum
# TODO: are there rules for this column other than text?
# organization_id = db.Column(db.Text)
# location = db.Column(db.Text) # TODO: location object
# # TODO: neighborhood seems like a weird identifier that may not always
# # apply in consistent ways across municipalities.
# neighborhood = db.Column(db.Text)
# stop_type = db.Column(db.Text) # TODO: enum
# call_type = db.Column(db.Text) # TODO: enum
# has_multimedia = db.Column(db.Boolean)
# from_report = db.Column(db.Boolean)
# # These may require an additional table. Also can dox a victim
# was_victim_arrested = db.Column(db.Boolean)
# arrest_id = db.Column(db.Integer) # TODO: foreign key of some sort?
# # Does an existing warrant count here?
# criminal_case_brought = db.Column(db.Boolean)
# case_id = db.Column(db.Integer) # TODO: foreign key of some sort?
# TODO: are there rules for this column other than text?
# organization_id = db.Column(db.Text)
# location = db.Column(db.Text) # TODO: location object
# # TODO: neighborhood seems like a weird identifier that may not always
# # apply in consistent ways across municipalities.
# neighborhood = db.Column(db.Text)
# stop_type = db.Column(db.Text) # TODO: enum
# call_type = db.Column(db.Text) # TODO: enum
# has_multimedia = db.Column(db.Boolean)
# from_report = db.Column(db.Boolean)
# # These may require an additional table. Also can dox a victim
# was_victim_arrested = db.Column(db.Boolean)
# arrest_id = db.Column(db.Integer) # TODO: foreign key of some sort?
# # Does an existing warrant count here?
# criminal_case_brought = db.Column(db.Boolean)
# case_id = db.Column(db.Integer) # TODO: foreign key of some sort?


class SourceDetails(db.Model):
Expand Down
21 changes: 12 additions & 9 deletions backend/database/models/partner.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@

from __future__ import annotations # allows type hinting of class itself
from sqlalchemy.ext.associationproxy import association_proxy
from sqlalchemy.orm import RelationshipProperty
from ..core import db, CrudMixin
from enum import Enum
from datetime import datetime
Expand Down Expand Up @@ -27,10 +28,10 @@ def get_value(self):
class PartnerMember(db.Model, CrudMixin):
__tablename__ = "partner_user"
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
partner_id = db.Column(db.Integer, db.ForeignKey('partner.id'),
primary_key=True)
user_id = db.Column(db.Integer, db.ForeignKey('user.id'),
primary_key=True)
partner_id = db.Column(
db.Integer, db.ForeignKey("partner.id"), primary_key=True
)
user_id = db.Column(db.Integer, db.ForeignKey("user.id"), primary_key=True)
user = db.relationship("User", back_populates="partner_association")
partner = db.relationship("Partner", back_populates="member_association")
role = db.Column(db.Enum(MemberRole))
Expand All @@ -53,10 +54,12 @@ class Partner(db.Model, CrudMixin):
name = db.Column(db.Text)
url = db.Column(db.Text)
contact_email = db.Column(db.Text)
reported_incidents = db.relationship(
'Incident', backref='source', lazy="select")
member_association = db.relationship(
'PartnerMember', back_populates="partner", lazy="select")
reported_incidents: RelationshipProperty[int] = db.relationship(
"Incident", backref="source", lazy="select"
)
member_association: RelationshipProperty[PartnerMember] = db.relationship(
"PartnerMember", back_populates="partner", lazy="select"
)
members = association_proxy("member_association", "user")

def __repr__(self):
Expand Down
56 changes: 50 additions & 6 deletions backend/routes/partners.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
from typing import Any

from backend.auth.jwt import min_role_required
from backend.mixpanel.mix import track_to_mp
from backend.database.models.user import User, UserRole
from flask import Blueprint, abort, current_app, request
from flask_jwt_extended import get_jwt
from flask_jwt_extended.view_decorators import jwt_required
from flask_sqlalchemy import Pagination
from ..database import Partner, PartnerMember, MemberRole, db
from ..database import Partner, PartnerMember, MemberRole, db, Incident
from ..schemas import (
CreatePartnerSchema,
AddMemberSchema,
Expand All @@ -15,8 +17,10 @@
partner_member_to_orm,
partner_to_orm,
validate,
incident_orm_to_json,
)


bp = Blueprint("partner_routes", __name__, url_prefix="/api/v1/partners")


Expand Down Expand Up @@ -146,6 +150,9 @@ class Config:
@min_role_required(UserRole.PUBLIC)
@validate() # type: ignore
def get_partner_users(partner_id: int):
# check if the partner exists
partner = Partner.get(partner_id)

# Get the page number from the query parameters (default to 1)
page = request.args.get("page", 1, type=int)

Expand All @@ -155,13 +162,9 @@ def get_partner_users(partner_id: int):
# Query the PartnerMember table for records with
# the given partner_id and paginate the results
pagination: Pagination = PartnerMember.query.filter_by(
partner_id=partner_id
partner_id=partner.id
).paginate(page=page, per_page=per_page, error_out=False)

# If the partner_id is invalid, return a 404 error
if pagination.total == 0:
return {"message": "Partner not found"}, 404

# Get the User objects associated with the members on the current page
users: list[User] = [
User.query.get(member.user_id) for member in pagination.items
Expand Down Expand Up @@ -242,3 +245,44 @@ def add_member_to_partner(partner_id: int):
},
)
return partner_member_orm_to_json(created)


@bp.route("/<int:partner_id>/incidents", methods=["GET"])
@jwt_required() # type: ignore
@min_role_required(UserRole.PUBLIC)
@validate() # type: ignore
def get_incidents(partner_id: int):
"""
Get all incidents associated with a partner.
Accepts Query Parameters for pagination:
per_page: number of results per page
page: page number
"""

# check if the partner exists
partner: Partner = Partner.get(partner_id) # type: ignore

# Get the page number from the query parameters (default to 1)
page = request.args.get("page", 1, type=int)

# Get the number of items per page from the query parameters (default to 20)
per_page = request.args.get("per_page", 20, type=int)

# Query the Incident table for records with the given partner_id
# and paginate the results
pagination: Any = Incident.query.filter_by(source_id=partner.id).paginate(
page=page, per_page=per_page, error_out=False
)

incidents: list[dict[str, Any]] = [
incident_orm_to_json(incident) for incident in pagination.items
]

# Convert the Incident objects to dictionaries and return them as JSON
return {
"results": incidents,
"page": pagination.page,
"totalPages": pagination.pages,
"totalResults": pagination.total,
}
52 changes: 43 additions & 9 deletions backend/tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@
from backend.auth import user_manager
from backend.config import TestingConfig
from backend.database import User, UserRole, db
from backend.database import Partner, PartnerMember, MemberRole
from backend.database import Partner, PartnerMember, MemberRole, Incident
from datetime import datetime
from pytest_postgresql.janitor import DatabaseJanitor
from sqlalchemy import insert
from typing import Any

example_email = "[email protected]"
admin_email = "[email protected]"
Expand Down Expand Up @@ -81,6 +82,35 @@ def example_user(db_session):
return user


@pytest.fixture # type: ignore
def example_incidents(db_session: Any, example_partner: Partner):
incidents = [
Incident(
source_id=example_partner.id,
date_record_created=datetime.now(),
time_of_incident=datetime.now(),
time_confidence=90,
complaint_date=datetime.now().date(),
closed_date=datetime.now().date(),
location="Location 1",
longitude=12.34,
latitude=56.78,
description="Description 1",
stop_type="Stop Type 1",
call_type="Call Type 1",
has_attachments=True,
from_report=True,
was_victim_arrested=False,
criminal_case_brought=True,
)
]
for incident in incidents:
db_session.add(incident)
db_session.commit()

return incidents


@pytest.fixture
def admin_user(db_session):
user = User(
Expand All @@ -102,17 +132,19 @@ def partner_admin(db_session, example_partner):
email=p_admin_email,
password=user_manager.hash_password(example_password),
role=UserRole.CONTRIBUTOR, # This is not a system admin,
# so we can't use ADMIN here
# so we can't use ADMIN here
first_name="contributor",
last_name="last",
phone_number="(012) 345-6789",
)
db_session.add(user)
db_session.commit()
insert_statement = insert(PartnerMember).values(
partner_id=example_partner.id, user_id=user.id,
role=MemberRole.ADMIN, date_joined=datetime.now(),
is_active=True
partner_id=example_partner.id,
user_id=user.id,
role=MemberRole.ADMIN,
date_joined=datetime.now(),
is_active=True,
)
db_session.execute(insert_statement)
db_session.commit()
Expand All @@ -127,14 +159,16 @@ def partner_publisher(db_session, example_partner):
password=user_manager.hash_password(example_password),
role=UserRole.CONTRIBUTOR,
first_name="contributor",
last_name="last"
last_name="last",
)
db_session.add(user)
db_session.commit()
insert_statement = insert(PartnerMember).values(
partner_id=example_partner.id, user_id=user.id,
role=MemberRole.PUBLISHER, date_joined=datetime.now(),
is_active=True
partner_id=example_partner.id,
user_id=user.id,
role=MemberRole.PUBLISHER,
date_joined=datetime.now(),
is_active=True,
)
db_session.execute(insert_statement)
db_session.commit()
Expand Down
Loading

0 comments on commit d5c6f8d

Please sign in to comment.