Skip to content

Commit

Permalink
Updated Officer APIs
Browse files Browse the repository at this point in the history
- Get officer by ID
- Get all officers
- Create officer

Fix error in officer model
Add enum property for Partner MemberRole
  • Loading branch information
DMalone87 committed Sep 29, 2024
1 parent 6a2ddd9 commit ec51733
Show file tree
Hide file tree
Showing 5 changed files with 159 additions and 26 deletions.
22 changes: 17 additions & 5 deletions backend/database/models/officer.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,13 @@ def __repr__(self):


class Officer(StructuredNode, JsonSerializable):
__property_order__ = [
"uid", "first_name", "middle_name",
"last_name", "suffix",
"race", "ethnicity", "gender",
"date_of_birth"
]

uid = UniqueIdProperty()
first_name = StringProperty()
middle_name = StringProperty()
Expand All @@ -89,11 +96,16 @@ class Officer(StructuredNode, JsonSerializable):

# Relationships
state_ids = RelationshipTo('StateID', "HAS_STATE_ID")
units = Relationship('Unit', "MEMBER_OF_UNIT")
litigation = Relationship('Litigation', "NAMED_IN")
allegations = Relationship('Allegation', "ACCUSED_OF")
investigations = Relationship('Investigation', "LEAD_BY")
commands = Relationship('Unit', "COMMANDS")
units = Relationship(
'backend.database.models.agency.Unit', "MEMBER_OF_UNIT")
litigation = Relationship(
'backend.database.models.litigation.Litigation', "NAMED_IN")
allegations = Relationship(
'backend.database.models.complaint.Allegation', "ACCUSED_OF")
investigations = Relationship(
'backend.database.models.complaint.Investigation', "LEAD_BY")
commands = Relationship(
'backend.database.models.agency.Unit', "COMMANDS")

def __repr__(self):
return f"<Officer {self.id}>"
9 changes: 9 additions & 0 deletions backend/database/models/partner.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,15 @@ def __init__(self, **kwargs):
date_joined = DateTimeProperty(default=datetime.now())
is_active = BooleanProperty(default=True)

@property
def role_enum(self) -> MemberRole:
"""
Get the role as a MemberRole enum.
Returns:
MemberRole: The role as a MemberRole enum.
"""
return MemberRole(self.role)

def is_administrator(self):
return self.role == MemberRole.ADMIN

Expand Down
38 changes: 18 additions & 20 deletions backend/routes/officers.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,13 @@
from backend.auth.jwt import min_role_required
from backend.mixpanel.mix import track_to_mp
from mixpanel import MixpanelException
from backend.database.models.user import UserRole
from backend.schemas import validate_request, ordered_jsonify, paginate_results
from backend.database.models.user import UserRole, User
from backend.database.models.agency import Agency
from backend.database.models.officer import Officer
from .tmp.pydantic.officers import CreateOfficer, UpdateOfficer
from flask import Blueprint, abort, request
from flask_jwt_extended import get_jwt
from flask_jwt_extended.view_decorators import jwt_required
from pydantic import BaseModel

Expand Down Expand Up @@ -128,16 +131,21 @@ class AddEmploymentListSchema(BaseModel):
@bp.route("/", methods=["POST"])
@jwt_required()
@min_role_required(UserRole.CONTRIBUTOR)
#@validate(json=CreateOfficerSchema)
@validate_request(CreateOfficer)
def create_officer():
"""Create an officer profile.
"""
logger = logging.getLogger("create_officer")
body: CreateOfficer = request.validated_body
jwt_decoded = get_jwt()
current_user = User.get(jwt_decoded["sub"])

try:
officer = Officer.from_dict(request.context.json)
except Exception as e:
abort(400, description=str(e))
# try:
officer = Officer.from_dict(body.dict())
# except Exception as e:
# abort(400, description=str(e))

