Personal Chat App in Django using Channels and Websockets

In this Series of article we will learn how we can create personal chat application or whatsapp clone in django using channels and websockets.

This is the part 1 of the series, so make sure you subscribe to the news-letter to get the notification about the next part and also make sure to watch youtube tutorial for easier understanding.

What is Django-channels

Channels is a project that takes Django and extends its abilities beyond HTTP – to handle WebSockets, chat protocols, IoT protocols, and more. It’s built on a Python specification called ASGI.

With WebSockets (via Django Channels) managing the communication between the client and the server, whenever a user is authenticated, an event will be broadcasted to every other connected user. Each user’s screen will change automatically, without them having to reload their browsers.

Learn to integrate celery with django

Requirements

  1. Python3
  2. Django
  3. Django channels
  4. Redis

Objectives for this article

  1. Register and authenticate user
  2. Create base, index and chat template

Getting Started

Create and open brand new django project with name whatsapp_clone and then move into this directory and create an app name accounts and chat. Also, register these apps under installed_apps in settings.py

As for this tutorial we will register user directly from the admin page. Although, we will implement login page for the user.

create template directory, under this directory create a file name base.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>{% block title %}{% endblock %}</title>

    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css"
        integrity="sha384-9aIt2nRpC12Uk9gS9baDl411NQApFmC26EwAOH8WgZl5MYYxFfc+NcPb1dKGj7Sk" crossorigin="anonymous">

    <!-- Latest compiled and minified JavaScript -->
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/umd/popper.min.js"
        integrity="sha384-9/reFTGAW83EW2RDu2S0VKaIzap3H66lZH81PoYlFhbGU+6BZp6G7niu735Sk7lN"
        crossorigin="anonymous"></script>
    <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js"
        integrity="sha384-B4gt1jrGC7Jh4AgTPSdUtOBvfO8shuf57BaghqFfPlYxofvL8/KUEfYiJOMMV+rV"
        crossorigin="anonymous"></script>
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.css"
        integrity="sha512-5A8nwdMOWrSz20fDsjczgUidUBR8liPYU+WymTZP1lmY9G6Oc7HlZv156XqnsgNUzTyMefFTcsFH/tnJE/+xBg=="
        crossorigin="anonymous" />
    {% block css %}{% endblock %}
</head>

<body>
    {% block content %}{% endblock %}
    {% block javascript %}{% endblock %}
</body>

</html>

This file contains all the necessary links, such as bootstrap, jquery, font awesome and etc. We will extend all other template from this html file.

Index.html

Now create index.html inside template directory.

{% extends 'base.html' %}
{% load static %}

{% block title %}WhatsApp{% endblock %}

{% block css %}
<link rel="stylesheet" href="{% static 'css/index.css' %}">
{% endblock %}

{% block content %}
<div class="back-container">
    <div class="container-fluid front-container">
        <div class="back-top"></div>
        <div class="back-main"></div>
    </div>
    <div class="container front-container1">
        <div class="row">
            <div class="col-sm-4 contacts">
                <div class="contact-table-scroll">
                    <table class="table table-hover">
                        <tbody>
                            {% for user in users %}
                            <tr>
                                <td><img src="{% static 'assets/dp.png' %}" alt="" class="profile-image rounded-circle">
                                </td>
                                <td><a href="{% url 'chat' username=user.username%}">{{user.username}}</a></td>
                            </tr>
                            {% endfor %}

                            <!-- end -->
                        </tbody>
                    </table>
                </div>

            </div>
        </div>
    </div>

</div>

{% endblock %}

In this page we are rendering all users except the current user or logged in user.

Now make view to handle this index page request.

from django.shortcuts import render
from django.contrib.auth import get_user_model
from chats.models import ChatModel
# Create your views here.


User = get_user_model()


def index(request):
    users = User.objects.exclude(username=request.user.username)
    return render(request, 'index.html', context={'users': users})


def chatPage(request):
    users = User.objects.exclude(username=request.user.username)
    return render(request, 'main_chat.html', context={'users': users})

In index function we are getting all users except the current or logged in user.

Now it’s time to design chat page, also make sure to add URL for these views.

Create main_chat.html file inside templates directory.

{% extends 'base.html' %}
{% load static %}

{% block title %}WhatsApp{% endblock %}

{% block css %}
<link rel="stylesheet" href="{% static 'css/index.css' %}">
{% endblock %}

