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:
92
models/user.py
Normal file
92
models/user.py
Normal file
@@ -0,0 +1,92 @@
|
||||
"""
|
||||
Модель пользователя
|
||||
"""
|
||||
import asyncio
|
||||
from dataclasses import dataclass
|
||||
from datetime import datetime
|
||||
from typing import Optional
|
||||
|
||||
from config.constants import EMPTY_VALUES
|
||||
|
||||
|
||||
def escape_html(text: str) -> str:
|
||||
"""Экранирование HTML символов"""
|
||||
if not text:
|
||||
return ""
|
||||
return (text
|
||||
.replace('&', '&')
|
||||
.replace('<', '<')
|
||||
.replace('>', '>')
|
||||
.replace('"', '"')
|
||||
.replace("'", '''))
|
||||
|
||||
|
||||
@dataclass
|
||||
class User:
|
||||
"""Модель пользователя бота"""
|
||||
|
||||
id: Optional[int] = None
|
||||
telegram_id: int = None
|
||||
username: Optional[str] = None
|
||||
first_name: str = ""
|
||||
last_name: Optional[str] = None
|
||||
chat_id: int = None
|
||||
profile_link: str = ""
|
||||
is_active: bool = True
|
||||
is_superuser: bool = False
|
||||
created_at: Optional[datetime] = None
|
||||
updated_at: Optional[datetime] = None
|
||||
banned_until: Optional[datetime] = None
|
||||
ban_reason: Optional[str] = None
|
||||
|
||||
@property
|
||||
def full_name(self) -> str:
|
||||
"""Полное имя пользователя"""
|
||||
parts = []
|
||||
if self.first_name:
|
||||
parts.append(escape_html(self.first_name))
|
||||
if self.last_name:
|
||||
parts.append(escape_html(self.last_name))
|
||||
return ' '.join(parts) if parts else 'Неизвестно'
|
||||
|
||||
@property
|
||||
def display_name(self) -> str:
|
||||
"""Отображаемое имя пользователя"""
|
||||
if self.username:
|
||||
return f"@{escape_html(self.username)}"
|
||||
return escape_html(self.full_name)
|
||||
|
||||
@property
|
||||
def is_banned(self) -> bool:
|
||||
"""Проверка, забанен ли пользователь"""
|
||||
if not self.banned_until:
|
||||
return False
|
||||
return datetime.now() < self.banned_until
|
||||
|
||||
|
||||
@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 update_timestamp(self):
|
||||
"""Обновление времени последнего обновления"""
|
||||
self.updated_at = datetime.now()
|
||||
|
||||
Reference in New Issue
Block a user