Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Sending messages to groups in Django Channels 2

I am completely stuck in that I cannot get group messaging to work with Channels 2! I have followed all tutorials and docs that I could find, but alas I haven't found what the issue seems to be yet. What I am trying to do right now is to have one specific URL that when visited should broadcast a simple message to a group named "events".

First things first, here are the relevant and current settings that I employ in Django:

CHANNEL_LAYERS = {
    'default': {
        'BACKEND': 'channels_redis.core.RedisChannelLayer',
        'CONFIG': {
            'hosts': [('localhost', 6379)],
        },
    }
}

ASGI_APPLICATION = 'backend.routing.application'

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'rest_framework',
    'corsheaders',
    'channels',
    'channels_redis',
    'backend.api'
]

Next, here is my EventConsumer, extending the JsonWebsocketConsumer in a very basic way. All this does is echo back when receiving a message, which works! So, the simple send_json response arrives as it should, it is ONLY group broadcasting that does not work.

class EventConsumer(JsonWebsocketConsumer):
    groups = ["events"]

    def connect(self):
        self.accept()

    def disconnect(self, close_code):
        print("Closed websocket with code: ", close_code)
        self.close()

    def receive_json(self, content, **kwargs):
        print("Received event: {}\nFrom: {}\nGroups: 
                               {}".format(content, 
                                          self.channel_layer, 
                                          self.groups))

        self.send_json(content)

    def event_notification(self, event):
        self.send_json(
            {
                'type': 'test',
                'content': event
            }
        )

And here is the URL configurations for the URL that I want to trigger the broadcast:

Project urls.py

from backend.events import urls as event_urls

urlpatterns = [
    url(r'^events/', include(event_urls))
]

Events app urls.py

from backend.events.views import alarm

urlpatterns = [
    url(r'alarm', alarm)
]

And finally, the view itself where the group broadcast should take place:

from django.shortcuts import HttpResponse
from channels.layers import get_channel_layer
from asgiref.sync import async_to_sync


def alarm(req):
    layer = get_channel_layer()
    async_to_sync(layer.group_send)('events', {'type': 'test'})
    return HttpResponse('<p>Done</p>')
like image 782
Måns Thörnvik Avatar asked Feb 18 '18 18:02

Måns Thörnvik


2 Answers

I found the solution while writing this questions and thought that someone else might also make use of it! Since most of the questions out here are about channels version prior to 2.0 and above, this is how you should handle group_send events in your consumers.

The problem did not only lie in how I used the group_send function though, I had wrongly assumed that adding the groups class variable to my EventConsumer should automatically add it to that/those groups, it does NOT! You have to add groups manually in the connect class function and remove groups in the disconnect function!

The problem then also lied in that my consumer did not have proper event handlers specified. In my view file, where the alarm request is taken in, I had set the 'type' to 'test'. Test was not reflected in my EventConsumer class so the event could not be processed. As noted in the multichat example here on line number 146, the helper functions get called depending on the type of the event sent. So an event type of 'event.alarm' should have a corresponding function of event_alarm in your consumer! Simple, but not so well documented :). Here is what the final solution looked like:

In consumers.py, note the group_add in connect and the group_discard in disconnect!

class EventConsumer(JsonWebsocketConsumer):

    def connect(self):
        async_to_sync(self.channel_layer.group_add)(
            'events',
            self.channel_name
        )
        self.accept()

    def disconnect(self, close_code):
        print("Closed websocket with code: ", close_code)
        async_to_sync(self.channel_layer.group_discard)(
            'events',
            self.channel_name
        )
        self.close()

    def receive_json(self, content, **kwargs):
        print("Received event: {}".format(content))
        self.send_json(content)

    # ------------------------------------------------------------------------------------------------------------------
    # Handler definitions! handlers will accept their corresponding message types. A message with type event.alarm
    # has to have a function event_alarm
    # ------------------------------------------------------------------------------------------------------------------

    def events_alarm(self, event):
        self.send_json(
            {
                'type': 'events.alarm',
                'content': event['content']
            }
        )

So, the above function events_alarm gets called from the following group_send:

from django.shortcuts import HttpResponse

from channels.layers import get_channel_layer

from asgiref.sync import async_to_sync


def alarm(req):
    layer = get_channel_layer()
    async_to_sync(layer.group_send)('events', {
        'type': 'events.alarm',
        'content': 'triggered'
    })
    return HttpResponse('<p>Done</p>')

Please let me know if you need any more clarifications to the question/answer! Cheers!

like image 145
Måns Thörnvik Avatar answered Nov 20 '22 21:11

Måns Thörnvik


I had a similar issue to this for a while too, though the reason my group_send was not working was because a websocket was not actually connected.

When testing the dev server reloaded which disconnected the socket, so subsequent calls were not getting run by the consumer. Refreshing the frontend reconnected the socket and group_send started working.

Though this doesn't directly address the question I hope this might help somebody.

like image 3
Richard Pryce Avatar answered Nov 20 '22 21:11

Richard Pryce