mirror of
https://github.com/RichieCahill/dotfiles.git
synced 2026-04-17 13:08:19 -04:00
fixed treefmt
This commit is contained in:
@@ -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)
|
||||||
|
|||||||
Reference in New Issue
Block a user