22.6. Django Channels

Django Channels is a project that takes Django and extends its abilities beyond HTTP - to handle WebSockets, chat protocols, IoT protocols, and more. It's built on a Python specification called ASGI.

../../_images/django-channels-groups.png

22.6.1. Install

Run command in system terminal:

$ python -m pip install 'channels[daphne]'

22.6.2. Settings

Append to myproject/settings.py:

INSTALLED_APPS += ['daphne', 'chat']
ASGI_APPLICATION = 'myproject.asgi.application'
CHANNEL_LAYERS = {
    'default': {
        'BACKEND': 'channels.layers.InMemoryChannelLayer',
    }
}

Note

During development you can use InMemoryChannelLayer config. For production use RedisChannelLayer (see below).

22.6.3. Template

File chat/templates/chat/index.html:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <title>Chat</title>
</head>
<body>
<h1>Chat</h1>

<form id="form">
    <input type="text" id="message" name="message" placeholder="Type your message here">
    <button type="submit">Send</button>
</form>

<div id="messages">
</div>

<script>
    document.addEventListener('DOMContentLoaded', () => {

        const ws = new WebSocket('ws://{{ request.get_host }}/ws/chat/');
        ws.onmessage = (response) => {
            data = JSON.parse(response.data);
            switch (data.type) {
                case 'system': console.log(data.value); break;
                case 'message': onMessage(data.value); break;
            }
        };

        onMessage = (msg) => {
            let history = document.getElementById('history');
            let message = document.createElement('p');
            message.textContent = msg;
            history.appendChild(message);
        };

        const form = document.querySelector('form');
        form.addEventListener('submit', (event) => {
            event.preventDefault();
            ws.send(JSON.stringify({
                type: 'message',
                value: form.message.value
            }));
            form.reset();
        });

    });
</script>

</body>
</html>

22.6.4. View

Append to file shop/views.py:

from django.shortcuts import render

def chat(request):
    return render(request, 'chat/index.html')

22.6.5. URLs

Append to file myproject/urls.py:

from chat.views import chat

urlpatterns += [path('chat/', chat, name='chat')]

22.6.6. Consumers

File chat/consumers.py:

from channels.generic.websocket import JsonWebsocketConsumer
from asgiref.sync import async_to_sync


class ChatConsumer(JsonWebsocketConsumer):
    def connect(self):
        self.room_group_name = 'chat'
        func = async_to_sync(self.channel_layer.group_add)
        func(self.room_group_name, self.channel_name)
        self.accept()
        self.send_json({
            'type': 'system',
            'value': 'connected',
        })

    def receive_json(self, content):
        func = async_to_sync(self.channel_layer.group_send)
        func(self.room_group_name, {
            'type': 'handle',
            'value': content['value'],
        })

    def handle(self, event):
        self.send_json({
            'type': 'message',
            'value': event['value'],
        })

    def disconnect(self, close_code):
        self.send_json({
            'type': 'system',
            'value': 'disconnected',
        })

22.6.7. Routing

File chat/routing.py:

from django.urls import re_path
from .consumers import ChatConsumer

websocket_urlpatterns = [
    re_path(r'ws/chat/', ChatConsumer.as_asgi())
]

22.6.8. ASGI

  • channels.auth.AuthMiddlewareStack - Uses Django session authentication

  • It is not needed for public channels

Modify myproject/asgi.py:

import os
from django.core.asgi import get_asgi_application
from channels.routing import ProtocolTypeRouter, URLRouter
from channels.auth import AuthMiddlewareStack
import chat.routing

os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'myproject.settings')

application = ProtocolTypeRouter({
    'http': get_asgi_application(),
    'websocket': AuthMiddlewareStack(
        URLRouter(chat.routing.websocket_urlpatterns),
    ),
})

22.6.9. Redis

  • For production environment

Run command in system terminal:

$ python -m pip install channels_redis

Append to myproject/settings.py:

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

22.6.10. Further Reading