import json import logging from asgiref.sync import async_to_sync from channels.generic.websocket import AsyncJsonWebsocketConsumer from channels.db import database_sync_to_async from university.models import UserOnlineExam, UserOnlineExamMessage, User logger = logging.getLogger(__name__) class OnlineExamConsumer(AsyncJsonWebsocketConsumer): async def connect(self): self.room_group_name = None await self.accept() self.exam_slug = self.scope['url_route']['kwargs']['exam_slug'] self.user = self.scope['user'] if not self.user.is_authenticated: await self.send_json({ 'error': 'User is not authenticated' }) await self.close() return user_id, online_exam_id = await self.get_user_online_exam_id() if not online_exam_id: await self.send_json({ 'error': 'Exam not found' }) await self.close() return self.room_group_name = 'online_exam-{}-{}'.format(user_id, online_exam_id) await self.channel_layer.group_add( self.room_group_name, self.channel_name ) async def disconnect(self, close_code): if self.room_group_name: await self.channel_layer.group_discard( self.room_group_name, self.channel_name ) async def receive(self, text_data=None, bytes_data=None, **kwargs): if text_data: try: content = await self.decode_json(text_data) except Exception: await self.send_json({ 'error': 'Invalid input, required json' }) return if not isinstance(content, dict): await self.send_json({ 'error': 'Invalid input' }) return await self.receive_json(content, **kwargs) else: raise ValueError("No text section for incoming WebSocket frame!") # Receive message from WebSocket async def receive_json(self, content): event_type = content['event'] if event_type == 'ping': await self.send_json({ 'event': 'pong', 'sequence_id': content.get('sequence_id') }) elif event_type == 'ack': await self.ack_event(content.get('sequence_id')) else: pass async def forward_event(self, event): await self.send_json(event) @database_sync_to_async def get_user_online_exam_id(self): try: user_online_exam = UserOnlineExam.objects.get( user=self.user, online_exam__slug=self.exam_slug) return user_online_exam.user.id, user_online_exam.online_exam.id except Exception: return None, None @database_sync_to_async def ack_event(self, message_id): try: message = UserOnlineExamMessage.objects.get( user=self.user, online_exam__slug=self.exam_slug, pk=message_id) message.ack = True message.save() except UserOnlineExamMessage.DoesNotExist: pass