{% block content %}
<div class="back-container">
    <div class="container-fluid front-container">
        <div class="back-top"></div>
        <div class="back-main"></div>
    </div>
    <div class="container front-container1">
        <div class="row chat-top">
            <div class="col-sm-4 border-right border-secondary">
                <img src="{% static 'assets/dp.png' %}" alt="" class="profile-image rounded-circle">
                <span class="ml-2">{{request.user.username}}</span>
                <span class="float-right mt-2">
                    <svg width="1em" height="1em" viewBox="0 0 16 16" class="bi bi-circle" fill="currentColor"
                        xmlns="http://www.w3.org/2000/svg">
                        <path fill-rule="evenodd"
                            d="M8 15A7 7 0 1 0 8 1a7 7 0 0 0 0 14zm0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16z" />
                    </svg>
                    <svg width="1em" height="1em" viewBox="0 0 16 16" class="bi bi-chat-left-fill mx-3"
                        fill="currentColor" xmlns="http://www.w3.org/2000/svg">
                        <path fill-rule="evenodd"
                            d="M2 0a2 2 0 0 0-2 2v12.793a.5.5 0 0 0 .854.353l2.853-2.853A1 1 0 0 1 4.414 12H14a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2H2z" />
                    </svg>
                    <svg width="1em" height="1em" viewBox="0 0 16 16" class="bi bi-three-dots-vertical mr-2"
                        fill="currentColor" xmlns="http://www.w3.org/2000/svg">
                        <path fill-rule="evenodd"
                            d="M9.5 13a1.5 1.5 0 1 1-3 0 1.5 1.5 0 0 1 3 0zm0-5a1.5 1.5 0 1 1-3 0 1.5 1.5 0 0 1 3 0zm0-5a1.5 1.5 0 1 1-3 0 1.5 1.5 0 0 1 3 0z" />
                    </svg>
                </span>


            </div>
            <div class="col-sm-8">
                <img src="{% static 'assets/dp.png' %}" alt="" class="profile-image rounded-circle">
                <span class="ml-2">{{user.username}}</span>
                <span class="float-right mt-2">
                    <svg width="1em" height="1em" viewBox="0 0 16 16" class="bi bi-search" fill="currentColor"
                        xmlns="http://www.w3.org/2000/svg">
                        <path fill-rule="evenodd"
                            d="M10.442 10.442a1 1 0 0 1 1.415 0l3.85 3.85a1 1 0 0 1-1.414 1.415l-3.85-3.85a1 1 0 0 1 0-1.415z" />
                        <path fill-rule="evenodd"
                            d="M6.5 12a5.5 5.5 0 1 0 0-11 5.5 5.5 0 0 0 0 11zM13 6.5a6.5 6.5 0 1 1-13 0 6.5 6.5 0 0 1 13 0z" />
                    </svg>
                    <svg width="1em" height="1em" viewBox="0 0 16 16" class="bi bi-three-dots-vertical mx-3"
                        fill="currentColor" xmlns="http://www.w3.org/2000/svg">
                        <path fill-rule="evenodd"
                            d="M9.5 13a1.5 1.5 0 1 1-3 0 1.5 1.5 0 0 1 3 0zm0-5a1.5 1.5 0 1 1-3 0 1.5 1.5 0 0 1 3 0zm0-5a1.5 1.5 0 1 1-3 0 1.5 1.5 0 0 1 3 0z" />
                    </svg>
                </span>
            </div>
        </div>
        <div class="row">
            <div class="col-sm-4 contacts">
                <div class="contact-table-scroll">
                    <table class="table table-hover">
                        <tbody>
                            {% for user in users %}
                            <tr>
                                <td><img src="{% static 'assets/dp.png' %}" alt="" class="profile-image rounded-circle">
                                </td>
                                <td><a href="{% url 'chat' username=user.username %}">{{user.username}}</a></td>
                            </tr>
                            {% endfor %}

                            <!-- end -->
                        </tbody>
                    </table>
                </div>

            </div>
            <div class="col-sm-8 message-area">
                <div class="message-table-scroll">
                    <table class="table">
                        <tbody id='chat-body'>
                            {% for message in messages %}
                            {% if message.sender == request.user.username %}
                            <tr>
                                <td>
                                    <p class="bg-success p-2 mt-2 mr-5 shadow-sm text-white float-right rounded">
                                        {{message.message}}
                                    </p>
                                </td>
                                <td>
                                    <p><small class="p-1 shadow-sm">{{message.timestamp|time:'H:i'}}</small>
                                    </p>
                                </td>
                            </tr>
                            {% else %}
                            <tr>
                                <td>
                                    <p class="bg-primary p-2 mt-2 mr-5 shadow-sm text-white float-left rounded">
                                        {{message.message}}
                                    </p>
                                </td>
                                <td>
                                    <p><small class="p-1 shadow-sm">{{message.timestamp|time:'H:i'}}</small>
                                    </p>
                                </td>
                            </tr>
                            {% endif %}
                            {% endfor %}
                        </tbody>
                    </table>
                </div>
                <div class="row message-box p-3">
                    <div class="col-sm-2 mt-2">
                        <svg width="1em" height="1em" viewBox="0 0 16 16" class="bi bi-emoji-smile" fill="currentColor"
                            xmlns="http://www.w3.org/2000/svg">
                            <path fill-rule="evenodd"
                                d="M8 15A7 7 0 1 0 8 1a7 7 0 0 0 0 14zm0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16z" />
                            <path fill-rule="evenodd"
                                d="M4.285 9.567a.5.5 0 0 1 .683.183A3.498 3.498 0 0 0 8 11.5a3.498 3.498 0 0 0 3.032-1.75.5.5 0 1 1 .866.5A4.498 4.498 0 0 1 8 12.5a4.498 4.498 0 0 1-3.898-2.25.5.5 0 0 1 .183-.683z" />
                            <path
                                d="M7 6.5C7 7.328 6.552 8 6 8s-1-.672-1-1.5S5.448 5 6 5s1 .672 1 1.5zm4 0c0 .828-.448 1.5-1 1.5s-1-.672-1-1.5S9.448 5 10 5s1 .672 1 1.5z" />
                        </svg>
                        <svg width="1em" height="1em" viewBox="0 0 16 16" class="bi bi-paperclip mx-2"
                            fill="currentColor" xmlns="http://www.w3.org/2000/svg">
                            <path fill-rule="evenodd"
                                d="M4.5 3a2.5 2.5 0 0 1 5 0v9a1.5 1.5 0 0 1-3 0V5a.5.5 0 0 1 1 0v7a.5.5 0 0 0 1 0V3a1.5 1.5 0 1 0-3 0v9a2.5 2.5 0 0 0 5 0V5a.5.5 0 0 1 1 0v7a3.5 3.5 0 1 1-7 0V3z" />
                        </svg>
                        <svg width="1em" height="1em" viewBox="0 0 16 16" class="bi bi-cash" fill="currentColor"
                            xmlns="http://www.w3.org/2000/svg">
                            <path fill-rule="evenodd"
                                d="M15 4H1v8h14V4zM1 3a1 1 0 0 0-1 1v8a1 1 0 0 0 1 1h14a1 1 0 0 0 1-1V4a1 1 0 0 0-1-1H1z" />
                            <path
                                d="M13 4a2 2 0 0 0 2 2V4h-2zM3 4a2 2 0 0 1-2 2V4h2zm10 8a2 2 0 0 1 2-2v2h-2zM3 12a2 2 0 0 0-2-2v2h2zm7-4a2 2 0 1 1-4 0 2 2 0 0 1 4 0z" />
                        </svg>
                    </div>
                    <div class="col-sm-8">
                        <input type="text" class="form-control" id="message_input" placeholder="Write message...">

                    </div>
                    <div class="col-sm-2 mt-1">
                        <div class="control">
                            <button class="btn btn-success" id="chat-message-submit">Submit</button>
                        </div>
                    </div>

                </div>
            </div>
        </div>
    </div>

