208 lines
9.2 KiB
Python
208 lines
9.2 KiB
Python
"""
|
||
Базовые модели для работы с базой данных
|
||
"""
|
||
|
||
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})>" |