Implement user-specific question numbering and update database schema. Added triggers for automatic question numbering and adjustments upon deletion. Enhanced CRUD operations to manage user_question_number effectively.
This commit is contained in:
118
models/question.py
Normal file
118
models/question.py
Normal file
@@ -0,0 +1,118 @@
|
||||
"""
|
||||
Модель вопроса
|
||||
"""
|
||||
import asyncio
|
||||
from dataclasses import dataclass
|
||||
from datetime import datetime
|
||||
from enum import Enum
|
||||
from typing import Optional
|
||||
|
||||
from config.constants import DEFAULT_QUESTION_PREVIEW_LENGTH, EMPTY_VALUES
|
||||
|
||||
|
||||
class QuestionStatus(Enum):
|
||||
"""Статусы вопроса"""
|
||||
PENDING = "pending" # Ожидает ответа
|
||||
ANSWERED = "answered" # Отвечен
|
||||
REJECTED = "rejected" # Отклонен
|
||||
DELETED = "deleted" # Удален
|
||||
|
||||
|
||||
@dataclass
|
||||
class Question:
|
||||
"""Модель вопроса"""
|
||||
|
||||
id: Optional[int] = None
|
||||
from_user_id: Optional[int] = None # ID отправителя (может быть None для анонимных)
|
||||
to_user_id: int = None # ID получателя
|
||||
message_text: str = "" # Текст вопроса
|
||||
answer_text: Optional[str] = None # Текст ответа
|
||||
is_anonymous: bool = True # Анонимный ли вопрос
|
||||
message_id: Optional[int] = None # ID сообщения в Telegram
|
||||
created_at: Optional[datetime] = None
|
||||
answered_at: Optional[datetime] = None
|
||||
is_read: bool = False # Прочитан ли вопрос
|
||||
status: QuestionStatus = QuestionStatus.PENDING
|
||||
user_question_number: Optional[int] = None # Локальный номер вопроса для пользователя
|
||||
|
||||
# Lazy loading атрибуты
|
||||
_from_user: Optional['User'] = None
|
||||
_to_user: Optional['User'] = None
|
||||
_user_loader: Optional[callable] = None
|
||||
|
||||
@property
|
||||
def is_answered(self) -> bool:
|
||||
"""Проверка, отвечен ли вопрос"""
|
||||
return self.status == QuestionStatus.ANSWERED
|
||||
|
||||
@property
|
||||
def is_pending(self) -> bool:
|
||||
"""Проверка, ожидает ли вопрос ответа"""
|
||||
return self.status == QuestionStatus.PENDING
|
||||
|
||||
|
||||
@classmethod
|
||||
def _parse_datetime(cls, date_str) -> Optional[datetime]:
|
||||
"""Безопасный парсинг datetime из строки"""
|
||||
if not date_str or date_str in EMPTY_VALUES:
|
||||
return None
|
||||
try:
|
||||
return datetime.fromisoformat(date_str)
|
||||
except (ValueError, TypeError):
|
||||
return None
|
||||
|
||||
@classmethod
|
||||
async def _parse_datetime_async(cls, date_str) -> Optional[datetime]:
|
||||
"""Асинхронный безопасный парсинг datetime из строки"""
|
||||
if not date_str or date_str in EMPTY_VALUES:
|
||||
return None
|
||||
try:
|
||||
loop = asyncio.get_event_loop()
|
||||
return await loop.run_in_executor(None, datetime.fromisoformat, date_str)
|
||||
except (ValueError, TypeError):
|
||||
return None
|
||||
|
||||
|
||||
def mark_as_answered(self, answer_text: str):
|
||||
"""Отметить вопрос как отвеченный"""
|
||||
self.answer_text = answer_text
|
||||
self.status = QuestionStatus.ANSWERED
|
||||
self.answered_at = datetime.now()
|
||||
|
||||
def mark_as_rejected(self):
|
||||
"""Отметить вопрос как отклоненный"""
|
||||
self.status = QuestionStatus.REJECTED
|
||||
self.answered_at = datetime.now()
|
||||
|
||||
def mark_as_deleted(self):
|
||||
"""Отметить вопрос как удаленный"""
|
||||
self.status = QuestionStatus.DELETED
|
||||
self.answered_at = datetime.now()
|
||||
self.user_question_number = None # Удаленные вопросы не имеют номера
|
||||
|
||||
def set_user_loader(self, loader_func: callable):
|
||||
"""Установка функции для загрузки пользователей"""
|
||||
self._user_loader = loader_func
|
||||
|
||||
async def get_from_user(self) -> Optional['User']:
|
||||
"""Lazy loading автора вопроса"""
|
||||
if self._from_user is None and self.from_user_id and self._user_loader:
|
||||
self._from_user = await self._user_loader(self.from_user_id)
|
||||
return self._from_user
|
||||
|
||||
async def get_to_user(self) -> Optional['User']:
|
||||
"""Lazy loading получателя вопроса"""
|
||||
if self._to_user is None and self.to_user_id and self._user_loader:
|
||||
self._to_user = await self._user_loader(self.to_user_id)
|
||||
return self._to_user
|
||||
|
||||
def get_question_preview(self, max_length: int = DEFAULT_QUESTION_PREVIEW_LENGTH) -> str:
|
||||
"""Получить превью вопроса"""
|
||||
if len(self.message_text) <= max_length:
|
||||
return self.message_text
|
||||
return self.message_text[:max_length] + "..."
|
||||
|
||||
def get_display_number(self) -> int:
|
||||
"""Получить номер вопроса для отображения (приоритет user_question_number)"""
|
||||
return self.user_question_number if self.user_question_number is not None else self.id
|
||||
|
||||
Reference in New Issue
Block a user