This commit is contained in:
2025-09-14 22:09:39 +05:00
parent 8ad2bfbe59
commit 3c418a97b4
30 changed files with 3404 additions and 0 deletions

208
database/models.py Normal file
View File

@@ -0,0 +1,208 @@
"""
Базовые модели для работы с базой данных
"""
from sqlalchemy import Column, Integer, String, DateTime, Boolean, Text, BigInteger, ForeignKey, LargeBinary
from sqlalchemy.orm import relationship
from sqlalchemy.sql import func
from database.database import Base
class User(Base):
"""Модель пользователя телеграм бота"""
__tablename__ = "users"
id = Column(Integer, primary_key=True, index=True)
telegram_id = Column(BigInteger, unique=True, index=True, nullable=False)
username = Column(String(255), nullable=True)
first_name = Column(String(255), nullable=True)
last_name = Column(String(255), nullable=True)
language_code = Column(String(10), default='ru')
is_active = Column(Boolean, default=True)
is_admin = Column(Boolean, default=False)
created_at = Column(DateTime(timezone=True), server_default=func.now())
updated_at = Column(DateTime(timezone=True), onupdate=func.now())
# Связь с результатами тестов
test_results = relationship("TestResult", back_populates="user")
def __repr__(self):
return f"<User(id={self.id}, telegram_id={self.telegram_id}, username={self.username})>"
class Message(Base):
"""Модель сообщений"""
__tablename__ = "messages"
id = Column(Integer, primary_key=True, index=True)
telegram_message_id = Column(BigInteger, nullable=False)
user_id = Column(Integer, nullable=False) # Внешний ключ на users.id
text = Column(Text, nullable=True)
message_type = Column(String(50), default='text') # text, photo, document, etc.
created_at = Column(DateTime(timezone=True), server_default=func.now())
def __repr__(self):
return f"<Message(id={self.id}, user_id={self.user_id}, type={self.message_type})>"
class BotSettings(Base):
"""Модель настроек бота"""
__tablename__ = "bot_settings"
id = Column(Integer, primary_key=True, index=True)
key = Column(String(255), unique=True, nullable=False)
value = Column(Text, nullable=True)
description = Column(Text, nullable=True)
created_at = Column(DateTime(timezone=True), server_default=func.now())
updated_at = Column(DateTime(timezone=True), onupdate=func.now())
def __repr__(self):
return f"<BotSettings(key={self.key}, value={self.value})>"
# ===== МОДЕЛИ ДЛЯ СИСТЕМЫ ТЕСТИРОВАНИЯ =====
class TestCategory(Base):
"""Категории тестов"""
__tablename__ = "test_categories"
id = Column(Integer, primary_key=True, index=True)
name = Column(String(255), unique=True, nullable=False) # "Тест Климова", "Профориентация" и т.д.
description = Column(Text, nullable=True)
is_active = Column(Boolean, default=True)
created_at = Column(DateTime(timezone=True), server_default=func.now())
# Связь с тестами
tests = relationship("Test", back_populates="category")
def __repr__(self):
return f"<TestCategory(id={self.id}, name={self.name})>"
class Test(Base):
"""Конкретные тесты"""
__tablename__ = "tests"
id = Column(Integer, primary_key=True, index=True)
category_id = Column(Integer, ForeignKey("test_categories.id"), nullable=False)
title = Column(String(255), nullable=False) # "📘 Тест Климова"
description = Column(Text, nullable=True)
instructions = Column(Text, nullable=True) # Инструкции перед тестом
is_active = Column(Boolean, default=True)
created_at = Column(DateTime(timezone=True), server_default=func.now())
# Связи
category = relationship("TestCategory", back_populates="tests")
questions = relationship("Question", back_populates="test", cascade="all, delete-orphan")
user_results = relationship("TestResult", back_populates="test")
def __repr__(self):
return f"<Test(id={self.id}, title={self.title})>"
class Image(Base):
"""Хранение изображений в базе данных"""
__tablename__ = "images"
id = Column(Integer, primary_key=True, index=True)
filename = Column(String(255), nullable=False) # Оригинальное имя файла
content_type = Column(String(100), nullable=False) # image/jpeg, image/png и т.д.
file_size = Column(Integer, nullable=False) # Размер в байтах
image_data = Column(LargeBinary, nullable=False) # Бинарные данные изображения
alt_text = Column(String(255), nullable=True) # Альтернативный текст для доступности
created_at = Column(DateTime(timezone=True), server_default=func.now())
# Связь с вопросами
questions = relationship("Question", back_populates="image")
def __repr__(self):
return f"<Image(id={self.id}, filename={self.filename}, size={self.file_size})>"
class Question(Base):
"""Вопросы тестов"""
__tablename__ = "questions"
id = Column(Integer, primary_key=True, index=True)
test_id = Column(Integer, ForeignKey("tests.id"), nullable=False)
order_index = Column(Integer, nullable=False) # Порядок вопроса в тесте
question_text = Column(Text, nullable=True) # Текст вопроса (может быть пустым если только картинка)
image_id = Column(Integer, ForeignKey("images.id"), nullable=True) # Привязка к изображению
is_active = Column(Boolean, default=True)
created_at = Column(DateTime(timezone=True), server_default=func.now())
# Связи
test = relationship("Test", back_populates="questions")
image = relationship("Image", back_populates="questions")
answer_options = relationship("AnswerOption", back_populates="question", cascade="all, delete-orphan")
user_answers = relationship("UserAnswer", back_populates="question")
def __repr__(self):
return f"<Question(id={self.id}, test_id={self.test_id}, order={self.order_index})>"
class AnswerOption(Base):
"""Варианты ответов на вопросы"""
__tablename__ = "answer_options"
id = Column(Integer, primary_key=True, index=True)
question_id = Column(Integer, ForeignKey("questions.id"), nullable=False)
option_text = Column(Text, nullable=False) # Текст варианта ответа
category = Column(String(255), nullable=True) # Категория для подсчета результатов (например, "Человек-природа")
order_index = Column(Integer, nullable=False) # Порядок варианта (1, 2, 3...)
is_correct = Column(Boolean, default=False) # Для тестов с правильными ответами
created_at = Column(DateTime(timezone=True), server_default=func.now())
# Связи
question = relationship("Question", back_populates="answer_options")
user_answers = relationship("UserAnswer", back_populates="selected_option")
def __repr__(self):
return f"<AnswerOption(id={self.id}, question_id={self.question_id}, text='{self.option_text[:50]}')>"
class TestResult(Base):
"""Результаты прохождения тестов пользователями"""
__tablename__ = "test_results"
id = Column(Integer, primary_key=True, index=True)
user_id = Column(Integer, ForeignKey("users.id"), nullable=False)
test_id = Column(Integer, ForeignKey("tests.id"), nullable=False)
started_at = Column(DateTime(timezone=True), server_default=func.now())
completed_at = Column(DateTime(timezone=True), nullable=True)
is_completed = Column(Boolean, default=False)
result_data = Column(Text, nullable=True) # JSON с результатами по категориям
# Связи
user = relationship("User", back_populates="test_results")
test = relationship("Test", back_populates="user_results")
answers = relationship("UserAnswer", back_populates="test_result", cascade="all, delete-orphan")
def __repr__(self):
return f"<TestResult(id={self.id}, user_id={self.user_id}, test_id={self.test_id}, completed={self.is_completed})>"
class UserAnswer(Base):
"""Ответы пользователей на конкретные вопросы"""
__tablename__ = "user_answers"
id = Column(Integer, primary_key=True, index=True)
test_result_id = Column(Integer, ForeignKey("test_results.id"), nullable=False)
question_id = Column(Integer, ForeignKey("questions.id"), nullable=False)
selected_option_id = Column(Integer, ForeignKey("answer_options.id"), nullable=False)
answered_at = Column(DateTime(timezone=True), server_default=func.now())
# Связи
test_result = relationship("TestResult", back_populates="answers")
question = relationship("Question", back_populates="user_answers")
selected_option = relationship("AnswerOption", back_populates="user_answers")
def __repr__(self):
return f"<UserAnswer(id={self.id}, question_id={self.question_id}, option_id={self.selected_option_id})>"