import otree.common from otree.channels import utils as channel_utils from otree.models import Participant, BasePlayer, BaseGroup from otree.lookup import get_page_lookup import logging from otree.database import NoResultFound logger = logging.getLogger(__name__) async def live_payload_function(participant_code, page_name, payload): try: participant = Participant.objects_get(code=participant_code) except NoResultFound: logger.warning(f'Participant not found: {participant_code}') return lookup = get_page_lookup(participant._session_code, participant._index_in_pages) app_name = lookup.app_name models_module = otree.common.get_models_module(app_name) PageClass = lookup.page_class # this could be incorrect if the player advances right after liveSend is executed. # maybe just return if it doesn't match. (but leave it in for now and see how much that occurs, # don't want silent failures.) if page_name != PageClass.__name__: logger.warning( f'Ignoring liveSend message from {participant_code} because ' f'they are on page {PageClass.__name__}, not {page_name}.' ) return player = models_module.Player.objects_get( round_number=lookup.round_number, participant=participant ) # it makes sense to check the group first because # if the player forgot to define it on the Player, # we shouldn't fall back to checking the group. you could get an error like # 'Group' has no attribute 'live_auction' which would be confusing. # also, we need this 'group' object anyway. # and this is a good place to show the deprecation warning. group = player.group live_method = PageClass.live_method retval = call_live_method_compat(live_method, player, payload) if not retval: return if not isinstance(retval, dict): msg = f'live method must return a dict' raise LiveMethodBadReturnValue(msg) Player: BasePlayer = models_module.Player pcodes_dict = { d[0]: d[1] for d in Player.objects_filter(group=group) .join(Participant) .with_entities(Player.id_in_group, Participant.code,) } if 0 in retval: if len(retval) > 1: raise LiveMethodBadReturnValue( 'If dict returned by live_method has key 0, it must not contain any other keys' ) else: for pid in retval: if pid not in pcodes_dict: msg = f'live_method has invalid return value. No player with id_in_group={repr(pid)}' raise LiveMethodBadReturnValue(msg) pcode_retval = {} for pid, pcode in pcodes_dict.items(): payload = retval.get(pid, retval.get(0)) if payload is not None: pcode_retval[pcode] = payload await _live_send_back( participant._session_code, participant._index_in_pages, pcode_retval ) class LiveMethodBadReturnValue(Exception): pass async def _live_send_back(session_code, page_index, pcode_retval): '''separate function for easier patching''' for pcode, retval in pcode_retval.items(): group_name = channel_utils.live_group(session_code, page_index, pcode) await channel_utils.group_send( group=group_name, data=retval, ) def call_live_method_compat(live_method, player, payload): if isinstance(live_method, str): return player.call_user_defined(live_method, payload) # noself style return live_method(player, payload)