From 370cf2654a5193e7abce588eae785e5588e1454a Mon Sep 17 00:00:00 2001 From: Ryan Park Date: Tue, 28 May 2024 18:53:02 -0400 Subject: [PATCH 01/11] Created new model and relationships for Unit --- backend/database/models/agency.py | 1 + backend/database/models/employment.py | 8 +++----- backend/database/models/unit.py | 0 3 files changed, 4 insertions(+), 5 deletions(-) create mode 100644 backend/database/models/unit.py diff --git a/backend/database/models/agency.py b/backend/database/models/agency.py index c20088209..97402f6e5 100644 --- a/backend/database/models/agency.py +++ b/backend/database/models/agency.py @@ -20,6 +20,7 @@ class Agency(db.Model, CrudMixin): hq_city = db.Column(db.Text) hq_zip = db.Column(db.Text) jurisdiction = db.Column(db.Enum(Jurisdiction)) + units = db.relationship('Unit', backref='agency') # total_officers = db.Column(db.Integer) officer_association = db.relationship("Employment", back_populates="agency") diff --git a/backend/database/models/employment.py b/backend/database/models/employment.py index f5e8772c2..c5f76e8b4 100644 --- a/backend/database/models/employment.py +++ b/backend/database/models/employment.py @@ -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", backref="unit_association") def __repr__(self): return f"" @@ -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 ): """ @@ -85,8 +85,6 @@ 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( @@ -95,7 +93,7 @@ def merge_employment_records( badge_number=records[0].badge_number, earliest_employment=earliest_employment, latest_employment=latest_employment, - unit=unit, + unit=records[0].unit_id, highest_rank=highest_rank, currently_employed=currently_employed, ) diff --git a/backend/database/models/unit.py b/backend/database/models/unit.py new file mode 100644 index 000000000..e69de29bb From c279085258ab5305467d0a391d7e94c4ebd948fb Mon Sep 17 00:00:00 2001 From: Ryan Park Date: Mon, 3 Jun 2024 20:57:07 -0400 Subject: [PATCH 02/11] feature: Converts the Agency Model's unit property into a DB Unit Model --- backend/database/models/agency.py | 5 +++-- backend/database/models/employment.py | 4 ++-- backend/database/models/unit.py | 15 +++++++++++++++ 3 files changed, 20 insertions(+), 4 deletions(-) diff --git a/backend/database/models/agency.py b/backend/database/models/agency.py index 97402f6e5..7b4e26b3a 100644 --- a/backend/database/models/agency.py +++ b/backend/database/models/agency.py @@ -1,6 +1,6 @@ -from ..core import db, CrudMixin from enum import Enum from sqlalchemy.ext.associationproxy import association_proxy +from ..core import CrudMixin, db class Jurisdiction(str, Enum): @@ -20,9 +20,10 @@ class Agency(db.Model, CrudMixin): hq_city = db.Column(db.Text) hq_zip = db.Column(db.Text) jurisdiction = db.Column(db.Enum(Jurisdiction)) - units = db.relationship('Unit', backref='agency') # 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") diff --git a/backend/database/models/employment.py b/backend/database/models/employment.py index c5f76e8b4..27424c2d4 100644 --- a/backend/database/models/employment.py +++ b/backend/database/models/employment.py @@ -29,7 +29,7 @@ class Employment(db.Model, CrudMixin): officer = db.relationship("Officer", back_populates="agency_association") agency = db.relationship("Agency", back_populates="officer_association") - unit = db.relationship("Unit", backref="unit_association") + unit = db.relationship("Unit", back_populates="officer_association") def __repr__(self): return f"" @@ -90,10 +90,10 @@ def merge_employment_records( 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=records[0].unit_id, highest_rank=highest_rank, currently_employed=currently_employed, ) diff --git a/backend/database/models/unit.py b/backend/database/models/unit.py index e69de29bb..d181bb28f 100644 --- a/backend/database/models/unit.py +++ b/backend/database/models/unit.py @@ -0,0 +1,15 @@ +from ..core import db +from sqlalchemy.ext.associationproxy import association_proxy + + +class Unit(db.Model): + id = db.Column(db.Integer, primary_key=True) + name = db.Column(db.Text) + 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"" From 0ecd29f591934b5e1a2bd88e5f509d7b7cdc3aa3 Mon Sep 17 00:00:00 2001 From: Ryan Park Date: Mon, 3 Jun 2024 22:21:43 -0400 Subject: [PATCH 03/11] Imported unit in schemas.py --- backend/schemas.py | 1 + 1 file changed, 1 insertion(+) diff --git a/backend/schemas.py b/backend/schemas.py index b38fd5d0d..f0192fa7c 100644 --- a/backend/schemas.py +++ b/backend/schemas.py @@ -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 From 12c5d3115704c38ee597f1e33a999abe9d55d80b Mon Sep 17 00:00:00 2001 From: Ryan Park Date: Tue, 4 Jun 2024 18:55:01 -0400 Subject: [PATCH 04/11] Created skeleton for schema --- backend/database/models/unit.py | 2 +- backend/schemas.py | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/backend/database/models/unit.py b/backend/database/models/unit.py index d181bb28f..c41c37e4f 100644 --- a/backend/database/models/unit.py +++ b/backend/database/models/unit.py @@ -6,8 +6,8 @@ class Unit(db.Model): id = db.Column(db.Integer, primary_key=True) name = db.Column(db.Text) agency_id = db.Column(db.Integer, db.ForeignKey('agency.id')) - agency = db.relationship("Agency", back_populates="units") + agency = db.relationship("Agency", back_populates="units") officer_association = db.relationship('Employment', back_populates='unit') officers = association_proxy('officer_association', 'officer') diff --git a/backend/schemas.py b/backend/schemas.py index f0192fa7c..226270a99 100644 --- a/backend/schemas.py +++ b/backend/schemas.py @@ -123,6 +123,7 @@ def validate(auth=True, **kwargs): ] _agency_list_attributes = [ + 'units', 'officer_association', 'officers' ] @@ -188,6 +189,7 @@ def schema_create(model_type: DeclarativeMeta, **kwargs) -> ModelMetaclass: _BaseCreateIncidentSchema = schema_create(Incident) _BaseCreateOfficerSchema = schema_create(Officer) _BaseCreateAgencySchema = schema_create(Agency) +CreateUnitSchema = schema_create(Unit) CreateStateIDSchema = schema_create(StateID) CreateEmploymentSchema = schema_create(Employment) CreateAccusationSchema = schema_create(Accusation) @@ -256,6 +258,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) @@ -294,9 +297,14 @@ class OfficerSchema(_BaseOfficerSchema, _OfficerMixin): class AgencySchema(_BaseAgencySchema, _AgencyMixin): + units: List[CreateUnitSchema] officer_association: List[CreateEmploymentSchema] +class UnitSchema(_BaseUnitSchema): + pass + + class PartnerSchema(_BasePartnerSchema, _PartnerMixin): reported_incidents: List[IncidentSchema] From 6804f37ae243748952b657890869c6133ba693b1 Mon Sep 17 00:00:00 2001 From: Ryan Park Date: Tue, 4 Jun 2024 18:55:50 -0400 Subject: [PATCH 05/11] Reverted updates made to merge_employment_records --- backend/database/models/employment.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/backend/database/models/employment.py b/backend/database/models/employment.py index 27424c2d4..673efb2ee 100644 --- a/backend/database/models/employment.py +++ b/backend/database/models/employment.py @@ -66,6 +66,7 @@ def get_highest_rank(records: list[Employment]): def merge_employment_records( records: list[Employment], + unit: str = None, currently_employed: bool = None ): """ @@ -85,15 +86,17 @@ 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, ) From 302c980f2d5b20c7447a1d68dc3b11a3e05c0218 Mon Sep 17 00:00:00 2001 From: Ryan Park Date: Tue, 4 Jun 2024 19:14:10 -0400 Subject: [PATCH 06/11] Corrected import order --- backend/database/models/agency.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/database/models/agency.py b/backend/database/models/agency.py index 7b4e26b3a..09048f676 100644 --- a/backend/database/models/agency.py +++ b/backend/database/models/agency.py @@ -1,6 +1,6 @@ +from ..core import CrudMixin, db from enum import Enum from sqlalchemy.ext.associationproxy import association_proxy -from ..core import CrudMixin, db class Jurisdiction(str, Enum): From 3c3ceb93889128104b9391fe516012d09d1b0271 Mon Sep 17 00:00:00 2001 From: Ryan Park Date: Mon, 10 Jun 2024 16:39:37 -0400 Subject: [PATCH 07/11] Add CrudMixin to Unit Model --- backend/database/models/unit.py | 4 ++-- backend/schemas.py | 26 ++++++++++++++++++++++++-- 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/backend/database/models/unit.py b/backend/database/models/unit.py index c41c37e4f..4f55cfa5b 100644 --- a/backend/database/models/unit.py +++ b/backend/database/models/unit.py @@ -1,8 +1,8 @@ -from ..core import db +from ..core import CrudMixin, db from sqlalchemy.ext.associationproxy import association_proxy -class Unit(db.Model): +class Unit(db.Model, CrudMixin): id = db.Column(db.Integer, primary_key=True) name = db.Column(db.Text) agency_id = db.Column(db.Integer, db.ForeignKey('agency.id')) diff --git a/backend/schemas.py b/backend/schemas.py index 226270a99..daab69420 100644 --- a/backend/schemas.py +++ b/backend/schemas.py @@ -128,6 +128,11 @@ def validate(auth=True, **kwargs): 'officers' ] +_unit_list_attributes = [ + 'officer_association', + 'officers' +] + _partner_list_attrs = ["reported_incidents"] @@ -189,7 +194,7 @@ def schema_create(model_type: DeclarativeMeta, **kwargs) -> ModelMetaclass: _BaseCreateIncidentSchema = schema_create(Incident) _BaseCreateOfficerSchema = schema_create(Officer) _BaseCreateAgencySchema = schema_create(Agency) -CreateUnitSchema = schema_create(Unit) +_BaseCreateUnitSchema = schema_create(Unit) CreateStateIDSchema = schema_create(StateID) CreateEmploymentSchema = schema_create(Employment) CreateAccusationSchema = schema_create(Accusation) @@ -244,6 +249,11 @@ class CreateAgencySchema(_BaseCreateAgencySchema, _AgencyMixin): hq_zip: Optional[str] +class CreateUnitSchema(_BaseCreateUnitSchema, _UnitMixin): + name: str + agency_id: int + + AddMemberSchema = sqlalchemy_to_pydantic( PartnerMember, exclude=["id", "date_joined", "partner", "user"] ) @@ -302,7 +312,7 @@ class AgencySchema(_BaseAgencySchema, _AgencyMixin): class UnitSchema(_BaseUnitSchema): - pass + officer_association: List[CreateEmploymentSchema] class PartnerSchema(_BasePartnerSchema, _PartnerMixin): @@ -410,6 +420,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() From f843a0b8d40eaf9458b34778bb06c262492198fb Mon Sep 17 00:00:00 2001 From: Ryan Park Date: Mon, 10 Jun 2024 16:59:36 -0400 Subject: [PATCH 08/11] Added additional Unit properties --- backend/database/models/unit.py | 10 ++++++++++ backend/schemas.py | 19 +++++++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/backend/database/models/unit.py b/backend/database/models/unit.py index 4f55cfa5b..a97721da9 100644 --- a/backend/database/models/unit.py +++ b/backend/database/models/unit.py @@ -5,6 +5,16 @@ 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 = 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") diff --git a/backend/schemas.py b/backend/schemas.py index daab69420..a61e9f222 100644 --- a/backend/schemas.py +++ b/backend/schemas.py @@ -129,6 +129,7 @@ def validate(auth=True, **kwargs): ] _unit_list_attributes = [ + 'agency', 'officer_association', 'officers' ] @@ -186,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) @@ -251,6 +262,14 @@ class CreateAgencySchema(_BaseCreateAgencySchema, _AgencyMixin): 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] agency_id: int From ca6e634c6053415d607635c5950e7926b32ed37b Mon Sep 17 00:00:00 2001 From: Ryan Park Date: Mon, 10 Jun 2024 21:23:38 -0400 Subject: [PATCH 09/11] Updated __init__.py to include unit model --- backend/database/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/backend/database/__init__.py b/backend/database/__init__.py index bacb988ac..2592bf863 100644 --- a/backend/database/__init__.py +++ b/backend/database/__init__.py @@ -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 * From 10edcac1d5f11259f242459b8c214547c6c7b59a Mon Sep 17 00:00:00 2001 From: Ryan Park Date: Mon, 10 Jun 2024 21:47:52 -0400 Subject: [PATCH 10/11] Corrected commander_id unit property --- backend/database/models/unit.py | 2 +- backend/schemas.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/backend/database/models/unit.py b/backend/database/models/unit.py index a97721da9..50aca467e 100644 --- a/backend/database/models/unit.py +++ b/backend/database/models/unit.py @@ -14,7 +14,7 @@ class Unit(db.Model, CrudMixin): agency_url = db.Column(db.Text) officers_url = db.Column(db.Text) - commander = db.Column(db.Integer, db.ForeignKey('officer.id')) + 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") diff --git a/backend/schemas.py b/backend/schemas.py index a61e9f222..33b96d2e6 100644 --- a/backend/schemas.py +++ b/backend/schemas.py @@ -270,6 +270,7 @@ class CreateUnitSchema(_BaseCreateUnitSchema, _UnitMixin): zip: Optional[str] agency_url: Optional[str] officers_url: Optional[str] + commander_id: int agency_id: int From 8ad9a90e7c9fed6b894c17157799d2b1beec6883 Mon Sep 17 00:00:00 2001 From: Ryan Park Date: Mon, 10 Jun 2024 22:35:36 -0400 Subject: [PATCH 11/11] Updates merge_employment_records to include Unit model --- backend/database/models/employment.py | 5 +---- backend/routes/agencies.py | 1 - backend/routes/officers.py | 1 - 3 files changed, 1 insertion(+), 6 deletions(-) diff --git a/backend/database/models/employment.py b/backend/database/models/employment.py index 673efb2ee..27424c2d4 100644 --- a/backend/database/models/employment.py +++ b/backend/database/models/employment.py @@ -66,7 +66,6 @@ def get_highest_rank(records: list[Employment]): def merge_employment_records( records: list[Employment], - unit: str = None, currently_employed: bool = None ): """ @@ -86,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, ) diff --git a/backend/routes/agencies.py b/backend/routes/agencies.py index 83894ef10..2de831885 100644 --- a/backend/routes/agencies.py +++ b/backend/routes/agencies.py @@ -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 ) diff --git a/backend/routes/officers.py b/backend/routes/officers.py index 935438674..184c8e5ec 100644 --- a/backend/routes/officers.py +++ b/backend/routes/officers.py @@ -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 )