Getting Started with Django Channels

In this tutorial, we will use Django Channels to create a real-time application that updates a list of users as they log in and out.

With WebSockets (via Django Channels) managing the communication between the client and the server, whenever a user is authenticated, an event will be broadcasted to every other connected user. Each user’s screen will change automatically, without them having to reload their browsers.

NOTE: We recommend that you have some experience with Django before beginning this tutorial. Also, you should be familiar with the concept of WebSockets.

Our application uses:

  • Python (v3.6.0)
  • Django (v1.10.5)
  • Django Channels (v1.0.3)
  • Redis (v3.2.8)

Objectives

By the end of this tutorial, you will be able to…

  1. Add Web sockets support to a Django project via Django Channels
  2. Set up a simple connection between Django and a Redis server
  3. Implement basic user authentication
  4. Leverage Django Signals to take action when a user logs in or out

Getting Started

First, create a new virtual environment to isolate our project’s dependencies:

$ mkdir django-example-channels
$ cd django-example-channels
$ python3.6 -m venv env
$ source env/bin/activate
(env)$

Install Django, Django Channels, and ASGI Redis, and then create a new Django project and app:

(env)$ pip install django==1.10.5 channels==1.0.2 asgi_redis==1.0.0
(env)$ django-admin.py startproject example_channels
(env)$ cd example_channels
(env)$ python manage.py startapp example
(env)$ python manage.py migrate

NOTE: During the course of this tutorial, we will create a variety of different files and folders. Please refer to the folder structure from the project’s repository if you get stuck.

Next, download and install Redis. If you’re on a Mac, we recommend using Homebrew:

$ brew install redis

Start the Redis server in a new terminal window and make sure that it is running on its default port, 6379. The port number will be important when we tell Django how to communicate with Redis.

Complete the setup by updating INSTALLED_APPS in the project’s settings.py file:

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'channels',
    'example',
]

Then Configure the CHANNEL_LAYERS by setting a default backend and routing:

CHANNEL_LAYERS = {
    'default': {
        'BACKEND': 'asgi_redis.RedisChannelLayer',
        'CONFIG': {
            'hosts': [('localhost', 6379)],
        },
        'ROUTING': 'example_channels.routing.channel_routing',
    }
}

This uses a Redis backend which is also needed in production.

WebSockets 101

Normally, Django uses HTTP to communicate between the client and server:

  1. The client sends an HTTP request to the server.
  2. Django parses the request, extracts a URL, and then matches it to a view.
  3. The view processes the request and returns an HTTP response to the client.

Unlike HTTP, the WebSockets protocol allows bi-directional communication, meaning that the server can push data to the client without being prompted by the user. With HTTP, only the client that made a request receives a response. With WebSockets, the server can communicate with multiple clients simultaneously. As we will see later on in this tutorial, we send WebSockets messages using the ws:// prefix, as opposed to http://.

NOTE: Before diving in, quickly review the Channels Concepts documentation.

Consumers and Groups

Let’s create our first consumer, which handle the basic connections between the client and the server. Create a new file called example_channels/example/consumers.py:

from channels import Group


def ws_connect(message):
    Group('users').add(message.reply_channel)


def ws_disconnect(message):
    Group('users').discard(message.reply_channel)   

Consumers are the counterpart to Django views. Any user connecting to our app will be added to the “users” group and will receive messages sent by the server. When the client disconnects from our app, the channel is removed from the group, and the user will stop receiving messages.

Next, let’s set up routes, which work in almost the same manner as Django URL configuration, by adding the following code to a new file called example_channels/routing.py:

from channels.routing import route
from example.consumers import ws_connect, ws_disconnect


channel_routing = [
    route('websocket.connect', ws_connect),
    route('websocket.disconnect', ws_disconnect),
]

So, we defined channel_routing instead of urlpatterns and route() instead of url(). Notice that we linked our consumer functions to WebSockets.

Templates

Let’s write up some HTML that can communicate with our server via a WebSocket. Create a “templates” folder within “example” and then add an “example” folder within “templates” - “example_channels/example/templates/example”.

Add a _base.html file:

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <link href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">
  <title>Example Channels</title>
</head>
<body>
  <div class="container">
    <br>
    {% block content %}{% endblock content %}
  </div>
  <script src="//code.jquery.com/jquery-3.1.1.min.js"></script>
  {% block script %}{% endblock script %}
</body>
</html>

And user_list.html:

{% extends 'example/_base.html' %}

{% block content %}{% endblock content %}

{% block script %}
  <script>
    var socket = new WebSocket('ws://' + window.location.host + '/users/');

    socket.onopen = function open() {
      console.log('WebSockets connection created.');
    };

    if (socket.readyState == WebSocket.OPEN) {
      socket.onopen();
    }
  </script>
{% endblock script %}

Now, when the client successfully opens a connection with the server using a WebSocket, we will see a confirmation message print to the console.

Views

Set up a supporting Django view to render our template within example_channels/example/views.py:

from django.shortcuts import render


def user_list(request):
    return render(request, 'example/user_list.html')

Add the URL to example_channels/example/urls.py:

from django.conf.urls import url
from example.views import user_list


urlpatterns = [
    url(r'^$', user_list, name='user_list'),
]

Update the project URL as well in example_channels/example_channels/urls.py:

from django.conf.urls import include, url
from django.contrib import admin

urlpatterns = [
    url(r'^admin/', admin.site.urls),
    url(r'^', include('example.urls', namespace='example')),
]

Test

Ready to test?

(env)$ python manage.py runserver

NOTE: You can alternatively run python manage.py runserver --noworker and python manage.py runworker in two different terminals to test the interface and worker servers as two separate processes. Both methods work!

 

Back to Top