- Add migrations 019/020: extend Person (first/last name, nickname, is_favourite, company, job_title, mobile, category) and Location (is_frequent, contact_number, email) - Update Person/Location models, schemas, and routers with new fields + name denormalisation - Create shared component library: EntityTable, EntityDetailPanel, CategoryFilterBar, CopyableField, CategoryAutocomplete, useTableVisibility hook - Rebuild LocationsPage: table layout with sortable columns, detail side panel, category filter bar, frequent pinned section - Extend LocationForm with contact number, email, frequent toggle, category autocomplete - Add animated panel transitions to ProjectDetail (55/45 split with cubic-bezier easing) - Update TypeScript interfaces for Person and Location Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
67 lines
1.9 KiB
Python
67 lines
1.9 KiB
Python
from pydantic import BaseModel, ConfigDict, model_validator
|
|
from datetime import datetime, date
|
|
from typing import Optional
|
|
|
|
|
|
class PersonCreate(BaseModel):
|
|
name: Optional[str] = None
|
|
first_name: Optional[str] = None
|
|
last_name: Optional[str] = None
|
|
nickname: Optional[str] = None
|
|
email: Optional[str] = None
|
|
phone: Optional[str] = None
|
|
mobile: Optional[str] = None
|
|
address: Optional[str] = None
|
|
birthday: Optional[date] = None
|
|
category: Optional[str] = None
|
|
is_favourite: bool = False
|
|
company: Optional[str] = None
|
|
job_title: Optional[str] = None
|
|
notes: Optional[str] = None
|
|
|
|
@model_validator(mode='after')
|
|
def require_some_name(self) -> 'PersonCreate':
|
|
if not any([self.name, self.first_name, self.last_name, self.nickname]):
|
|
raise ValueError('At least one name field is required')
|
|
return self
|
|
|
|
|
|
class PersonUpdate(BaseModel):
|
|
name: Optional[str] = None
|
|
first_name: Optional[str] = None
|
|
last_name: Optional[str] = None
|
|
nickname: Optional[str] = None
|
|
email: Optional[str] = None
|
|
phone: Optional[str] = None
|
|
mobile: Optional[str] = None
|
|
address: Optional[str] = None
|
|
birthday: Optional[date] = None
|
|
category: Optional[str] = None
|
|
is_favourite: Optional[bool] = None
|
|
company: Optional[str] = None
|
|
job_title: Optional[str] = None
|
|
notes: Optional[str] = None
|
|
|
|
|
|
class PersonResponse(BaseModel):
|
|
id: int
|
|
name: str
|
|
first_name: Optional[str]
|
|
last_name: Optional[str]
|
|
nickname: Optional[str]
|
|
email: Optional[str]
|
|
phone: Optional[str]
|
|
mobile: Optional[str]
|
|
address: Optional[str]
|
|
birthday: Optional[date]
|
|
category: Optional[str]
|
|
relationship: Optional[str]
|
|
is_favourite: bool
|
|
company: Optional[str]
|
|
job_title: Optional[str]
|
|
notes: Optional[str]
|
|
created_at: datetime
|
|
updated_at: datetime
|
|
|
|
model_config = ConfigDict(from_attributes=True)
|