from django.db import models from django.utils import timezone from django.utils.translation import gettext_lazy as _ from apps.account.models import User from apps.course.models import Course def chat_upload_path(instance, filename): """ Generate upload path for chat attachments Format: chat/room_{room_id}/YYYY/MM/DD/filename """ date = timezone.now() return f'chat/room_{instance.room_id}/{date.year}/{date.month:02d}/{date.day:02d}/{filename}' class RoomMessage(models.Model): class RoomTypeChoices(models.TextChoices): GROUP = 'group', _('Group') PRIVATE = 'private', _('Private') name = models.CharField( max_length=255, verbose_name=_("Room Name") ) description = models.TextField( verbose_name=_("Description"), blank=True, null=True ) course = models.ForeignKey(Course, on_delete=models.CASCADE, null=True, blank=True, related_name="room_messages", verbose_name=_("Course")) initiator = models.ForeignKey( User, on_delete=models.CASCADE, related_name="initiated_rooms", verbose_name=_("Initiator") ) recipient = models.ForeignKey( User, on_delete=models.CASCADE, related_name="messages_received", verbose_name=_("Recipient"), null=True, blank=True ) created_at = models.DateTimeField(auto_now_add=True, verbose_name=_("Created At")) updated_at = models.DateTimeField( auto_now=True, verbose_name=_("Updated At") ) is_locked = models.BooleanField( default=False, verbose_name=_("Is Locked"), help_text=_("If True, only the professor and admins can send new messages.") ) room_type = models.CharField( max_length=10, choices=RoomTypeChoices.choices, default=RoomTypeChoices.GROUP, verbose_name=_("Room Type") ) unread_messages_count = models.IntegerField(default=0, verbose_name=_("Unread Messages Count")) def __str__(self): if self.room_type == self.RoomTypeChoices.GROUP: return f"Group Room: {self.course.title if self.course else 'N/A'}" return f"Private Room with {self.recipient}" class Meta: verbose_name = _("Room Message") verbose_name_plural = _("Room Messages") class ChatMessage(models.Model): class ChatTypeChoices(models.TextChoices): TEXT = 'text', _('Text') FILE = 'file', _('File') AUDIO = 'audio', _('Audio') IMAGE = 'image', _('Image') room = models.ForeignKey( RoomMessage, on_delete=models.CASCADE, related_name="messages", verbose_name=_("Room"), ) sender = models.ForeignKey( User, on_delete=models.CASCADE, related_name="messages_sent", verbose_name=_("Sender") ) content = models.TextField(verbose_name=_("Message Content")) content_type = models.CharField( max_length=10, choices=ChatTypeChoices.choices, default=ChatTypeChoices.TEXT, verbose_name=_("Chat Type") ) content_size = models.PositiveIntegerField( verbose_name=_("Content Size (bytes)"), blank=True, null=True ) file_attachment = models.FileField( upload_to=chat_upload_path, blank=True, null=True, max_length=500, verbose_name=_("File Attachment"), help_text=_("For file and audio messages") ) image_attachment = models.ImageField( upload_to=chat_upload_path, blank=True, null=True, max_length=500, verbose_name=_("Image Attachment"), help_text=_("For image messages") ) is_read = models.BooleanField(default=False, verbose_name=_("Is Read")) message_metadata = models.JSONField(blank=True, null=True, verbose_name=_("Message Metadata")) sent_at = models.DateTimeField(auto_now_add=True, verbose_name=_("Sent At")) updated_at = models.DateTimeField(auto_now=True, verbose_name=_("Updated At")) deleted_at = models.DateTimeField(null=True, blank=True, verbose_name=_("Deleted At")) is_deleted = models.BooleanField(default=False, verbose_name=_("Is deleted")) @property def file_url(self): """ Get file URL - works for both old and new messages For backward compatibility with messages using content field """ if self.image_attachment: return self.image_attachment.url elif self.file_attachment: return self.file_attachment.url elif self.content and self.content_type != 'text': # Legacy messages with URL in content field return self.content return None def delete(self, *args, **kwargs): """Override delete to remove uploaded files""" if self.file_attachment: self.file_attachment.delete(save=False) if self.image_attachment: self.image_attachment.delete(save=False) super().delete(*args, **kwargs) def __str__(self): return f"Message from {self.sender} in {self.room}" class Meta: verbose_name = _("Chat Message") verbose_name_plural = _("Chat Messages") class MessageReadStatus(models.Model): user = models.ForeignKey( User, on_delete=models.CASCADE, related_name="read_statuses", verbose_name=_("User") ) message = models.ForeignKey( ChatMessage, on_delete=models.CASCADE, related_name="read_statuses", verbose_name=_("Message") ) is_read = models.BooleanField(default=False, verbose_name=_("Is Read")) read_at = models.DateTimeField(null=True, blank=True, verbose_name=_("Read At")) class Meta: unique_together = ("user", "message") # جلوگیری از ثبت تکراری verbose_name = _("Message Read Status") verbose_name_plural = _("Message Read Statuses") def __str__(self): return f"User {self.user.fullname} read Message {self.message.id}: {self.is_read}"