minor: org pydantic model cleanup

Contact models also updated since they are now fully incorporated into orgs.

Issue #9
This commit is contained in:
Chris Milne 2026-05-27 16:51:46 +01:00
parent 216836e2fd
commit 4bf5933376
3 changed files with 47 additions and 90 deletions

View file

@ -9,7 +9,6 @@ from typing import Optional
from pydantic import EmailStr, ConfigDict
from src.organisation.constants import ContactType
from src.schemas import CustomBaseModel
@ -25,50 +24,11 @@ class ContactAddress(CustomBaseModel):
postal_code: Optional[str] = None
class ContactContactGetResponse(CustomBaseModel):
email: str
first_name: str
last_name: str
phonenumber: str
vat_number: Optional[str] = None
class ContactAddressGetResponse(CustomBaseModel):
post_office_box_number: Optional[str] = None
street_address: Optional[str] = None # If using a PO box, there would be no street address
street_address_line_2: Optional[str] = None
locality: str
address_region: Optional[str] = None
country_code: str
postal_code: str
class ContactContactPostRequest(CustomBaseModel):
email: EmailStr
first_name: str
last_name: str
phonenumber: str
vat_number: Optional[str] = None
post_office_box_number: Optional[str] = None
street_address: Optional[str] = None
street_address_line_2: Optional[str] = None
locality: str
address_region: Optional[str] = None
country_code: str
postal_code: str
class ContactUpdateRequest(CustomBaseModel):
class ContactModel(CustomBaseModel):
email: Optional[EmailStr] = None
first_name: Optional[str] = None
last_name: Optional[str] = None
phonenumber: Optional[str] = None
vat_number: Optional[str] = None
post_office_box_number: Optional[str] = None
street_address: Optional[str] = None
street_address_line_2: Optional[str] = None
locality: Optional[str] = None
address_region: Optional[str] = None
country_code: Optional[str] = None
postal_code: Optional[str] = None
class ContactOrgGetResponse(CustomBaseModel):
name: str
contact_types: list[ContactType]
address: ContactAddress

View file

