diff --git a/stude/api/routing.py b/stude/api/routing.py new file mode 100644 index 0000000..2ecbbbd --- /dev/null +++ b/stude/api/routing.py @@ -0,0 +1,8 @@ +from django.urls import re_path +from channels.routing import URLRouter +import student_status.routing +import student_status.consumers +websocket_urlpatterns = [ + re_path(r'student_status/', + URLRouter(student_status.routing.websocket_urlpatterns)) +] diff --git a/stude/config/asgi.py b/stude/config/asgi.py index 771a0e5..41094d5 100644 --- a/stude/config/asgi.py +++ b/stude/config/asgi.py @@ -1,7 +1,10 @@ import os -from channels.routing import ProtocolTypeRouter +from channels.routing import ProtocolTypeRouter, URLRouter +from channels.auth import AuthMiddlewareStack from django.core.asgi import get_asgi_application +import api.routing +from django.urls import re_path os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'config.settings') # Initialize Django ASGI application early to ensure the AppRegistry @@ -11,4 +14,9 @@ django_asgi_app = get_asgi_application() application = ProtocolTypeRouter({ "http": django_asgi_app, # Just HTTP for now. (We can add other protocols later.) + 'websocket': AuthMiddlewareStack( + URLRouter( + [re_path(r'ws/', URLRouter(api.routing.websocket_urlpatterns))] + ) + ), }) diff --git a/stude/student_status/consumers.py b/stude/student_status/consumers.py new file mode 100644 index 0000000..5c3affe --- /dev/null +++ b/stude/student_status/consumers.py @@ -0,0 +1,49 @@ +# consumers.py +import json +from .models import StudentStatus +from .serializers import StudentStatusSerializer +from djangochannelsrestframework.generics import GenericAsyncAPIConsumer +from djangochannelsrestframework.decorators import action +from djangochannelsrestframework.observer import model_observer, observer +from channels.db import database_sync_to_async +import asyncio +from djangochannelsrestframework.mixins import ( + ListModelMixin, + RetrieveModelMixin, +) + + +class StudentStatusConsumer( + ListModelMixin, + RetrieveModelMixin, + GenericAsyncAPIConsumer, +): + + queryset = StudentStatus.objects.all() + serializer_class = StudentStatusSerializer + + async def websocket_connect(self, message): + # This method is called when the websocket is handshaking as part of the connection process. + await self.accept() + self.send_updates_task = asyncio.create_task(self.send_updates()) + + async def websocket_disconnect(self, message): + # This method is called when the WebSocket closes for any reason. + # Here we want to cancel our periodic task that sends updates + self.send_updates_task.cancel() + + @database_sync_to_async + def get_student_statuses(self): + queryset = self.get_queryset() + return StudentStatusSerializer(queryset, many=True).data + + async def send_updates(self): + while True: + try: + data = await self.get_student_statuses() + print(f"Sending update: {data}") # existing debug statement + await self.send(text_data=json.dumps(data)) + await asyncio.sleep(0.5) + except Exception as e: + print(f"Exception in send_updates: {e}") + break # Break the loop on error diff --git a/stude/student_status/routing.py b/stude/student_status/routing.py new file mode 100644 index 0000000..34c10f4 --- /dev/null +++ b/stude/student_status/routing.py @@ -0,0 +1,7 @@ +# routing.py +from django.urls import re_path +from . import consumers + +websocket_urlpatterns = [ + re_path(r"active", consumers.StudentStatusConsumer.as_asgi()), +] diff --git a/stude/student_status/urls.py b/stude/student_status/urls.py index 963e0cd..45c6b5c 100644 --- a/stude/student_status/urls.py +++ b/stude/student_status/urls.py @@ -3,5 +3,5 @@ from .views import StudentStatusAPIView, ActiveStudentStatusListAPIView urlpatterns = [ path('self/', StudentStatusAPIView.as_view()), - path('active_list/', ActiveStudentStatusListAPIView.as_view()), + path('list/', ActiveStudentStatusListAPIView.as_view()), ]