Open In App

Realtime chat app using Django

Chat Room has been the most basic step toward creating real-time and live projects. The chat page that we will create will be a simple HTML boilerplate with a simple h1 text with the name of the current user and a link to log out to the user who is just logged in. You may need to comment on the line until we create auth system for this. This ensures that when two users are chatting, one can log out and it will not affect the other user. The other will be still logged in and he can send and receive messages. 

Prerequisites:

Introduction to Django Channels and Asynchronous Programming

Channels are the python project which was created to extend the ability of Django to the next level. We were working in standard Django which did not support asynchronous and channels and connection via WebSockets to create real-time applications. Channels extend the ability of Django beyond HTTP and make it work with WebSockets, chat protocols, IoT protocols, and more. It is built on ASGI support which stands for Asynchronous Server Gateway Interface. ASGI is the successor of WSGI which provides an interface between async and python. Channels provide the functionality of ASGI, by extending WSGI to it, and it provides ASGI support with WSGI. Channels also bundle the event-driven architecture with the channel layers, a system that allows you to easily communicate between processes and separate your project into different processes. 

Steps for creating the chat application

Step 1: Install and setup Django

Step 2: Create your virtual environment.

Step 3: Then create a Django project named ChatApp. For creating the project write the command in your terminal.

django-admin startproject ChatApp

Step 4: After the Creation of the Project, the folder structure should look like this, and then open your favorite IDE for working further.

 

Step 5: Install django-channels for working with the chat app. This will install channels to your environment.

python -m pip install -U channels

Note : Starting from version 4.0.0 of channels, ASGI runserver in development mode does not work anymore. You will have to install daphne as well.

python -m pip install -U daphne

After installing daphne, add daphne to your installed apps.

# code

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    
    # add django daphne
    'daphne' , 
]

Step 6: After installing channels, add channels to your installed apps. This will let Django know that channels had been introduced in the project and we can work further.

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

Step 7: Set the ASGI application to your default ASGI file in the project. Now run the server, you will notice that the ASGI server will take place over the Django server and it will support ASGI now And finally, set your ASGI_APPLICATION setting to point to that routing object as your root application:

import os

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

django_asgi_app = get_asgi_application()

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

ASGI_APPLICATION = 'ChatApp.asgi.application'

To run the server, write the following command in the terminal.

python3 manage.py runserver

Step 8: Create a new app that will have all the chat functionality. To create an app write a command in the terminal. 

python manage.py startapp chat

And add your app to the installed apps in settings.py.

Step 9: Create a few files in your chat app 

Paste this code into your ChatApp/urls.py. This will route you to your chat app.

from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    path("", include("chat.urls")),
]

Paste this code into your chat/urls.py. This will route you toward views.

from django.urls import path, include
from chat import views as chat_views
from django.contrib.auth.views import LoginView, LogoutView


urlpatterns = [
    path("", chat_views.chatPage, name="chat-page"),

    # login-section
    path("auth/login/", LoginView.as_view
         (template_name="chat/LoginPage.html"), name="login-user"),
    path("auth/logout/", LogoutView.as_view(), name="logout-user"),
]

Paste this code into your views.py. This will route your views to the chatPage.html that had been created in the templates folder of the chat app.

from django.shortcuts import render, redirect


def chatPage(request, *args, **kwargs):
    if not request.user.is_authenticated:
        return redirect("login-user")
    context = {}
    return render(request, "chat/chatPage.html", context)

The URL is in Django format, this is Django syntax to map to a URL. We will create a URL named "logout-user", then Django will map this URL name to the URL from the template.  Django provides a few pythonic syntaxes to deal with the control statement. Here we have provided {% if request.user.is_authenticated %} line in the HTML, this is given by Django which ensures that if there is any user who is logged in, then only displays the logout link. 

<!DOCTYPE html>
<html>
  <body>
    <center><h1>Hello , Welcome to my chat site ! {{request.user}}</h1></center>
    <br>
    {% if request.user.is_authenticated  %}
    <center> Logout the chat Page <a href = "{% url 'logout-user' %}">Logout</a></center>
    {% endif %}
    <div
      class="chat__item__container"
      id="id_chat_item_container"
      style="font-size: 20px"
    >
      <br />
      <input type="text" id="id_message_send_input" />
      <button type="submit" id="id_message_send_button">Send Message</button>
      <br />
      <br />
    </div>
    <script>
      const chatSocket = new WebSocket("ws://" + window.location.host + "/");
      chatSocket.onopen = function (e) {
        console.log("The connection was setup successfully !");
      };
      chatSocket.onclose = function (e) {
        console.log("Something unexpected happened !");
      };
      document.querySelector("#id_message_send_input").focus();
      document.querySelector("#id_message_send_input").onkeyup = function (e) {
        if (e.keyCode == 13) {
          document.querySelector("#id_message_send_button").click();
        }
      };
      document.querySelector("#id_message_send_button").onclick = function (e) {
        var messageInput = document.querySelector(
          "#id_message_send_input"
        ).value;
        chatSocket.send(JSON.stringify({ message: messageInput, username : "{{request.user.username}}"}));
      };
      chatSocket.onmessage = function (e) {
        const data = JSON.parse(e.data);
        var div = document.createElement("div");
        div.innerHTML = data.username + " : " + data.message;
        document.querySelector("#id_message_send_input").value = "";
        document.querySelector("#id_chat_item_container").appendChild(div);
      };
    </script>
  </body>
