Django

⌘K
  1. Home
  2. Django
  3. chat
  4. User To User Chat
  5. Part 5: Implementing WebSocket Communication

Part 5: Implementing WebSocket Communication

Step-by-Step Instructions


Step 1: Refine the Chat Consumer

  1. Update the ChatConsumer in chat/consumers.py to handle additional WebSocket functionality:
from channels.generic.websocket import AsyncWebsocketConsumer
from asgiref.sync import sync_to_async
from .models import ChatRoom, Message
from django.contrib.auth.models import User
import json

class ChatConsumer(AsyncWebsocketConsumer):
    async def connect(self):
        self.room_name = self.scope['url_route']['kwargs']['room_name']
        self.room_group_name = f'chat_{self.room_name}'

        # Add user presence
        self.user = self.scope["user"]
        if not self.user.is_authenticated:
            await self.close()
            return

        # Join room group
        await self.channel_layer.group_add(
            self.room_group_name,
            self.channel_name
        )

        # Accept connection
        await self.accept()

        # Notify group about user joining
        await self.channel_layer.group_send(
            self.room_group_name,
            {
                'type': 'user_join',
                'username': self.user.username,
            }
        )

    async def disconnect(self, close_code):
        # Notify group about user leaving
        await self.channel_layer.group_send(
            self.room_group_name,
            {
                'type': 'user_leave',
                'username': self.user.username,
            }
        )

        # Leave room group
        await self.channel_layer.group_discard(
            self.room_group_name,
            self.channel_name
        )

    async def receive(self, text_data):
        try:
            data = json.loads(text_data)
            message = data.get('message', '').strip()

            if message:
                # Save message to database
                await self.save_message(self.user.username, self.room_name, message)

                # Send message to the group
                await self.channel_layer.group_send(
                    self.room_group_name,
                    {
                        'type': 'chat_message',
                        'message': message,
                        'username': self.user.username,
                    }
                )
        except Exception as e:
            # Handle invalid JSON or other issues
            await self.send(text_data=json.dumps({
                'error': 'Invalid message format or content.'
            }))

    async def chat_message(self, event):
        # Broadcast a chat message to WebSocket clients
        message = event['message']
        username = event['username']

        await self.send(text_data=json.dumps({
            'message': message,
            'username': username,
        }))

    async def user_join(self, event):
        # Notify WebSocket clients about a new user joining
        username = event['username']
        await self.send(text_data=json.dumps({
            'info': f'{username} has joined the chat.'
        }))

    async def user_leave(self, event):
        # Notify WebSocket clients about a user leaving
        username = event['username']
        await self.send(text_data=json.dumps({
            'info': f'{username} has left the chat.'
        }))

    @sync_to_async
    def save_message(self, username, room_name, message):
        room = ChatRoom.objects.get(name=room_name)
        user = User.objects.get(username=username)
        Message.objects.create(user=user, room=room, content=message)

Step 2: Enhance the Frontend for Presence Updates

  1. Update the room.html template to handle notifications for user join/leave events:
<h2>Room: {{ room_name }}</h2>
<div id="chat-log"></div>
<input id="chat-message-input" type="text" size="100">
<button id="chat-message-submit">Send</button>

<script>
    const roomName = "{{ room_name }}";
    const chatSocket = new WebSocket(
        'ws://' + window.location.host + '/ws/chat/' + roomName + '/'
    );

    chatSocket.onmessage = function(e) {
        const data = JSON.parse(e.data);

        if (data.message) {
            document.querySelector('#chat-log').innerHTML += (
                '<p><strong>' + data.username + ':</strong> ' + data.message + '</p>'
            );
        } else if (data.info) {
            document.querySelector('#chat-log').innerHTML += (
                '<p><em>' + data.info + '</em></p>'
            );
        } else if (data.error) {
            console.error(data.error);
        }
    };

    chatSocket.onclose = function(e) {
        console.error('Chat socket closed unexpectedly');
    };

    document.querySelector('#chat-message-submit').onclick = function(e) {
        const messageInputDom = document.querySelector('#chat-message-input');
        const message = messageInputDom.value;
        chatSocket.send(JSON.stringify({ 'message': message }));
        messageInputDom.value = '';
    };
