Ejemplo n.º 1
0
    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)
Ejemplo n.º 2
0
    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)
Ejemplo n.º 3
0
    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
Ejemplo n.º 4
0
    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))
Ejemplo n.º 5
0
 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
             },
         )
Ejemplo n.º 6
0
    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)
Ejemplo n.º 7
0
    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)
Ejemplo n.º 8
0
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
Ejemplo n.º 9
0
 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)