This commit is contained in:
2026-04-09 19:28:41 +03:00
commit 9fa723bb4c
43 changed files with 2804 additions and 0 deletions

13
app/models/__init__.py Normal file
View File

@@ -0,0 +1,13 @@
from app.models.base import Base, BaseModel
from app.models.form import Form, FormField, Field
from app.models.submission import Submission, SubmissionAuditLog
__all__ = [
"Base",
"BaseModel",
"Form",
"FormField",
"Field",
"Submission",
"SubmissionAuditLog",
]

41
app/models/base.py Normal file
View File

@@ -0,0 +1,41 @@
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column, Integer, DateTime
from datetime import datetime
from typing import Any
# Базовый класс для декларативных моделей
Base = declarative_base()
class BaseModel(Base):
"""
Абстрактный базовый класс для всех моделей.
Содержит общие поля и методы.
"""
__abstract__ = True
id = Column(Integer, primary_key=True, index=True)
created_at = Column(DateTime, default=datetime.utcnow, nullable=False)
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow, nullable=False)
def dict(self) -> dict[str, Any]:
"""Преобразует модель в словарь"""
return {
column.name: getattr(self, column.name)
for column in self.__table__.columns
}
def update(self, **kwargs) -> None:
"""Обновляет поля модели"""
for key, value in kwargs.items():
if hasattr(self, key):
setattr(self, key, value)
@classmethod
def get_column_names(cls) -> list[str]:
"""Возвращает список имен колонок модели"""
return [column.name for column in cls.__table__.columns]
def __repr__(self) -> str:
"""Строковое представление модели"""
return f"<{self.__class__.__name__}(id={self.id})>"

79
app/models/form.py Normal file
View File

@@ -0,0 +1,79 @@
from sqlalchemy import Column, Integer, String, DateTime, Boolean, Text, ForeignKey, Index
from sqlalchemy.orm import relationship
from sqlalchemy.dialects.postgresql import JSONB, UUID
from datetime import datetime
import uuid
from app.models.base import BaseModel
class Form(BaseModel):
__tablename__ = "forms"
uuid = Column(UUID(as_uuid=True), default=uuid.uuid4, unique=True, nullable=False, index=True)
name = Column(String(200), nullable=False)
description = Column(Text)
version = Column(Integer, default=1)
is_active = Column(Boolean, default=True)
is_published = Column(Boolean, default=False)
form_settings = Column(JSONB, default={}) # переименовано с settings
created_by = Column(Integer, nullable=True)
# Relationships
fields = relationship("FormField", back_populates="form", cascade="all, delete-orphan")
submissions = relationship("Submission", back_populates="form", cascade="all, delete-orphan")
__table_args__ = (
Index('ix_forms_name_active', 'name', 'is_active'),
Index('ix_forms_created_at', 'created_at'),
)
@property
def fields_count(self) -> int:
return len(self.fields)
@property
def submissions_count(self) -> int:
return len(self.submissions)
class FormField(BaseModel):
__tablename__ = "form_fields"
form_id = Column(Integer, ForeignKey("forms.id", ondelete="CASCADE"))
field_id = Column(Integer, ForeignKey("fields.id", ondelete="CASCADE"))
order = Column(Integer, default=0)
is_required = Column(Boolean, default=False)
default_value = Column(JSONB, nullable=True)
visibility_conditions = Column(JSONB, default={})
validation_rules_override = Column(JSONB, nullable=True)
# Relationships
form = relationship("Form", back_populates="fields")
field = relationship("Field", back_populates="form_fields")
__table_args__ = (
Index('ix_form_fields_order', 'form_id', 'order'),
Index('ix_form_fields_unique', 'form_id', 'field_id', unique=True),
)
class Field(BaseModel):
__tablename__ = "fields"
name = Column(String(100), unique=True, nullable=False, index=True)
label = Column(String(200), nullable=False)
field_type = Column(String(50), nullable=False)
placeholder = Column(String(200))
help_text = Column(Text)
field_options = Column(JSONB, default={}) # переименовано с options
validation_rules = Column(JSONB, default={})
field_metadata = Column(JSONB, default={}) # переименовано с metadata
created_at = Column(DateTime, default=datetime.utcnow)
# Relationships
form_fields = relationship("FormField", back_populates="field")
__table_args__ = (
Index('ix_fields_type', 'field_type'),
Index('ix_fields_name_type', 'name', 'field_type'),
)

51
app/models/submission.py Normal file
View File

@@ -0,0 +1,51 @@
from sqlalchemy import Column, Integer, DateTime, ForeignKey, String, Float, Boolean, Index
from sqlalchemy.orm import relationship
from sqlalchemy.dialects.postgresql import JSONB, UUID
from datetime import datetime
import uuid
from app.models.base import BaseModel
class Submission(BaseModel):
__tablename__ = "submissions"
form_id = Column(Integer, ForeignKey("forms.id", ondelete="CASCADE"))
submission_uuid = Column(UUID(as_uuid=True), default=uuid.uuid4, unique=True, nullable=False, index=True)
submission_data = Column(JSONB, nullable=False) # переименовано с data
submission_metadata = Column(JSONB, default={}) # переименовано с metadata
status = Column(String(50), default="completed")
submitted_at = Column(DateTime, default=datetime.utcnow, index=True)
submitted_by = Column(Integer, nullable=True)
# Для аналитики
completion_time_seconds = Column(Float, nullable=True)
referrer = Column(String(500))
# Relationships
form = relationship("Form", back_populates="submissions")
audit_logs = relationship("SubmissionAuditLog", back_populates="submission")
__table_args__ = (
Index('ix_submissions_data_gin', submission_data, postgresql_using='gin'),
Index('ix_submissions_metadata_gin', submission_metadata, postgresql_using='gin'),
Index('ix_submissions_form_status', 'form_id', 'status'),
Index('ix_submissions_date_status', 'submitted_at', 'status'),
Index('ix_submissions_created_at', 'created_at'),
)
class SubmissionAuditLog(BaseModel):
__tablename__ = "submission_audit_logs"
submission_id = Column(Integer, ForeignKey("submissions.id", ondelete="CASCADE"))
action = Column(String(50))
changed_data = Column(JSONB)
ip_address = Column(String(45))
user_agent = Column(String(500))
submission = relationship("Submission", back_populates="audit_logs")
__table_args__ = (
Index('ix_audit_submission', 'submission_id'),
Index('ix_audit_created_at', 'created_at'),
)