chat implemented, testing needed
This commit is contained in:
@@ -3,8 +3,9 @@ import json
|
||||
|
||||
from channels.db import database_sync_to_async
|
||||
from channels.generic.websocket import AsyncWebsocketConsumer
|
||||
from django.utils import timezone
|
||||
|
||||
from .models import Chat, Message
|
||||
from .models import Chat, ChatReadStatus, Message
|
||||
|
||||
|
||||
class ChatConsumer(AsyncWebsocketConsumer):
|
||||
@@ -95,6 +96,14 @@ class ChatConsumer(AsyncWebsocketConsumer):
|
||||
"user": user.username,
|
||||
})
|
||||
|
||||
elif msg_type == "mark_read":
|
||||
await _mark_read(chat_id=self.chat_id, user=user)
|
||||
await self.channel_layer.group_send(self.chat_name, {
|
||||
"type": "read.status",
|
||||
"user": user.username,
|
||||
"chat_id": int(self.chat_id),
|
||||
})
|
||||
|
||||
else:
|
||||
await self.send(text_data=json.dumps({"error": "Unsupported message type."}))
|
||||
|
||||
@@ -155,6 +164,13 @@ class ChatConsumer(AsyncWebsocketConsumer):
|
||||
"user": event["user"],
|
||||
}))
|
||||
|
||||
async def read_status(self, event):
|
||||
await self.send(text_data=json.dumps({
|
||||
"type": "read_status",
|
||||
"user": event["user"],
|
||||
"chat_id": event["chat_id"],
|
||||
}))
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# DB helpers (run in thread pool via database_sync_to_async)
|
||||
@@ -179,3 +195,12 @@ def _create_message(chat_id, sender, content, reply_to_id=None):
|
||||
def _toggle_reaction(message_id, user, emoji):
|
||||
message = Message.objects.get(pk=message_id)
|
||||
return message.toggle_reaction(user, emoji)
|
||||
|
||||
|
||||
@database_sync_to_async
|
||||
def _mark_read(chat_id, user):
|
||||
ChatReadStatus.objects.update_or_create(
|
||||
user=user,
|
||||
chat_id=chat_id,
|
||||
defaults={"last_read_at": timezone.now()},
|
||||
)
|
||||
|
||||
28
backend/social/chat/migrations/0002_chatreadstatus.py
Normal file
28
backend/social/chat/migrations/0002_chatreadstatus.py
Normal file
@@ -0,0 +1,28 @@
|
||||
# Generated by Django 5.2.7 on 2026-05-19 22:40
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('chat', '0001_initial'),
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='ChatReadStatus',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('last_read_at', models.DateTimeField(auto_now=True)),
|
||||
('chat', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='read_statuses', to='chat.chat')),
|
||||
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='chat_read_statuses', to=settings.AUTH_USER_MODEL)),
|
||||
],
|
||||
options={
|
||||
'unique_together': {('user', 'chat')},
|
||||
},
|
||||
),
|
||||
]
|
||||
@@ -213,4 +213,24 @@ class MessageFile(SoftDeleteModel):
|
||||
uploaded_at = models.DateTimeField(auto_now_add=True)
|
||||
|
||||
def __str__(self):
|
||||
return f"Media {self.id} for Message {self.message.id}"
|
||||
return f"Media {self.id} for Message {self.message.id}"
|
||||
|
||||
|
||||
class ChatReadStatus(models.Model):
|
||||
user = models.ForeignKey(
|
||||
settings.AUTH_USER_MODEL,
|
||||
on_delete=models.CASCADE,
|
||||
related_name='chat_read_statuses',
|
||||
)
|
||||
chat = models.ForeignKey(
|
||||
Chat,
|
||||
on_delete=models.CASCADE,
|
||||
related_name='read_statuses',
|
||||
)
|
||||
last_read_at = models.DateTimeField(auto_now=True)
|
||||
|
||||
class Meta:
|
||||
unique_together = ('user', 'chat')
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.user} read {self.chat} at {self.last_read_at}"
|
||||
@@ -1,5 +1,6 @@
|
||||
from rest_framework import serializers
|
||||
from .models import Chat, Message, MessageFile, MessageHistory, MessageReaction
|
||||
from drf_spectacular.utils import extend_schema_field
|
||||
from .models import Chat, ChatReadStatus, Message, MessageFile, MessageHistory, MessageReaction
|
||||
|
||||
|
||||
class MessageFileSerializer(serializers.ModelSerializer):
|
||||
@@ -60,14 +61,28 @@ class MessageSendSerializer(serializers.Serializer):
|
||||
|
||||
|
||||
class ChatSerializer(serializers.ModelSerializer):
|
||||
unread_count = serializers.SerializerMethodField(read_only=True)
|
||||
|
||||
@extend_schema_field(serializers.IntegerField())
|
||||
def get_unread_count(self, obj):
|
||||
request = self.context.get('request')
|
||||
if not request or not request.user.is_authenticated:
|
||||
return 0
|
||||
last_read = ChatReadStatus.objects.filter(
|
||||
user=request.user, chat=obj
|
||||
).values_list('last_read_at', flat=True).first()
|
||||
if last_read is None:
|
||||
return obj.messages.filter(is_deleted=False).count()
|
||||
return obj.messages.filter(created_at__gt=last_read, is_deleted=False).count()
|
||||
|
||||
class Meta:
|
||||
model = Chat
|
||||
fields = [
|
||||
'id', 'chat_type', 'owner', 'name',
|
||||
'icon', 'banner', 'members', 'moderators',
|
||||
'hub', 'created_at', 'updated_at',
|
||||
'hub', 'created_at', 'updated_at', 'unread_count',
|
||||
]
|
||||
read_only_fields = ['owner', 'created_at', 'updated_at']
|
||||
read_only_fields = ['owner', 'created_at', 'updated_at', 'unread_count']
|
||||
|
||||
|
||||
class ChatMemberSerializer(serializers.Serializer):
|
||||
|
||||
Reference in New Issue
Block a user