working along django channels official tutorial and creating a chat service with chat groups
requirements.txt
django==2.1
channels==2.1.3
asgi_redis==1.4.3
cmd
> pip install django
> pip install channels
> pip install asgi_redis
Launch ubuntu1804 using the command ubuntu1804
Ubuntu1804
> ubuntu1804
$ sudo apt-get install redis-server
$ redis-server
Ubuntu1804 is the ubuntu terminal and some features of ubuntu from the ubuntu app donwloaded from play store, follow the instructions given in the description of the app
Once redis-server is installed you don't need to start it everytime, if an error comes on
running the command redis-server
that means server is already running and you can
restart the server using
cmd
> redis-cli shutdown
> redis-server
Start fresh django project mysite inside virtualenv or outside of it as we discussed in django_channels github repo
cmd
> django-admin startproject mysite
> cd mysite
> python manage.py startapp chat
Start a new app named chat and add it in your installed apps settings
Add a index.html file in your templates folder
Using javascript/jquery we are going to open the url for the room name entered in the input
templates/chat/index.html
<!-- SCRIPTS -->
<script type="text/javascript" charset="utf-8" async defer>
var roomName = $("#room-name-input").focus();
$("#room-name-input").keyup(function(e) {
if (e.keyCode === 13) {
$("#room-name-submit").click();
}
});
$("#room-name-submit").click(function(e) {
var roomName = $("#room-name-input").val();
window.location.pathname = '/chat/' + roomName + '/';
})
</script>
We have used jquery but you can use plain javascript. Using the .val() function from jquery
we fetch the value of the input form with id room-name-input and then using javascript
window.location.pathname
we enter the url for room name
Now we have to create a view and url for that view in our chat app
chat/views.py
from django.shortcuts import render
def index(request):
return render(request, 'chat/index.html', {})
Create new file in chat app
chat/urls.py
from django.urls import path
from .views import index
urlpatterns = [
path('', index, name='index'),
]
include the chat app urls in main project urlconf
mysite/urls.py
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
path('chat/', include('chat.urls')),
]
Now let's check
mysite
> python manage.py runserver
Enter in anything and press enter or just hit the button and you will be taken to the new url 127.0.0.1/chat/.
In the section we are going to integrate our simple non-functional django app with channels library and make our chat rooms functional
We will be creating routes for channels just like we create urls in urlconf for our django project, routes work in the same way, it tells the channels what to run when an HTTP request is received by channels server.
Create a new file for routing inside mysite folder where our settings.py file lies
mysite/routing.py
from channels.routing import ProtocolTypeRouter
application = ProtocolTypeRouter({
# (http->django views is added by default)
})
We don't need to add our views they are added by default as the comment says
Now we need to tell our django project about channels by including library inside our project installed apps settings
mysite/settings.py
INSTALLED_APPS = [
'channels',
'chat',
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
]
Now for pointing channels to root configuration, edit the mysite/settings.py file and add
ASGI_APPLICATION
variable with value a path for our routing file
ASGI_APPLICATION = 'mysite.routing.application'
mysite is the folder, routing is the routing.py file we just created and application is what we added in routing file.
Run the server again and you will see that channels has overtaken our default django server.
> python manage.py runserver
Performing system checks...
System check identified no issues (0 silenced).
September 01, 2018 - 10:24:20
Django version 2.0.7, using settings 'mysite.settings'
Starting ASGI/Channels version 2.1.3 development server at http://127.0.0.1:8000/
Quit the server with CTRL-BREAK.
2018-09-01 10:24:20,773 - INFO - server - HTTP/2 support not enabled (install the http2 and tls Twisted extras)
2018-09-01 10:24:20,773 - INFO - server - Configuring endpoint tcp:port=8000:interface=127.0.0.1
2018-09-01 10:24:20,773 - INFO - server - Listening on TCP address 127.0.0.1:8000
Locate to the same url 127.0.0.1/chat and you should see the same page again, in your console you should see that channels is working finely with our project
In this section we will be creating a new view for room where users can chat with each other while in the same room.
Create a new template for this view
templates/chat/room.html
{% block script %}
<script type="text/javascript" charset="utf-8" async defer>
var roomName = {{ room_name_json }};
var chatSocket = new Websocket(
'ws://' + window.location.host + 'ws/chat/' + roomName + '/'
);
chatSocket.onmessage = function(e) {
var data = JSON.parse(e.data);
var message = data['message'];
$("#chat-log").val() += (message +'\n');
};
chatSocket.onclose = function(e) {
console.log("chat socket closed unexpectedly");
};
$("#chat-message-input").focus();
$("#chat-message-input").keyup(function(e) {
if (e.keyCode === 13) {
$("#chat-message-submit").click();
}
});
$("#chat-message-submit").click(function(e) {
var $messageInput = $("#chat-message-input");
var message = $messageInput.val();
chatSocket.send(JSON.stringify({
'message': message
}));
$messageInput.val('');
});
</script>
{% endblock %}
We send the context variable room_name_json from our room_view inside chat/views.py
import json
from django.shortcuts import render
from django.utils.safestring import mark_safe
def room_view(request, room_name):
return render(request, 'chat/room.html', {
'room_name_json': mark_safe(json.dumps(room_name))
})
-
If chatSocket received a message then we parse the json data and store the message from it in a js variable and add the message in the textarea.
-
If we close the browser then we print a message on the console.
-
If user pressed enter we click the send button.
-
If the send button is pressed then we fetch the entered message from the input and send message from the WebSocket instance and make the input field empty.
After our view is ready we create a url for our view.
chat/urls.py
...
path('<room_name>/', room_view, name='room-view'),
NOTE: '/' should be added after <room_name> in the url for our room_view
Now we will create a basic consumer that accepts WebSocket connections on the path
ws/chat/room_name/
that takes any message it receives on the WebSocket and
echoes it back to the same WebSocket.
NOTE: We have used a ws/ prefix for sending the web socket because using it we can distinguish between the WebSocket connections from ordinary HTTP connections
Create a new file inside the chat app consumers.py
. Inside that file we simply
create synchronous WebSocket consumer that accepts all connections, receives messages
from its client, and echos those messages back to the same client. For now it does not
broadcast messages to other clients in the same room.
We need to create a routing configuration for tha chat app that has a route to the consumer. Create a new file inside chat app routing.py
chat/routing.py
from django.urls import path
from .consumers import ChatConsumer
websocket_urlpatterns = [
path('ws/chat/<room_name>/', ChatConsumer),
]
The next step is to point the root routing configuration at the chat.routing module.
mysite/routing.py
import chat.routing
from channels.auth import AuthMiddlewareStack
from channels.routing import ProtocolTypeRouter, URLRouter
application = ProtocolTypeRouter({
'websocket': AuthMiddlewareStack(
URLRouter(
chat.routing.websocket_urlpatterns
)
)
})
What above piece of code does is, we create a new key named 'websocket' in the ProtocolTypeRouter, any connection is made to the channels server it is inspected by ProtocolTypeRouter and if it is a WebSocket connection like ws:// or wss:// then it is passed onto AuthMiddlewareStack.
AuthMiddlewareStack provides the connection with a reference to the currently
authenticated user just like the django's AuthenticationMiddleware does with the
request object through which we can use the request variables such as request.user
.
AuthMiddlewareStack hands over the connection to the URLRouter which simply routes the HTTP path to a particular consumer based on the provided url patterns just like django does with its urls and then urls routes to particular views.
Run migrations to apply database changes (Django’s session framework needs the database) and then start the Channels development server:
Open http://127.0.0.1:8000/chat/lobby/ and enter any message in the input field and you should see the message echoed back to you from the channels server.
When we enter a message, that message is echoed back to us and appears in chat log but if open a new browser window with the same chat room then the message we have sent till now don't appear in the new window, so for that we are going to create a channel layer for multiple instances of channels.
What is channel layer ?
A channel layer is a form of communication system which allows multiple consumer instances to talk to each other and other django parts.
What will a channel layer do ?
-
A channel is like a mailbox where everyone who has the name of the channel can send messages to the channel.
-
A group is a group of related channels. Anyone who has the name of the group can add or remove the channels from group or send messages to all channels in the group.
In our chat application we want to have multiple instances of ChatConsumer in the same room communicate with each other. To do that we will have each ChatConsumer add its channel to a group whose name is based on the room name. That will allow ChatConsumers to transmit messages to all other ChatConsumers in the same room.
We will use a channel layer that uses redis as its backing store. To start a redis server on port 6379, run the following command
> redis-server
We need to install channels_redis so that channels know how to interface with redis
> pip install channels_redis
Before we can use a channel layer we need to configure it. Edit the
mysite/settings.py file and add CHANNELS_LAYER
setting to the bottom
# mysite/settings.py
# Channels
ASGI_APPLICATION = 'mysite.routing.application'
CHANNEL_LAYERS = {
'default': {
'BACKEND': 'channels_redis.core.RedisChannelLayer',
'CONFIG': {
"hosts": [('127.0.0.1', 6379)],
},
},
}
For checking if channel layer can communicate with redis. Start a django shell and run
.../mysite> python manage.py shell
>>> import channels.layers
>>> channel_layer = channels.layers.get_channel_layer()
>>> from asgiref.sync import async_to_sync
>>> async_to_sync(channel_layer.send)('test_channel', {'type': 'hello'})
>>> async_to_sync(channel_layer.receive)('test_channel')
{'type': 'hello'}
Type Control-D to exit the Django shell.