from channels.generic.websocket import AsyncJsonWebsocketConsumer from channels.db import database_sync_to_async from django.core.exceptions import PermissionDenied from boxofficefantasy.models import League, Season from boxofficefantasy.views import parse_season_slug from draft.models import DraftSession, DraftSessionParticipant import asyncio from django.contrib.auth.models import User from draft.constants import ( DraftMessage, DraftPhase, DraftGroupChannelNames, ) from draft.state import DraftStateManager from typing import Any import logging logger = logging.getLogger(__name__) # __name__ = module path import random class DraftConsumerBase(AsyncJsonWebsocketConsumer): group_names: DraftGroupChannelNames draft_state: DraftStateManager user: User async def connect(self): draft_hashid = self.scope["url_route"]["kwargs"].get("draft_session_id_hashed") self.draft_session = await self.get_draft_session( draft_session_id_hashed=draft_hashid, ) self.draft_participants = await self.get_draft_participants( session=self.draft_session ) self.group_names = DraftGroupChannelNames(draft_hashid) self.draft_state = DraftStateManager(self.draft_session) self.user = self.scope["user"] if not self.should_accept_user(): await self.channel_layer.send( self.channel_name, { "type": "direct.message", "subtype": DraftMessage.PARTICIPANT_JOIN_REJECT, "payload": {"current_user": self.user.username}, }, ) await self.close() await self.channel_layer.group_send( self.group_names.admin, { "type": "broadcast.admin", "subtype": DraftMessage.PARTICIPANT_JOIN_REJECT, "payload": {"user": self.user.username}, }, ) return else: await self.accept() await self.channel_layer.group_add( self.group_names.session, self.channel_name ) await self.channel_layer.group_send( self.group_names.session, { "type": "broadcast.session", "subtype": DraftMessage.USER_JOIN_INFORM, "payload": {"user": self.user.username}, }, ) await self.channel_layer.send( self.channel_name, { "type": "direct.message", "subtype": DraftMessage.STATUS_SYNC_INFORM, "payload": { **self.draft_state, "user": self.user.username, "participants": [ user.username for user in self.draft_participants ], }, }, ) await self.channel_layer.send( self.channel_name, { "type": "direct.message", "subtype": DraftMessage.USER_IDENTIFICATION_INFORM, "payload": {"user": self.user.username}, }, ) async def should_accept_user(self) -> bool: return self.user.is_authenticated async def receive_json(self, content): logger.info(f"receiving message {content}") event_type = content.get("type") if event_type == DraftMessage.STATUS_SYNC_REQUEST: await self.send_json( { "type": DraftMessage.STATUS_SYNC_INFORM, "payload": self.get_draft_status(), } ) # Broadcast Handlers async def direct_message(self, event): await self._dispatch_broadcast(event) async def broadcast_session(self, event): await self._dispatch_broadcast(event) async def _dispatch_broadcast(self, event): logger.info(f"dispatching message {event}") subtype = event.get("subtype") payload = event.get("payload", {}) await self.send_json({"type": subtype, "payload": payload}) # === Methods === def get_draft_status(self) -> dict[str, Any]: return # === DB Access === @database_sync_to_async def get_draft_session(self, draft_session_id_hashed) -> DraftSession: draft_session_id = DraftSession.decode_id(draft_session_id_hashed) if draft_session_id: draft_session = DraftSession.objects.select_related( "season", "season__league", "settings", ).prefetch_related("participants").get(pk=draft_session_id) else: raise Exception() return draft_session @database_sync_to_async def get_draft_participants(self, session) -> list[DraftSessionParticipant]: participants = session.participants.all() return list(participants.all()) class DraftAdminConsumer(DraftConsumerBase): async def connect(self): await super().connect() if not self.user.is_staff: await self.close() return await self.channel_layer.group_add(self.group_names.admin, self.channel_name) async def receive_json(self, content): await super().receive_json(content) logger.info(f"Receive message {content}") event_type = content.get("type") if ( event_type == DraftMessage.PHASE_CHANGE_REQUEST and content.get("destination") == DraftPhase.DETERMINE_ORDER ): await self.determine_draft_order() if ( event_type == DraftMessage.PHASE_CHANGE_REQUEST and content.get("destination") == DraftPhase.NOMINATING ): await self.start_nominate() if event_type == DraftMessage.DRAFT_INDEX_ADVANCE_REQUEST: self.draft_state.draft_index_advance() await self.channel_layer.group_send( self.group_names.session, { "type": "broadcast.session", "subtype": DraftMessage.DRAFT_INDEX_ADVANCE_CONFIRM, "payload": {**self.draft_state}, }, ) if event_type == DraftMessage.NOMINATION_SUBMIT_REQUEST: movie_id = content.get("payload", {}).get("movie_id") user = content.get("payload", {}).get("user") self.draft_state.start_nomination(movie_id) await self.channel_layer.group_send( self.group_names.session, { "type": "broadcast.session", "subtype": DraftMessage.NOMINATION_CONFIRM, "payload": { "current_movie": self.draft_state[ "current_movie" ], "nominating_participant": user, }, }, ) if event_type == DraftMessage.BID_START_REQUEST: self.draft_state.start_bidding() await self.channel_layer.group_send( self.group_names.session, { "type": "broadcast.session", "subtype": DraftMessage.BID_START_INFORM, "payload": {**self.draft_state}, }, ) def should_accept_user(self): return super().should_accept_user() and self.user.is_staff # === Draft logic === async def start_nominate(self): await self.set_draft_phase(DraftPhase.NOMINATING) await self.channel_layer.group_send( self.group_names.session, { "type": "broadcast.session", "subtype": DraftMessage.PHASE_CHANGE_CONFIRM, "payload": {"phase": self.draft_state.phase}, }, ) async def determine_draft_order(self): self.draft_state.determine_draft_order() next_picks = self.draft_state.next_picks(include_current=True) await self.channel_layer.group_send( self.group_names.session, { "type": "broadcast.session", "subtype": DraftMessage.ORDER_DETERMINE_CONFIRM, "payload": {**self.draft_state}, }, ) async def set_draft_phase(self, destination: DraftPhase): self.draft_state.phase = destination await self.channel_layer.group_send( self.group_names.session, { "type": "broadcast.session", "subtype": DraftMessage.PHASE_CHANGE_CONFIRM, "payload": {"phase": self.draft_state.phase}, }, ) # === Broadcast Handlers === async def broadcast_admin(self, event): await self._dispatch_broadcast(event) class DraftParticipantConsumer(DraftConsumerBase): async def connect(self): await super().connect() self.draft_state.connect_participant(self.user.username) await self.channel_layer.group_send( self.group_names.session, { "type": "broadcast.session", "subtype": DraftMessage.PARTICIPANT_JOIN_CONFIRM, "payload": { "user": self.user.username, "connected_participants": self.draft_state.connected_participants, }, }, ) await self.channel_layer.group_add( self.group_names.participant, self.channel_name ) async def disconnect(self, close_code): self.draft_state.disconnect_participant(self.user.username) await self.channel_layer.group_send( self.group_names.session, { "type": "broadcast.session", "subtype": DraftMessage.PARTICIPANT_LEAVE_INFORM, "payload": { "user": self.user.username, "connected_participants": self.draft_state.connected_participants, }, }, ) await super().disconnect(close_code) self.draft_state.disconnect_participant(self.user.username) await self.channel_layer.group_discard( self.group_names.session, self.channel_name ) def should_accept_user(self): return super().should_accept_user() and self.user in self.draft_participants async def receive_json(self, content): await super().receive_json(content) event_type = content.get("type") if event_type == DraftMessage.NOMINATION_SUBMIT_REQUEST: await self.channel_layer.group_send( self.group_names.admin, { "type": "broadcast.admin", "subtype": event_type, "payload": { "movie_id": content.get("payload", {}).get("id"), "user": content.get("payload", {}).get("user"), }, }, ) if event_type == DraftMessage.BID_PLACE_REQUEST: bid_amount = content.get('payload',{}).get('bid_amount') self.draft_state.place_bid(self.user, bid_amount) await self.channel_layer.group_send( self.group_names.session, { "type": "broadcast.session", "subtype": DraftMessage.BID_PLACE_CONFIRM, "payload": {**self.draft_state}, }, ) # === Broadcast handlers === async def broadcast_participant(self, event): await self._dispatch_broadcast(event) # === Draft === async def nominate(self, movie_title): ... async def place_bid(self, amount, user): ... # === Example DB Access === @database_sync_to_async def add_draft_participant(self): self.participant, _ = DraftSessionParticipant.objects.get_or_create( user=self.user, draft=self.draft_session, defaults={"budget": self.draft_session.settings.starting_budget}, )