Introduction to Django Channels
In this article, we’re going to talk about Django channels and how it can be a game changer for the Django framework.
Django is an already established framework in the market, but it is synchronous and nowadays with the high demand for real time applications, It could not be left behind. Django channels comes into play to solve this issue.
Django channels’ goal is to extend the Django framework, adding to it a new layer to handle the use of WebSockets and background tasks.
To use as an example, we’re going to create a simple webapp called coinpricemonitor, to watch the bitcoin and litecoin prices in real time. If you want to take a look at the full code, it’s here. To create this example I used Django 1.11 and Channels 1.1.6.
What do I have to change to use Django channels?
All that comes with Channels is entirely optional and, as said before, It just aims to extend Django, giving it the ability to create real time applications. It would not be good if a new release of the framework would break everyone’s request system.
If Django Channels was in the core of Django already, as it intends to be in the future, you wouldn’t need to install anything. You would only have to update your project to Django’s newest release.
How to install
While Channels is not in the core of Django yet, we need to install it. In order to do it, just run the command:
pip install channels.
After that, add
channels to your
Django channels request cycle
Using Channels, now you will have two types of requests to handle, the http requests and the websocket requests.
When the request arrives from the client, it will pass through an Interface server, a server that understands the ASGI (Asynchronous Server Gateway Interface) protocol. It will redirect the request to an ASGI router, the channel layer that will check if it’s a http request, and send it to its respective view, or a websocket request, and send it to its consumer, which is very similar to a view.
In the following sections, we will see these components that integrate the new request cycle with Channels.
Nowadays the Django framework uses a WSGI (Web Server Gateway Interface), which is an interface for python applications to handle requests. But to work with asynchronous applications, we need to use another interface, which is ASGI (Asynchronous Server Gateway Interface), that can handle websocket requests as well.
You will have to use a server that uses this approach. Good for us that Andrew Goodwin, the creator of Channels, made as well a server called
Daphne. To handle this protocol as well.
Daphne already comes with Channels, so you won’t need to install it.
Now, you are handling two types of requests: the http requests that are going to be routed to views as normal, and websocket requests that are going to be routed to consumers, which are similar to your already known views. To handle both requests we need a router. It is a part of the channel layer, which is the backend of Channels.
Some brokers that can be used as routers are:
- asgi_redis — You will need to install Redis to use this broker.
- asgi_rabbitmq — You will need to install Rabbitmq to use this broker.
You can find all these brokers in PyPI, meaning that you can use
pip to install them.
Here’s an example how to setup the channel layer with the
It’s quite simple to setup the channels layer, here’s the meaning of each key:
- default — Default settings for the channel layer.
- BACKEND — Broker you are going to use. In our case,
- CONFIG — Configurations of the channels, like hosts and capacity of each channel. You can check out all options here.
- ROUTING — To where the incoming messages are going to be routed to.
They are the ones that are going to process the consumers.
The main data structure in Channels is a Channel, and its concept is described as:
Channel is a data structure that is a first-in first-out queue It is an ordered, first-in first-out queue with message expiry and at-most-once delivery to only one listener at a time. — Channels documentation
Types of Channels
One type of channel is the routing one, that depending from where the message came, it will route to the proper consumer. There are three routing channels:
Do you remember in the configuration of the channel layer that we set up routing? So, when a message arrives, it will be routed by one of the routing channels inside the
routing.py file. We have three routing channels, one when the user is trying to start a websocket connection, another one when the user sends a message and a third for when a client wants to close the connection.
When a message arrives in the
websocket.connect channel, the message will be sent to the
ws_connect consumer, which will process the message.
Another type of Channel is the
reply_channel , which comes with the message object. It is an identifier to knowledge from where that message came and to know where the consumer should send back its response.
Consumers and Groups
A consumer is a function that will receive a message (websocket request), process it and may return or not a response.
Let’s start taking a look step by step how our coinpricemonitor was built. First with its
This consumer receives a message and replies with a flag
accept which will tell that the browser should accept the connection. We can return just a few flags besides
accept, let’s take a look at them:
accept — To accept a new websocket connection.
text — To send a string, but you can import
json and use
json.dumps() to send a dictionary.
bytes — Send encrypted data to the frontend, in order to not let others see what you are sending.
In our case, We are using the
accept key, so when the message is sent, it arrives in the
onOpen socket method in the frontend and will print into the console the message “Connected to websocket!”:
Notice that we used the method
send to send a response to the client, but this method is not from the message object, it’s from the message’s attribute
reply_channel. This is another type of channel that represents the user’s channel, to inform which channel send back the response.
As every client has its
reply_channel, when we have to send the new price to each user, we would need to keep track of the users connected in a list, and send them the new price. But if we wanted to separate users in groups, like in rooms in a chat in real time, we’d have to keep track of different lists with channels for each room. That’d be hard to do.
Fortunately, Channels has a way to solve that problem with a data structure called
Group. We create a group and add in it all channels, so when a update is required, we just need to send the message to the group.
Let’s create a new group called “btc-price” and add the user’s channel to it when he connects to the server:
Now that we have a way to group channels, we actually are going to need one more group, because besides showing the bitcoin price, we can change to see the
litecoin (which is another cryptocurrency) price. A group of channels is going to receive the bitcoin price, and the other one is going to receive the litecoin price.
It is necessary to keep track in which group the user is. To accomplish that, we’re going to store in the user’s session the name of the group.
channel_session_user is imported, It is a channel decorator that gets a Django session for us and enables it in the message object with the attribute
channel_session. So a new key called
coin-group is added with the value
btc-price to keep track in which group the user’s channel is in.
Then the new bitcoin price is sent to the frontend. I did it through a periodic task on celery, to make a simple get request to the bittrex API to get the last bitcoin price and broadcast that price to the
get_coin_price helper function:
Now that you know how channels handles a connection, what about the frontend? How to get to the
ws_connect consumer? When the page loads, It’s required to create a websocket connection to the backend, to start receiving the bitcoin current price in real time. After the
load event is triggered, the
setupWebsocket function will be called, which will create a WebSocket object with a url, if the project is running locally, you can use
ws://localhost:8000/ws/. We setup socket methods for when the connection is established (onopen) and when a message comes from the backend (onmessage).
After the creation of a WebSocket object, it will try to connect to its Url. This message will arrive in the channel layer, which will be routed to the
websocket.connect channel, leading to the
Another flow from the frontend to the backend is when you click in the button to see the
litecoin price. In resume, the bitcoin price and icon are being hidden and switched for the litecoin ones. The main part is in the end:
websocket.send(). This is a websocket method that enables data to be sent through it. A flag is being sent to let the backend knows that the litecoin price is wanted, not bitcoin anymore.
Since the data is being sent, the message will be routed to the
websocket.receive channel, leading to the
Here we get the data that was sent from the frontend and make an
if statement in order to check either the user wants the litecoin price or the bitcoin price. If the user wants the litecoin price, we will remove the user from the group of channels that receives the bitcoin price and put the user’s channel in the group that receives the litecoin price and change the value of the key
ltc-price to keep track of the user’s channel. In the other hand, if the client wants the bitcoin price, we put the client’s channel in the
btc-price group and discard the channel from the
ltc-price group and change the value of the key
After your group is changed, a new value is received in the
onMessage socket method in the frontend.
Depending of which type of coin value was received, it will need to be changed in the proper element.
The last flow is the disconnection flow. It’s triggered when the browser tab is closed. Doing so, the
Websocket object in the frontend will trigger a disconnect event to the backend to finish the websocket connection.
The message will be routed to the
websocket.disconnect channel, which will lead to the
ws_disconnect just removes the user’s channel from the last group that it was in. In order to know which group it was, we check the value in the
coin-group stored key is checked.
Channels is a really powerful library for real time applications, as shown, and this was just a small taste of what it is capable of doing. If you already know Django and have the need to make these types of applications, give channels a chance and see how it works for you.
That’s basically all you need to know to start with Django channels. If you got interested, here are some links that might be helpful: