Back
Featured image of post Simple Django Websocket Realtime Chat

Simple Django Websocket Realtime Chat

The Case

If you’re looking at adding real-time capabilities to a Django project with WebSocket, you have two main options.

Using Django Channels, a project adding WebSocket to Django, among other features. This approach is fully supported by Django. However, it requires switching to a new deployment architecture.

Deploying a separate WebSocket server next to your Django project. This technique is well suited when you need to add a small set of real-time features — maybe a notification service — to a HTTP application.

so we can build real-time app using ASGI service and webSocket then integrate to Django, to integrate with django, we need to install some package call Channel

Create Django Project

to create django project we need to setup virtualenv

pip install virtualenv

Creating Virtualenv

to create using this command

virtualenv venv

then activate

venv\Scripts\activate

if on linux or mac

source venv/bin/activate

Installing Required Package using pip

The next step is install required package using pip

pip install django channels

Create Django Project using Django admin

django-admin startproject core

and rename core directory to backend

| backend
|
├───chat
│   ├───migrations
│   │   └───__pycache__
│   ├───templates
│   │   └───chat
│   └───__pycache__
└───core
    ├───settings
    │   └───__pycache__
    └───__pycache__

and then create the chat app using manage.py

python manage.py startapp chat

Configure the apps

in installed app append channels and chat like this

# settings/base.py
"""
Django settings for core project.

Generated by 'django-admin startproject' using Django 4.0.1.

For more information on this file, see
https://docs.djangoproject.com/en/4.0/topics/settings/

For the full list of settings and their values, see
https://docs.djangoproject.com/en/4.0/ref/settings/
"""
import os
from pathlib import Path

# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent.parent


# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/4.0/howto/deployment/checklist/

# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = 'django-insecure-$x4(6tpf$&@+kv+_=^*4vh0cp1xjzygx@xp6rb&@db^29c_xo#'

# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True

ALLOWED_HOSTS = []


# Application definition

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',

    # Third Party
    'channels',
]

ASGI_APPLICATION = 'core.asgi.application'

# CHANNEL LAYER DEV ONLY
CHANNEL_LAYERS = {
    'default': {
        'BACKEND': 'channels.layers.InMemoryChannelLayer'
    }
}

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

ROOT_URLCONF = 'core.urls'

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [
            os.path.join(BASE_DIR, 'templates')
            ],
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]

WSGI_APPLICATION = 'core.wsgi.application'


# Database
# https://docs.djangoproject.com/en/4.0/ref/settings/#databases

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': BASE_DIR / 'db.sqlite3',
    }
}


# Password validation
# https://docs.djangoproject.com/en/4.0/ref/settings/#auth-password-validators

AUTH_PASSWORD_VALIDATORS = [
    {
        'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
    },
]


# Internationalization
# https://docs.djangoproject.com/en/4.0/topics/i18n/

LANGUAGE_CODE = 'en-us'

TIME_ZONE = 'UTC'

USE_I18N = True

USE_TZ = True


# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/4.0/howto/static-files/

STATIC_URL = 'static/'

# Default primary key field type
# https://docs.djangoproject.com/en/4.0/ref/settings/#default-auto-field

DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'


here is a development settings

# settings/development.py
from .base import *

INSTALLED_APPS += [
    'chat'
]


DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': BASE_DIR / 'db.sqlite3',
    }
}

Create Route URL Patterns

to create url pattern create file named routing.py and define websocket route

# chat/routing.py

from django.urls import re_path
from . import consumers

websocket_urlpatterns = [
    re_path(r'ws/socket-server/', consumers.ChatConsumer.as_asgi())
]

Configure ASGI

the next step is configure ASGI to establish connection to server

# core/asgi.py

"""
ASGI config for core project.

It exposes the ASGI callable as a module-level variable named ``application``.

For more information on this file, see
https://docs.djangoproject.com/en/4.0/howto/deployment/asgi/
"""

import os

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

import chat.routing

os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'core.settings.development')

# configure asgi for chat
application = ProtocolTypeRouter(
    {
        'http': get_asgi_application(),
        'websocket': AuthMiddlewareStack(
            URLRouter(
                chat.routing.websocket_urlpatterns
            )
        ),
    }
)

Create Customers

to communicate with each other we need to configure connection, it call customers.py

# chat/customers.py

from email import message
import json
from asgiref.sync import async_to_sync
from channels.generic.websocket import WebsocketConsumer

class ChatConsumer(WebsocketConsumer):
    def connect(self):
        self.room_group_name = 'test' 
        async_to_sync(self.channel_layer.group_add)(
            self.room_group_name,
            self.channel_name
        )

        self.accept()
       
    
    def receive(self, text_data):
        text_data_json = json.loads(text_data)
        message = text_data_json['message']
        
        async_to_sync(self.channel_layer.group_send)(
            self.room_group_name,
            {
                'type': 'chat_message',
                'message': message
            }
        )

    def chat_message(self, event):
        message = event['message']

        self.send(text_data=json.dumps(
            {
                'type': 'chat',
                'message': message 
            }
        ))

Create Views to Rendering Templates

and then we need to configure route for rendering html so creating views.py inside chat folder

# chat/views.py

from django.shortcuts import render

# Create your views here.

def lobby(request):
    return render(request, 'chat/lobby.html')

Configure URLPattern to Mapping Views

The next step is configure routing

from django.urls import path
from . import views

urlpatterns = [
    path('', views.lobby, name='lobby')
]

and add to core/urls.py

"""core URL Configuration

The `urlpatterns` list routes URLs to views. For more information please see:
    https://docs.djangoproject.com/en/4.0/topics/http/urls/
Examples:
Function views
    1. Add an import:  from my_app import views
    2. Add a URL to urlpatterns:  path('', views.home, name='home')
Class-based views
    1. Add an import:  from other_app.views import Home
    2. Add a URL to urlpatterns:  path('', Home.as_view(), name='home')
Including another URLconf
    1. Import the include() function: from django.urls import include, path
    2. Add a URL to urlpatterns:  path('blog/', include('blog.urls'))
"""
from django.contrib import admin
from django.urls import path, include

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

Creating HTML Template To Pass the webSocket Connection

to add Template create html file call lobby.html inside templates/chat/ directory

<!-- chat/templates/chat/lobby.html -->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Chat Lobby</title>
</head>
<body>
    <h1>Lobby</h1>

    <form action="" id="form">
        <input type="text" name="message" id="">
    </form>

    <div id="messages"></div>

    <!-- web socket connection -->
    <script type="text/javascript">
        let url = `ws://${window.location.host}/ws/socket-server/`

        const chatSocket = new WebSocket(url);
        chatSocket.onmessage = function(e) {
            let data = JSON.parse(e.data);
            console.log('Data: ', data);
            
            // validate data from backend 
            if (data.type === 'chat') {
                let messages = document.getElementById('messages')

                messages.insertAdjacentHTML('beforeend', `<div><p>${data.message}</p></div>`)
            }
        }

        // form handling
        let form = document.getElementById('form')
        form.addEventListener('submit', (e)=>{
            e.preventDefault()
            let message = e.target.message.value
            chatSocket.send(JSON.stringify(
                {
                    'message': message
                }
            ))
            form.reset()
        })
    </script>

</body>
</html>

Test The APP

run django project with this command

python manage.py runserver

Application Testing
Application Testing

Conclusion

With web Socket we make web appication can handle real-time event, Source Code Available on Github

comments powered by Disqus
Built with Hugo
Theme Stack designed by Jimmy