From 5877c462dce75465cc6c52b16ede4547fea55dd1 Mon Sep 17 00:00:00 2001
From: Chiemezuo
Date: Wed, 8 Oct 2025 23:14:29 +0100
Subject: [PATCH 1/5] Added async chat app structure
---
example/asgi.py | 11 ++++++++++-
example/async_/urls.py | 1 +
example/async_/views.py | 5 +++++
example/templates/chat/index.html | 27 +++++++++++++++++++++++++++
example/templates/index.html | 1 +
example/urls.py | 2 ++
requirements_dev.txt | 1 +
7 files changed, 47 insertions(+), 1 deletion(-)
create mode 100644 example/templates/chat/index.html
diff --git a/example/asgi.py b/example/asgi.py
index 7c5c501f6..8f0fc2876 100644
--- a/example/asgi.py
+++ b/example/asgi.py
@@ -9,8 +9,17 @@
import os
+from channels.routing import ProtocolTypeRouter
from django.core.asgi import get_asgi_application
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,
+ # Just HTTP for now. (We can add other protocols later.)
+ }
+)
diff --git a/example/async_/urls.py b/example/async_/urls.py
index ad19cbc83..bc32f2386 100644
--- a/example/async_/urls.py
+++ b/example/async_/urls.py
@@ -6,4 +6,5 @@
urlpatterns = [
path("async/db/", views.async_db_view, name="async_db_view"),
*sync_urlpatterns,
+ path("async/chat", views.async_chat_index, name="chat"),
]
diff --git a/example/async_/views.py b/example/async_/views.py
index 7326e0d0b..ddb48f57f 100644
--- a/example/async_/views.py
+++ b/example/async_/views.py
@@ -1,5 +1,6 @@
from django.contrib.auth.models import User
from django.http import JsonResponse
+from django.shortcuts import render
async def async_db_view(request):
@@ -7,3 +8,7 @@ async def async_db_view(request):
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")
diff --git a/example/templates/chat/index.html b/example/templates/chat/index.html
new file mode 100644
index 000000000..d78899eae
--- /dev/null
+++ b/example/templates/chat/index.html
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+ Chat Rooms
+
+
+ What chat room would you like to enter?
+
+
+
+
+
+
diff --git a/example/templates/index.html b/example/templates/index.html
index a10c2b5ac..3753516a8 100644
--- a/example/templates/index.html
+++ b/example/templates/index.html
@@ -16,6 +16,7 @@ Index of Tests
Hotwire Turbo
htmx
Bad form
+ Chat app
Django Admin
{% endcache %}
diff --git a/example/urls.py b/example/urls.py
index 86e6827fc..f07885c68 100644
--- a/example/urls.py
+++ b/example/urls.py
@@ -1,3 +1,4 @@
+from async_.views import async_chat_index
from django.contrib import admin
from django.urls import path
from django.views.generic import TemplateView
@@ -20,6 +21,7 @@
),
path("jinja/", jinja2_view, name="jinja"),
path("async/", async_home, name="async_home"),
+ path("async/chat/", async_chat_index, name="async_chat"),
path("async/db/", async_db, name="async_db"),
path("async/db-concurrent/", async_db_concurrent, name="async_db_concurrent"),
path("jquery/", TemplateView.as_view(template_name="jquery/index.html")),
diff --git a/requirements_dev.txt b/requirements_dev.txt
index 90e490192..4e8544469 100644
--- a/requirements_dev.txt
+++ b/requirements_dev.txt
@@ -7,6 +7,7 @@ Jinja2
# Django Async
daphne
whitenoise # To avoid dealing with static files
+channels
# Testing
From b7c088384780a9718d61f6e94a2b6aa2f784ee44 Mon Sep 17 00:00:00 2001
From: Chiemezuo
Date: Tue, 14 Oct 2025 10:36:29 +0100
Subject: [PATCH 2/5] implement Django channels chat server
---
example/asgi.py | 10 +++++--
example/async_/consumers.py | 41 ++++++++++++++++++++++++++
example/async_/routing.py | 8 +++++
example/async_/urls.py | 1 -
example/async_/views.py | 4 +++
example/settings.py | 8 +++++
example/templates/chat/index.html | 2 +-
example/templates/chat/room.html | 49 +++++++++++++++++++++++++++++++
example/urls.py | 3 +-
requirements_dev.txt | 1 +
10 files changed, 122 insertions(+), 5 deletions(-)
create mode 100644 example/async_/consumers.py
create mode 100644 example/async_/routing.py
create mode 100644 example/templates/chat/room.html
diff --git a/example/asgi.py b/example/asgi.py
index 8f0fc2876..90df0854c 100644
--- a/example/asgi.py
+++ b/example/asgi.py
@@ -9,9 +9,13 @@
import os
-from channels.routing import ProtocolTypeRouter
+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.
@@ -20,6 +24,8 @@
application = ProtocolTypeRouter(
{
"http": django_asgi_app,
- # Just HTTP for now. (We can add other protocols later.)
+ "websocket": AllowedHostsOriginValidator(
+ AuthMiddlewareStack(URLRouter(websocket_urlpatterns))
+ ),
}
)
diff --git a/example/async_/consumers.py b/example/async_/consumers.py
new file mode 100644
index 000000000..313bf279f
--- /dev/null
+++ b/example/async_/consumers.py
@@ -0,0 +1,41 @@
+# This is the consumer logic for the Django Channels "Web Socket" chat app
+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 = f"chat_{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}))
diff --git a/example/async_/routing.py b/example/async_/routing.py
new file mode 100644
index 000000000..3175e503c
--- /dev/null
+++ b/example/async_/routing.py
@@ -0,0 +1,8 @@
+# This is the routing logic for the Django Channels "Web Socket" chat app
+from django.urls import re_path
+
+from . import consumers
+
+websocket_urlpatterns = [
+ re_path(r"ws/chat/(?P\w+)/$", consumers.ChatConsumer.as_asgi()),
+]
diff --git a/example/async_/urls.py b/example/async_/urls.py
index bc32f2386..ad19cbc83 100644
--- a/example/async_/urls.py
+++ b/example/async_/urls.py
@@ -6,5 +6,4 @@
urlpatterns = [
path("async/db/", views.async_db_view, name="async_db_view"),
*sync_urlpatterns,
- path("async/chat", views.async_chat_index, name="chat"),
]
diff --git a/example/async_/views.py b/example/async_/views.py
index ddb48f57f..142b646af 100644
--- a/example/async_/views.py
+++ b/example/async_/views.py
@@ -12,3 +12,7 @@ async def async_db_view(request):
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})
diff --git a/example/settings.py b/example/settings.py
index ffaa09fe5..1bf25db61 100644
--- a/example/settings.py
+++ b/example/settings.py
@@ -70,6 +70,14 @@
WSGI_APPLICATION = "example.wsgi.application"
ASGI_APPLICATION = "example.asgi.application"
+CHANNEL_LAYERS = {
+ "default": {
+ "BACKEND": "channels_redis.core.RedisChannelLayer",
+ "CONFIG": {
+ "hosts": [("127.0.0.1", 6379)],
+ },
+ }
+}
# Cache and database
diff --git a/example/templates/chat/index.html b/example/templates/chat/index.html
index d78899eae..6d65ca3f0 100644
--- a/example/templates/chat/index.html
+++ b/example/templates/chat/index.html
@@ -20,7 +20,7 @@
}
document.querySelector('#room-name-submit').onclick = function(e) {
var roomName = document.querySelector('#room-name-input').value;
- window.location.pathname = '/chat/' + roomName + '/';
+ window.location.pathname = 'async/chat/' + roomName + '/';
}
+
+
+
+ {{ room_name|json_script:"room-name" }}
+
+
diff --git a/example/templates/chat/room.html b/example/templates/chat/room.html
new file mode 100644
index 000000000..0065467b7
--- /dev/null
+++ b/example/templates/chat/room.html
@@ -0,0 +1,49 @@
+
+
+
+