@ -19,6 +19,7 @@ from fastapi.params import Query
from psycopg.errors import UniqueViolation
from sqlalchemy.exc import IntegrityError
from contact.schemas import ContactModel
from src.exceptions import UnprocessableContent, Conflict
from src.contact.models import Contact
from src.contact.schemas import ContactAddress
@ -31,10 +32,10 @@ from src.auth.dependencies import super_admin_dependency, org_model_root_claim_q
from src.organisation.dependencies import org_model_body_dependency
from src.organisation.constants import ContactType
from src.organisation.models import Organisation as Org
from src.organisation.schemas import OrgOrgPostRequest, OrgQuestionnairePatchRequest, OrgStatusPatchRequest, \
OrgContactPatchRequest, \
OrgUserPostRequest, OrgUserGetResponse, OrgContactGetResponse, OrgOrgGetResponse, OrgRootPatchRequest, \
OrgGroupGetResponse, OrgUserDeleteRequest, OrgDeleteOrgRequest
from src.organisation.schemas import OrgPostOrgRequest, OrgPatchQuestionnaireRequest, OrgPatchStatusRequest, \
OrgPatchContactRequest, \
OrgPostUserRequest, OrgGetUserResponse, OrgGetContactResponse, OrgGetOrgResponse, OrgPatchRootRequest, \
OrgGetGroupResponse, OrgDeleteUserRequest, OrgDeleteOrgRequest
router = APIRouter(
@ -43,7 +44,7 @@ router = APIRouter(
)
@router.get("/id", response_model=OrgOrgGetResponse)
@router.get("/id", response_model=OrgGetOrgResponse)
async def get_org_by_id(org_model: org_model_root_claim_query_dependency):
response = {
"name": org_model.name,
@ -58,7 +59,7 @@ async def get_org_by_id(org_model: org_model_root_claim_query_dependency):
@router.post("/")
async def create_org(db: db_dependency, user_model: user_model_claims_dependency, request_model: OrgOrgPostRequest):
async def create_org(db: db_dependency, user_model: user_model_claims_dependency, request_model: OrgPostOrgRequest):
if request_model.intake_questionnaire:
intake_questionnaire = request_model.intake_questionnaire.model_dump()
else:
@ -85,7 +86,7 @@ async def create_org(db: db_dependency, user_model: user_model_claims_dependency
@router.patch("/questionnaire")
async def update_questionnaire(db: db_dependency, org_model: org_model_root_claim_query_dependency, request_model: OrgQuestionnairePatchRequest):
async def update_questionnaire(db: db_dependency, org_model: org_model_root_claim_query_dependency, request_model: OrgPatchQuestionnaireRequest):
"""
Route for updating questionnaire.
The partial bool allows for submission of partially completed questionnaire and/or
@ -101,19 +102,19 @@ async def update_questionnaire(db: db_dependency, org_model: org_model_root_clai
@router.patch("/status")
async def update_status(db: db_dependency, org_model: org_model_body_dependency, su: super_admin_dependency, request_model: OrgStatusPatchRequest):
async def update_status(db: db_dependency, org_model: org_model_body_dependency, su: super_admin_dependency, request_model: OrgPatchStatusRequest):
org_model.status = request_model.status
db.commit()
@router.get("/users", response_model=OrgUserGetResponse)
@router.get("/users", response_model=OrgGetUserResponse)
async def get_users(org_model: org_model_root_claim_query_dependency):
return {"users": [user.email for user in org_model.user_rel]}
@router.post("/users")
async def add_user_to_org(db: db_dependency, org_model: org_model_root_claim_body_dependency, user_model: user_model_body_dependency, request_model: OrgUserPostRequest):
async def add_user_to_org(db: db_dependency, org_model: org_model_root_claim_body_dependency, user_model: user_model_body_dependency, request_model: OrgPostUserRequest):
if user_model in org_model.user_rel:
raise Conflict(message="User already a part of this organisation")
org_model.user_rel.append(user_model)
@ -127,18 +128,18 @@ async def delete_organisation_by_id(db: db_dependency, org_model: org_model_body
@router.patch("/root_user", status_code=status.HTTP_204_NO_CONTENT)
async def update_root_user(db: db_dependency, org_model: org_model_body_dependency, user_model: user_model_body_dependency, su: super_admin_dependency, request_model: OrgRootPatchRequest):
async def update_root_user(db: db_dependency, org_model: org_model_body_dependency, user_model: user_model_body_dependency, su: super_admin_dependency, request_model: OrgPatchRootRequest):
org_model.root_user_rel = user_model
db.commit()
@router.get("/groups", response_model=OrgGroupGetResponse)
@router.get("/groups", response_model=OrgGetGroupResponse)
async def get_org_groups(org_model: org_model_root_claim_query_dependency):
return {"groups": [group.name for group in org_model.group_rel]}
@router.delete("/user", status_code=status.HTTP_204_NO_CONTENT)
async def remove_user_from_org(db: db_dependency, org_model: org_model_root_claim_body_dependency, user_model: user_model_body_dependency, request_model: OrgUserDeleteRequest):
async def remove_user_from_org(db: db_dependency, org_model: org_model_root_claim_body_dependency, user_model: user_model_body_dependency, request_model: OrgDeleteUserRequest):
if user_model not in org_model.user_rel:
return
@ -146,7 +147,7 @@ async def remove_user_from_org(db: db_dependency, org_model: org_model_root_clai
db.commit()
@router.get("/contact", response_model=OrgContactGetResponse)
@router.get("/contact", response_model=OrgGetContactResponse)
async def get_contact(org_model: org_model_root_claim_query_dependency, contact_type: Annotated[ContactType, Query()]):
match contact_type:
case "billing":
@ -161,14 +162,14 @@ async def get_contact(org_model: org_model_root_claim_query_dependency, contact_
if contact_model is None:
raise ContactNotFoundException()
return OrgContactGetResponse.model_construct(
**contact_model.__dict__,
address=ContactAddress.model_validate(contact_model)
)
address = ContactAddress.model_validate(contact_model)
contact_response = ContactModel.model_construct(**contact_model.__dict__, address=address)
return {"contact": contact_response}
@router.patch("/contact", response_model=OrgContactGetResponse)
async def update_contact(db: db_dependency, org_model: org_model_root_claim_body_dependency, request_model: OrgContactPatchRequest):
@router.patch("/contact", response_model=OrgGetContactResponse)
async def update_contact(db: db_dependency, org_model: org_model_root_claim_body_dependency, request_model: OrgPatchContactRequest):
match request_model.contact_type:
case "billing":
contact_model = org_model.billing_contact_rel
@ -190,11 +191,9 @@ async def update_contact(db: db_dependency, org_model: org_model_root_claim_body
raise UnprocessableContent("Invalid keys in update request")
db.flush()
response = OrgContactGetResponse.model_construct(
**contact_model.__dict__,
address=ContactAddress.model_validate(contact_model)
)
address = ContactAddress.model_validate(contact_model)
contact_response = ContactModel.model_construct(**contact_model.__dict__, address=address)
db.commit()
return response
return {"contact": contact_response}

View file

@ -10,8 +10,11 @@ from typing import Optional
from pydantic import EmailStr, ConfigDict
from src.schemas import CustomBaseModel
from src.contact.schemas import ContactModel
from src.user.schemas import UserIDMixin
from src.organisation.constants import Status, ContactType
from src.contact.schemas import ContactAddress
class OrgQuestionnaire(CustomBaseModel):
question_one: str
@ -21,18 +24,19 @@ class OrgQuestionnaire(CustomBaseModel):
class OrgIDMixin(CustomBaseModel):
organisation_id: int
class OrgOrgPostRequest(CustomBaseModel):
class OrgPostOrgRequest(CustomBaseModel):
name: str
intake_questionnaire: Optional[OrgQuestionnaire] = None
class OrgQuestionnairePatchRequest(OrgIDMixin):
class OrgPatchQuestionnaireRequest(OrgIDMixin):
intake_questionnaire: OrgQuestionnaire
partial: bool
class OrgStatusPatchRequest(OrgIDMixin):
class OrgPatchStatusRequest(OrgIDMixin):
status: Status
class OrgContactPatchRequest(OrgIDMixin):
class OrgPatchContactRequest(OrgIDMixin):
contact_type: ContactType
email: Optional[EmailStr] = None
@ -48,33 +52,27 @@ class OrgContactPatchRequest(OrgIDMixin):
country_code: Optional[str] = None
postal_code: Optional[str] = None
class OrgUserPostRequest(OrgIDMixin):
user_id: int
class OrgPostUserRequest(OrgIDMixin, UserIDMixin):
pass
class OrgUserDeleteRequest(OrgIDMixin):
user_id: int
class OrgDeleteUserRequest(OrgIDMixin, UserIDMixin):
pass
class OrgRootPatchRequest(OrgIDMixin):
user_id: int
class OrgPatchRootRequest(OrgIDMixin, UserIDMixin):
pass
class OrgUserGetResponse(CustomBaseModel):
class OrgGetUserResponse(CustomBaseModel):
users: list[str]
class OrgGroupGetResponse(CustomBaseModel):
class OrgGetGroupResponse(CustomBaseModel):
groups: list[str]
class OrgContactGetResponse(CustomBaseModel):
class OrgGetContactResponse(CustomBaseModel):
model_config = ConfigDict(from_attributes=True, extra="ignore")
email: Optional[str] = None
first_name: Optional[str] = None
last_name: Optional[str] = None
phonenumber: Optional[str] = None
vat_number: Optional[str] = None
contact: ContactModel
address: ContactAddress
class OrgOrgGetResponse(CustomBaseModel):
class OrgGetOrgResponse(CustomBaseModel):
name: str
status: Status
root_user: Optional[str] = None
@ -83,4 +81,4 @@ class OrgOrgGetResponse(CustomBaseModel):
security_contact: Optional[str] = None
class OrgDeleteOrgRequest(OrgIDMixin):
pass
pass