python -m pip install -U channels["daphne"]
- Add
dephnetoINSTALLED_APPSinsettings.py
INSTALLED_APPS = (
"daphne",
...
)- Then, adjust your project’s
asgi.pyfile, e.g.myproject/asgi.py, to wrap the Django ASGI application:
import os
from channels.routing import ProtocolTypeRouter
from django.core.asgi import get_asgi_application
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'mysite.settings')
# Initialize Django ASGI application early to ensure the AppRegistry
# is populated before importing code that may import ORM models.
django_asgi_app = get_asgi_application()
application = ProtocolTypeRouter({
"http": django_asgi_app,
# Just HTTP for now. (We can add other protocols later.)
})- And finally, add
ASGI_APPLICATIONinsettings.py
ASGI_APPLICATION = "myproject.asgi.application"- Add the room view to
chat/views.py:
def room(request, room_name):
return render(request, "chat/room.html", {"room_name": room_name})- Create the route for the room view in
chat/urls.py:
urlpatterns = [
...
path("<str:room_name>/", views.room, name="room"),
]- Create a new file
chat/consumers.py:
# chat/consumers.py
import json
from channels.generic.websocket import WebsocketConsumer
class ChatConsumer(WebsocketConsumer):
def connect(self):
self.accept()
def disconnect(self, close_code):
pass
def receive(self, text_data):
text_data_json = json.loads(text_data)
message = text_data_json["message"]
self.send(text_data=json.dumps({"message": message}))- Create a new file
chat/routing.py:
# chat/routing.py
from django.urls import re_path
from . import consumers
websocket_urlpatterns = [
re_path(r"ws/chat/(?P<room_name>\w+)/$", consumers.ChatConsumer.as_asgi()),
]- The next step is to point the main ASGI configuration at the chat.routing module. In
mysite/asgi.py, importAuthMiddlewareStack,URLRouter, andchat.routing; and insert a'websocket'key in theProtocolTypeRouterlist in the following format:
# project/asgi.py
import os
from channels.auth import AuthMiddlewareStack
from channels.routing import ProtocolTypeRouter, URLRouter
from channels.security.websocket import AllowedHostsOriginValidator
from django.core.asgi import get_asgi_application
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "mysite.settings")
# Initialize Django ASGI application early to ensure the AppRegistry
# is populated before importing code that may import ORM models.
django_asgi_app = get_asgi_application()
import chat.routing
application = ProtocolTypeRouter(
{
"http": django_asgi_app,
"websocket": AllowedHostsOriginValidator(
AuthMiddlewareStack(URLRouter(chat.routing.websocket_urlpatterns))
),
}
)docker run -p 6379:6379 -d redis:5python3 -m pip install channels_redis- Before we can use a channel layer, we must configure it. Edit the
project/settings.pyfile and add aCHANNEL_LAYERSsetting to the bottom. It should look like
# project/settings.py
# Channels
ASGI_APPLICATION = "mysite.asgi.application"
CHANNEL_LAYERS = {
"default": {
"BACKEND": "channels_redis.core.RedisChannelLayer",
"CONFIG": {
"hosts": [("127.0.0.1", 6379)],
},
},
}- But iam using inmemory channel layer
CHANNEL_LAYERS = {
"default": {
"BACKEND": "channels.layers.InMemoryChannelLayer"
}
}- Now that we have a channel layer, let’s use it in
ChatConsumer. Put the following code inchat/consumers.py, replacing the old code:
# chat/consumers.py
import json
from asgiref.sync import async_to_sync
from channels.generic.websocket import WebsocketConsumer
class ChatConsumer(WebsocketConsumer):
def connect(self):
self.room_name = self.scope["url_route"]["kwargs"]["room_name"]
self.room_group_name = "chat_%s" % self.room_name
# Join room group
async_to_sync(self.channel_layer.group_add)(
self.room_group_name, self.channel_name
)
self.accept()
def disconnect(self, close_code):
# Leave room group
async_to_sync(self.channel_layer.group_discard)(
self.room_group_name, self.channel_name
)
# Receive message from WebSocket
def receive(self, text_data):
text_data_json = json.loads(text_data)
message = text_data_json["message"]
# Send message to room group
async_to_sync(self.channel_layer.group_send)(
self.room_group_name, {"type": "chat_message", "message": message}
)
# Receive message from room group
def chat_message(self, event):
message = event["message"]
# Send message to WebSocket
self.send(text_data=json.dumps({"message": message}))-
When a user posts a message, a JavaScript function will transmit the message over WebSocket to a ChatConsumer. The ChatConsumer will receive that message and forward it to the group corresponding to the room name.
-
self.scope["url_route"]["kwargs"]["room_name"]- Obtains the
'room_name'parameter from the URL route inchat/routing.pythat opened the WebSocket connection to the consumer. - بيحصل علي الباراميتر اللي اسمه
room_nameمن الراوت اللي فاتح الكونكشن
- Obtains the
-
self.room_group_name = "chat_%s" % self.room_name- Constructs a Channels group name directly from the user-specified room name, without any quoting or escaping.
- بيبني اسم للجروب بيستخدم فيه اسم الروم اللي اتحط في الURL
-
async_to_sync(self.channel_layer.group_add)(self.room_group_name, self.channel_name)- Joins a group.
- بيضيف الكونسيومر ده للجروب اللي اسمه
self.room_group_name
-
async_to_sync(self.channel_layer.group_discard)(self.room_group_name, self.channel_name)- Leaves a group.
- بيشيل الكونسيومر ده من الجروب اللي اسمه
self.room_group_name
-
async_to_sync(self.channel_layer.group_send)(self.room_group_name, {"type": "chat_message", "message": message})- Sends an event to a group.
- An event has a special
'type'key corresponding to the name of the method that should be invoked on consumers that receive the event. - بيبعت ايفنت للجروب اللي اسمه
self.room_group_name {"type": "chat_message", "message": message}: بيبعت ايفنت من نوعchat_messageو بيبعت معاه الرساله اللي اتبعتتchat_messageهو الاسم اللي هيتعرف عليه الكونسيومر اللي هيستقبل الايفنت دهmessageهو الاسم اللي هيستخدمه الكونسيومر اللي هيستقبل الايفنت ده للوصول للرساله اللي اتبعتتself.send(text_data=json.dumps({"message": message})): بيبعت الرساله اللي اتبعتت للكونسيومر اللي هيستقبل الايفنت ده
- Let’s rewrite
ChatConsumerto be asynchronous. Put the following code inchat/consumers.py:
# chat/consumers.py
import json
from channels.generic.websocket import AsyncWebsocketConsumer
class ChatConsumer(AsyncWebsocketConsumer):
async def connect(self):
self.room_name = self.scope["url_route"]["kwargs"]["room_name"]
self.room_group_name = "chat_%s" % self.room_name
# Join room group
await self.channel_layer.group_add(self.room_group_name, self.channel_name)
await self.accept()
async def disconnect(self, close_code):
# Leave room group
await self.channel_layer.group_discard(self.room_group_name, self.channel_name)
# Receive message from WebSocket
async def receive(self, text_data):
text_data_json = json.loads(text_data)
message = text_data_json["message"] # comes from the frontend
# Send message to room group
await self.channel_layer.group_send(
self.room_group_name, {"type": "chat_message", "msgGroup": message}
)
# Receive message from room group
async def chat_message(self, event):
message = event["msgGroup"]
# Send message to WebSocket
await self.send(text_data=json.dumps({"message": message}))- This new code is for ChatConsumer is very similar to the original code, with the following differences:
WebsocketConsumerhas been replaced byAsyncWebsocketConsumer.- All methods have been made
async def. awaithas been added before all calls intoself.channel_layer.async_to_synchas been removed from the import list.async_to_synchas been removed from all calls intoself.channel_layer.ChatConsumernow inherits fromAsyncWebsocketConsumerrather thanWebsocketConsumer.- All methods are
async defrather than justdef. awaitis used to call asynchronous functions that perform I/O.async_to_syncis no longer needed when calling methods on the channel layer.
- If you are writing asynchronous code, however, you will need to call database methods in a safe, synchronous context, using
database_sync_to_async. | If you wish to control the maximum number of threads used, set theASGI_THREADSenvironment variable to the maximum number you wish to allow. By default, the number of threads is set to “the number of CPUs * 5” for Python 3.7 and below, and min(32, os.cpu_count() + 4) for Python 3.8+.
channels.db.database_sync_to_asyncis a version ofasgiref.sync.sync_to_asyncthat also cleans up database connections on exit.- To use it, write your ORM queries in a separate function or method, and then call it with
database_sync_to_asynclike so:
from channels.db import database_sync_to_async
async def connect(self):
self.username = await database_sync_to_async(self.get_name)()
def get_name(self):
return User.objects.all()[0].name- You can also use it as a decorator:
from channels.db import database_sync_to_async
async def connect(self):
self.username = await self.get_name()
@database_sync_to_async
def get_name(self):
return User.objects.all()[0].name- Let’s modify
ChatConsumerto get the username from the database, Put the following code inchat/consumers.py:
# connect function
async def connect(self):
self.user = await self.get_name()
...
await self.accept()
# get_name function to get username for first user
@database_sync_to_async
def get_name(self):
return User.objects.all()[0].username
#---------------------------------------------------------------------------/
# modify receive function to send username to room group
async def receive(self, text_data):
text_data_json = json.loads(text_data)
message = text_data_json["message"]
await self.channel_layer.group_send(
self.room_group_name,
{
"type": "chat_message",
"msgGroup": message,
"username": self.user, # New
},
)
#---------------------------------------------------------------------------/
# modify chat_message function to send username to websocket
async def chat_message(self, event):
message = event["msgGroup"]
username = event["username"] # New
# add username to text_data
await self.send(
text_data=json.dumps({"message": message, "username": username})
)-
You can change the query to get the username from the database however you like. For example, you could get the username from the session, or from a cookie, or from a token in the URL.
-
Let's modify
room.htmlto display the username, Put the following code inchat/templates/chat/room.html:
chatSocket.onmessage = function(e) {
const data = JSON.parse(e.data);
document.querySelector('#chat-log').value += (data.username + ': ' +data.message + '\n');
};