async def pre_disconnect(self, room_name, participant_label, tab_unique_id): if room_name in get_room_dict(): room = get_room_dict()[room_name] else: # doesn't get shown because not yet localized await self.send_json( {'error': 'Invalid room name "{}".'.format(room_name)}) return # should use filter instead of get, # because if the DB is recreated, # the record could already be deleted await database_sync_to_async(self.delete_visit)( participant_label=participant_label, room_name=room_name, tab_unique_id=tab_unique_id, ) event = {'status': 'remove_participant'} if room.has_participant_labels(): if await database_sync_to_async(self.visit_exists)( participant_label=participant_label, room_name=room_name): return # it's ok if there is a race condition -- # in JS removing a participant is idempotent event['participant'] = participant_label admin_group = channel_utils.room_admin_group_name(room_name) await channel_utils.group_send_wrapper(group=admin_group, type='roomadmin_update', event=event)
def dispatch(self, request, room_name): self.room_name = room_name self.room = get_room_dict()[room_name] if self.room.has_session(): return redirect('RoomWithSession', room_name) return super().dispatch(request)
def get_room(self): from otree.room import get_room_dict try: room_name = RoomToSession.objects.get(session=self).room_name return get_room_dict()[room_name] except RoomToSession.DoesNotExist: return None
def handle(self, session_config_name, num_participants, room_name, **kwargs): session = create_session( session_config_name=session_config_name, num_participants=num_participants, ) if room_name: room = get_room_dict()[room_name] room.set_session(session) logger.info("Created session with code {} in room '{}'\n".format( session.code, room_name)) else: logger.info("Created session with code {}\n".format(session.code))
async def post_connect(self, room_name, participant_label, tab_unique_id): if room_name in get_room_dict(): room = get_room_dict()[room_name] else: # doesn't get shown because not yet localized await self.send_json( {'error': 'Invalid room name "{}".'.format(room_name)}) return if await database_sync_to_async(room.has_session)(): await self.room_session_ready() else: try: await database_sync_to_async( self.create_participant_room_visit)( participant_label=participant_label, room_name=room_name, tab_unique_id=tab_unique_id, last_updated=time.time(), ) except django.db.IntegrityError: # possible that the tab connected twice # without disconnecting in between # because of WebSocket failure # tab_unique_id is unique=True, # so this will throw an integrity error. # 2017-09-17: I saw the integrityerror on macOS. # previously, we logged this, but i see no need to do that. pass await channel_utils.group_send_wrapper( type='roomadmin_update', group=channel_utils.room_admin_group_name(room_name), event={ 'status': 'add_participant', 'participant': participant_label }, )
async def post_connect(self, room): room_object = get_room_dict()[room] now = time.time() stale_threshold = now - 15 present_list = await database_sync_to_async(self.get_list)( room_name=room_object.name, last_updated__gte=stale_threshold) await self.send_json({ 'status': 'load_participant_lists', 'participants_present': present_list }) # prune very old visits -- don't want a resource leak # because sometimes not getting deleted on WebSocket disconnect very_stale_threshold = now - 10 * 60 await database_sync_to_async(self.delete_old_visits )(room_name=room_object.name, last_updated__lt=very_stale_threshold)
def dispatch(self, request, room): self.room_name = room try: room = get_room_dict()[self.room_name] except KeyError: return HttpResponseNotFound('Invalid room specified in url') label = self.request.GET.get('participant_label', '') if room.has_participant_labels(): if label: missing_label = False invalid_label = label not in room.get_participant_labels() else: missing_label = True invalid_label = False # needs to be easy to re-enter label, in case we are in kiosk # mode if missing_label or invalid_label and not room.use_secure_urls: return render( request, "otree/RoomInputLabel.html", {'invalid_label': invalid_label}, ) if room.use_secure_urls: hash = self.request.GET.get('hash') if hash != make_hash(label): return HttpResponseNotFound( 'Invalid hash parameter. use_secure_urls is True, ' 'so you must use the participant-specific URL.') session = room.get_session() if session is None: self.tab_unique_id = otree.common.random_chars_10() self._socket_url = channel_utils.room_participant_path( room_name=self.room_name, participant_label=label, # random chars in case the participant has multiple tabs open tab_unique_id=self.tab_unique_id, ) return render( request, "otree/WaitPageRoom.html", { 'view': self, 'title_text': _('Venligst vent..'), 'body_text': _('Venter på spillet..'), }, ) if label: cookies = None else: cookies = request.session # 2017-08-02: changing the behavior so that even in a room without # participant_label_file, 2 requests for the same start URL with same label # will return the same participant. Not sure if the previous behavior # (assigning to 2 different participants) was intentional or bug. return participant_start_page_or_404(session, label=label, cookies=cookies)
def create_session( session_config_name, *, num_participants, label='', room_name=None, is_mturk=False, is_demo=False, edited_session_config_fields=None, ) -> Session: num_subsessions = 0 edited_session_config_fields = edited_session_config_fields or {} try: session_config = SESSION_CONFIGS_DICT[session_config_name] except KeyError: msg = 'Spil type "{}" not found in settings.SESSION_CONFIGS.' raise KeyError(msg.format(session_config_name)) from None else: # copy so that we don't mutate the original # .copy() returns a dict, so need to convert back to SessionConfig session_config = SessionConfig(session_config.copy()) session_config.update(edited_session_config_fields) # check validity and converts serialized decimal & currency values # back to their original data type (because they were serialized # when passed through channels session_config.clean() with transaction.atomic(): # 2014-5-2: i could implement this by overriding the __init__ on the # Session model, but I don't really know how that works, and it seems # to be a bit discouraged: http://goo.gl/dEXZpv # 2014-9-22: preassign to groups for demo mode. # check that it divides evenly session_lcm = session_config.get_lcm() if num_participants is None: # most games are multiplayer, so if it's under 2, we bump it to 2 num_participants = max(session_lcm, 2) else: if num_participants % session_lcm: msg = ('Spil type {}: Antal spillere ({}) is not a multiple ' 'of group size ({})').format(session_config['name'], num_participants, session_lcm) raise ValueError(msg) if is_mturk: mturk_num_participants = (num_participants / settings.MTURK_NUM_PARTICIPANTS_MULTIPLE) else: mturk_num_participants = -1 session = Session.objects.create( config=session_config, label=label, is_demo=is_demo, num_participants=num_participants, mturk_num_participants=mturk_num_participants, ) # type: Session Participant.objects.bulk_create([ Participant(id_in_session=id_in_session, session=session) for id_in_session in list(range(1, num_participants + 1)) ]) participant_values = session.participant_set.order_by('id').values( 'code', 'id') ParticipantLockModel.objects.bulk_create([ ParticipantLockModel(participant_code=participant['code']) for participant in participant_values ]) participant_to_player_lookups = [] page_index = 0 for app_name in session_config['app_sequence']: views_module = common.get_pages_module(app_name) models_module = get_models_module(app_name) Constants = models_module.Constants num_subsessions += Constants.num_rounds round_numbers = list(range(1, Constants.num_rounds + 1)) Subsession = models_module.Subsession Group = models_module.Group Player = models_module.Player Subsession.objects.bulk_create([ Subsession(round_number=round_number, session=session) for round_number in round_numbers ]) subsessions = (Subsession.objects.filter( session=session).order_by('round_number').values( 'id', 'round_number')) ppg = Constants.players_per_group if ppg is None or Subsession._has_group_by_arrival_time(): ppg = num_participants num_groups_per_round = int(num_participants / ppg) groups_to_create = [] for subsession in subsessions: for id_in_subsession in range(1, num_groups_per_round + 1): groups_to_create.append( Group( session=session, subsession_id=subsession['id'], round_number=subsession['round_number'], id_in_subsession=id_in_subsession, )) Group.objects.bulk_create(groups_to_create) groups = (Group.objects.filter(session=session).values( 'id_in_subsession', 'subsession_id', 'id').order_by('id_in_subsession')) groups_lookup = defaultdict(list) for group in groups: subsession_id = group['subsession_id'] groups_lookup[subsession_id].append(group['id']) players_to_create = [] for subsession in subsessions: subsession_id = subsession['id'] round_number = subsession['round_number'] participant_index = 0 for group_id in groups_lookup[subsession_id]: for id_in_group in range(1, ppg + 1): participant = participant_values[participant_index] players_to_create.append( Player( session=session, subsession_id=subsession_id, round_number=round_number, participant_id=participant['id'], group_id=group_id, id_in_group=id_in_group, )) participant_index += 1 # Create players Player.objects.bulk_create(players_to_create) players_flat = Player.objects.filter(session=session).values( 'id', 'participant__code', 'participant__id', 'subsession__id', 'round_number', ) players_by_round = [[] for _ in range(Constants.num_rounds)] for p in players_flat: players_by_round[p['round_number'] - 1].append(p) for round_number, round_players in enumerate(players_by_round, start=1): for View in views_module.page_sequence: page_index += 1 for p in round_players: participant_code = p['participant__code'] url = View.get_url( participant_code=participant_code, name_in_url=Constants.name_in_url, page_index=page_index, ) participant_to_player_lookups.append( ParticipantToPlayerLookup( participant_id=p['participant__id'], participant_code=participant_code, page_index=page_index, app_name=app_name, player_pk=p['id'], subsession_pk=p['subsession__id'], session_pk=session.pk, url=url, )) ParticipantToPlayerLookup.objects.bulk_create( participant_to_player_lookups) session.participant_set.update(_max_page_index=page_index) with otree.db.idmap.use_cache(): # possible optimization: check if # Subsession.creating_session == BaseSubsession.creating_session # if so, skip it. # but this will only help people who didn't override creating_session # in that case, the session usually creates quickly, anyway. for subsession in session.get_subsessions(): subsession.before_session_starts() subsession.creating_session() otree.db.idmap.save_objects() # 2017-09-27: moving this inside the transaction session._set_admin_report_app_names() session.save() # we don't need to mark it ready=True here...because it's in a # transaction # this should happen after session.ready = True if room_name is not None: from otree.room import get_room_dict room = get_room_dict()[room_name] room.set_session(session) return session
def post(self, request, room_name): self.room = get_room_dict()[room_name] self.room.set_session(None) # in case any failed to be cleared through regular ws.disconnect ParticipantRoomVisit.objects.filter(room_name=room_name).delete() return redirect('RoomWithoutSession', room_name)