From 1d9fe9bfe35336f7ede68c4e2abca52c941ac5efa1fa5d012f6039df01ad5028 Mon Sep 17 00:00:00 2001 From: Vladislav Date: Tue, 30 Jun 2026 19:13:50 -0400 Subject: [PATCH] =?UTF-8?q?Bot=20commit:=20=D0=94=D0=BE=D0=B1=D0=B0=D0=B2?= =?UTF-8?q?=D0=BB=D0=B5=D0=BD=20=D1=84=D0=B0=D0=B9=D0=BB=20models.py?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- models.py | 450 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 450 insertions(+) create mode 100644 models.py diff --git a/models.py b/models.py new file mode 100644 index 0000000..1a2d4f8 --- /dev/null +++ b/models.py @@ -0,0 +1,450 @@ +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, + )