2024-08-14 16:46:48 +03:00
|
|
|
from enum import IntEnum, Enum
|
2024-08-21 11:36:02 +03:00
|
|
|
from typing import Annotated, Union, Literal
|
2024-08-14 16:21:23 +03:00
|
|
|
from uuid import UUID, uuid4
|
2024-08-10 20:38:54 +03:00
|
|
|
|
2024-08-14 16:46:48 +03:00
|
|
|
from pydantic import Field, field_validator, field_serializer
|
2024-08-10 20:38:54 +03:00
|
|
|
|
|
|
|
from models import BaseModel
|
|
|
|
|
|
|
|
|
2024-08-14 16:46:48 +03:00
|
|
|
class FormError(Enum):
|
|
|
|
MIN_LENGTH_ERR = "min_length must be greater than or equal to 0."
|
|
|
|
MAX_LENGTH_ERR = "max_length must be greater than 0."
|
|
|
|
MAX_LENGTH_LESS_THAN_MIN_LENGTH = "max_length cannot be less than min_length."
|
|
|
|
MIN_VALUES_ERR = "min_values must be greater than or equal to 1."
|
|
|
|
MAX_VALUES_ERR = "max_values cannot be less than min_length or greater than the number of options."
|
|
|
|
SIMMILAR_ID_ERR = "All questions must have different id's"
|
2024-08-20 09:45:07 +03:00
|
|
|
SCALE_MIN_VALUE_ERR = "min_value must be in range from 0 to 1"
|
|
|
|
SCALE_MAX_VALUE_ERR = "max_value must be in range from 2 to 10"
|
2024-08-21 11:36:02 +03:00
|
|
|
EMPTY_OPTIONS_ERR = "Options field cannot be empty"
|
2024-08-14 16:46:48 +03:00
|
|
|
|
|
|
|
|
2024-08-12 16:57:40 +03:00
|
|
|
class QuestionType(IntEnum):
|
|
|
|
text = 1
|
|
|
|
selector = 2
|
2024-08-20 09:45:07 +03:00
|
|
|
scale = 3
|
2024-08-10 20:38:54 +03:00
|
|
|
|
|
|
|
|
2024-08-17 16:01:26 +03:00
|
|
|
class TextValidator(IntEnum):
|
|
|
|
tin = 1
|
|
|
|
snils = 2
|
|
|
|
|
|
|
|
|
2024-08-10 20:38:54 +03:00
|
|
|
class BaseQuestion(BaseModel):
|
2024-08-14 16:21:23 +03:00
|
|
|
id: UUID = Field(default_factory=uuid4)
|
2024-08-10 20:38:54 +03:00
|
|
|
question_type: QuestionType
|
|
|
|
label: str = Field(min_length=1)
|
|
|
|
description: str | None = Field(None, min_length=1)
|
2024-08-20 20:07:11 +03:00
|
|
|
image_url: str | None = None
|
2024-08-11 16:45:17 +03:00
|
|
|
required: bool = True
|
|
|
|
|
2024-08-14 16:46:48 +03:00
|
|
|
@field_serializer("id")
|
|
|
|
def serialize_id(self, id: UUID):
|
|
|
|
return str(id)
|
|
|
|
|
2024-08-11 16:45:17 +03:00
|
|
|
|
|
|
|
class Option(BaseModel):
|
|
|
|
label: str
|
2024-08-21 11:36:02 +03:00
|
|
|
image_url: str | None = None
|
2024-08-10 20:38:54 +03:00
|
|
|
|
|
|
|
|
|
|
|
class TextQuestion(BaseQuestion):
|
2024-08-21 11:36:02 +03:00
|
|
|
question_type: Literal[QuestionType.text] = QuestionType.text
|
2024-08-17 16:01:26 +03:00
|
|
|
validator: TextValidator | None = None
|
2024-08-10 20:38:54 +03:00
|
|
|
min_length: int | None = None
|
|
|
|
max_length: int | None = None
|
2024-08-11 16:45:17 +03:00
|
|
|
|
2024-08-12 16:57:40 +03:00
|
|
|
@field_validator("min_length")
|
2024-08-11 16:45:17 +03:00
|
|
|
@classmethod
|
2024-08-12 16:57:40 +03:00
|
|
|
def validate_min_length(cls, v, info):
|
2024-08-17 16:01:26 +03:00
|
|
|
validator = info.data.get("validator")
|
|
|
|
if v is not None:
|
|
|
|
if v < 0:
|
|
|
|
raise ValueError(FormError.MIN_LENGTH_ERR.value)
|
|
|
|
if validator is not None:
|
|
|
|
return None
|
2024-08-11 16:45:17 +03:00
|
|
|
return v
|
|
|
|
|
2024-08-12 16:57:40 +03:00
|
|
|
@field_validator("max_length")
|
2024-08-11 16:45:17 +03:00
|
|
|
@classmethod
|
2024-08-12 16:57:40 +03:00
|
|
|
def validate_max_length(cls, v, info):
|
|
|
|
min_length = info.data.get("min_length")
|
2024-08-17 16:01:26 +03:00
|
|
|
validator = info.data.get("validator")
|
2024-08-11 16:45:17 +03:00
|
|
|
if v is not None:
|
|
|
|
if v <= 0:
|
2024-08-15 21:01:01 +03:00
|
|
|
raise ValueError(FormError.MAX_LENGTH_TOO_SMALL.value)
|
2024-08-11 16:45:17 +03:00
|
|
|
if min_length is not None and v < min_length:
|
2024-08-15 21:01:01 +03:00
|
|
|
raise ValueError(FormError.MAX_LENGTH_LESS_THAN_MIN_LENGTH.value)
|
2024-08-17 16:01:26 +03:00
|
|
|
if validator is not None:
|
|
|
|
return None
|
2024-08-11 16:45:17 +03:00
|
|
|
return v
|
|
|
|
|
|
|
|
|
2024-08-20 09:45:07 +03:00
|
|
|
class ScaleQuestion(BaseQuestion):
|
2024-08-21 11:36:02 +03:00
|
|
|
question_type: Literal[QuestionType.scale] = QuestionType.scale
|
2024-08-20 09:45:07 +03:00
|
|
|
min_value: int
|
|
|
|
min_label: str | None = None
|
|
|
|
max_value: int
|
|
|
|
max_label: str | None = None
|
|
|
|
|
|
|
|
@field_validator("min_value")
|
|
|
|
@classmethod
|
|
|
|
def validate_min_value(cls, v, info):
|
|
|
|
if v not in [0, 1]:
|
|
|
|
raise ValueError(FormError.SCALE_MIN_VALUE_ERR.value)
|
|
|
|
return v
|
|
|
|
|
|
|
|
@field_validator("max_value")
|
|
|
|
@classmethod
|
|
|
|
def validate_max_value(cls, v, info):
|
|
|
|
if v < 2 or v > 10:
|
|
|
|
raise ValueError(FormError.SCALE_MAX_VALUE_ERR.value)
|
|
|
|
return v
|
|
|
|
|
|
|
|
|
2024-08-11 16:45:17 +03:00
|
|
|
class SelectorQuestion(BaseQuestion):
|
2024-08-21 11:36:02 +03:00
|
|
|
question_type: Literal[QuestionType.selector] = QuestionType.selector
|
2024-08-11 16:45:17 +03:00
|
|
|
min_values: int = 1
|
|
|
|
max_values: int | None = None
|
2024-08-21 11:36:02 +03:00
|
|
|
options: list[Option]
|
|
|
|
|
|
|
|
@field_validator("options")
|
|
|
|
@classmethod
|
|
|
|
def validate_options(cls, v, info):
|
|
|
|
if len(v) < 1:
|
|
|
|
raise ValueError(FormError.EMPTY_OPTIONS_ERR.value)
|
|
|
|
return v
|
2024-08-11 16:45:17 +03:00
|
|
|
|
2024-08-12 16:57:40 +03:00
|
|
|
@field_validator("min_values")
|
2024-08-11 16:45:17 +03:00
|
|
|
@classmethod
|
2024-08-12 16:57:40 +03:00
|
|
|
def validate_min_values(cls, v, info):
|
2024-08-21 11:36:02 +03:00
|
|
|
if v is not None and v < 1:
|
2024-08-15 21:01:01 +03:00
|
|
|
raise ValueError(FormError.MIN_VALUES_ERR.value)
|
2024-08-11 16:45:17 +03:00
|
|
|
return v
|
|
|
|
|
2024-08-12 16:57:40 +03:00
|
|
|
@field_validator("max_values")
|
2024-08-11 16:45:17 +03:00
|
|
|
@classmethod
|
2024-08-12 16:57:40 +03:00
|
|
|
def validate_max_values(cls, v, info):
|
|
|
|
min_values = info.data.get("min_values")
|
2024-08-21 11:36:02 +03:00
|
|
|
if v is not None and min_values > v:
|
2024-08-15 21:01:01 +03:00
|
|
|
raise ValueError(FormError.MAX_VALUES_ERR.value)
|
2024-08-11 16:45:17 +03:00
|
|
|
return v
|
|
|
|
|
|
|
|
|
2024-08-21 11:36:02 +03:00
|
|
|
Question = Annotated[Union[SelectorQuestion, TextQuestion, ScaleQuestion], Field(discriminator='question_type')]
|
2024-08-11 16:45:17 +03:00
|
|
|
|
|
|
|
|
2024-08-17 16:01:26 +03:00
|
|
|
class Page(BaseModel):
|
|
|
|
text: str | None = Field(None, min_length=1)
|
2024-08-12 16:57:40 +03:00
|
|
|
questions: list[Question] = []
|
|
|
|
|
2024-08-14 16:21:23 +03:00
|
|
|
@field_validator("questions")
|
|
|
|
@classmethod
|
2024-08-14 16:46:48 +03:00
|
|
|
def validate_questions(cls, v, info):
|
2024-08-14 16:21:23 +03:00
|
|
|
uuids = set()
|
2024-08-14 16:46:48 +03:00
|
|
|
for question in v:
|
|
|
|
uuids.add(question.id)
|
2024-08-14 16:21:23 +03:00
|
|
|
|
2024-08-14 16:46:48 +03:00
|
|
|
if len(v) != len(uuids):
|
2024-08-15 21:01:01 +03:00
|
|
|
raise ValueError(FormError.SIMMILAR_ID_ERR.value)
|
2024-08-14 16:21:23 +03:00
|
|
|
|
|
|
|
return v
|
|
|
|
|
2024-08-12 16:57:40 +03:00
|
|
|
|
2024-08-17 16:01:26 +03:00
|
|
|
class FormData(BaseModel):
|
|
|
|
name: str = Field(min_length=1)
|
|
|
|
pages: list[Page] = []
|
|
|
|
|
|
|
|
@property
|
|
|
|
def questions(self) -> list[Question]:
|
|
|
|
questions = []
|
|
|
|
for page in self.pages:
|
|
|
|
questions.extend(page.questions)
|
|
|
|
return questions
|
|
|
|
|
|
|
|
|
2024-08-11 16:45:17 +03:00
|
|
|
class Form(BaseModel):
|
|
|
|
id: int
|
2024-08-12 16:57:40 +03:00
|
|
|
data: FormData
|