Add user state updates and bidding error handling in draft consumers
- Implement user state tracking and broadcasting on connect/disconnect and phase changes - Add bid start and place rejection handling with error messages to frontend and backend - Enhance movie serializer with TMDB integration and update relevant frontend components
This commit is contained in:
@@ -4,23 +4,19 @@ 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 draft.state import DraftStateManager, DraftStateException
|
||||
from typing import Any
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__) # __name__ = module path
|
||||
|
||||
|
||||
import random
|
||||
|
||||
|
||||
class DraftConsumerBase(AsyncJsonWebsocketConsumer):
|
||||
group_names: DraftGroupChannelNames
|
||||
draft_state: DraftStateManager
|
||||
@@ -61,6 +57,7 @@ class DraftConsumerBase(AsyncJsonWebsocketConsumer):
|
||||
return
|
||||
else:
|
||||
await self.accept()
|
||||
self.draft_state.connect_participant(self.user.username)
|
||||
await self.channel_layer.group_add(
|
||||
self.group_names.session, self.channel_name
|
||||
)
|
||||
@@ -72,14 +69,6 @@ class DraftConsumerBase(AsyncJsonWebsocketConsumer):
|
||||
"payload": {"user": self.user.username},
|
||||
},
|
||||
)
|
||||
await self.channel_layer.group_send(
|
||||
self.group_names.session,
|
||||
{
|
||||
"type": "direct.message",
|
||||
"subtype": DraftMessage.DRAFT_STATUS_INFORM,
|
||||
"payload": self.draft_state.to_dict(),
|
||||
},
|
||||
)
|
||||
await self.channel_layer.send(
|
||||
self.channel_name,
|
||||
{
|
||||
@@ -88,6 +77,7 @@ class DraftConsumerBase(AsyncJsonWebsocketConsumer):
|
||||
"payload": {"user": self.user.username},
|
||||
},
|
||||
)
|
||||
await self.broadcast_state()
|
||||
|
||||
async def should_accept_user(self) -> bool:
|
||||
return self.user.is_authenticated
|
||||
@@ -106,6 +96,14 @@ class DraftConsumerBase(AsyncJsonWebsocketConsumer):
|
||||
# --- Convenience helpers ---
|
||||
async def send_draft_state(self):
|
||||
"""Send the current draft state only to this client."""
|
||||
await self.channel_layer.send(
|
||||
self.channel_name,
|
||||
{
|
||||
"type": "direct.message",
|
||||
"subtype": DraftMessage.USER_STATE_INFORM,
|
||||
"payload": self.draft_state.user_state(self.user),
|
||||
}
|
||||
)
|
||||
await self.channel_layer.send(
|
||||
self.channel_name,
|
||||
{
|
||||
@@ -117,6 +115,14 @@ class DraftConsumerBase(AsyncJsonWebsocketConsumer):
|
||||
|
||||
async def broadcast_state(self):
|
||||
"""Broadcast current draft state to all in session group."""
|
||||
await self.channel_layer.group_send(
|
||||
self.group_names.session,
|
||||
{
|
||||
"type": "broadcast.session",
|
||||
"subtype": DraftMessage.USER_STATE_INFORM,
|
||||
"payload": [self.draft_state.user_state(user) for user in self.draft_participants],
|
||||
}
|
||||
)
|
||||
await self.channel_layer.group_send(
|
||||
self.group_names.session,
|
||||
{
|
||||
@@ -214,6 +220,18 @@ class DraftAdminConsumer(DraftConsumerBase):
|
||||
},
|
||||
)
|
||||
await self.broadcast_state()
|
||||
|
||||
case DraftPhase.BIDDING:
|
||||
await self.set_draft_phase(DraftPhase.BIDDING)
|
||||
await self.channel_layer.group_send(
|
||||
self.group_names.session,
|
||||
{
|
||||
"type": "broadcast.session",
|
||||
"subtype": DraftMessage.PHASE_CHANGE_CONFIRM,
|
||||
"payload": {"phase": self.draft_state.phase},
|
||||
},
|
||||
)
|
||||
await self.broadcast_state()
|
||||
|
||||
case DraftMessage.DRAFT_INDEX_ADVANCE_REQUEST:
|
||||
self.draft_state.draft_index_advance()
|
||||
@@ -245,16 +263,26 @@ class DraftAdminConsumer(DraftConsumerBase):
|
||||
await self.broadcast_state()
|
||||
|
||||
case 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},
|
||||
},
|
||||
)
|
||||
await self.broadcast_state()
|
||||
try:
|
||||
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},
|
||||
},
|
||||
)
|
||||
await self.broadcast_state()
|
||||
except DraftStateException as e:
|
||||
await self.channel_layer.send(
|
||||
self.channel_name, {
|
||||
"type": "direct.message",
|
||||
"subtype": DraftMessage.BID_START_REJECT,
|
||||
"payload": {'message': str(e)}
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
# === Draft logic ===
|
||||
|
||||
@@ -314,12 +342,14 @@ class DraftParticipantConsumer(DraftConsumerBase):
|
||||
},
|
||||
},
|
||||
)
|
||||
await self.broadcast_state()
|
||||
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
|
||||
|
||||
@@ -333,7 +363,7 @@ class DraftParticipantConsumer(DraftConsumerBase):
|
||||
"type": "broadcast.admin",
|
||||
"subtype": event_type,
|
||||
"payload": {
|
||||
"movie_id": content.get("payload", {}).get("id"),
|
||||
"movie_id": content.get("payload", {}).get("movie_id"),
|
||||
"user": content.get("payload", {}).get("user"),
|
||||
},
|
||||
},
|
||||
@@ -341,15 +371,26 @@ class DraftParticipantConsumer(DraftConsumerBase):
|
||||
|
||||
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},
|
||||
},
|
||||
)
|
||||
try:
|
||||
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": {'user': self.user.username, 'bid': bid_amount},
|
||||
},
|
||||
)
|
||||
except DraftStateException as e:
|
||||
await self.channel_layer.group_send(
|
||||
self.group_names.session,
|
||||
{
|
||||
"type": "broadcast.session",
|
||||
"subtype": DraftMessage.BID_PLACE_REJECT,
|
||||
"payload": {'user': self.user.username, 'bid': bid_amount, 'error':str(e)},
|
||||
},
|
||||
)
|
||||
await self.broadcast_state()
|
||||
|
||||
# === Broadcast handlers ===
|
||||
|
||||
@@ -358,11 +399,8 @@ class DraftParticipantConsumer(DraftConsumerBase):
|
||||
|
||||
# === Draft ===
|
||||
|
||||
async def nominate(self, movie_title): ...
|
||||
|
||||
async def place_bid(self, amount, user): ...
|
||||
|
||||
# === Example DB Access ===
|
||||
# === DB Access ===
|
||||
|
||||
@database_sync_to_async
|
||||
def add_draft_participant(self):
|
||||
|
||||
Reference in New Issue
Block a user