UMBRA/backend/app/schemas/person.py
Kyle Pope 8cbc95939a Fix issues from QA review: schema safety, search scope, clipboard handling, UX polish
- 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>
2026-02-24 21:19:22 +08:00

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)