Django Channels for Real Time Updates

Published 23 May, 2022

Reading time: 5 Mins


How to convert a Django project into a real-time capable project. So that it will be easy to implement websockets, https2 features. And will be useful for creating chat application and notification system from Django Channels

Is it possible to update the Django frontend without refreshing the page completely?. The previous version of Django is not made for real-time communication. If you need to send a real-time notification then you need to call the API continuously and it’s called polling. And if updates are available in the API then we can easily update the Frontend via Javascript.

But things changed in recent updates. Packages like Channels make it easy. Now anyone can able to convert from a Django monolith into a real-time capable project. All we need is a “Channels” package and things like http2 and Websockets will work.

In this tutorial, I’m focusing on a very simple approach you can do to your Django project so it’ll get the real-time capability. For all other things, there are excellent tutorials in Channels documentation.

Setting up the project.

For existing project please skip this section as needed. Also if you’re in doubt read it anyway because this is how I architecture my Django project.

Create a new Django project using the django-admin command and create a apps for it. Below mention project structure is what I’m comfortable with if you want you can also create the structure based on your needs. For details about how to create a project structure please refere to this article which I’ve wrote about this specific case.

virutalenv venv

pip install Django Channels channels-redis Redis

django-admin startproject realtime .

mkdir realtime/core

python manage.py startapp core realtime/core

vi realtime/core/apps.py # name = 'realtime.core'

vi realtime/settings.py 

INSTALLED_APPS = [
    ...
    "realtime.core"
    ...
]

Establish the connection

In order for this to work Frontend needs to get updates from the server without making any requests. This is usually done by connecting both frontend and backend via Websockets. Websockets is a communication protocol that uses single TCP connections. Once the connection happened it can send and receive anytime as long as the connections are open. Websockets connections will return a 101 status code meaning the status is pending. Pending status is fine because that’s the status code for the WebSocket request just like HTTP status is 200 are so. If the connection is not made then it will return the error status code. Once established backend can send messages anytime they need to send messages.

Let’s see how to initialize the Websockets in the Frontend. I created the base and index.html in the templates folder.

(venv) ➜  real-time-test-project git:(main) ✗ find realtime
realtime
realtime/core/templates
realtime/core/templates/index.html
realtime/core/templates/base.html

Inside the index.html file I added below javascript code.

const wsInstance = new WebSocket(`ws://${window.location.host}/real-time/`)

wsInstance.onmessage = (message) => {
    console.log(message.data)
}

I created a new WebSocket instance in the javascript. If you load this page in the browser you will see the 404 error message. It’s because there is no route is configured. Like Django URL we need to configure Websockets URLs in the Django project. But beforehand we need to alter the asgi.py file.

This asgi.py file was created for you when you created the Django project. Most of all it’s not doing anything but when you configure your Django project with Channels we need to alter it slightly.

Now go to the settings.py file and add this below line after the WSGI_APPLICATION variable.

WSGI_APPLICATION = "yourproject.wsgi.application"

ASGI_APPLICATION = "yourproject.asgi.application"

We don’t need the WSGI_APPLICATION variable going forward but let it be there until we complete the real-time project configuration. Also, don’t forget to install via pip and add channels into INSTALLED_APPS.

# asgi.py

import os
from django.core.asgi import get_asgi_application
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'project.settings')
application = get_asgi_application()

Change above file into this

import os

from channels.routing import ProtocolTypeRouter, URLRouter
from django.core.asgi import get_asgi_application

os.environ.setdefault("DJANGO_SETTINGS_MODULE", "project.settings")
django_asgi_app = get_asgi_application()

application = ProtocolTypeRouter(
    {
        "http": django_asgi_app,
        # Just HTTP for now. (We can add other protocols later.)
    }
)

Now if you issue a runserver command it will work normally. Note that this runserver comes from Channels packages rather than Django runserver. And you can see that difference from the console log when it’s starting.

To add Websocket support you need to edit the asgi.py file again.

from channels.routing import ProtocolTypeRouter, URLRouter
from django.urls import path
from realtime.core.consumers import RealTimeConsumer
application = ProtocolTypeRouter(
    {
        "http": django_asgi_app,
        "websocket": URLRouter([path("real-time/", RealTimeConsumer.as_asgi())]),
    }
)

Note I’m using path not re_path because for a simple route configuration it will work but you should always use re_path if you have nested one. The issue is still not fixed and will fixed in future releases.

Just like urls.py, I configured a URLRouter that takes the list of Django paths. I suggest you create two files now in your app.

  1. consumers.py This is like views.py but for the WebSocket views. We call it consumers.
  2. routing.py This is also like urls.py but for the Django Websockets. As you can see we already defined this in asgi.py but I recommend you move that list of URLs into this routing.py going forward.

One last thing you need to do right now is to add a channels layers settings in the settings.py file. Make sure you install this pip install redis

CHANNEL_LAYERS = {
    "default": {
        "BACKEND": "channels_redis.core.RedisChannelLayer",
        "CONFIG": {
            "hosts": [("redis-server-name", 6379)],
        },
    },
}

Write Consumers for Websockets

For this demo, I’m going to explain a simple Websocket function that takes the message from the Frontend and it will return the message to the Frontend.

# asgi.py
from channels.generic.websocket import WebsocketConsumer

class RealTimeConsumer(WebsocketConsumer):
    def connect(self):
        self.accept()
        self.send(text_data="Return message from backend")
    
    def disconnect(self, code):
        print("Connection is disconnected")

The above class function takes the WebsocketConsumer and it has two methods one is connected and another one is a disconnect. For every time a web socket is trying to establish the web socket connection this function gets called. You can accept the connection and after that, you can send a message to the consumer.

Conclusion

We saw how to create a javascript function that establishes a Websocket connection to the backend and how to convert asgi.py to real-time capability by adding the “WebSocket” protocol in it. After that how to create a route and consumer function. And finally how to write a basic WebsocketConsumer function so that it can respond to the WebSocket connection is established.

The entire code is now available in Github https://github.com/rajasimon/django-real-time. If you have any questions regarding this setup please make an issue on the Github repo and I will gladly help you from there.

This article published under development on django tags. If you wish to receive email from me when I post a new blog post then please subscribe to my newsletter.


You might also like