</html>

{{request.user.userrname}} tells the username of the currently logged-in user. If the user is logged in, it will give its username; if it's not logged in, it will print nothing. The chat page looks like this now, because there is no current logged-in user and {{request.user.username}} prints out nothing.  

<!DOCTYPE html>
<html>
<body>
    <form method ="post">
        {% csrf_token %}
        {{form.as_p}}
        <br>
        <button type = "submit">Login</button>
    </form>
</body>
</html>

Implement the WebSockets, asynchronous, and Django channels

Till now we have set up our standard Django project. After implementing the Django application. Now is the time to create the ASGI application.

Step 10: Firstly migrate your database. 

python manage.py makemigrations
python manage.py migrate

Step 11: Open routing.py and create a route for ChatConsumer (which we will be creating in the next step). Now we have two types of routings in the project. First is urls.py which is for the native Django routing of URLs, and another is for the WebSockets for ASGI support of Django. 

chat/routing.py

from django.urls import path , include
from chat.consumers import ChatConsumer

# Here, "" is routing to the URL ChatConsumer which 
# will handle the chat functionality.
websocket_urlpatterns = [
    path("" , ChatConsumer.as_asgi()) , 
] 

Step 12. Open consumers.py will handle the events, like onmessage event, onopen event, etc, We will see these events in chatPage.html where we have created the socket connection. 

Code explanation: 

chat/consumers.py

import json
from channels.generic.websocket import AsyncWebsocketConsumer

class ChatConsumer(AsyncWebsocketConsumer):
    async def connect(self):
        self.roomGroupName = "group_chat_gfg"
        await self.channel_layer.group_add(
            self.roomGroupName ,
            self.channel_name
        )
        await self.accept()
    async def disconnect(self , close_code):
        await self.channel_layer.group_discard(
            self.roomGroupName , 
            self.channel_layer 
        )
    async def receive(self, text_data):
        text_data_json = json.loads(text_data)
        message = text_data_json["message"]
        username = text_data_json["username"]
        await self.channel_layer.group_send(
            self.roomGroupName,{
                "type" : "sendMessage" ,
                "message" : message , 
                "username" : username ,
            })
    async def sendMessage(self , event) : 
        message = event["message"]
        username = event["username"]
        await self.send(text_data = json.dumps({"message":message ,"username":username}))
      

The ChatConsumer which we are mapping in the routing.py is the same in this consumers.py  which we have just created. These scripts are on asynchronous mode hence we are working with async and await here.

Step 13: Write the below code in your asgi.py for making it work with sockets and creating routings. 

ChatApp/asgi.py

import os
from django.core.asgi import get_asgi_application

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

from channels.auth import AuthMiddlewareStack
from channels.routing import ProtocolTypeRouter , URLRouter
from chat import routing

application = ProtocolTypeRouter(
    {
        "http" : get_asgi_application() , 
        "websocket" : AuthMiddlewareStack(
            URLRouter(
                routing.websocket_urlpatterns
            )    
        )
    }
)

We usually work with wsgi.py which is in the standard Django without any asynchronous support. But here we are using asynchronous channels. So we have to define the routings in a different way than URLs. For HTTP we define that use the normal application which we were already using, now we have introduced another protocol, that is ws ( WebSocket ) for which you have to route. The ProtocolTypeRouter creates routes for different types of protocols used in the application. AuthMiddlewareStack authenticates the routes and instances for the Authentication and URLRouter routes the ws ( WebSocket connections ). The protocol for WebSockets is known as "ws". For different requests we use HTTP.

Here the router routes the WebSocket URL to a variable in the chat app that is "websocket_urlpatterns" and this variable holds the routes for the WebSocket connections. 

Step 14: This code defines the channel layer in which we will be working and sharing data. For the deployment and production level, don't use InMemoryChannelLayer, because there are huge chances for your data leakage. This is not good for production. For production use the Redis channel.

ChatApp/settings.py

CHANNEL_LAYERS = {
    "default": {
        "BACKEND": "channels.layers.InMemoryChannelLayer"
    }
}

Step 15: Now, we need to create 2 users for that we will use "python manage.py createsuperuser" command which creates a superuser in the system. 

Step 16: We have set the parameter LOGIN_REDIRECT_URL = "chat-page", this is the name of our landing page URL. This means that whenever the user gets logged in, he will be sent to the chatPage as a verified user and he is eligible to chat through. Now similarly we need to set up the LOGOUT_REDIRECT_URL for the site. 

ChatApp/settings.py

Deployment

Now, run your server and move to the site and start two different browsers to log into two other users. It is because if you have logged in with first user credentials, the login details are stored in the cookies, then if you log in from second user details in the same browser even with different tabs, So, you cannot chat with two other users in the same browser, that's why to use two different browsers. 

Article Tags :