</div>
{{user.id|json_script:"json-username"}}
{{request.user.username|json_script:"json-message-username"}}
{% endblock %}

For detailed video, watch video lecture.

Styling the chat page

Configure settings for serving static file in django(Follow for Detailed Video Tutorial) and then create a static directory and inside static folder create folder named CSS and inside CSS folder create index.css

.back-top {
  background-color: #009688;
  height: 15vh;
}

.back-main {
  background-color: #d9dbd5;
  height: 85vh;
}

.back-container {
  position: relative;
}

.front-container {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
}
.front-container1 {
  position: absolute;
  top: 20px;
  left: 0;
  right: 0;
}

.chat-top {
  background-color: #ededed;
  min-height: 50px;
}

.profile-image {
  margin-top: 7px;
  height: 30px;
  width: 30px;
}

.contacts {
  background-color: #ffffff;
  min-height: 600px;
}

.contact-table-scroll {
  overflow-y: scroll;
  height: 600px;
}

.message-area {
  background-color: #e4e1de;
  min-height: 600px;
}

.message-table-scroll {
  overflow-y: scroll;
  height: 550px;
}

.message-box {
  background-color: #f0f0f0;
}

Make Sure to link this file to main_chat.html

That’s it for this article.

Don’t want to read article, consider watching youtube video tutorial

Follow and subscribe to newsletter for more and next part of tutorial.

Advertisement

This Post Has 2 Comments

  1. lakshit

    Amazing post, but where is the next part?
    You wrote about youtube videos, but I can’t find any.

    1. pydjango

      Thanks for the comment Lakshit, next part of the video series and article will be published, probably today or tomorrow

Leave a Reply