I am trying to build a messenger like application using Django, Django-rest-framework, and Django-channels. Right now I can only send messages from one user to another user. But I want to add features like, new messages will be on top in the inbox along with the username and one user can delete messages from his side, just like messenger, whats app, etc. When someone sends a message his message will be on top and will show a notification. I am unable to find out perfect model design and perfect system for doing it.
Model.py
from django.contrib.auth import get_user_model
from django.db import models
User = get_user_model()
class Message(models.Model):
sender = models.ForeignKey(User, on_delete=models.CASCADE,
related_name='sender_messages')
receiver = models.ForeignKey(User, on_delete=models.CASCADE,
related_name='receiver_messages')
text = models.TextField()
date_created = models.DateTimeField(auto_now_add=True)
def __str__(self):
return '{} to {}'.format(self.sender.name, self.receiver.name)
consumer
import json
from asgiref.sync import async_to_sync
from django.contrib.auth import get_user_model
from channels.generic.websocket import WebsocketConsumer
from .models import Message
User = get_user_model()
class ChatConsumer(WebsocketConsumer):
def connect(self):
"""
Join channel group by chatname.
"""
self.group_name = 'chat_{0}'.format(self.scope['url_route']['kwargs']['chatname'])
async_to_sync(self.channel_layer.group_add)(
self.group_name,
self.channel_name,
)
self.accept()
def disconnect(self, close_code):
"""
Leave channel by group name.
"""
async_to_sync(self.channel_layer.group_discard)(
self.group_name,
self.channel_name
)
def receive(self, text_data):
"""
Receive message from websocket and send message to channel group.
"""
text_data_json = json.loads(text_data)
name = text_data_json['name']
message = text_data_json['message']
# Store message.
receiver = User.objects.get(
name=self.group_name.replace('chat_', '')
.replace(self.scope['user'].name, '')
.replace('-', ''))
Message(sender=self.scope['user'], receiver=receiver, text=message).save()
async_to_sync(self.channel_layer.group_send)(
self.group_name,
{
'type': 'chat_message',
'name': name,
'message': message,
}
)
def chat_message(self, event):
"""
Receive message from channel group and send message to websocket.
"""
self.send(text_data=json.dumps({
'name': event['name'],
'message': event['message'],
}))
view
class HomeView(LoginRequiredMixin, TemplateView):
template_name = 'home.html'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
# List all users for chatting. Except myself.
context['users'] = User.objects.exclude(id=self.request.user.id) \
.values('name')
context['users'] = User.objects.exclude(gender=self.request.user.gender)
return context
class ChatHomeApi(viewsets.ModelViewSet):
serializer_class = InboxRetrieveSerializer
authentication_classes = [TokenAuthentication]
pagination_class = GeneralPagination
def get_queryset(self):
return Chat_Inbox.objects.filter(sender=self.request.user)
class ChatView(LoginRequiredMixin, TemplateView):
template_name = 'chat.html'
def dispatch(self, request, **kwargs):
# Get the person we are chatting with, if not exist raise 404.
receiver_name = kwargs['chatname'].replace(
request.user.name, '').replace('-', '')
kwargs['receiver'] = get_object_or_404(User, name=receiver_name)
return super().dispatch(request, **kwargs)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['receiver'] = kwargs['receiver']
return context
class MessagesAPIView(LoginRequiredMixin, View):
def get(self, request, chatname):
# Grab two users based on the chat name.
users = User.objects.filter(name__in=chatname.split('-'))
# Filters messages between this two users.
result = Message.objects.filter(
Q(sender=users[0], receiver=users[1]) | Q(sender=users[1], receiver=users[0])
).annotate(
name=F('sender__name'), message=F('text'),
).order_by('date_created').values('name', 'message', 'date_created')
return JsonResponse(list(result), safe=False)
So basically, I want to show new messages on top of the inbox, and a user can delete messages from his side, and this might not affect the other user. Thanks in advance
For the ordering, you can send the time stamp along with all messages and store that timestamp.
Now at the time of retrieving the messages, you can retrieve them by using order_by
in Django.
for e.g.,
def receive(self, text_data):
text_data_json = json.loads(text_data)
name = text_data_json['name']
message = text_data_json['message']
timestamp = timezone.now()
# Store message.
receiver = User.objects.get(
name=self.group_name.replace('chat_', '')
.replace(self.scope['user'].name, '')
.replace('-', ''))
Message(sender=self.scope['user'], receiver=receiver, text=message).save()
async_to_sync(self.channel_layer.group_send)(
self.group_name,
{
'type': 'chat_message',
'name': name,
'message': message,
'timestamp': timestamp.isoformat()
}
)
async def chat_message(self, event):
# Send message to WebSocket
await self.send(text_data=json.dumps({
'message': 'Message Received Successfully',
'data': {
'message': event['message'],
'message_type': event['message_type'],
'timestamp': event['timestamp']
}
}))