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

[FEAT] Convert the Unit property into a db Unit model #398

1 change: 1 addition & 0 deletions backend/database/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
# Alembic only does a couple models, not all of them.

from .models.agency import *
from .models.unit import *
from .models.attorney import *
from .models.case_document import *
from .models.incident import *
Expand Down
4 changes: 3 additions & 1 deletion backend/database/models/agency.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from ..core import db, CrudMixin
from ..core import CrudMixin, db
from enum import Enum
from sqlalchemy.ext.associationproxy import association_proxy

Expand All @@ -22,6 +22,8 @@ class Agency(db.Model, CrudMixin):
jurisdiction = db.Column(db.Enum(Jurisdiction))
# total_officers = db.Column(db.Integer)

units = db.relationship("Unit", back_populates="agency")

officer_association = db.relationship("Employment", back_populates="agency")
officers = association_proxy("officer_association", "officer")

Expand Down
8 changes: 3 additions & 5 deletions backend/database/models/employment.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,16 @@ class Employment(db.Model, CrudMixin):
id = db.Column(db.Integer, primary_key=True)
officer_id = db.Column(db.Integer, db.ForeignKey("officer.id"))
agency_id = db.Column(db.Integer, db.ForeignKey("agency.id"))
unit_id = db.Column(db.Integer, db.ForeignKey("unit.id"))
earliest_employment = db.Column(db.Text)
latest_employment = db.Column(db.Text)
badge_number = db.Column(db.Text)
unit = db.Column(db.Text)
highest_rank = db.Column(db.Enum(Rank))
currently_employed = db.Column(db.Boolean)

officer = db.relationship("Officer", back_populates="agency_association")
agency = db.relationship("Agency", back_populates="officer_association")
unit = db.relationship("Unit", back_populates="officer_association")

def __repr__(self):
return f"<Employment {self.id}>"
Expand Down Expand Up @@ -65,7 +66,6 @@ def get_highest_rank(records: list[Employment]):

def merge_employment_records(
records: list[Employment],
unit: str = None,
currently_employed: bool = None
):
"""
Expand All @@ -85,17 +85,15 @@ def merge_employment_records(
"""
earliest_employment, latest_employment = get_employment_range(records)
highest_rank = get_highest_rank(records)
if unit is None:
unit = records[0].unit
if currently_employed is None:
currently_employed = records[0].currently_employed
return Employment(
officer_id=records[0].officer_id,
agency_id=records[0].agency_id,
unit_id=records[0].unit_id,
badge_number=records[0].badge_number,
earliest_employment=earliest_employment,
latest_employment=latest_employment,
unit=unit,
highest_rank=highest_rank,
currently_employed=currently_employed,
)
25 changes: 25 additions & 0 deletions backend/database/models/unit.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
from ..core import CrudMixin, db
from sqlalchemy.ext.associationproxy import association_proxy


class Unit(db.Model, CrudMixin):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.Text)
website_url = db.Column(db.Text)
phone = db.Column(db.Text)
email = db.Column(db.Text)
description = db.Column(db.Text)
address = db.Column(db.Text)
zip = db.Column(db.Text)
agency_url = db.Column(db.Text)
officers_url = db.Column(db.Text)

commander_id = db.Column(db.Integer, db.ForeignKey('officer.id'))
agency_id = db.Column(db.Integer, db.ForeignKey('agency.id'))

agency = db.relationship("Agency", back_populates="units")
officer_association = db.relationship('Employment', back_populates='unit')
officers = association_proxy('officer_association', 'officer')

def __repr__(self):
return f"<Unit {self.name}>"
1 change: 0 additions & 1 deletion backend/routes/agencies.py
Original file line number Diff line number Diff line change
Expand Up @@ -231,7 +231,6 @@ def add_officer_to_agency(agency_id: int):
employment.agency_id = agency_id
employment = merge_employment_records(
employments.all() + [employment],
unit=record.unit,
currently_employed=record.currently_employed
)

Expand Down
1 change: 0 additions & 1 deletion backend/routes/officers.py
Original file line number Diff line number Diff line change
Expand Up @@ -304,7 +304,6 @@ def update_employment(officer_id: int):
employment.officer_id = officer_id
employment = merge_employment_records(
employments.all() + [employment],
unit=record.unit,
currently_employed=record.currently_employed
)

