Files
vontor-cz/backend/social/chat/models.py
Brunobrno deb853b564 Add chat models and scaffold pages/posts apps
Implemented comprehensive models for chat functionality, including Chat, Message, MessageHistory, MessageReaction, and MessageFile. Updated ChatConsumer to enforce authentication and improve message handling. Added initial scaffolding for 'pages' and 'posts' Django apps with basic files.
2025-12-26 04:48:39 +01:00

178 lines
5.2 KiB
Python

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'
)
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}"