logger.info(f"Officer {officer.uid} created by User {current_user.uid}")
track_to_mp(
request,
"create_officer",
Expand All @@ -149,10 +157,9 @@ def create_officer():


# Get an officer profile
@bp.route("/<int:officer_id>", methods=["GET"])
@bp.route("/<officer_id>", methods=["GET"])
@jwt_required()
@min_role_required(UserRole.PUBLIC)
# @validate()
def get_officer(officer_id: int):
"""Get an officer profile.
"""
Expand All @@ -166,7 +173,6 @@ def get_officer(officer_id: int):
@bp.route("/", methods=["GET"])
@jwt_required()
@min_role_required(UserRole.PUBLIC)
# @validate()
def get_all_officers():
"""Get all officers.
Accepts Query Parameters for pagination:
Expand All @@ -178,24 +184,16 @@ def get_all_officers():
q_per_page = args.get("per_page", 20, type=int)

all_officers = Officer.nodes.all()
pagination = all_officers.paginate(
page=q_page, per_page=q_per_page, max_per_page=100
)
results = paginate_results(all_officers, q_page, q_per_page)

return {
"results": [
officer_orm_to_json(officer) for officer in pagination.items],
"page": pagination.page,
"totalPages": pagination.pages,
"totalResults": pagination.total,
}
return ordered_jsonify(results), 200


# Update an officer profile
@bp.route("/<int:officer_id>", methods=["PUT"])
@jwt_required()
@min_role_required(UserRole.CONTRIBUTOR)
#@validate(json=CreateOfficerSchema)
@validate_request(UpdateOfficer)
def update_officer(officer_id: int):
"""Update an officer profile.
"""
Expand Down
1 change: 0 additions & 1 deletion backend/routes/partners.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@
@bp.route("/<partner_id>", methods=["GET"])
@jwt_required()
@min_role_required(UserRole.PUBLIC)
# @validate()
def get_partners(partner_id: str):
"""Get a single partner by ID."""
p = Partner.nodes.get_or_none(uid=partner_id)
Expand Down
115 changes: 115 additions & 0 deletions backend/routes/tmp/pydantic/officers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
from pydantic import BaseModel, Field
from typing import List, Optional, Dict, Any, Union
from .common import PaginatedResponse


class StateId(BaseModel):
uid: Optional[str] = Field(None, description="The UUID of this state id")
state: Optional[str] = Field(None, description="The state of the state id")
id_name: Optional[str] = Field(None, description="The name of the id. For example, Tax ID, Driver's License, etc.")
value: Optional[str] = Field(None, description="The value of the id.")


class BaseEmployment(BaseModel):
officer_uid: Optional[str] = Field(None, description="The UID of the officer.")
agency_uid: Optional[str] = Field(None, description="The UID of the agency the officer is employed by.")
unit_uid: Optional[str] = Field(None, description="The UID of the unit the officer is assigned to.")
earliest_employment: Optional[str] = Field(None, description="The earliest known date of employment")
latest_employment: Optional[str] = Field(None, description="The latest known date of employment")
badge_number: Optional[str] = Field(None, description="The badge number of the officer")
highest_rank: Optional[str] = Field(None, description="The highest rank the officer has held during this employment.")
commander: Optional[bool] = Field(None, description="Indicates that the officer commanded the unit during this employment.")


class AddEmployment(BaseEmployment, BaseModel):
officer_uid: Optional[str] = Field(None, description="The UID of the officer.")
agency_uid: Optional[str] = Field(None, description="The UID of the agency the officer is employed by.")
unit_uid: Optional[str] = Field(None, description="The UID of the unit the officer is assigned to.")
earliest_employment: Optional[str] = Field(None, description="The earliest known date of employment")
latest_employment: Optional[str] = Field(None, description="The latest known date of employment")
badge_number: Optional[str] = Field(None, description="The badge number of the officer")
highest_rank: Optional[str] = Field(None, description="The highest rank the officer has held during this employment.")
commander: Optional[bool] = Field(None, description="Indicates that the officer commanded the unit during this employment.")


class AddEmploymentFailed(BaseModel):
agency_uid: Optional[str] = Field(None, description="The uid of the agency that could not be added.")
reason: Optional[str] = Field(None, description="The reason the employment record could not be added")


class AddEmploymentList(BaseModel):
agencies: Optional[List[AddEmployment]] = Field(None, description="The units to add to the officer's employment history.")


class Employment(BaseEmployment, BaseModel):
officer_uid: Optional[str] = Field(None, description="The UID of the officer.")
agency_uid: Optional[str] = Field(None, description="The UID of the agency the officer is employed by.")
unit_uid: Optional[str] = Field(None, description="The UID of the unit the officer is assigned to.")
earliest_employment: Optional[str] = Field(None, description="The earliest known date of employment")
latest_employment: Optional[str] = Field(None, description="The latest known date of employment")
badge_number: Optional[str] = Field(None, description="The badge number of the officer")
highest_rank: Optional[str] = Field(None, description="The highest rank the officer has held during this employment.")
commander: Optional[bool] = Field(None, description="Indicates that the officer commanded the unit during this employment.")


class AddEmploymentResponse(BaseModel):
created: List[Employment] = ...
failed: List[AddEmploymentFailed] = ...
total_created: int = ...
total_failed: int = ...


class EmploymentList(PaginatedResponse, BaseModel):
results: Optional[List[Employment]] = None


class BaseOfficer(BaseModel):
first_name: Optional[str] = Field(None, description="First name of the officer")
middle_name: Optional[str] = Field(None, description="Middle name of the officer")
last_name: Optional[str] = Field(None, description="Last name of the officer")
suffix: Optional[str] = Field(None, description="Suffix of the officer's name")
ethnicity: Optional[str] = Field(None, description="The ethnicity of the officer")
gender: Optional[str] = Field(None, description="The gender of the officer")
date_of_birth: Optional[str] = Field(None, description="The date of birth of the officer")
state_ids: Optional[List[StateId]] = Field(None, description="The state ids of the officer")


class CreateOfficer(BaseOfficer, BaseModel):
first_name: Optional[str] = Field(None, description="First name of the officer")
middle_name: Optional[str] = Field(None, description="Middle name of the officer")
last_name: Optional[str] = Field(None, description="Last name of the officer")
suffix: Optional[str] = Field(None, description="Suffix of the officer's name")
ethnicity: Optional[str] = Field(None, description="The ethnicity of the officer")
gender: Optional[str] = Field(None, description="The gender of the officer")
date_of_birth: Optional[str] = Field(None, description="The date of birth of the officer")
state_ids: Optional[List[StateId]] = Field(None, description="The state ids of the officer")


class UpdateOfficer(BaseOfficer, BaseModel):
first_name: Optional[str] = Field(None, description="First name of the officer")
middle_name: Optional[str] = Field(None, description="Middle name of the officer")
last_name: Optional[str] = Field(None, description="Last name of the officer")
suffix: Optional[str] = Field(None, description="Suffix of the officer's name")
ethnicity: Optional[str] = Field(None, description="The ethnicity of the officer")
gender: Optional[str] = Field(None, description="The gender of the officer")
date_of_birth: Optional[str] = Field(None, description="The date of birth of the officer")
state_ids: Optional[List[StateId]] = Field(None, description="The state ids of the officer")


class Officer(BaseOfficer, BaseModel):
first_name: Optional[str] = Field(None, description="First name of the officer")
middle_name: Optional[str] = Field(None, description="Middle name of the officer")
last_name: Optional[str] = Field(None, description="Last name of the officer")
suffix: Optional[str] = Field(None, description="Suffix of the officer's name")
ethnicity: Optional[str] = Field(None, description="The ethnicity of the officer")
gender: Optional[str] = Field(None, description="The gender of the officer")
date_of_birth: Optional[str] = Field(None, description="The date of birth of the officer")
state_ids: Optional[List[StateId]] = Field(None, description="The state ids of the officer")
uid: Optional[str] = Field(None, description="The uid of the officer")
employment_history: Optional[str] = Field(None, description="A link to retrieve the employment history of the officer")
allegations: Optional[str] = Field(None, description="A link to retrieve the allegations against the officer")
litigation: Optional[str] = Field(None, description="A link to retrieve the litigation against the officer")


class OfficerList(PaginatedResponse, BaseModel):
results: Optional[List[Officer]] = None

0 comments on commit ec51733

Please sign in to comment.