1. Introduction to Django Channels
Django Channels extends Django to handle asynchronous protocols like WebSockets, enabling real-time features in your applications. It allows you to build applications that can handle long-lived connections, such as chat applications or live notifications.
2. Installing Django Channels
To get started with Django Channels, you need to install it. You can do this using pip:
pip install channels[daphne]
3. Setting Up Channels in Django
After installing Channels, you need to add setup channel to your Django project.
3.1 Update your settings.py
file:
INSTALLED_APPS = [
...
'daphne', # Add Daphne for ASGI server
]
This will install the Daphne’s ASGI version of the runserver management command.
You can also add “channels” for Channel’s runworker command.
3.2 Create an asgi.py
file in your project directory:
myproject/
├── myapp/
├── myproject/
│ ├── __init__.py
│ ├── settings.py
│ ├── urls.py
│ ├── asgi.py # Create this file
│ └── wsgi.py
asgi.py
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, set your ASGI_APPLICATION setting to point to that routing object as your root application:
settings.py
ASGI_APPLICATION = 'myproject.asgi.application'
4. Routing in Django Channels
Django Channels uses a routing system similar to Django’s URL routing. You define a routing configuration that maps WebSocket URLs to consumers.
4.1 Create a routing.py
file in your app directory:
myapp/
├── routing.py # Create this file
routing.py
from django.urls import path
from . import consumers
websocket_urlpatterns = [
path('ws/some_path/', consumers.MyConsumer.as_asgi()), # Replace 'some_path' with your WebSocket path
# Add more WebSocket paths as needed
path('ws/another_path/', consumers.AnotherConsumer.as_asgi()), # Example of another WebSocket path
# You can add more paths as needed
path('ws/chat/<str:room_name>/', consumers.ChatConsumer.as_asgi()), # Example for chat rooms
]
4.2 Update your asgi.py
file to include the routing:
from channels.routing import ProtocolTypeRouter, URLRouter
import os
from django.core.asgi import get_asgi_application
from myapp.routing import websocket_urlpatterns # Import your routing file
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'myproject.settings')
django_asgi_app = get_asgi_application()
application = ProtocolTypeRouter({
"http": django_asgi_app,
"websocket": URLRouter(
websocket_urlpatterns # Use the WebSocket URL patterns defined in your routing file
),
})
5. Creating Consumers
5.1 What is a Consumer?
A consumer is a Python class that handles WebSocket connections. It defines methods to handle events like connecting, disconnecting, and receiving messages.
5.2 Creating a Simple Consumer
Consmer is of different types like AsyncConsumer, SyncConsumer, WebsocketConsumer, etc. But we use generic WebsocketConsumer for most of the cases.for now we will be using AsyncJsonWebsocketConsumer
which is a subclass of AsyncWebsocketConsumer
that handles JSON messages.
5.2.1 Method of AsyncJsonWebsocketConsumer
connect()
: Called when a WebSocket connection is established.
async def connect(self):
# This method is called when the WebSocket connection is established.
# You can perform actions like authentication or joining a group here.
await self.accept() # Accept the WebSocket connection
accept()
: Accepts the WebSocket connection.disconnect()
: Called when the WebSocket connection is closed.
async def disconnect(self, close_code):
# This method is called when the WebSocket connection is closed.
# You can perform cleanup actions here.
pass # No specific action needed for now
receive_json()
: Called when a JSON message is received from the WebSocket.
async def receive_json(self, content):
# This method is called when a JSON message is received from the WebSocket.
# You can process the received message here.
print("Received message:", content) # Print the received message
await self.send_json({"message": "Message received!"}) # Send a response back to the client
send_json()
: Sends a JSON message back to the WebSocket client.close()
: Closes the WebSocket connection.
5.3 Example of a Simple Consumer
from channels.generic.websocket import AsyncJsonWebsocketConsumer
class MyConsumer(AsyncJsonWebsocketConsumer):
async def connect(self):
await self.accept() # Accept the WebSocket connection
#await self.close() # Close the connection immediately for demonstration purposes
async def disconnect(self, close_code):
pass # No specific action needed for now
async def receive_json(self, content):
print("Received message:", content) # Print the received message
await self.send_json({"message": "Message received!"}) # Send a response back to the client
5.4 Channel Layers
Normally,We without channels layer we can communicate between the server and the client but for communication between different instance we need to use channel layers. Channel layers allow you to send messages between different instances of your application, enabling features like chat rooms .
Where channel layers are used:
- TO send message between different tab of the same browser
- TO make chat application
Were channel layers are not used:
- For sending message between server and client
Key termenology:
- Group: It is not appropriate to send message to individual user so in order to send message to multiple user we use group. It is like a room where multiple user can join and send message to each other.
- message: It is the data that we send to the group.Message must be dict
5.5 Setting Up Channel Layers
We use Redis as the channel layer backend.
for that we need to install the channels_redis
package:
pip install channels_redis
To setup the channel layer, add the following configuration to your settings.py
file:
CHANNEL_LAYERS = {
"default": {
"BACKEND": "channels_redis.core.RedisChannelLayer",
"CONFIG": {
"hosts": [("127.0.0.1", 6379)],
},
},
}
5.6 Key Methods for Channel Layers
get_channel_layer()
: This method retrives the default channel layer instance.
from channels.layers import get_channel_layer
channel_layer = get_channel_layer()
channel_layer
: This attribute retrives the default channel layer instance.channel_name
: This attribute retrives the name of the channel that will help to reach the consumer.
The following requires channel layer then we can use the following methods to send and receive messages:
send(channel_name, message)
: Sends a message to the specified channel.group_send(group_name, message)
: Sends a message to a group of channels.group_add(group_name, channel_name)
: Adds a channel to a group.group_discard(group_name, channel_name)
: Removes a channel from a group.
5.7. Some of the example of how to use is:
from channels.generic.websocket import AsyncJsonWebsocketConsumer
class ChatConsumer(AsyncJsonWebsocketConsumer):
async def functionName(self):
print(self.channel_name) # The name of the channel that will help to reach the consumer
print(self.channel_layer) # The default channel layer instance
5.8 Simple Chat Consumer Example for single room
from channels.generic.websocket import AsyncJsonWebsocketConsumer
class ChatConsumer(AsyncJsonWebsocketConsumer):
async def connect(self):
self.room_name= "my_room" # Replace with your room name
self.room_group_name = f"chat_{self.room_name}" # Create a unique group name for the room
# Join the room group
await self.channel_layer.group_add(
self.room_group_name,
self.channel_name
)
await self.channel_layer.group_send( # Send a message to the group when a user joins
self.room_group_name,
{
'type': 'chat.message',
'message': f'{self.channel_name} has joined the room.'
}
)
await self.send_json({ # Send a welcome message to the user
'message': f'You have joined the room: {self.room_name}'
})
await self.accept() # Accept the WebSocket connection
async def disconnect(self, close_code):
# Leave the room group
await self.channel_layer.group_discard( # Remove the user from the room group
self.room_group_name,
self.channel_name
)
await self.channel_layer.group_send( # Send a message to the group when a user leaves
self.room_group_name,
{
'type': 'chat.message',
'message': f'{self.channel_name} has left the room.'
}
)
async def receive_json(self, content):
message = content.get('message', '')
if message:
# Send the received message to the room group
await self.channel_layer.group_send(
self.room_group_name,
{
'type': 'chat.message',
'message': message
}
)
async def chat_message(self, event):
message = event['message']
# Send the message to the WebSocket
await self.send_json({
'message': message
})
5.9 Getting The group name dynamically
#routing.py
from django.urls import path
from . import consumers
websocket_urlpatterns = [
path('ws/chat/<str:room_name>/', consumers.ChatConsumer.as_asgi()), # Use a dynamic room name
]
5.10 Example of a Chat Consumer with Dynamic Room Name
from channels.generic.websocket import AsyncJsonWebsocketConsumer
class ChatConsumer(AsyncJsonWebsocketConsumer):
async def connect(self):
self.room_name = self.scope['url_route']['kwargs']['room_name'] # Get the room name from the URL
self.room_group_name = f"chat_{self.room_name}" # Create a unique group name for the room
# Join the room group
await self.channel_layer.group_add(
self.room_group_name,
self.channel_name
)
await self.accept() # Accept the WebSocket connection
async def disconnect(self, close_code):
# Leave the room group
await self.channel_layer.group_discard(
self.room_group_name,
self.channel_name
)
async def receive_json(self, content):
message = content.get('message', '')
if message:
# Send the received message to the room group
await self.channel_layer.group_send(
self.room_group_name,
{
'type': 'chat.message',
'message': message
}
)
async def chat_message(self, event):
message = event['message']
# Send the message to the WebSocket
await self.send_json({
'message': message
})
How it works:
- When a user connects to the WebSocket, the
connect
method retrieves the room name from the URL and creates a unique group name for that room. for example: When user connect tows/chat/room1/
, theroom_name
will beroom1
and theroom_group_name
will bechat_room1
. - The user is then added to the room group using
group_add
Whena a user disconnects, the disconnect
method removes the user from the room group using group_discard
.
- When a user sends a message, the
receive_json
method sends that message to the room group usinggroup_send
.
note:
self.scope['url_route']['kwargs']['room_name']
is used to get the room name from the URL. This allows you to create dynamic chat rooms based on the URL path.
6. Database Integration with Django Channels
Since We are using AsyncJsonWebsocketConsumer, we use database_sync_to_async
to interact with the database in an asynchronous manner. This decorator allows you to call synchronous Django ORM methods from an asynchronous context.
To Import it we use
from channels.db import database_sync_to_async
To use it
await database_sync_to_async(query)()
Example
from channels.generic.websocket import AsyncJsonWebsocketConsumer
from channels.db import database_sync_to_async
from .models import Message # Import your Django model
class ChatConsumer(AsyncJsonWebsocketConsumer):
async def receive_json(self, content):
message = content.get('message', '')
if message:
# Save the message to the database
await database_sync_to_async(Message.objects.create(content=message, room_name=self.room_name))()
# Send the received message to the room group
await self.channel_layer.group_send(
self.room_group_name,
{
'type': 'chat.message',
'message': message
}
)
7.Authentication in Django Channels
Authentication in Django Channels is similar to Django’s authentication system, but it requires some additional steps to ensure that the user is authenticated before allowing them to connect to a WebSocket.
7.1 Middleware for Authentication
There is a middleware called AuthMiddlewareStack
that can be used to handle authentication in Django Channels. It wraps the ASGI application and adds the user to the scope.
7.2 Setting Up Authentication Middleware
In your asgi.py
file, you can add the AuthMiddlewareStack
to your application:
from channels.auth import AuthMiddlewareStack
from channels.routing import ProtocolTypeRouter, URLRouter
from django.core.asgi import get_asgi_application
from myapp.routing import websocket_urlpatterns # Import your routing file
import os
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'myproject.settings')
django_asgi_app = get_asgi_application()
application = ProtocolTypeRouter({
"http": django_asgi_app,
"websocket": AuthMiddlewareStack( # Use AuthMiddlewareStack for WebSocket connections
URLRouter(
websocket_urlpatterns # Use the WebSocket URL patterns defined in your routing file
)
),
})
7.3 Accessing the User in the Consumer
In your consumer, you can access the authenticated user from the scope
:
from channels.generic.websocket import AsyncJsonWebsocketConsumer
class ChatConsumer(AsyncJsonWebsocketConsumer):
async def connect(self):
self.user = self.scope['user'] # Access the authenticated user
if self.user.is_authenticated:
self.room_name = self.scope['url_route']['kwargs']['room_name']
self.room_group_name = f"chat_{self.room_name}"
await self.channel_layer.group_add(
self.room_group_name,
self.channel_name
)
await self.accept()
else:
await self.close() # Close the connection if the user is not authenticated
8. Session in Django Channels
In Django Channels, you can access the session data in your consumers. The session data is stored in the scope
of the WebSocket connection, allowing you to retrieve and manipulate session data just like you would in a regular Django view.
for this you need to use SessionMiddlewareStack
in your asgi.py
file:
from channels.sessions import SessionMiddlewareStack
from channels.routing import ProtocolTypeRouter, URLRouter
from django.core.asgi import get_asgi_application
import os
from myapp.routing import websocket_urlpatterns # Import your routing file
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'myproject.settings')
django_asgi_app = get_asgi_application()
application = ProtocolTypeRouter({
"http": django_asgi_app,
"websocket": SessionMiddlewareStack( # Use SessionMiddlewareStack for WebSocket connections
URLRouter(
websocket_urlpatterns # Use the WebSocket URL patterns defined in your routing file
)
),
})
8.1 Accessing Session Data in the Consumer
In your consumer, you can access the session data from the scope
:
from channels.generic.websocket import AsyncJsonWebsocketConsumer
class ChatConsumer(AsyncJsonWebsocketConsumer):
async def connect(self):
self.session = self.scope['session'] # Access the session data
10. Send data to specific user
To send data from one user to specific user can use send
method of the channel layer and channel_name
of the user.
self.channel_layer.send(channel_name,message)
10.1 Example of Sending Data to a Specific User
from channels.generic.websocket import AsyncJsonWebsocketConsumer
class NotificationConsumer(AsyncJsonWebsocketConsumer):
async def connect(self):
await self.accept() # Accept the WebSocket connection
await self.channel_layer.send(
channel_name, # The channel name of the specific user
{
'message': 'You have a new notification!'
}
)
11. Sending Data from outside the consumer
To send data from outside the consumer, you can use the channel_layer
to send messages to a specific channel or group. This is useful for sending notifications or updates to users without them having to be connected to a WebSocket.
11.1 Example of Sending Data from Outside the Consumer
from channels.layers import get_channel_layer
from asgiref.sync import async_to_sync
class NotificationService:
def __init__(self):
self.channel_layer = get_channel_layer() # Get the channel layer instance
def send_notification(self, channel_name, message):
async_to_sync(self.channel_layer.send)(channel_name, {
'type': 'notification.message', # Custom type for handling the message
'message': message
})
11.2 Example of Handling the Notification in the Consumer
from channels.generic.websocket import AsyncJsonWebsocketConsumer
class NotificationConsumer(AsyncJsonWebsocketConsumer):
async def connect(self):
await self.accept() # Accept the WebSocket connection
async def disconnect(self, close_code):
pass # Handle disconnection if needed
async def notification_message(self, event):
message = event['message'] # Get the message from the event
await self.send_json({'message': message}) # Send the message to the WebSocket client