from django.conf import settings from django.db import models from django.utils.translation import gettext_lazy as _ from apps.profiles.models.institution import Institution class ChatRoom(models.Model): class RoomType(models.TextChoices): DIRECT = 'direct', _('Direct Message') GROUP = 'group', _('Group Conversation') room_type = models.CharField( max_length=20, choices=RoomType.choices, default=RoomType.DIRECT, verbose_name=_('Room Type') ) title = models.CharField( max_length=255, blank=True, null=True, verbose_name=_('Conversation Title') ) participant_users = models.ManyToManyField( settings.AUTH_USER_MODEL, blank=True, related_name='chat_rooms', verbose_name=_('Participant Users') ) participant_institutions = models.ManyToManyField( Institution, blank=True, related_name='chat_rooms', verbose_name=_('Participant Institutions') ) created_by = models.ForeignKey( settings.AUTH_USER_MODEL, on_delete=models.SET_NULL, null=True, blank=True, related_name='created_chat_rooms', verbose_name=_('Created By') ) created_at = models.DateTimeField( auto_now_add=True, verbose_name=_('Created At') ) updated_at = models.DateTimeField( auto_now=True, verbose_name=_('Last Activity') ) class Meta: ordering = ('-updated_at',) verbose_name = _('Chat Room') verbose_name_plural = _('Chat Rooms') def __str__(self): return self.title or f"Chat Room #{self.id} ({self.room_type})" @property def centrifugo_channel(self) -> str: return f"chat:room_{self.id}" def is_participant(self, user) -> bool: if not user or not user.is_authenticated: return False if getattr(user, 'is_super_admin', False) or getattr(user, 'is_regional_admin', False): return True if self.participant_users.filter(id=user.id).exists(): return True # Check if user belongs to any participant institution user_inst_ids = user.institution_memberships.values_list('institution_id', flat=True) if self.participant_institutions.filter(id__in=user_inst_ids).exists(): return True return False def get_last_message(self): return self.messages.all().select_related('sender', 'sender_institution').order_by('-created_at', '-id').first() def get_unread_count(self, user) -> int: if not user or not user.is_authenticated: return 0 return self.messages.exclude(sender=user).exclude(read_by=user).count() def get_display_title(self, current_user=None) -> str: if self.title: return self.title if self.room_type == self.RoomType.DIRECT and current_user: other_user = self.participant_users.exclude(id=current_user.id).first() if other_user: return other_user.fullname or other_user.email other_inst = self.participant_institutions.first() if other_inst: return other_inst.name return f"Conversation #{self.id}" class ChatMessage(models.Model): room = models.ForeignKey( ChatRoom, on_delete=models.CASCADE, related_name='messages', verbose_name=_('Chat Room') ) sender = models.ForeignKey( settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name='sent_chat_messages', verbose_name=_('Sender') ) sender_institution = models.ForeignKey( Institution, on_delete=models.SET_NULL, null=True, blank=True, related_name='institution_chat_messages', verbose_name=_('Sender Institution Affiliation') ) content = models.TextField( blank=True, null=True, verbose_name=_('Message Text') ) attachment = models.FileField( upload_to='chat/attachments/%Y/%m/', blank=True, null=True, verbose_name=_('Attachment File') ) attachment_name = models.CharField( max_length=255, blank=True, null=True, verbose_name=_('Attachment Filename') ) read_by = models.ManyToManyField( settings.AUTH_USER_MODEL, blank=True, related_name='read_chat_messages', verbose_name=_('Read Receipts') ) created_at = models.DateTimeField( auto_now_add=True, verbose_name=_('Timestamp') ) class Meta: ordering = ('created_at',) verbose_name = _('Chat Message') verbose_name_plural = _('Chat Messages') def __str__(self): return f"[{self.room.id}] {self.sender.fullname or self.sender.email}: {self.content[:30] if self.content else 'Attachment'}" def save(self, *args, **kwargs): is_new = self.pk is None if self.attachment and not self.attachment_name: self.attachment_name = self.attachment.name.split('/')[-1] super().save(*args, **kwargs) if is_new: # Mark as read by sender immediately self.read_by.add(self.sender) # Update room activity timestamp ChatRoom.objects.filter(id=self.room_id).update(updated_at=self.created_at)