Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/changes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ Pending
* Updated logic that forces values to strings (``force_str``) to render
"Django Debug Toolbar was unable to parse value." when there's a decoding
error.
* Added Django Channels chat app to the example project.

6.0.0 (2025-07-22)
------------------
Expand Down
17 changes: 16 additions & 1 deletion example/asgi.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,23 @@

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

from .async_.routing import websocket_urlpatterns

os.environ.setdefault("DJANGO_SETTINGS_MODULE", "example.async_.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 = get_asgi_application()
application = ProtocolTypeRouter(
{
"http": django_asgi_app,
"websocket": AllowedHostsOriginValidator(
AuthMiddlewareStack(URLRouter(websocket_urlpatterns))
),
Comment on lines +27 to +29
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is why the toolbar doesn't work with the channels' consumers. When you send a chat message via the websocket, that message isn't visible in the toolbar anywhere. Only the HTTP requests are. It bypasses the middleware that the toolbar works with.

To start, we would need to define a another middleware that is a subclass of channels.middleware.BaseMiddleware. There may be other issues that arise after that. However, I think this is enough for us to declare the toolbar doesn't support Django Channels' consumers yet 😢

}
)
37 changes: 37 additions & 0 deletions example/async_/consumers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# This is the consumer logic for the Django Channels "Web Socket" chat app
# https://channels.readthedocs.io/en/latest/tutorial/index.html
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 = f"chat_{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"]

# Send message to room group
await self.channel_layer.group_send(
self.room_group_name, {"type": "chat.message", "message": message}
)

# Receive message from room group
async def chat_message(self, event):
message = event["message"]

# Send message to WebSocket
await self.send(text_data=json.dumps({"message": message}))
9 changes: 9 additions & 0 deletions example/async_/routing.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# This is the routing logic for the Django Channels "Web Socket" chat app
# https://channels.readthedocs.io/en/latest/tutorial/index.html
from django.urls import re_path

from . import consumers

websocket_urlpatterns = [
re_path(r"ws/chat/(?P<room_name>\w+)/$", consumers.ChatConsumer.as_asgi()),
]
9 changes: 9 additions & 0 deletions example/async_/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,12 @@
from ..settings import * # noqa: F403

ROOT_URLCONF = "example.async_.urls"

CHANNEL_LAYERS = {
"default": {
"BACKEND": "channels_redis.core.RedisChannelLayer",
"CONFIG": {
"hosts": [("127.0.0.1", 6379)],
},
}
}
4 changes: 4 additions & 0 deletions example/async_/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,11 @@
from example.async_ import views
from example.urls import urlpatterns as sync_urlpatterns

from .views import async_chat_index, async_chat_room

urlpatterns = [
path("async/db/", views.async_db_view, name="async_db_view"),
path("async/chat/", async_chat_index, name="async_chat"),
path("async/chat/<str:room_name>/", async_chat_room, name="room"),
*sync_urlpatterns,
]
9 changes: 9 additions & 0 deletions example/async_/views.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,18 @@
from django.contrib.auth.models import User
from django.http import JsonResponse
from django.shortcuts import render


async def async_db_view(request):
names = []
async for user in User.objects.all():
names.append(user.username)
return JsonResponse({"names": names})


def async_chat_index(request):
return render(request, "chat/index.html")


def async_chat_room(request, room_name):
return render(request, "chat/room.html", {"room_name": room_name})
1 change: 0 additions & 1 deletion example/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,6 @@
WSGI_APPLICATION = "example.wsgi.application"
ASGI_APPLICATION = "example.asgi.application"


# Cache and database

CACHES = {"default": {"BACKEND": "django.core.cache.backends.dummy.DummyCache"}}
Expand Down
31 changes: 31 additions & 0 deletions example/templates/chat/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
{% comment %}
This template is from the Django Channels "Web Socket" chat app
https://channels.readthedocs.io/en/latest/tutorial/index.html
{% endcomment %}

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Chat Rooms</title>
</head>
<body>
What chat room would you like to enter?<br>
<input type="text" id="room-name-input" size="100"><br>
<input type="button" id="room-name-submit" value="Enter">

<script>
document.querySelector('#room-name-input').focus();
document.querySelector('#room-name-input').onkeyup = function(e) {
if (e.key === 'Enter') { // enter, return
document.querySelector('#room-name-submit').click()
}
}
document.querySelector('#room-name-submit').onclick = function(e) {
var roomName = document.querySelector('#room-name-input').value;
window.location.pathname = 'async/chat/' + roomName + '/';
}
</script>
</body>
</html>
53 changes: 53 additions & 0 deletions example/templates/chat/room.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
{% comment %}
This template is from the Django Channels "Web Socket" chat app
https://channels.readthedocs.io/en/latest/tutorial/index.html
{% endcomment %}

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Chat Room</title>
</head>
<body>
<textarea id="chat-log" cols="100" rows="20"></textarea><br>
<input type="text" id="chat-message-input" size="100"><br>
<input type="button" id="chat-message-submit" value="Send">
{{ room_name|json_script:"room-name" }}
<script>
const roomName = JSON.parse(document.getElementById('room-name').textContent);

const chatSocket = new WebSocket(
'ws://'
+ window.location.host
+ '/ws/chat/'
+ roomName
+ '/'
);

chatSocket.onmessage = function(e) {
const data = JSON.parse(e.data);
document.querySelector('#chat-log').value += (data.message + '\n');
};

chatSocket.onclose = function(e) {
console.error('Chat socket closed unexpectedly');
}
document.querySelector('#chat-message-input').focus();
document.querySelector('#chat-message-input').onkeyup = function(e) {
if (e.key === 'Enter') { // enter, return
document.querySelector('#chat-message-submit').click()
}
};

document.querySelector('#chat-message-submit').onclick = function(e) {
const messageInputDom = document.querySelector('#chat-message-input');
const message = messageInputDom.value;
chatSocket.send(JSON.stringify({
'message': message
}));
messageInputDom.value = '';
}
</script>
</body>
</html>
1 change: 1 addition & 0 deletions example/templates/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ <h1>Index of Tests</h1>
<button id="incrementXHR" data-url="{% url 'ajax_increment' %}" type="button">Increment via XHR</button>
</p>
{% comment %}
<li><a href="{% url 'async_chat' %}">Chat app</a></li>
<button id="asyncRequest" data-url="{% url 'async_db_view' %}" type="button">Make async call</button>
{% endcomment %}

Expand Down
2 changes: 2 additions & 0 deletions requirements_dev.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ Jinja2
# Django Async
daphne
whitenoise # To avoid dealing with static files
channels
channels_redis

# Testing

Expand Down