Nowadays, when all sorts of chat rooms have become extremely popular, when every second large company has launched or developed its own instant messenger, when an increase of smiles and change in the text size is considered as innovation, in the era of iMessages, Slack, Hipchat, Messager, Google Allo, Zulip, etc. I will tell you how to keep up with the trend and write your own chat, using
We will take multichat as an example. This article can be considered as step-by-step instructions on creating such a chat.
This package allows our application to interact with a user not only using HTTP 1.1 (request-response), but also using HTTP/2 and WebSocket.
WebSocket is designed for exchanging messages between the client and the web server in real time. You should consider it as an open channel between the client and the server, with the ability to subscribe to the events sent to it.
Let's define the main functions, which our chat app will have.
User authorization will be done on the basis of the built-in django.contrib.auth. Django-channels support cookies-based user authentication, this is what we need.
Separate chat rooms with the possibility of adding, deleting and editing via standard Django admin control panel.
Message delivery in real time, which we will do using django-channels and websocket.
We will use python 3.5.x, but python 2.7 is also supported.
- Hey, wait a minute! Why do we need Redis? We came here to read about websockets. - The typical reader.
- Django-channels are not only about websockets. - Anonymous
Django-channels consist of three components:
Interface servers- which handle requests (WSGI and ASGI) and put them in a queue.
Channels- is a first-
in first-out queuefor messages that need to be stored in data structures such as
IPCetc. The message can be delivered only to one listener, message delivery order depends only on its getting into the queue.
Workers- monitor channels and handle requests.
For more detailed information, see documentation;
Note that such architecture can scale horizontally the app. You can vary the number of processes for each component and run them on separate servers.
django-channels so that it will use the above-mentioned redis as the channel storage.
We will add the following lines to
We will create
multichat/routing.py (next to
settings.py) and add a basic message handler. For example we will display text of a received message in the console. The file will look like this:
Now let’s run the Django web server ./
manage.py runserver, as well as open the interactive session interpreter in a parallel window:
First of all, let’s join our websocket,
django-channels runs it by default on the same the url, but the protocol
wss is used depending on whether encryption is used or not, so in our case the address of the connection is
ws://localhost:8000. We are connecting:
If we take a look at our web server logs, we'll see something like this:
So it means we have successfully connected. Now, let’s try to send a message:
As we can see, the message has been duplicated in our server log:
Let’s create a template for our future application:
Now let’s add it to the
We will expand it later.
Let’s work with a homepage, we will create a template
templates/index.html for it:
But so that we could get to it, let's add a corresponding view in the
chat/views.py, here's what you should get in the end:
Now let's add it to the
We have a
login we use the built-in view from contrib.auth:
django.contrib.auth.views.login view uses a template
registration/login.html, which we have not set yet. We’ll create
templates/registration/login.html with the following content:
Now, to enable the user to get on the home page after logging in, we need to add the following line in the
LOGIN_REDIRECT_URL = "/"
The same for logout:
LOGOUT_REDIRECT_URL = "/"
That’s all. Let's go to the main page and verify our authorization, enter your user data which was created earlier:
Let's talk about rooms, each room can have its own name, also it would be nice to be able to create separate rooms where only superusers can enter. Let’s add a ‘Room’ class to
Create and run the migrations:
It would be nice to provide the possibility of adding them, for this we can certainly create a separate page, provide access rights etc., but it is easier to just use django admin control panel. Add the following line in the
Voila, now we can create, edit and delete via django standard admin control panel.
Let’s add displaying of registered rooms list on our home page, for this we add a queryset with rooms to the home page context:
Then we can display them in the
templates/index.html (do not forget to envisage the absence of rooms, in this case you can simply ask the user to add them with reference to django):
The handlers may include such events as: a user connection or disconnection from the websocket, data sending to the websocket. We also need to keep track of when the user logs into the chat, disconnects from it and sends a message.
When a user connects to the websocket, first of all we have to identify it. In this case there is a decorator
channel_session_user_from_http in the
django-channels arsenal, but how does it work? This decorator takes a user from http session and inserts it into the
channel-base session (which uses the django standard
SESSION_ENGINE documentation), after which it will be available in the
We must also take care of creating a
rooms for the user. It is necessary to be able to subscribe the user to the events in a particular room.
To do this, we’ll create a
chat/consumers.py file and declare
ws_connect function in it:
When a user disconnects from the websocket, we need to clean up his opening session. To identify the user, we’ll need the
channel_session_user which adds user instance to the received message, based on the user ID from the
channel session. Unlike
channel_session_user_from_http, it turns on channel session implicitly.
In order to provide a possibility to send a message to all users who are in a particular room,
django-channels provides an excellent opportunity to group
channels (more details here).
Let’s add the models
websocket_group, which will return a unique
channels.Group for each room through id:
Now let’s add the following lines into
For comfortable work we will send the data in the form of
json, so when you receive a message, we will parse
json, and send the received data into the
Let’s add the following lines into
One of the important features of our app is the ability to enter a specific chat room, but what will happen if an unauthorized user tries to do it? and what if the requested room does not exist? or if the user has no rights to enter this room at all? That's right, an error will occur! And in the first two cases, it will clearly be thrown at the system level, and in the third case the user will likely get to the secret room. To prevent such a trouble from happening, you need to add error handling.
chat/exceptions.py file and declare
ClientError class in it:
In the future, if any error occurs, when using this class we will notify the client-side. Now let's create
chat/utils.py and declare decorators needed for error handling there.
Since we want to notify users about something, let's add some gradation of messages by a level of importance. To do this, let's create a
chat/settings.py, where we define the types of messages:
Also, we will need a simple way to deliver a message of any content to all users connected to this room. To do this, let’s add the
send_message method the
Room class, after that it will be as follows:
Add the following lines into
Let’s add the following lines into
Let’s add the following lines to
After previous actions our
chat/consumers.py should look as follows:
chat/routing.py and add declared signals there:
And now we’ll add routing that we have created in the main
multichat/routing.py, after which it will look as follows
We have written a lot of things on the backend, but where is the chat itself? So, let's create a front end part.
Firstly, we’ll create
static/main.js, where we will be coding. Let’s connect it, also we will need
jquery and any library to reconnect to websocket. In this example we will use
ReconnectingWebSocket. We open our
templates/index.html and append the following lines:
And also do not forget to create several rooms, I'm sure that if you are reading this article, then you know how to use Django admin panel for sure :)
Now it is possible to code a chat, as we know, only the authorized user can get into this page, so we will not make any further checks in this regard (but if you decide to use this code in production, the additional check on the front end will be useful).
Why do we use
ReconnectingWebSocket? Everything is very simple. With this library,
WebSocket will be automatically re-connected after calling the
onclose event. I just want to note that reconnection does not start immediately, but with a small delay that can be adjusted by transmitting value in milliseconds to the designer of the
reconnectInterval class. If you want, you can specify
timeoutInterval. But will it work if we lose the connection? I don’t think so. :)
Let’s connect to our socket:
Stop, stop, stop. This code will be difficult to debug, let's display in the event console, such as the attempt to connect, the client is successfully connected and disconnected from the socket.
WebSocket provides the ability to define onopen and onclose methods, which will be called when the connection is successful, or when there is disconnection from the server. As a result, we get something like this:
Let's open the console, reload the page and see what we've got. If you see the following lines, then life is good:
It's time to add the possibility to connect and disconnect from a particular room. We do not want to bother with page making, this article has purely training nature, so we will add the necessary elements directly from js. Is it good or bad? Within this training material - it is acceptable, especially when reading these lines, you understand that we are sorry for what we’ve done.
And so, as the
WebSocket provides duplex communication, we can send data to a server,
socket.send method is used for it. This method accepts any string and sends it through the
WebSocket to the server. As you can remember, our message handler expects to receive
JSON - let’s not disappoint it, we will wrap a dictionary with the data in
Let’s add the following code to the
Let's take a look at our Code carefully and think what we are lack of. There is no processing of responses from the server. It will help us in the
onmessage event, whenever any kind of information is received, the
socket.onmessage method will be called.
As a parameter it receives a message, and as you can remember, we expect to get valid
json which we have to parse. Also, we should take into account the processing errors from the server, in this training material we will just show an error as an
alert for a user. So, let’s add the following code:
You have the right to fully control the data returned in the response, see
Let's check what we've got. Make sure that the console is open, refresh the page and try clicking once on the room button to connect to it and disconnect from it. Everything is fine, if you see the following lines in the console:
Our work with the chat proceeds, there is not much left to do - we will add the possibility to send and receive messages. We’ll begin with sending of course.
As you can see in the previous chapter, when we get the information from the server about the successful connection to the room, we add a form with an input field of the message text and a button to send it in our template. Now let's hung the
submit event handler on it, with which we will send the text input field entered in WebSocket. Add the following code:
I think that if you've read up to this point, then the code seems rather obvious and there is no need in further explanation.
We need to display messages received from users who are with us in the same room. Let’s renew our
static/main.js will look like this:
In it we are trying to define a user’s message, and depending on the type, display the message.
Well, that's all, our chat is ready and it even can be used. :)
All code shown in this tutorial material was taken from here, it has only minor cosmetic changes.
Also you can find it in our repository.
django-channels framework is designed to simplify the developer's life, bringing the
WebSockets and HTTP2 support in the world of simple and ordinary HTTP 1.1. Using this library allows to standardize the communication with
WebSocket, and standardization of our work is very important. Among other things, this library allows horizontal scaling, as well as does not create any problems associated with asynchrony.
In my current project I had a task to use twitter API. Twitter uses OAuth for authentication, which is pretty dreary. To avoid fiddling with it all the time, I've moved authentication to decorator. If key is available - nothing happens, just view is launched as usual. It's convenient that there's no need for additional twitter settings in user profile. Code is in article.
When dealing with one of our projects (LookSMI media monitoring platform) we have to handle the huge volume of data – and its quantity is constantly growing. At the same time, we must run quick searches with smart rules. In this article I'll explain how we have achieved required performance.
Earlier in our previous article "Improve efficiency of your SELECT queries" we discussed ways to profile and optimize the performance of SELECT queries. However, to write complex yet efficient SQL queries, there is a thing to remember about.