from django.db import models from django.conf import settings from django.core.exceptions import ValidationError from django.utils import timezone class Chat(models.Model): owner = models.ForeignKey( settings.AUTH_USER_MODEL, on_delete=models.SET_NULL, null=True, related_name='owned_chats' ) members = models.ManyToManyField( settings.AUTH_USER_MODEL, related_name='chats', blank=True ) moderators = models.ManyToManyField( settings.AUTH_USER_MODEL, related_name='moderated_chats', blank=True ) created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) def save(self, *args, **kwargs): is_new = self._state.adding super().save(*args, **kwargs) # LOGIC: Ensure owner is always a member and moderator if is_new and self.owner: self.members.add(self.owner) self.moderators.add(self.owner) def __str__(self): return f"Chat {self.id}" class Message(models.Model): chat = models.ForeignKey(Chat, related_name='messages', on_delete=models.CASCADE) sender = models.ForeignKey( settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name='sent_messages' ) #odpověď na jinou zprávu reply_to = models.ForeignKey( 'self', null=True, blank=True, on_delete=models.SET_NULL, related_name='replies' ) content = models.TextField(blank=True) # --- TRACKING EDIT STATUS --- # We add these so the frontend doesn't need to check MessageHistory table is_edited = models.BooleanField(default=False) edited_at = models.DateTimeField(null=True, blank=True) created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) def clean(self): # VALIDATION: Ensure sender is actually in the chat # Note: We check self.id to avoid running this on creation if logic depends on M2M # But generally, a sender must be a member. if self.chat and self.sender: if not self.chat.members.filter(id=self.sender.id).exists(): raise ValidationError("Sender is not a member of this chat.") def save(self, *args, **kwargs): # Optional: Run validation before saving # self.full_clean() super().save(*args, **kwargs) # --- HELPER METHODS FOR WEBSOCKETS / VIEWS --- def edit_content(self, new_text): """ Handles the complex logic of editing: 1. Checks if text actually changed. 2. Saves old text to History. 3. Updates current text and timestamps. """ if self.content == new_text: return False # No change happened # 1. Save History MessageHistory.objects.create( message=self, old_content=self.content ) # 2. Update Self self.content = new_text self.is_edited = True self.edited_at = timezone.now() self.save() return True def toggle_reaction(self, user, emoji): """ Handles Add/Remove/Switch logic. Returns a tuple: (action, reaction_object) action can be: 'added', 'removed', 'switched' """ try: reaction = MessageReaction.objects.get(message=self, user=user) if reaction.emoji == emoji: # Same emoji -> Remove it (Toggle) reaction.delete() return 'removed', None else: # Different emoji -> Switch it reaction.emoji = emoji reaction.save() return 'switched', reaction except MessageReaction.DoesNotExist: # New reaction -> Create it reaction = MessageReaction.objects.create( message=self, user=user, emoji=emoji ) return 'added', reaction def __str__(self): return f"Message {self.id} from {self.sender}" class MessageHistory(models.Model): message = models.ForeignKey( Message, on_delete=models.CASCADE, related_name='edit_history' ) old_content = models.TextField() archived_at = models.DateTimeField(auto_now_add=True) class Meta: ordering = ['-archived_at'] class MessageReaction(models.Model): message = models.ForeignKey( Message, on_delete=models.CASCADE, related_name='reactions' ) user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE) emoji = models.CharField(max_length=10) created_at = models.DateTimeField(auto_now_add=True) class Meta: unique_together = ('message', 'user') def __str__(self): return f"{self.user} reacted {self.emoji}" class MessageFile(models.Model): message = models.ForeignKey( Message, on_delete=models.CASCADE, related_name='media_files' ) file = models.FileField(upload_to='chat_uploads/%Y/%m/%d/') media_type = models.CharField(max_length=20, choices=[ ('IMAGE', 'Image'), ('VIDEO', 'Video'), ('FILE', 'File') ], default='FILE') uploaded_at = models.DateTimeField(auto_now_add=True) def __str__(self): return f"Media {self.id} for Message {self.message.id}"