Search code examples
djangowebsocketdjango-channels

Django channels: Save messages to database with duplicates when two or more tabs opened


I'm using this solution for save messages to DB Django channels: Save messages to database

But I have a problem with duplicates (in DB) when the user (message's author) has two or more tabs opened in the browser with same chat room.

What's wrong? Please help!

import json

from channels.db import database_sync_to_async
from channels.generic.websocket import AsyncWebsocketConsumer

from .models import Message


class ChatConsumer(AsyncWebsocketConsumer):
    @database_sync_to_async
    def create_chat(self, msg, sender):
        return Message.objects.create(sender=sender, msg=msg)

    async def connect(self):
        self.room_name = self.scope['url_route']['kwargs']['room_name']
        self.room_group_name = 'chat_%s' % self.room_name
        await self.channel_layer.group_add(self.room_group_name, self.channel_name)
        await self.accept()

    async def disconnect(self, close_code):
        await self.channel_layer.group_discard(self.room_group_name, self.channel_name)

    async def receive(self, text_data):
        text_data_json = json.loads(text_data)
        message = text_data_json['message']
        sender = text_data_json['sender']
        await self.channel_layer.group_send(self.room_group_name, {
            'type': 'chat_message',
            'message': message,
            'sender': sender
        })

    async def chat_message(self, event):
        message = event['message']
        sender = event['sender']
        new_msg = await self.create_chat(sender, message)  # It is necessary to await creation of messages
        await self.send(text_data=json.dumps({
            'message': new_msg.message,
            'sender': new_msg.sender
        }))

Solution

  • Solution is here. Don't call create_chat (put data to DB) from the function witch send message to WebSocket (to each opened connection / opened browser's tabs). Just call it from receive function.

    # Receive message from WebSocket
    async def receive(self, text_data):
        text_data_json = json.loads(text_data)
        message = text_data_json['message']
        sender = text_data_json['username']
        case = text_data_json['case_id']
        chat_type = text_data_json['chat_type']
    
        @sync_to_async
        def get_user(sender):
            return User.objects.get(username=sender)
        
        @sync_to_async
        def get_case(case):
            return Case.objects.get(id=case)
        
        user_obj = await get_user(sender)
        case_obj = await get_case(case)
    
        new_msg = await self.create_chat(user_obj, message, case_obj, chat_type)
    
        # Send message to room group
        await self.channel_layer.group_send(
            self.room_group_name, {
                'type': 'chat_message',
                'message': message,
                'username': sender,
                'case':case,
                'chat_type':chat_type,
                'time': new_msg.creted_time,
            }
        )