fixed treefmt

This commit is contained in:
2026-03-22 18:53:18 -04:00
parent 66f972ac2b
commit b4d9562591

View File

@@ -1,12 +1,13 @@
"""HTMX server-rendered view router.""" """HTMX server-rendered view router."""
from pathlib import Path from pathlib import Path
from typing import Annotated, Any
from fastapi import APIRouter, Form, HTTPException, Request from fastapi import APIRouter, Form, HTTPException, Request
from fastapi.responses import HTMLResponse, RedirectResponse from fastapi.responses import HTMLResponse, RedirectResponse
from fastapi.templating import Jinja2Templates from fastapi.templating import Jinja2Templates
from sqlalchemy import select from sqlalchemy import select
from sqlalchemy.orm import selectinload from sqlalchemy.orm import Session, selectinload
from python.api.dependencies import DbSession from python.api.dependencies import DbSession
from python.orm.richie.contact import Contact, ContactRelationship, Need, RelationshipType from python.orm.richie.contact import Contact, ContactRelationship, Need, RelationshipType
@@ -16,11 +17,39 @@ templates = Jinja2Templates(directory=TEMPLATES_DIR)
router = APIRouter(tags=["views"]) router = APIRouter(tags=["views"])
FAMILIAL_TYPES = {"parent", "child", "sibling", "grandparent", "grandchild", "aunt_uncle", "niece_nephew", "cousin", "in_law"} FAMILIAL_TYPES = {
"parent",
"child",
"sibling",
"grandparent",
"grandchild",
"aunt_uncle",
"niece_nephew",
"cousin",
"in_law",
}
FRIEND_TYPES = {"best_friend", "close_friend", "friend", "acquaintance", "neighbor"} FRIEND_TYPES = {"best_friend", "close_friend", "friend", "acquaintance", "neighbor"}
PARTNER_TYPES = {"spouse", "partner"} PARTNER_TYPES = {"spouse", "partner"}
PROFESSIONAL_TYPES = {"mentor", "mentee", "business_partner", "colleague", "manager", "direct_report", "client"} PROFESSIONAL_TYPES = {"mentor", "mentee", "business_partner", "colleague", "manager", "direct_report", "client"}
CONTACT_STRING_FIELDS = (
"name",
"legal_name",
"suffix",
"gender",
"current_job",
"timezone",
"profile_pic",
"bio",
"goals",
"social_structure_style",
"safe_conversation_starters",
"topics_to_avoid",
"ssn",
)
CONTACT_INT_FIELDS = ("age", "self_sufficiency_score")
def _group_relationships(relationships: list[ContactRelationship]) -> dict[str, list[ContactRelationship]]: def _group_relationships(relationships: list[ContactRelationship]) -> dict[str, list[ContactRelationship]]:
"""Group relationships by category.""" """Group relationships by category."""
@@ -45,7 +74,7 @@ def _group_relationships(relationships: list[ContactRelationship]) -> dict[str,
return groups return groups
def _build_contact_name_map(database: DbSession, contact: Contact) -> dict[int, str]: def _build_contact_name_map(database: Session, contact: Contact) -> dict[int, str]:
"""Build a mapping of contact IDs to names for relationship display.""" """Build a mapping of contact IDs to names for relationship display."""
related_ids = {rel.related_contact_id for rel in contact.related_to} related_ids = {rel.related_contact_id for rel in contact.related_to}
related_ids |= {rel.contact_id for rel in contact.related_from} related_ids |= {rel.contact_id for rel in contact.related_from}
@@ -54,9 +83,7 @@ def _build_contact_name_map(database: DbSession, contact: Contact) -> dict[int,
if not related_ids: if not related_ids:
return {} return {}
related_contacts = list( related_contacts = list(database.scalars(select(Contact).where(Contact.id.in_(related_ids))).all())
database.scalars(select(Contact).where(Contact.id.in_(related_ids))).all()
)
return {related.id: related.name for related in related_contacts} return {related.id: related.name for related in related_contacts}
@@ -65,41 +92,34 @@ def _get_relationship_type_display() -> dict[str, str]:
return {rel_type.value: rel_type.display_name for rel_type in RelationshipType} return {rel_type.value: rel_type.display_name for rel_type in RelationshipType}
def _parse_contact_form( async def _parse_contact_form(request: Request) -> dict[str, Any]:
name: str, """Parse contact form data from a multipart/form request."""
legal_name: str, form_data = await request.form()
suffix: str, result: dict[str, Any] = {}
age: str,
gender: str, for field in CONTACT_STRING_FIELDS:
current_job: str, value = form_data.get(field, "")
timezone: str, result[field] = str(value) if value else None
profile_pic: str,
bio: str, for field in CONTACT_INT_FIELDS:
goals: str, value = form_data.get(field, "")
social_structure_style: str, result[field] = int(value) if value else None
self_sufficiency_score: str,
safe_conversation_starters: str, result["need_ids"] = [int(value) for value in form_data.getlist("need_ids")]
topics_to_avoid: str, return result
ssn: str,
) -> dict:
"""Parse form fields into a dict for contact creation/update.""" def _save_contact_from_form(database: Session, contact: Contact, form_result: dict[str, Any]) -> None:
return { """Apply parsed form data to a Contact and save associated needs."""
"name": name, need_ids = form_result.pop("need_ids")
"legal_name": legal_name or None,
"suffix": suffix or None, for key, value in form_result.items():
"age": int(age) if age else None, setattr(contact, key, value)
"gender": gender or None,
"current_job": current_job or None, if need_ids:
"timezone": timezone or None, contact.needs = list(database.scalars(select(Need).where(Need.id.in_(need_ids))).all())
"profile_pic": profile_pic or None, else:
"bio": bio or None, contact.needs = []
"goals": goals or None,
"social_structure_style": social_structure_style or None,
"self_sufficiency_score": int(self_sufficiency_score) if self_sufficiency_score else None,
"safe_conversation_starters": safe_conversation_starters or None,
"topics_to_avoid": topics_to_avoid or None,
"ssn": ssn or None,
}
@router.get("/", response_class=HTMLResponse) @router.get("/", response_class=HTMLResponse)
@@ -107,51 +127,22 @@ def _parse_contact_form(
def contact_list_page(request: Request, database: DbSession) -> HTMLResponse: def contact_list_page(request: Request, database: DbSession) -> HTMLResponse:
"""Render the contacts list page.""" """Render the contacts list page."""
contacts = list(database.scalars(select(Contact)).all()) contacts = list(database.scalars(select(Contact)).all())
return templates.TemplateResponse( return templates.TemplateResponse(request, "contact_list.html", {"contacts": contacts})
request, "contact_list.html", {"contacts": contacts}
)
@router.get("/contacts/new", response_class=HTMLResponse) @router.get("/contacts/new", response_class=HTMLResponse)
def new_contact_page(request: Request, database: DbSession) -> HTMLResponse: def new_contact_page(request: Request, database: DbSession) -> HTMLResponse:
"""Render the new contact form page.""" """Render the new contact form page."""
all_needs = list(database.scalars(select(Need)).all()) all_needs = list(database.scalars(select(Need)).all())
return templates.TemplateResponse( return templates.TemplateResponse(request, "contact_form.html", {"contact": None, "all_needs": all_needs})
request, "contact_form.html", {"contact": None, "all_needs": all_needs}
)
@router.post("/htmx/contacts/new") @router.post("/htmx/contacts/new")
def create_contact_form( async def create_contact_form(request: Request, database: DbSession) -> RedirectResponse:
database: DbSession,
name: str = Form(...),
legal_name: str = Form(""),
suffix: str = Form(""),
age: str = Form(""),
gender: str = Form(""),
current_job: str = Form(""),
timezone: str = Form(""),
profile_pic: str = Form(""),
bio: str = Form(""),
goals: str = Form(""),
social_structure_style: str = Form(""),
self_sufficiency_score: str = Form(""),
safe_conversation_starters: str = Form(""),
topics_to_avoid: str = Form(""),
ssn: str = Form(""),
need_ids: list[int] = Form([]),
) -> RedirectResponse:
"""Handle the create contact form submission.""" """Handle the create contact form submission."""
contact_data = _parse_contact_form( form_result = await _parse_contact_form(request)
name, legal_name, suffix, age, gender, current_job, timezone, contact = Contact()
profile_pic, bio, goals, social_structure_style, self_sufficiency_score, _save_contact_from_form(database, contact, form_result)
safe_conversation_starters, topics_to_avoid, ssn,
)
contact = Contact(**contact_data)
if need_ids:
needs = list(database.scalars(select(Need).where(Need.id.in_(need_ids))).all())
contact.needs = needs
database.add(contact) database.add(contact)
database.commit() database.commit()
@@ -197,57 +188,23 @@ def contact_detail_page(contact_id: int, request: Request, database: DbSession)
@router.get("/contacts/{contact_id}/edit", response_class=HTMLResponse) @router.get("/contacts/{contact_id}/edit", response_class=HTMLResponse)
def edit_contact_page(contact_id: int, request: Request, database: DbSession) -> HTMLResponse: def edit_contact_page(contact_id: int, request: Request, database: DbSession) -> HTMLResponse:
"""Render the edit contact form page.""" """Render the edit contact form page."""
contact = database.scalar( contact = database.scalar(select(Contact).where(Contact.id == contact_id).options(selectinload(Contact.needs)))
select(Contact)
.where(Contact.id == contact_id)
.options(selectinload(Contact.needs))
)
if not contact: if not contact:
raise HTTPException(status_code=404, detail="Contact not found") raise HTTPException(status_code=404, detail="Contact not found")
all_needs = list(database.scalars(select(Need)).all()) all_needs = list(database.scalars(select(Need)).all())
return templates.TemplateResponse( return templates.TemplateResponse(request, "contact_form.html", {"contact": contact, "all_needs": all_needs})
request, "contact_form.html", {"contact": contact, "all_needs": all_needs}
)
@router.post("/htmx/contacts/{contact_id}/edit") @router.post("/htmx/contacts/{contact_id}/edit")
def update_contact_form( async def update_contact_form(contact_id: int, request: Request, database: DbSession) -> RedirectResponse:
contact_id: int,
database: DbSession,
name: str = Form(...),
legal_name: str = Form(""),
suffix: str = Form(""),
age: str = Form(""),
gender: str = Form(""),
current_job: str = Form(""),
timezone: str = Form(""),
profile_pic: str = Form(""),
bio: str = Form(""),
goals: str = Form(""),
social_structure_style: str = Form(""),
self_sufficiency_score: str = Form(""),
safe_conversation_starters: str = Form(""),
topics_to_avoid: str = Form(""),
ssn: str = Form(""),
need_ids: list[int] = Form([]),
) -> RedirectResponse:
"""Handle the edit contact form submission.""" """Handle the edit contact form submission."""
contact = database.get(Contact, contact_id) contact = database.get(Contact, contact_id)
if not contact: if not contact:
raise HTTPException(status_code=404, detail="Contact not found") raise HTTPException(status_code=404, detail="Contact not found")
contact_data = _parse_contact_form( form_result = await _parse_contact_form(request)
name, legal_name, suffix, age, gender, current_job, timezone, _save_contact_from_form(database, contact, form_result)
profile_pic, bio, goals, social_structure_style, self_sufficiency_score,
safe_conversation_starters, topics_to_avoid, ssn,
)
for key, value in contact_data.items():
setattr(contact, key, value)
needs = list(database.scalars(select(Need).where(Need.id.in_(need_ids))).all()) if need_ids else []
contact.needs = needs
database.commit() database.commit()
return RedirectResponse(url=f"/contacts/{contact_id}", status_code=303) return RedirectResponse(url=f"/contacts/{contact_id}", status_code=303)
@@ -258,14 +215,10 @@ def add_need_to_contact_htmx(
contact_id: int, contact_id: int,
request: Request, request: Request,
database: DbSession, database: DbSession,
need_id: int = Form(...), need_id: Annotated[int, Form()],
) -> HTMLResponse: ) -> HTMLResponse:
"""Add a need to a contact and return updated manage-needs partial.""" """Add a need to a contact and return updated manage-needs partial."""
contact = database.scalar( contact = database.scalar(select(Contact).where(Contact.id == contact_id).options(selectinload(Contact.needs)))
select(Contact)
.where(Contact.id == contact_id)
.options(selectinload(Contact.needs))
)
if not contact: if not contact:
raise HTTPException(status_code=404, detail="Contact not found") raise HTTPException(status_code=404, detail="Contact not found")
@@ -278,9 +231,7 @@ def add_need_to_contact_htmx(
database.commit() database.commit()
database.refresh(contact) database.refresh(contact)
return templates.TemplateResponse( return templates.TemplateResponse(request, "partials/manage_needs.html", {"contact": contact})
request, "partials/manage_needs.html", {"contact": contact}
)
@router.post("/htmx/contacts/{contact_id}/add-relationship", response_class=HTMLResponse) @router.post("/htmx/contacts/{contact_id}/add-relationship", response_class=HTMLResponse)
@@ -288,15 +239,11 @@ def add_relationship_htmx(
contact_id: int, contact_id: int,
request: Request, request: Request,
database: DbSession, database: DbSession,
related_contact_id: int = Form(...), related_contact_id: Annotated[int, Form()],
relationship_type: str = Form(...), relationship_type: Annotated[str, Form()],
) -> HTMLResponse: ) -> HTMLResponse:
"""Add a relationship and return updated manage-relationships partial.""" """Add a relationship and return updated manage-relationships partial."""
contact = database.scalar( contact = database.scalar(select(Contact).where(Contact.id == contact_id).options(selectinload(Contact.related_to)))
select(Contact)
.where(Contact.id == contact_id)
.options(selectinload(Contact.related_to))
)
if not contact: if not contact:
raise HTTPException(status_code=404, detail="Contact not found") raise HTTPException(status_code=404, detail="Contact not found")
@@ -319,7 +266,8 @@ def add_relationship_htmx(
contact_names = _build_contact_name_map(database, contact) contact_names = _build_contact_name_map(database, contact)
return templates.TemplateResponse( return templates.TemplateResponse(
request, "partials/manage_relationships.html", request,
"partials/manage_relationships.html",
{"contact": contact, "contact_names": contact_names}, {"contact": contact, "contact_names": contact_names},
) )
@@ -329,7 +277,7 @@ def update_relationship_weight_htmx(
contact_id: int, contact_id: int,
related_contact_id: int, related_contact_id: int,
database: DbSession, database: DbSession,
closeness_weight: int = Form(...), closeness_weight: Annotated[int, Form()],
) -> HTMLResponse: ) -> HTMLResponse:
"""Update a relationship's closeness weight from HTMX range input.""" """Update a relationship's closeness weight from HTMX range input."""
relationship = database.scalar( relationship = database.scalar(
@@ -350,8 +298,8 @@ def update_relationship_weight_htmx(
def create_need_htmx( def create_need_htmx(
request: Request, request: Request,
database: DbSession, database: DbSession,
name: str = Form(...), name: Annotated[str, Form()],
description: str = Form(""), description: Annotated[str, Form()] = "",
) -> HTMLResponse: ) -> HTMLResponse:
"""Create a need via form data and return updated needs list.""" """Create a need via form data and return updated needs list."""
need = Need(name=name, description=description or None) need = Need(name=name, description=description or None)