from pydantic import BaseModel, Field, model_validator, validator, ConfigDict from typing import List, Optional, Literal from datetime import datetime, date, timezone from fastapi.security import OAuth2PasswordRequestForm import json from fastapi import File, Form, UploadFile class NotifyItem(BaseModel): user_id: str text: str title: Optional[str] = "Уведомление" link: str = "/profile" type_item: str = "c" class DeliverySendRequest(BaseModel): sender: str too: str item: str delivery_type: Literal["СДЭК", "Самостоятельно"] track: Optional[str] = Field(None, description="Трэк-номер для СДЭК") class DeliveryAcceptRequest(BaseModel): item: str class UserRegister(BaseModel): username: Optional[str] phone: Optional[str] email: Optional[str] # img: Optional[UploadFile] password: str client_id: str roles: List[str] = ["user"] extra_scopes: List[str] = [] birthday: Optional[date] grade: Optional[str] female_name_lastname: Optional[str] = Field(None, alias="Female_Name_Lastame") living_place: Optional[str] offer_accepted: Optional[bool] = False offer_accepted_at: Optional[datetime] = None @model_validator(mode='after') def at_least_one_identifier(self): if not any([self.username, self.phone, self.email]): raise ValueError("At least one of username, phone or email is required") return self model_config = {"validate_by_name": True} class FormCompetition(BaseModel): id: str title: str address: Optional[str] topic: Optional[str] short_desc: Optional[str] rules_file_id: Optional[str] poster_file_id: Optional[str] min_paintings: Optional[int] max_paintings: Optional[int] class UserLoginForm(OAuth2PasswordRequestForm): pass class LoginRequest(BaseModel): identifier: str password: str client_id: str class RestoreRequest(BaseModel): identifier: str next: str class ResetPasswordRequest(BaseModel): token: str new_password: str class AdminLoginRequest(BaseModel): user_id: str class PartnercompetitionRequest(BaseModel): competition_id: str post_id: str status: str class CompetitionArtistRequest(BaseModel): competition_id: str user_id: str class Partnercompetitionstop(BaseModel): competition_id: str class PartnerExhibitionDelete(BaseModel): exhibition_id: str class PostEditDescription(BaseModel): post_id: str description: str class TokenResponse(BaseModel): access_token: str refresh_token: str token_type: str = "bearer" class RefreshRequest(BaseModel): refresh_token: str | None = None client_id: str | None = None class CompetitionCreate(BaseModel): datetime_start: datetime date_finish: datetime poster: UploadFile type_: str title: str topic: Optional[str] = None nominations: List[str] = [] address: str short_desc: Optional[str] = None rules_link: Optional[str] = None rules: Optional[UploadFile] = None min_paintings: int max_paintings: int emailsNotify: bool = False serviceSocial: bool = False userSocial: bool = False @model_validator(mode="after") def make_aware(cls, values): tz = timezone.utc if not values.rules_link and not values.rules: raise ValueError( "Укажите хотя бы один из параметров: `rules_link` ИЛИ `rules` (файл)." ) if values.datetime_start and values.datetime_start.tzinfo is None: values.datetime_start = values.datetime_start.replace(tzinfo=tz) elif values.datetime_start: values.datetime_start = values.datetime_start.astimezone(tz) if values.date_finish and values.date_finish.tzinfo is None: values.date_finish = values.date_finish.replace(tzinfo=tz) elif values.date_finish: values.date_finish = values.date_finish.astimezone(tz) return values @validator("nominations", pre=True, always=True) def parse_nominations(cls, v): if v is None: return None if isinstance(v, list): return v try: data = json.loads(v) return data if isinstance(data, list) else [data] except (json.JSONDecodeError, TypeError): return [v] class ExhibitionCreate(BaseModel): datetime_start: datetime date_finish: datetime poster: UploadFile rules: Optional[UploadFile] = None rules_link: Optional[str] = None tizer: Optional[UploadFile] = None tizer_link: Optional[str] = None title: str topic: Optional[str] = None address: Optional[str] = None place: Optional[str] = None @model_validator(mode="after") def make_aware(cls, values): tz = timezone.utc if not values.rules_link and not values.rules: raise ValueError( "Укажите хотя бы один из параметров: `rules_link` ИЛИ `rules` (файл)." ) if not values.tizer_link and not values.tizer: raise ValueError( "Укажите хотя бы один из параметров: `tizer_link` ИЛИ `tizer` (файл)." ) if values.datetime_start and values.datetime_start.tzinfo is None: values.datetime_start = values.datetime_start.replace(tzinfo=tz) elif values.datetime_start: values.datetime_start = values.datetime_start.astimezone(tz) if values.date_finish and values.date_finish.tzinfo is None: values.date_finish = values.date_finish.replace(tzinfo=tz) elif values.date_finish: values.date_finish = values.date_finish.astimezone(tz) return values async def build_payload( datetime_start: datetime = Form(...), date_finish: datetime = Form(...), poster: UploadFile = File(...), type_: str = Form(...), title: str = Form(...), topic: Optional[str] = Form(None), nominations: Optional[str] = Form(None), address: str = Form(...), short_desc: Optional[str] = Form(None), rules_link: Optional[str] = Form(None), rules: Optional[UploadFile] = File(None), min_paintings: int = Form(...), max_paintings: int = Form(...), emailsNotify: bool = Form(False), serviceSocial: bool = Form(False), userSocial: bool = Form(False), ) -> CompetitionCreate: return CompetitionCreate( datetime_start=datetime_start, date_finish=date_finish, poster=poster, type_=type_, title=title, topic=topic, nominations=nominations, address=address, short_desc=short_desc, rules_link=rules_link, rules=rules, min_paintings=min_paintings, max_paintings=max_paintings, emailsNotify=emailsNotify, serviceSocial=serviceSocial, userSocial=userSocial, ) class CompetitionUpdate(BaseModel): datetime_start: Optional[datetime] = None date_finish: Optional[datetime] = None poster: Optional[UploadFile] = None type_: Optional[str] = None title: Optional[str] = None topic: Optional[str] = None nominations: Optional[List[str]] = None rules_link: Optional[str] = None address: Optional[str] = None short_desc: Optional[str] = None rules: Optional[UploadFile] = None min_paintings: Optional[int] = None max_paintings: Optional[int] = None emailsNotify: Optional[bool] = False serviceSocial: Optional[bool] = False userSocial: Optional[bool] = False model_config = ConfigDict( arbitrary_types_allowed=True, json_encoders={ datetime: lambda v: v.isoformat() }, ) @model_validator(mode="after") def make_aware(cls, values): tz = timezone.utc if values.datetime_start and values.datetime_start.tzinfo is None: values.datetime_start = values.datetime_start.replace(tzinfo=tz) elif values.datetime_start: values.datetime_start = values.datetime_start.astimezone(tz) if values.date_finish and values.date_finish.tzinfo is None: values.date_finish = values.date_finish.replace(tzinfo=tz) elif values.date_finish: values.date_finish = values.date_finish.astimezone(tz) return values @validator("nominations", pre=True, always=True) def parse_nominations(cls, v): if v is None: return None if isinstance(v, list): return v try: data = json.loads(v) return data if isinstance(data, list) else [data] except (json.JSONDecodeError, TypeError): return [v] async def build_update_payload( datetime_start: Optional[datetime] = Form(None), date_finish: Optional[datetime] = Form(None), poster: Optional[UploadFile] = File(None), type_: Optional[str] = Form(...), title: Optional[str] = Form(None), topic: Optional[str] = Form(None), nominations: Optional[str] = Form(None), rules_link: Optional[str] = Form(None), address: Optional[str] = Form(None), short_desc: Optional[str] = Form(None), rules: Optional[UploadFile] = File(None), min_paintings: Optional[int] = Form(None), max_paintings: Optional[int] = Form(None), emailsNotify: Optional[bool] = Form(False), serviceSocial: Optional[bool] = Form(False), userSocial: Optional[bool] = Form(False), ) -> CompetitionUpdate: return CompetitionUpdate( datetime_start=datetime_start, date_finish=date_finish, poster=poster, type_=type_, title=title, topic=topic, nominations=nominations, rules_link=rules_link, address=address, short_desc=short_desc, rules=rules, min_paintings=min_paintings, max_paintings=max_paintings, emailsNotify=emailsNotify, serviceSocial=serviceSocial, userSocial=userSocial, ) class CompetitionPublishParams(BaseModel): competition_id: str emailsNotify: bool = False serviceSocial: bool = False userSocial: bool = False usersSocial: bool = False async def build_publish_payload( competition_id: str = Form(...), emailsNotify: bool = Form(False), serviceSocial: bool = Form(False), userSocial: bool = Form(False), usersSocial: bool = Form(False), ) -> CompetitionPublishParams: return CompetitionPublishParams( competition_id=competition_id, emailsNotify=emailsNotify, serviceSocial=serviceSocial, userSocial=userSocial, usersSocial=usersSocial, ) async def build_exhibition_payload( datetime_start: datetime = Form(...), date_finish: datetime = Form(...), poster: Optional[UploadFile] = File(...), rules: Optional[UploadFile] = File(None), rules_link: Optional[str] = Form(None), tizer: Optional[UploadFile] = File(None), tizer_link: Optional[str] = Form(None), title: str = Form(...), topic: Optional[str] = Form(None), address: Optional[str] = Form(None), place: Optional[str] = Form(None), ) -> ExhibitionCreate: return ExhibitionCreate( datetime_start=datetime_start, date_finish=date_finish, poster=poster, rules=rules, rules_link=rules_link, tizer=tizer, tizer_link=tizer_link, title=title, topic=topic, address=address, place=place, ) class ExhibitionUpdate(BaseModel): datetime_start: Optional[datetime] = None date_finish: Optional[datetime] = None poster: Optional[UploadFile] = None rules: Optional[UploadFile] = None rules_link: Optional[str] = None tizer: Optional[UploadFile] = None tizer_link: Optional[str] = None title: Optional[str] = None topic: Optional[str] = None address: Optional[str] = None place: Optional[str] = None short_desc: Optional[str] = None model_config = ConfigDict( arbitrary_types_allowed=True, json_encoders={ datetime: lambda v: v.isoformat() }, ) @model_validator(mode="after") def make_aware(cls, values): tz = timezone.utc if values.datetime_start and values.datetime_start.tzinfo is None: values.datetime_start = values.datetime_start.replace(tzinfo=tz) elif values.datetime_start: values.datetime_start = values.datetime_start.astimezone(tz) if values.date_finish and values.date_finish.tzinfo is None: values.date_finish = values.date_finish.replace(tzinfo=tz) elif values.date_finish: values.date_finish = values.date_finish.astimezone(tz) return values async def build_update_exhibition_payload( datetime_start: Optional[datetime] = Form(None), date_finish: Optional[datetime] = Form(None), poster: Optional[UploadFile] = File(None), rules: Optional[UploadFile] = File(None), rules_link: Optional[str] = Form(None), tizer: Optional[UploadFile] = File(None), tizer_link: Optional[str] = Form(None), title: Optional[str] = Form(None), topic: Optional[str] = Form(None), address: Optional[str] = Form(None), place: Optional[str] = Form(None), short_desc: Optional[str] = Form(None), ) -> ExhibitionUpdate: return ExhibitionUpdate( datetime_start=datetime_start, date_finish=date_finish, poster=poster, rules=rules, rules_link=rules_link, tizer=tizer, tizer_link=tizer_link, title=title, topic=topic, address=address, place=place, short_desc=short_desc, )