How to implement asynchronous socket status using django channels
Tags: python, django, socket, djangochannels
Published: 2021-01-23 09:04:16

While developing an analytical project using django we came across an issue where we have to show status when our users start analysis. We decided to implement this using django channels. There are many tutorials about django channels on the internet but all of them are a little complex or using django channels to build a chat application. So I decided to write this article where I will show how to send asynchronous status to users using django channels.

I am assuming you know at least the basics of django framework and how django works, so I am not going to show how to create a django project and django apps. I will directly start from implementing django channels.

First we will start a project **channelproj** and create an app **notifier**.

We need to install **channels** and **channels-redis** using pip :

```
pip install channels channels-redis
```
We need to install redis-server to out system:

```
sudo apt update
sudo apt install redis-server
```

Check redis-server status:
```
sudo systemctl status redis
```

![Redis Active Status](https://dev-to-uploads.s3.amazonaws.com/i/75ekxve9hzd5bgyo8cl8.png)

Here we can see redis server is active and running on port 6379

Then open django **channelproj/settings.py** file and add channels and notifier to INSTALLED_APPS.

```
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'channels', # Here we added channels 
    'notifier' # we will use django channels in this app
]
```

We also need to add this lines in **settings.py** file:

```
ASGI_APPLICATION = 'channelproj.asgi.application'

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

To know more about channel layer click [here](https://channels.readthedocs.io/en/stable/topics/channel_layers.html)

Now we will create a file **notifier/consumers.py** and add the following class

```
from channels.generic.websocket import AsyncJsonWebsocketConsumer

class StatusConsumer(AsyncJsonWebsocketConsumer):
    room_group_name = 'notify'

    async def connect(self):
        await self.channel_layer.group_add(
            self.room_group_name,
            self.channel_name
        )
        await self.accept()

    async def disconnect(self, code):
        await self.channel_layer.group_discard(
            self.room_group_name, 
            self.channel_layer
        )

    async def status_notifier(self, event):
        await self.send_json(event)
```

Here we are using **AsyncJsonWebsocketConsumer** and creating a group named notify and we have defined a function called **status_notifier** which will send all the events via socket url as json format.

We will create another file **notifier/routing.py** and add our socket urls

```
from django.urls import path

from .consumers import StatusConsumer

ws_urlpatterns = [
    path('ws/status/', StatusConsumer.as_asgi())
]
```
Now open **channelproj/asgi.py** file and edit the file as below:

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

from notifier.routing import ws_urlpatterns

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

application = ProtocolTypeRouter({
    "http": get_asgi_application(),
    "websocket": URLRouter(
    	ws_urlpatterns
    )
})
```

Here we have imported **ws_urlpatterns** from **notifier/routing.py** added **ProtocolTypeRouter** and defined our websocket urls using **URLRouter**

Now we will create a template **notifier/templates/home.html** in our notifier app where we will add a form with an input field.

```
  {% load static %}
  <html>
    <head>
      <title>Django Channels Status</title>
    </head>
    <body>
      <h1>Check Status</h1>

    <form action="{% url 'staus' %}" method="post">
      {% csrf_token %}
      <input type="text" placeholder="enter anything" name="TB_sample" id="TB_sample"/><br>
      <input type="submit" value="submit">
    </form>

  </body>
  </html>
```
Now we will add a two function in **notifier/views.py**

```
from django.shortcuts import render, redirect
from asgiref.sync import async_to_sync
from channels.layers import get_channel_layer
import time


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

def status_form(request):
    if request.method =='POST':
        num = int(request.POST['TB_sample'])
        progress = 10
        for i in range(num):
            room_group_name = f'notify'
            channel_layer = get_channel_layer()
            async_to_sync(channel_layer.group_send)(
                room_group_name, {
                    "type": "status.notifier",
                    "data": progress
                }
            )
            message = "Status Running"
            progress += 10
            time.sleep(1)
            
    context = {'message': message}
    return render(request, 'home.html', context)
```

Here we have defined a function **status_form** which will be called when a post request happens, it will get the post value in **num** variable and we have declared a variable called **progress** with value 10, we have defined a loop which will run and add 10 every time loop runs to **progress** and sleep for 1 second. We have added **channel_layer = get_channel_layer()** line to get the channel layer and **async_to_sync** method to send the data asynchronously to **room_group_name**.

**Note:** Here by declaring `"type": "status.notifier"` we are calling  `status_notifier` function we wrote in **notifier/consumers.py** file.

Also edit **channelproj/urls.py** file like this:

```
from notifier.views import index,status_form

urlpatterns = [
    path('', index),
    path('status/', status_form, name="status"),
    path('admin/', admin.site.urls),
]
```
Now if we run the project using `python manage.py runserver` and open `http://127.0.0.1:8000/` url in our browser it will look something like this

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/b9p7jgca5c979cb7i0qp.png)

To check socket status we will use a chrome extension called **Simple WebSocket Client**. Add the extension and click the extension icon in chrome and insert our web socket url `ws://127.0.0.1:8000/ws/status/` and click open, connection should be established.

Now go to project tab and insert 10 in input filed and submit the form and move to **Simple WebSocket Client** tab. We will see the status we are sending to socket appearing every second until the loop completes.

![Socket Status](https://dev-to-uploads.s3.amazonaws.com/i/y4fpihc6pvlcg2ffm6w3.png)

I have uploaded a sample project to github you can get [here](https://github.com/IsrajurRahman/django-channels-sample).

##Thank you for reading!