Django Channels – Introduction and Basic Setup
Django is a powerful Python framework for web development. It is fast, secure, and reliable. Channels allow Django projects to handle HTTP along with asynchronous protocols like WebSockets, MQTT, chatbots, and more.
Channels:
Channels preserve the synchronous behavior of Django and add a layer of asynchronous protocols allowing users to write the views that are entirely synchronous, asynchronous, or a mixture of both. Channels basically allow the application to support “long-running connections”. It replaces Django’s default WSGI with its ASGI.
ASGI:
ASGI (Asynchronous Server Gateway Interface) provides an interface between async Python web servers and applications while it supports all the features provided by WSGI.
Consumers:
A consumer is a basic unit of Channels. It is an event-driven class that supports both async and sync applications. Consumers can run longer and hence they support web sockets that need persistent connection.
In this post, we will set up a basic example of channels. We will build a calculator app that will allow the user to send multiple expressions to the server and receive the result through a single persistent connection.
Environment Setup:
- It is always a good idea to create a virtual environment for the python apps in order to avoid version conflicts. Run the following commands in the terminal to get started
easy-install pip python3 -m pip install virtualenv virtualenv venv source venv/bin/activate
- Now install Django and Channels:
pip install django pip install channels # On windows, try an unofficial wheel of 'Twisted' in case of dependency errors
LiveCalculator App:
Now start a Django project and create an app named ‘liveCalculator’
django-admin startproject sampleProject cd sampleProject python3 manage.py startapp liveCalculator
In sampleProject/settings.py, register channels and liveCalculator.
settings.py:
INSTALLED_APPS = [ 'channels', 'liveCalculator', 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', ]
In sampleProject/asgi.py, add the http protocol.
asgi.py:
Python3
import os import django from channels.http import AsgiHandler from channels.routing import ProtocolTypeRouter os.environ.setdefault( 'DJANGO_SETTINGS_MODULE' , 'sampleProject.settings' ) django.setup() application = ProtocolTypeRouter({ "http" : AsgiHandler(), # Just HTTP for now. (We can add other protocols later.) }) |
Now we need to register this asgi into our application. Add this line in sampleProject/settings.py :
ASGI_APPLICATION = "sampleProject.asgi.application"
Create a new folder liveCalculator/templates/liveCalculator and create a new file index.html inside it. It will be the starting page of our app. Add the following code in index.html:
index.html:
HTML
<!DOCTYPE html> < html lang = "en" > < head > < meta charset = "UTF-8" > < meta name = "viewport" content = "width=device-width, initial-scale=1.0" > < title >Live Calculator</ title > </ head > < body > < textarea name = "ta" id = "results" cols = "30" rows = "10" > </ textarea >< br > Enter the expression: < input type = "text" id = "exp" > < input type = "button" id = "submit" value = "Get Results" > < script > const socket = new WebSocket('ws://localhost:8000/ws/livec/'); socket.onmessage = (e) => { result = JSON.parse(e.data).result; document.getElementById("results").value += "Server: " + result + "\n"; } socket.onclose = (e) => { console.log("Socket closed!"); } document.querySelector('#exp').onkeyup = function (e) { if (e.keyCode === 13) { // enter, return document.querySelector('#submit ').click(); } }; document.querySelector("#submit").onclick = (e) => { inputfield = document.querySelector("#exp") exp = inputfield.value socket.send(JSON.stringify( { expression: exp } )) document.querySelector("#results").value += "You: " + exp + "\n"; inputfield.value = ""; } </ script > </ body > </ html > |
The above code will render a text area and an input box where the user can enter the expression. It will create a socket connection that we will make later and append the received result in the text area. When the user inputs the expression, it will send the expression through a socket connection.
Now create a view to render this page in liveCalculator/views.py :
liveCalculator/views.py:
Python3
from django.shortcuts import render # Create your views here. def index(request): return render(request, 'liveCalculator/index.html' , {}) |
Next, we need to create a route for this view. Add a new file urls.py in liveCalculator directory and add the following code:
liveCalculator/urls.py:
Python3
from django.conf.urls import url from . import views urlpatterns = [ url(r '^$' , views.index, name = "index" ), ] |
Register this route in sampleProject/urls.py :
sampleProject/urls.py:
Python3
from django.contrib import admin from django.urls import path from django.conf.urls import include, url urlpatterns = [ path( 'admin/' , admin.site.urls), url(r '^' , include( 'liveCalculator.urls' )) ] |
Now we need to create the consumer for our web socket connection. We will use the generic WebsocketConsumer class to implement its event-driven methods. Create a new file consumers.py in liveCalculator folder and add the following code:
consumers.py:
Python3
import json from channels.generic.websocket import WebsocketConsumer class Calculator(WebsocketConsumer): def connect( self ): self .accept() def disconnect( self , close_code): self .close() def receive( self , text_data): text_data_json = json.loads(text_data) expression = text_data_json[ 'expression' ] try : result = eval (expression) except Exception as e: result = "Invalid Expression" self .send(text_data = json.dumps({ 'result' : result })) |
The WebsocketConsumer class supports these user-defined methods:
- connect(): We can write the business logic of what should happen when the client sends a connection request.
- disconnect(): We can write the business logic of what should happen when the client sends a disconnection request.
- receive(): We can write the business logic of what should happen when the client sends a message.
It also supports these built-in methods:
- accept(): It will accept the incoming connection.
- close(): It will close the current connection.
- send(): It will send the specified message to the client.
We have simply used the above methods in our Calculator class to accept the connection, evaluate the expression when a message a received, and send it to the client.
Next, we also need to define the routing method for this consumer. Create a new file routing.py in the same folder and add the following code to it:
routing.py:
Python3
from django.urls import re_path from . import consumers websocket_urlpatterns = [ re_path(r 'ws/livec/$' , consumers.Calculator.as_asgi()), ] |
Note that we have used as_asgi() method on our Calculator class to use it for our application. This will enable the socket on ws://<IP:Port>/ws/livec. Now register routing.py into asgi.py by declaring the WebSocket protocol.
asgi.py:
Python3
import os from channels.auth import AuthMiddlewareStack from channels.routing import ProtocolTypeRouter, URLRouter from django.core.asgi import get_asgi_application import liveCalculator.routing os.environ.setdefault( "DJANGO_SETTINGS_MODULE" , "sampleProject.settings" ) application = ProtocolTypeRouter({ "http" : get_asgi_application(), "websocket" : AuthMiddlewareStack( URLRouter( liveCalculator.routing.websocket_urlpatterns ) ), }) |
We are almost done with our first Channels application. Save all the files and run the following commands in the terminal:
python3 manage.py makemigrations python3 manage.py migrate python3 manage.py runserver
Now open http://localhost:8000 on your browser, and you should see the output like this:
See the log of the server. Note that we have created the connection only once, and we can send the message multiple times without creating a new connection.
Please Login to comment...