diff --git a/docs/changes.rst b/docs/changes.rst index 667251b4f..04e9c8bd3 100644 --- a/docs/changes.rst +++ b/docs/changes.rst @@ -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) ------------------ diff --git a/example/asgi.py b/example/asgi.py index 7c5c501f6..90df0854c 100644 --- a/example/asgi.py +++ b/example/asgi.py @@ -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)) + ), + } +) diff --git a/example/async_/consumers.py b/example/async_/consumers.py new file mode 100644 index 000000000..04d0af6d3 --- /dev/null +++ b/example/async_/consumers.py @@ -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})) diff --git a/example/async_/routing.py b/example/async_/routing.py new file mode 100644 index 000000000..ddb7724f6 --- /dev/null +++ b/example/async_/routing.py @@ -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\w+)/$", consumers.ChatConsumer.as_asgi()), +] diff --git a/example/async_/settings.py b/example/async_/settings.py index f3bef673a..96eedaf6c 100644 --- a/example/async_/settings.py +++ b/example/async_/settings.py @@ -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)], + }, + } +} diff --git a/example/async_/urls.py b/example/async_/urls.py index ad19cbc83..7711ab305 100644 --- a/example/async_/urls.py +++ b/example/async_/urls.py @@ -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//", async_chat_room, name="room"), *sync_urlpatterns, ] diff --git a/example/async_/views.py b/example/async_/views.py index 7326e0d0b..142b646af 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,11 @@ 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") + + +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..1fe6aa79a 100644 --- a/example/settings.py +++ b/example/settings.py @@ -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"}} diff --git a/example/templates/chat/index.html b/example/templates/chat/index.html new file mode 100644 index 000000000..1996352a0 --- /dev/null +++ b/example/templates/chat/index.html @@ -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 %} + + + + + + + Chat Rooms + + + What chat room would you like to enter?
+
+ + + + + diff --git a/example/templates/chat/room.html b/example/templates/chat/room.html new file mode 100644 index 000000000..d2621cd0c --- /dev/null +++ b/example/templates/chat/room.html @@ -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 %} + + + + + + Chat Room + + +
+
+ + {{ room_name|json_script:"room-name" }} + + + diff --git a/example/templates/index.html b/example/templates/index.html index a10c2b5ac..cf1b233ec 100644 --- a/example/templates/index.html +++ b/example/templates/index.html @@ -26,6 +26,7 @@

Index of Tests

{% comment %} +
  • Chat app
  • {% endcomment %} diff --git a/requirements_dev.txt b/requirements_dev.txt index 90e490192..577398dc6 100644 --- a/requirements_dev.txt +++ b/requirements_dev.txt @@ -7,6 +7,8 @@ Jinja2 # Django Async daphne whitenoise # To avoid dealing with static files +channels +channels_redis # Testing