Expand Down
51 changes: 51 additions & 0 deletions backend/schemas.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
from .database.models.partner import Partner, PartnerMember, MemberRole
from .database.models.incident import Incident, SourceDetails
from .database.models.agency import Agency, Jurisdiction
from .database.models.unit import Unit
from .database.models.officer import Officer, StateID
from .database.models.employment import Employment
from .database.models.accusation import Accusation
Expand Down Expand Up @@ -122,6 +123,13 @@ def validate(auth=True, **kwargs):
]

_agency_list_attributes = [
'units',
'officer_association',
'officers'
]

_unit_list_attributes = [
'agency',
'officer_association',
'officers'
]
Expand Down Expand Up @@ -179,6 +187,16 @@ def none_to_list(cls, values: Dict[str, Any]) -> Dict[str, Any]:
return values


class _UnitMixin(BaseModel):
@root_validator(pre=True)
def none_to_list(cls, values: Dict[str, Any]) -> Dict[str, Any]:
values = {**values} # convert mappings to base dict type.
for i in _unit_list_attributes:
if not values.get(i):
values[i] = []
return values


def schema_create(model_type: DeclarativeMeta, **kwargs) -> ModelMetaclass:
return sqlalchemy_to_pydantic(model_type, exclude="id", **kwargs)

Expand All @@ -187,6 +205,7 @@ def schema_create(model_type: DeclarativeMeta, **kwargs) -> ModelMetaclass:
_BaseCreateIncidentSchema = schema_create(Incident)
_BaseCreateOfficerSchema = schema_create(Officer)
_BaseCreateAgencySchema = schema_create(Agency)
_BaseCreateUnitSchema = schema_create(Unit)
CreateStateIDSchema = schema_create(StateID)
CreateEmploymentSchema = schema_create(Employment)
CreateAccusationSchema = schema_create(Accusation)
Expand Down Expand Up @@ -241,6 +260,20 @@ class CreateAgencySchema(_BaseCreateAgencySchema, _AgencyMixin):
hq_zip: Optional[str]


class CreateUnitSchema(_BaseCreateUnitSchema, _UnitMixin):
name: str
website_url: Optional[str]
phone: Optional[str]
email: Optional[str]
description: Optional[str]
address: Optional[str]
zip: Optional[str]
agency_url: Optional[str]
officers_url: Optional[str]
commander_id: int
agency_id: int


AddMemberSchema = sqlalchemy_to_pydantic(
PartnerMember, exclude=["id", "date_joined", "partner", "user"]
)
Expand All @@ -255,6 +288,7 @@ def schema_get(model_type: DeclarativeMeta, **kwargs) -> ModelMetaclass:
_BaseOfficerSchema = schema_get(Officer)
_BasePartnerMemberSchema = schema_get(PartnerMember)
_BaseAgencySchema = schema_get(Agency)
_BaseUnitSchema = schema_get(Unit)
VictimSchema = schema_get(Victim)
PerpetratorSchema = schema_get(Perpetrator)
TagSchema = schema_get(Tag)
Expand Down Expand Up @@ -293,6 +327,11 @@ class OfficerSchema(_BaseOfficerSchema, _OfficerMixin):


class AgencySchema(_BaseAgencySchema, _AgencyMixin):
units: List[CreateUnitSchema]
officer_association: List[CreateEmploymentSchema]


class UnitSchema(_BaseUnitSchema):
officer_association: List[CreateEmploymentSchema]


Expand Down Expand Up @@ -401,6 +440,18 @@ def agency_orm_to_json(agency: Agency) -> dict:
)


def unit_to_orm(unit: CreateUnitSchema) -> Unit:
"""Convert the JSON unit into an ORM instance"""
orm_attrs = unit.dict()
return Unit(**orm_attrs)


def unit_orm_to_json(unit: Unit) -> dict:
return UnitSchema.from_orm(unit).dict(
exclude_none=True,
)


def employment_to_orm(employment: CreateEmploymentSchema) -> Employment:
"""Convert the JSON employment into an ORM instance"""
orm_attrs = employment.dict()
Expand Down
Loading