</script>

Step 3: Retrieve Chat History

  1. Update the ChatConsumer to load the last 50 messages when a user connects:
from channels.generic.websocket import AsyncWebsocketConsumer
from asgiref.sync import sync_to_async
from .models import ChatRoom, Message
from django.contrib.auth.models import User
import json

class ChatConsumer(AsyncWebsocketConsumer):
    async def connect(self):
        """Handle WebSocket connection."""
        self.room_name = self.scope['url_route']['kwargs']['room_name']
        self.room_group_name = f'chat_{self.room_name}'

        # Authenticate user
        self.user = self.scope["user"]
        if not self.user.is_authenticated:
            await self.close()
            return

        # Join room group
        await self.channel_layer.group_add(
            self.room_group_name,
            self.channel_name
        )

        # Accept connection
        await self.accept()

        # Send the last 50 messages to the client
        messages = await self.get_last_messages(self.room_name)
        for message in messages:
            await self.send(text_data=json.dumps({
                'message': message['content'],
                'username': message['username'],
            }))

        # Notify group about user joining
        await self.channel_layer.group_send(
            self.room_group_name,
            {
                'type': 'user_join',
                'username': self.user.username,
            }
        )

    @sync_to_async
    def get_last_messages(self, room_name):
        """Retrieve the last 50 messages for the chat room."""
        try:
            room = ChatRoom.objects.get(name=room_name)
            messages = room.messages.order_by('-timestamp')[:50]
            # Return a list of dictionaries to avoid ORM-related issues in async context
            return [
                {'content': message.content, 'username': message.user.username}
                for message in messages
            ][::-1]
        except ChatRoom.DoesNotExist:
            return []  # Return an empty list if the room does not exist

    async def disconnect(self, close_code):
        """Handle WebSocket disconnection."""
        # Notify group about user leaving
        await self.channel_layer.group_send(
            self.room_group_name,
            {
                'type': 'user_leave',
                'username': self.user.username,
            }
        )

        # Leave room group
        await self.channel_layer.group_discard(
            self.room_group_name,
            self.channel_name
        )

    async def receive(self, text_data):
        """Handle incoming messages from WebSocket."""
        try:
            data = json.loads(text_data)
            message = data.get('message', '').strip()

            if message:
                # Save message to the database
                await self.save_message(self.user.username, self.room_name, message)

                # Send message to the group
                await self.channel_layer.group_send(
                    self.room_group_name,
                    {
                        'type': 'chat_message',
                        'message': message,
                        'username': self.user.username,
                    }
                )
        except Exception:
            # Handle invalid JSON or other issues
            await self.send(text_data=json.dumps({
                'error': 'Invalid message format or content.'
            }))

    async def chat_message(self, event):
        """Broadcast a chat message to WebSocket clients."""
        message = event['message']
        username = event['username']

        await self.send(text_data=json.dumps({
            'message': message,
            'username': username,
        }))

    async def user_join(self, event):
        """Notify WebSocket clients about a new user joining."""
        username = event['username']
        await self.send(text_data=json.dumps({
            'info': f'{username} has joined the chat.'
        }))

    async def user_leave(self, event):
        """Notify WebSocket clients about a user leaving."""
        username = event['username']
        await self.send(text_data=json.dumps({
            'info': f'{username} has left the chat.'
        }))

    @sync_to_async
    def save_message(self, username, room_name, message):
        """Save a chat message to the database."""
        room = ChatRoom.objects.get(name=room_name)
        user = User.objects.get(username=username)
        Message.objects.create(user=user, room=room, content=message)

Step 4: Test WebSocket Communication

  1. Restart the server:
python manage.py runserver

Open multiple browser tabs or use different accounts to simulate multiple users joining a room.

Ensure:

  • Messages are broadcasted in real-time.
  • Join and leave notifications appear.
  • Chat history loads for new users.

How can we help?