/
storeksfeed
/
RAG-Bot
Обзор
Документация
Войти
/
storeksfeed
/
RAG-Bot
Код
Запросы
0
Задачи
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
master
app/core/models.py
369 строк
14 KB
Semyon Glazyrin
Added modular messenger integration, updated project structure and HTML templates
10 апр 2026, 14:06
Верифицирован
10 апр 2026, 14:06
00659aa
Код
Авторство
О чём код?
from django.db import models, transaction from django.utils import timezone import random import string from datetime import timedelta from django.contrib.auth.models import User class RegistrationRequest(models.Model): """ Model for storing user registration requests from Telegram bot. """ STATUS_CHOICES = [ ('pending', 'Ожидает рассмотрения'), ('approved', 'Одобрена'), ('rejected', 'Отклонена'), ] messenger = models.CharField( max_length=20, default='telegram', verbose_name='Мессенджер' ) messenger_id = models.BigIntegerField(verbose_name='ID пользователя') messenger_username = models.CharField(max_length=255, blank=True, null=True, verbose_name='Username') first_name = models.CharField(max_length=255, blank=True, null=True, verbose_name='Имя') last_name = models.CharField(max_length=255, blank=True, null=True, verbose_name='Фамилия') email = models.EmailField(blank=True, null=True, verbose_name='Email') email_verified = models.BooleanField(default=False, verbose_name='Email подтвержден') verification_code = models.CharField(max_length=6, blank=True, null=True, verbose_name='Код подтверждения') code_expires_at = models.DateTimeField(blank=True, null=True, verbose_name='Срок действия кода') user_message = models.TextField(blank=True, null=True, verbose_name='Сообщение от пользователя') registration_completed = models.BooleanField( default=True, verbose_name='Регистрация завершена' ) status = models.CharField( max_length=20, choices=STATUS_CHOICES, default='pending', verbose_name='Статус' ) # Timestamps created_at = models.DateTimeField(auto_now_add=True, verbose_name='Дата подачи') updated_at = models.DateTimeField(auto_now=True, verbose_name='Дата обновления') reviewed_at = models.DateTimeField(null=True, blank=True, verbose_name='Дата рассмотрения') rejection_reason = models.TextField(blank=True, null=True, verbose_name='Причина отклонения') notes = models.TextField(blank=True, null=True, verbose_name='Заметки администратора') class Meta: verbose_name = 'Заявка на регистрацию' verbose_name_plural = 'Заявки на регистрацию' ordering = ['-created_at'] constraints = [ models.UniqueConstraint(fields=['messenger', 'messenger_id'], name='uniq_registration_messenger_id'), ] def __str__(self): return f"{self.messenger_username or self.messenger_id} - {self.get_status_display()}" @transaction.atomic def approve(self): """Approve registration request""" self.status = 'approved' self.reviewed_at = timezone.now() self.save() # Create or reactivate approved user approved_user, created = BotUser.objects.get_or_create( messenger=self.messenger, messenger_id=self.messenger_id, defaults={ 'messenger_username': self.messenger_username, 'first_name': self.first_name, 'last_name': self.last_name, 'email': self.email, 'email_verified': self.email_verified, 'notes': self.notes, } ) # If user already existed (was previously approved then rejected), reactivate them if not created: approved_user.status = 'active' approved_user.messenger_username = self.messenger_username approved_user.first_name = self.first_name approved_user.last_name = self.last_name approved_user.email = self.email approved_user.email_verified = self.email_verified approved_user.notes = self.notes approved_user.status_message = None approved_user.status_changed_at = None approved_user.user_response = None approved_user.save() return approved_user @transaction.atomic def reject(self, reason=''): """Reject registration request""" # If user was previously approved, suspend them if self.status == 'approved': try: approved_user = BotUser.objects.get(messenger=self.messenger, messenger_id=self.messenger_id) approved_user.status = 'suspended' approved_user.status_changed_at = timezone.now() approved_user.save() except BotUser.DoesNotExist: pass self.status = 'rejected' self.reviewed_at = timezone.now() self.rejection_reason = reason self.save() def generate_verification_code(self): """Generate a 6-digit verification code and set expiration time""" self.verification_code = ''.join(random.choices(string.digits, k=6)) self.code_expires_at = timezone.now() + timedelta(minutes=10) self.email_verified = False self.save(update_fields=['verification_code', 'code_expires_at', 'email_verified']) return self.verification_code def verify_code(self, code): """ Verify the provided code against stored verification code. Returns True if valid, False otherwise. """ if not self.verification_code or not self.code_expires_at: return False # Check if code has expired if timezone.now() > self.code_expires_at: return False # Check if code matches if self.verification_code == code: self.email_verified = True self.verification_code = None self.code_expires_at = None self.save(update_fields=['email_verified', 'verification_code', 'code_expires_at']) return True return False def skip_verification(self): """Mark email verification as skipped""" self.email_verified = False self.verification_code = None self.code_expires_at = None self.save(update_fields=['email_verified', 'verification_code', 'code_expires_at']) class BotUser(models.Model): """ Model for approved users who can interact with the bot. """ internal_id = models.BigAutoField(primary_key=True) messenger = models.CharField( max_length=20, default='telegram', verbose_name='Мессенджер' ) messenger_id = models.BigIntegerField(verbose_name='ID пользователя') messenger_username = models.CharField(max_length=255, blank=True, null=True, verbose_name='Username') first_name = models.CharField(max_length=255, blank=True, null=True, verbose_name='Имя') last_name = models.CharField(max_length=255, blank=True, null=True, verbose_name='Фамилия') email = models.EmailField(blank=True, null=True, verbose_name='Email') email_verified = models.BooleanField(default=False, verbose_name='Email подтвержден') STATUS_CHOICES = [ ('active', 'Активен'), ('suspended', 'Приостановлен'), ('blocked', 'Заблокирован'), ] status = models.CharField( max_length=20, choices=STATUS_CHOICES, default='active', verbose_name='Статус' ) status_message = models.TextField(blank=True, null=True, verbose_name='Сообщение администратора') status_changed_at = models.DateTimeField(null=True, blank=True, verbose_name='Дата изменения статуса') user_response = models.TextField(blank=True, null=True, verbose_name='Ответ пользователя') # Timestamps approved_at = models.DateTimeField(auto_now_add=True, verbose_name='Дата одобрения') active_at = models.DateTimeField(null=True, blank=True, verbose_name='Последняя активность') # Statistics questions_count = models.IntegerField(default=0, verbose_name='Количество вопросов') # Admin notes notes = models.TextField(blank=True, null=True, verbose_name='Заметки администратора') class Meta: verbose_name = 'Одобренный пользователь' verbose_name_plural = 'Одобренные пользователи' ordering = ['-approved_at'] constraints = [ models.UniqueConstraint(fields=['messenger', 'messenger_id'], name='uniq_botuser_messenger_id'), ] def __str__(self): email_str = self.email or 'нет email' return f"{self.messenger_username or self.messenger_id} ({email_str})" def update_activity(self): """Update last activity timestamp""" self.active_at = timezone.now() self.save(update_fields=['active_at']) def increment_questions(self): """Increment questions counter""" self.questions_count += 1 self.active_at = timezone.now() self.save(update_fields=['questions_count', 'active_at']) class AdminContactProfile(models.Model): """Support contact settings for Django administrators.""" user = models.OneToOneField( User, on_delete=models.CASCADE, related_name='support_profile', verbose_name='Администратор' ) role_text = models.CharField( max_length=255, blank=True, verbose_name='Роль', help_text='Например: Техническая поддержка' ) show_in_support = models.BooleanField( default=False, verbose_name='Показывать в /support' ) class Meta: verbose_name = 'Контакт администратора' verbose_name_plural = 'Контакты администраторов' ordering = ['user__username'] def __str__(self): return self.user.get_full_name() or self.user.username class AdminMessengerContact(models.Model): """Messenger-specific contact entries for administrators.""" user = models.ForeignKey( User, on_delete=models.CASCADE, related_name='messenger_contacts', verbose_name='Администратор' ) messenger = models.CharField( max_length=50, verbose_name='Мессенджер', help_text='Идентификатор мессенджера, например: telegram, vk' ) contact = models.CharField( max_length=255, verbose_name='Контакт', help_text='Например: @username, club123 или ссылка' ) class Meta: verbose_name = 'Контакт администратора (мессенджер)' verbose_name_plural = 'Контакты администраторов (мессенджеры)' ordering = ['user__username', 'messenger'] constraints = [ models.UniqueConstraint(fields=['user', 'messenger'], name='uniq_admin_contact_user_messenger'), ] def __str__(self): return f"{self.user.username}: {self.messenger} -> {self.contact}" class QuestionAnswer(models.Model): """ Model for storing question-answer interactions with ratings. """ RATING_CHOICES = [ (0, 'Пропущено'), (1, '1 звезда'), (2, '2 звезды'), (3, '3 звезды'), (4, '4 звезды'), (5, '5 звёзд'), ] CATEGORY_CHOICES = [ ('global', 'Global'), ('naive', 'Vector'), ('local', 'Local'), ] user = models.ForeignKey( BotUser, on_delete=models.CASCADE, related_name='questions', verbose_name='Пользователь' ) question = models.TextField(verbose_name='Вопрос') answer = models.TextField(verbose_name='Ответ') category = models.CharField( max_length=20, choices=CATEGORY_CHOICES, verbose_name='Категория' ) # Rating fields rating = models.IntegerField( choices=RATING_CHOICES, default=0, verbose_name='Оценка' ) rating_changed = models.BooleanField( default=False, verbose_name='Оценка изменена' ) # Timestamps asked_at = models.DateTimeField(auto_now_add=True, verbose_name='Дата вопроса') rated_at = models.DateTimeField( null=True, blank=True, verbose_name='Дата оценки' ) processing_time = models.DecimalField( max_digits=8, decimal_places=2, null=True, blank=True, verbose_name='Время обработки (сек)', help_text='Время обработки вопроса в секундах' ) class Meta: verbose_name = 'Вопрос-Ответ' verbose_name_plural = 'Вопросы-Ответы' ordering = ['-asked_at'] indexes = [ models.Index(fields=['-asked_at']), models.Index(fields=['user', '-asked_at']), models.Index(fields=['rating']), ] def __str__(self): question_preview = self.question[:50] + '...' if len(self.question) > 50 else self.question return f"{self.user.messenger_username or self.user.messenger_id} - {question_preview}" def rate(self, rating_value): """ Rate the answer. Args: rating_value: 0-5 where 0 is skip, 1-5 are star ratings """ if self.rating != 0 and self.rating != rating_value: self.rating_changed = True self.rating = rating_value self.rated_at = timezone.now() self.save(update_fields=['rating', 'rating_changed', 'rated_at'])