简体   繁体   中英

django channels: notifications of message not working properly

I am writing a django module that handles real time message paired with notification. So far:
a conversation can only take place between no more than 2 users. a notification should be sent after each message.

I am currently working on getting the notifications to show up and the issue is that the notification gets rendered in the sender profile page and not in the recipient profile. I cant see where my error is

Here is what I have done: consumers.py

import json
from channels.generic.websocket import AsyncWebsocketConsumer
from channels.db import database_sync_to_async
from .models import Chat, ChatRoom
from accounts.models import User
from asgiref.sync import sync_to_async
from django.contrib.auth import get_user_model
from django.shortcuts import get_object_or_404
from asgiref.sync import async_to_sync

class ChatConsumer(AsyncWebsocketConsumer):
    async def connect(self):
        self.room_id = self.scope['url_route']['kwargs']['room_id']
        self.room_group_name = 'chat_%s' % self.room_id

        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']
        recipient = text_data_json['recipient']

        self.user_id = self.scope['user'].id

        # Find room object
        room = await database_sync_to_async(ChatRoom.objects.get)(pk=self.room_id)
        print('ok1')

        # Create new chat object
        chat = Chat(
            content=message,
            sender=self.scope['user'],
            room=room,

        )
        print('ok2')
        await database_sync_to_async(chat.save)()
        print("ok3")
        # get the recipient user
        recipient_user = await database_sync_to_async(User.objects.get)(id=recipient)
        print("ok4")

        await sync_to_async(chat.recipient.add)(recipient_user.id)
        print("ok5")
        await database_sync_to_async(chat.save)()


        await self.channel_layer.group_send(
            self.room_group_name,
            {
                'type': 'chat_message',
                'message': message,
                'user_id': self.user_id
            })

        # Send a notification to the recipient
        await self.channel_layer.send(
            recipient_user.username,
            {
                'type': 'notification',
                'message': message
            }
        )
        await self.send_notification(f'New message from {self.user_id}')
        print('notification has been created')

    async def chat_message(self, event):
        message = event['message']
        user_id = event['user_id']

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

    async def send_notification(self, message):
        await self.send(text_data=json.dumps({
            'type': 'notification',
            'message':message

        }))

and here is my room.js code, which is the javascript code handling the logic to display the messages logic and the notifications:

chatSocket.onmessage = function(e) {
    const data = JSON.parse(e.data);
    console.log("data",data)
    console.log("datatype",data.type)
    var message_type = data.type;
    console.log("message type",message_type)
    if(message_type === 'notification'){
            $("#notification-bar2").text(data.message);
            $("#notification-bar2").show();
        }
    if(message_type !== 'notification'){
    const messageElement = document.createElement('div')
    const userId = data['user_id']
    const loggedInUserId = JSON.parse(document.getElementById('user_id').textContent)
    console.log(loggedInUserId)
    messageElement.innerText = data.message

    if (userId === loggedInUserId) {
        messageElement.classList.add( 'relative', 'max-w-xl', 'px-4', 'py-2', 'text-gray-700', 'bg-gray-100','rounded', 'shadow','flex', 'justify-end','message', 'sender','block')
    } else {
        messageElement.classList.add('relative', 'max-w-xl', 'px-4', 'py-2', 'text-gray-700', 'bg-gray-100','rounded', 'shadow','flex', 'justify-start','message', 'receiver','block')
    }

    chatLog.appendChild(messageElement)

    if (document.querySelector('#emptyText')) {
        document.querySelector('#emptyText').remove()
    }
    }
};

I am super confused about why that would be, and be fresh starting with channels, there are still quite a bit of stuff that I dont understand super well, therefore any king help is greatly appreciated! I am more than happy to provide additionnal code if necessary

In your receive function you only have a recipient and a connected user. This code takes the recipient that comes with the message and sends it to that user(the recipient) letting it know that it came from this user(the sender). The message already comes tagged with the recipient that it goes to when it hits the websocket. We need to get rid of that and keep track of all of our connected users within the ChatRoom object. You can simply handle this in the view or when the user connects to the websocket. When a message is sent, get each recipient user in the room, which should be every user except for the user that sent the object.

    print('ok2')
    await database_sync_to_async(chat.save)()
    print("ok3")
    # get the recipient user
    # database_sync_to_async function that gets the list of users
    # dont forget to call users.remove(sender)
    for each in users:
        recipient_user = each
        print("ok4")

        await sync_to_async(chat.recipient.add)(recipient_user.id)
        print("ok5")
    await database_sync_to_async(chat.save)()

It seems you are attempting to send the notification in two different ways but doing it the wrong way.

        # Send a notification to the recipient
        await self.channel_layer.send(
            recipient_user.username,
            {
                'type': 'notification',
                'message': message
            }
        )
        await self.send_notification(f'New message from {self.user_id}')
        print('notification has been created')

The first method attempts to send it through the channel layer which is the correct way given that the current channel is that of the sender, so you need to pipe it through the channel layer to the channel of the recipient. The issue is that you are not using the correct name of the handler. You are using notification as type instead of send_notification so it is never handled. In the second case, you are calling the send_notification function directly in the current channel which is the sender's channel so it will get sent to the sender instead of the intended receiver. The entire code block above should be replaced with:

    # Send a notification to the recipient
    await self.channel_layer.send(
        recipient_user.username,
        {
            'type': 'send_notification',
            'message': f'New message from {self.user_id}'
        }
    )

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM