Загрузка данных
from datetime import datetime
from enum import Enum
from typing import List, Optional
from django.utils.translation import gettext_lazy as _
from pydantic import BaseModel, EmailStr, Field
# ==============================================================================
# ENUMS (Общие перечисления для валидации и выпадающих списков в OpenAPI)
# ==============================================================================
class GlobalRoleEnum(str, Enum):
"""Глобальные системные роли пользователей."""
ADMIN = "admin"
MANAGER = "manager"
USER = "user"
class ProjectPermissionEnum(str, Enum):
"""Допустимые типы объектных прав django-guardian в системе Paprika."""
VIEW = "view_project"
EDIT = "edit_project"
# ==============================================================================
# 1. МОДУЛЬ: АУТЕНТИФИКАЦИЯ (AUTH)
# ==============================================================================
class LoginRequestSchema(BaseModel):
"""Схема запроса для аутентификации пользователя."""
email: EmailStr = Field(
description=_("Unique electronic address of the user registered in the system."),
examples=["vfx_artist@paprika.studio"]
)
password: str = Field(
min_length=8,
max_length=128,
description=_("User password. Must be at least 8 characters long."),
examples=["p@ssw0rd_vfx_2026"]
)
class LoginResponseSchema(BaseModel):
"""Схема успешного ответа при аутентификации (Access токен)."""
access: str = Field(
description=_("Short-lived JWT Access token. Passed in the 'Authorization: Bearer' header."),
examples=["eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."]
)
class RefreshRequestSchema(BaseModel):
"""Схема для эндпоинта обновления токена. Тело должно быть пустым (токен в куке)."""
class Config:
extra = "forbid"
class RefreshResponseSchema(BaseModel):
"""Схема успешного обновления сессии."""
access: str = Field(
description=_("New valid JWT Access token."),
examples=["eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.new..."]
)
class LogoutRequestSchema(BaseModel):
"""Схема запроса для выхода из системы (пустое тело)."""
class Config:
extra = "forbid"
class LogoutResponseSchema(BaseModel):
"""Схема ответа при успешном выходе из системы."""
detail: str = Field(
default="Successfully logged out.",
description=_("Text confirmation of successful session termination."),
examples=["Successfully logged out."]
)
# ==============================================================================
# 2. МОДУЛЬ: ПРИГЛАШЕНИЯ (INVITATIONS)
# ==============================================================================
class InvitationProjectPermissionSchema(BaseModel):
"""Схема назначения прав на конкретный проект внутри инвайта."""
project_id: int = Field(
description=_("ID of the project to which access is granted."),
examples=[12]
)
permission: ProjectPermissionEnum = Field(
description=_("Permission level for the specified project."),
examples=[ProjectPermissionEnum.VIEW]
)
class InvitationCreateRequestSchema(BaseModel):
"""Схема запроса на создание инвайта администрацией."""
email: EmailStr = Field(
description=_("Email address where the invitation link will be sent."),
examples=["vfx_artist@paprika.studio"]
)
role: GlobalRoleEnum = Field(
default=GlobalRoleEnum.USER,
description=_("Global system role assigned to the user after registration.")
)
project_permissions: Optional[List[InvitationProjectPermissionSchema]] = Field(
default=None,
description=_("List of initial project-level permissions that will be automatically assigned.")
)
class InvitationResponseSchema(BaseModel):
"""Схема ответа с полной информацией о созданном инвайте."""
id: str = Field(description=_("Unique string identifier of the invitation record."), examples=["inv_8f2b3c9d"])
email: EmailStr = Field(description=_("Email address for which the invitation was generated."))
role: GlobalRoleEnum = Field(description=_("Target global role for the invitee."))
token: str = Field(description=_("Secure unique token included in the registration URL."), examples=["usr_tkn_abc123"])
status: str = Field(description=_("Current status of the invitation."), examples=["pending"])
invited_by: int = Field(description=_("ID of the administrator who issued the invitation."), examples=[1])
created_at: datetime = Field(description=_("Timestamp when the invitation was created."))
expires_at: datetime = Field(description=_("Timestamp after which the invitation link becomes invalid."))
class InvitationValidateResponseSchema(BaseModel):
"""Схема ответа для публичного эндпоинта валидации токена на фронтенде."""
email: EmailStr = Field(description=_("Email address bound to this token."))
role: GlobalRoleEnum = Field(description=_("Target role for the user."))
status: str = Field(description=_("Status of the token."), examples=["pending"])
expires_at: datetime = Field(description=_("Token expiration timestamp."))
class InvitationAcceptRequestSchema(BaseModel):
"""Схема запроса завершения регистрации от самого пользователя."""
first_name: str = Field(max_length=150, description=_("User's given name."), examples=["Alex"])
last_name: str = Field(max_length=150, description=_("User's family name."), examples=["Pro"])
password: str = Field(min_length=8, max_length=128, description=_("Secure password chosen by the user."))
# ==============================================================================
# 3. МОДУЛЬ: ЛИЧНЫЙ КАБИНЕТ (ME)
# ==============================================================================
class UserProfileResponseSchema(BaseModel):
"""Схема ответа, возвращающая полные данные профиля пользователя."""
id: int = Field(description=_("Unique database identifier of the user."), examples=[42])
email: EmailStr = Field(description=_("User's primary email address used for login."))
first_name: Optional[str] = Field(default=None, max_length=150, description=_("User's given name."))
last_name: Optional[str] = Field(default=None, max_length=150, description=_("User's family name."))
role: GlobalRoleEnum = Field(description=_("Global system role determining baseline permissions."))
avatar: Optional[str] = Field(default=None, description=_("Absolute URL to the user's avatar image."))
is_active: bool = Field(description=_("Designates whether this user account should be treated as active."))
date_joined: datetime = Field(description=_("Timestamp indicating when the user account was created."))
class UserProfileUpdateRequestSchema(BaseModel):
"""Схема запроса для частичного обновления (PATCH) профиля самим пользователем."""
first_name: Optional[str] = Field(default=None, max_length=150, description=_("Updated given name."))
last_name: Optional[str] = Field(default=None, max_length=150, description=_("Updated family name."))
class Config:
extra = "forbid"
# ==============================================================================
# 4. МОДУЛЬ: ОБЪЕКТНЫЕ ПРАВА ПРОЕКТОВ (GUARDIAN CONTEXT)
# ==============================================================================
class ProjectUserPermissionsSchema(BaseModel):
"""Схема, описывающая текущие права конкретного пользователя внутри проекта."""
user_id: int = Field(description=_("Unique database identifier of the user."), examples=[42])
email: EmailStr = Field(description=_("User's email address."))
permissions: List[ProjectPermissionEnum] = Field(description=_("List of assigned object-level permissions."))
class ProjectParticipantsListResponseSchema(BaseModel):
"""Схема ответа для получения списка всех участников проекта и их прав (GET)."""
project_id: int = Field(description=_("ID of the project being queried."), examples=[12])
users: List[ProjectUserPermissionsSchema] = Field(description=_("List of users in this project."))
class ProjectUserPermissionsSyncRequestSchema(BaseModel):
"""Схема запроса (PUT) для полной идемпотентной перезаписи прав пользователя на проект."""
permissions: List[ProjectPermissionEnum] = Field(
description=_("The exact list of permissions that should remain. Missing ones will be revoked."),
examples=[[ProjectPermissionEnum.VIEW, ProjectPermissionEnum.EDIT]]
)
class Config:
extra = "forbid"