Исходный код core.models

"""
Модуль моделей базы данных приложения core.
Содержит описание таблиц для пользователей, документов, комментариев и системы жалоб.
"""

import uuid
import base64
import gzip

from django.db import models
from django.contrib.auth.models import User
from django.db.models.signals import post_save
from django.dispatch import receiver
from django.utils import timezone


[документация] class Report(models.Model): """ Модель жалобы пользователя на другого пользователя. :param reporter: Пользователь, отправивший жалобу. :type reporter: django.contrib.auth.models.User :param reported_user: Пользователь, на которого отправлена жалоба. :type reported_user: django.contrib.auth.models.User :param reason: Причина жалобы. :type reason: str :param created_at: Дата и время создания жалобы. :type created_at: datetime :param resolved: Статус решения жалобы. :type resolved: bool """ reporter = models.ForeignKey( User, on_delete=models.CASCADE, related_name="sent_reports" ) reported_user = models.ForeignKey( User, on_delete=models.CASCADE, related_name="complaints_received" ) reason = models.TextField() created_at = models.DateTimeField(auto_now_add=True) resolved = models.BooleanField(default=False) def __str__(self): return f"{self.reporter} -> {self.reported_user}"
[документация] class Document(models.Model): """ Модель документа или онлайн-доски. :param title: Название документа. :type title: str :param content: Содержимое документа (текст или JSON доски). :type content: str :param doc_type: Тип документа ('text' или 'paint'). :type doc_type: str :param owner: Владелец документа. :type owner: django.contrib.auth.models.User """ DOC_TYPES = [ ('text', 'Текстовый документ'), ('paint', 'Онлайн доска'), ] title = models.CharField(max_length=255, default="Без названия") content = models.TextField(blank=True, default="") doc_type = models.CharField(max_length=10, choices=DOC_TYPES, default='text') owner = models.ForeignKey(User, on_delete=models.CASCADE, related_name='owned_documents') editors = models.ManyToManyField(User, related_name='editable_documents', blank=True) viewers = models.ManyToManyField(User, related_name='viewable_documents', blank=True) invite_token = models.CharField(max_length=64, unique=True, blank=True) created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True)
[документация] def set_content(self, data): """ Сохраняет контент документа в gzip-сжатом виде. :param data: Данные для сохранения. :type data: str или bytes """ if data is None: data = "" if isinstance(data, bytes): raw_bytes = data else: raw_bytes = str(data).encode("utf-8") self.content = gzip.compress(raw_bytes)
[документация] def get_content(self): """ Возвращает исходный контент документа в виде строки. :return: Распакованное содержимое документа. :rtype: str """ if isinstance(self.content, bytes): try: return gzip.decompress(self.content).decode("utf-8") except Exception: return self.content.decode("utf-8", errors="ignore") if isinstance(self.content, str) and self.content.startswith("gz:"): try: payload = base64.b64decode(self.content[3:].encode("ascii")) return gzip.decompress(payload).decode("utf-8") except Exception: return "" return self.content
[документация] def save(self, *args, **kwargs): """ Переопределенный метод сохранения документа. Генерирует уникальный токен приглашения при первом создании и обрабатывает сериализацию сжатого контента. """ if not self.invite_token: self.invite_token = uuid.uuid4().hex + uuid.uuid4().hex content_was_bytes = isinstance(self.content, bytes) content_cache = self.content if content_was_bytes else None if content_was_bytes: self.content = "gz:" + base64.b64encode(self.content).decode("ascii") super().save(*args, **kwargs) if content_was_bytes: self.content = content_cache
[документация] def user_has_access(self, user): """ Проверяет, есть ли у пользователя права на доступ к документу. :param user: Пользователь для проверки. :type user: django.contrib.auth.models.User :return: True, если доступ разрешен, иначе False. :rtype: bool """ if user.is_authenticated: return user == self.owner or \ self.editors.filter(id=user.id).exists() or \ self.viewers.filter(id=user.id).exists() return False
[документация] def can_edit_doc(self, user): """ Проверяет, есть ли у пользователя права на редактирование документа. :param user: Пользователь для проверки. :type user: django.contrib.auth.models.User :return: True, если пользователь может редактировать документ, иначе False. :rtype: bool """ if not user.is_authenticated: return False return user == self.owner or self.editors.filter(id=user.id).exists()
def __str__(self): return f"{self.title} ({self.doc_type})"
[документация] class PasswordResetCode(models.Model): """ Модель для хранения кодов сброса пароля. :param user: Пользователь, запросивший сброс. :type user: django.contrib.auth.models.User :param code: Шестизначный код подтверждения. :type code: str """ user = models.ForeignKey(User, on_delete=models.CASCADE) code = models.CharField(max_length=6) created_at = models.DateTimeField(auto_now_add=True)
[документация] def is_valid(self): """ Проверяет, действителен ли код сброса (не истек ли срок 15 минут). :return: True, если код действителен. :rtype: bool """ from django.utils import timezone from datetime import timedelta return timezone.now() < self.created_at + timedelta(minutes=15)
[документация] class Profile(models.Model): """ Профиль пользователя с дополнительными данными (аватар). :param user: Связанный пользователь. :type user: django.contrib.auth.models.User :param avatar: Путь к изображению аватара. :type avatar: str """ user = models.OneToOneField(User, on_delete=models.CASCADE) avatar = models.CharField(max_length=255, default="/static/core/images/profile/penguin.png") def __str__(self): return self.user.username
[документация] @receiver(post_save, sender=User) def create_user_profile(sender, instance, created, **kwargs): """Сигнал для автоматического создания профиля при регистрации пользователя.""" if created: Profile.objects.create(user=instance)
[документация] @receiver(post_save, sender=User) def save_user_profile(sender, instance, **kwargs): """Сигнал для сохранения профиля при сохранении пользователя.""" if hasattr(instance, 'profile'): instance.profile.save()
[документация] class Comment(models.Model): """ Модель комментария, привязанного к документу. :param document: Документ, к которому относится комментарий. :type document: Document :param sender: Отправитель комментария. :type sender: django.contrib.auth.models.User :param receiver: Получатель (владелец документа). :type receiver: django.contrib.auth.models.User """ document = models.ForeignKey( Document, on_delete=models.CASCADE, related_name="comments" ) sender = models.ForeignKey( User, on_delete=models.CASCADE, related_name="sent_comments" ) receiver = models.ForeignKey( User, on_delete=models.CASCADE, related_name="received_comments" ) text = models.TextField() created_at = models.DateTimeField(auto_now_add=True) is_read = models.BooleanField(default=False) def __str__(self): return f"{self.sender.username} -> {self.receiver.username}"
[документация] class UserPunishment(models.Model): """ Модель наказаний пользователя (баны и муты). """ user = models.OneToOneField( User, on_delete=models.CASCADE, related_name="punishment" ) reports_count = models.PositiveIntegerField(default=0) is_banned = models.BooleanField(default=False) banned_at = models.DateTimeField(null=True, blank=True) ban_reason = models.TextField(blank=True, default="") muted_until = models.DateTimeField(null=True, blank=True) muted_at = models.DateTimeField(null=True, blank=True) mute_reason = models.TextField(blank=True, default="")
[документация] def is_muted(self): """ Проверяет, замучен ли пользователь на данный момент. """ return self.muted_until is not None and timezone.now() < self.muted_until
[документация] def mute_left(self): """ Возвращает время окончания мута. """ if not self.is_muted(): return None return self.muted_until
def __str__(self): return self.user.username
[документация] class Complaint(models.Model): """ Модель жалобы на комментарий в системе. """ reporter = models.ForeignKey( User, on_delete=models.CASCADE, related_name="sent_complaints" ) reported_user = models.ForeignKey( User, on_delete=models.CASCADE, related_name="reports" ) comment = models.ForeignKey( Comment, on_delete=models.CASCADE ) reason = models.TextField() created_at = models.DateTimeField(auto_now_add=True)
[документация] class UserActivity(models.Model): """ Модель отслеживания сетевой активности пользователя. """ user = models.OneToOneField(User, on_delete=models.CASCADE) last_seen = models.DateTimeField(auto_now=True)
[документация] def is_online(self): """ Проверяет статус онлайна пользователя (активен в течение последних 5 минут). """ return timezone.now() - self.last_seen < timezone.timedelta(minutes=5)