- Remove `name` from PersonUpdate schema (always computed, prevents bypass)
- Auto-split legacy `name` into first/last on create when only name provided
- Expand backend search to cover first_name, last_name, nickname, email, company
- Add server_default=text('false') to is_favourite and is_frequent model columns
- Add .catch() to clipboard API call in CopyableField
- Extract duplicate renderHeader into shared function in PeoplePage
- Add Escape key handler to close mobile detail panel overlays
- Extract calculate() out of useTableVisibility effects to single function
- Guard getInitials against empty string input
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
67 lines
2.1 KiB
Python
67 lines
2.1 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 # legacy fallback — auto-split into first/last if provided alone
|
|
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 is intentionally omitted — always computed from first/last/nickname
|
|
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] # deprecated — kept for legacy calendar/birthday compat, will be removed
|
|
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)
|