[Fixed]-Sending a message to a single user using django-channels

20đź‘Ť

âś…

Expanding on @Flip’s answer of creating a group for that particular user.

In your python function in your ws_connect function you can add that user into a a group just for them:

consumers.py

from channels.auth import channel_session_user_from_http
from channels import Group

@channel_session_user_from_http
def ws_connect(message):
    if user.is_authenticated:
        Group("user-{}".format(user.id)).add(message.reply_channel)

To send that user a message from your python code:

my view.py

import json
from channels import Group

def foo(user):
    if user.is_authenticated:
        Group("user-{}".format(user.id)).send({
            "text": json.dumps({
            "foo": 'bar'
        })
    })

If they are connected they will receive the message. If the user is not connected to a websocket it will fail silently.

You will need to also ensure that you only connect one user to each user’s Group, otherwise multiple users could receive a message that you intended for only a specific user.

Have a look at django channels examples, particularly multichat for how to implement routing, creating the websocket connection on the client side and setting up django_channels.

Make sure you also have a look at the django channels docs.

👤lukeaus

25đź‘Ť

Little update since Groups work differently with channels 2 than they did with channels 1. There is no Group class anymore, as mentioned here.

The new groups API is documented here. See also here.

What works for me is:

# Required for channel communication
from channels.layers import get_channel_layer
from asgiref.sync import async_to_sync


def send_channel_message(group_name, message):
    channel_layer = get_channel_layer()
    async_to_sync(channel_layer.group_send)(
        '{}'.format(group_name),
        {
            'type': 'channel_message',
            'message': message
        }
    )

Do not forget to define a method to handle the message type in the Consumer!

    # Receive message from the group
    def channel_message(self, event):
        message = event['message']

        # Send message to WebSocket
        self.send(text_data=json.dumps({
            'message': message
        }))
👤user42488

13đź‘Ť

In Channels 2, you can save self.channel_name in a db on connect method that is a specific hash for each user. Documentation here

from asgiref.sync import async_to_sync
from channels.generic.websocket import AsyncJsonWebsocketConsumer
import json

class Consumer(AsyncJsonWebsocketConsumer):
    async def connect(self):
        self.room_group_name = 'room'

        if self.scope["user"].is_anonymous:
            # Reject the connection
            await self.close()
        else:
            # Accept the connection
            await self.channel_layer.group_add(
                self.room_group_name,
                self.channel_name
            )

            await self.accept()

        print( self.channel_name )

Last line returns something like specific.WxuYsxLK!owndoeYTkLBw

This specific hash you can save in user’s table.

👤Karl Zillner

5đź‘Ť

The best approach is to create the Group for that particular user. When ws_connect you can add that user into Group("%s" % <user>).add(message.reply_channel)

Note: My websocket url is ws://127.0.0.1:8000/<user>

👤Raja Simon

2đź‘Ť

Just to extend @luke_aus’s answer, if you are working with ResourceBindings, you can also make it so, that only users “owning” an object retrieve updates for these:

Just like @luke_aus answer we register the user to it’s own group where we can publish actions (update, create) etc that should only be visible to that user:

from channels.auth import channel_session_user_from_http,
from channels import Group

@channel_session_user_from_http
def ws_connect(message):
    Group("user-%s" % message.user).add(message.reply_channel)

Now we can change the corresponding binding so that it only publishes changes if the bound object belongs to that user, assuming a model like this:

class SomeUserOwnedObject(models.Model):
    owner = models.ForeignKey(User)

Now we can bind this model to our user group and all actions (update, create, etc) will only be published to this one user:

class SomeUserOwnedObjectBinding(ResourceBinding):
    # your binding might look like this:
    model = SomeUserOwnedObject
    stream = 'someuserownedobject'
    serializer_class = SomeUserOwnedObjectSerializer
    queryset = SomeUserOwnedObject.objects.all()

    # here's the magic to only publish to this user's group
    @classmethod
    def group_names(cls, instance, action):
        # note that this will also override all other model bindings
        # like `someuserownedobject-update` `someuserownedobject-create` etc
        return ['user-%s' % instance.owner.pk]
👤devsnd

0đź‘Ť

Although it’s late but I have a direct solution for channels 2 i.e using send instead of group_send

send(self, channel, message)
 |      Send a message onto a (general or specific) channel.

use it as –

await self.channel_layer.send(
            self.channel_name,
            {
                'type':'bad_request',
                'user':user.username,
                'message':'Insufficient Amount to Play',
                'status':'400'
            }
        )

handel it –

await self.send(text_data=json.dumps({
            'type':event['type'],
            'message': event['message'],
            'user': event['user'],
            'status': event['status']
        }))

Thanks

👤Pankaj Sharma

Leave a comment