예제 #1
0
    def _mark_completed_and_notify(self, group: Optional[BaseGroup]):
        # if group is not passed, then it's the whole subsession
        # could be 2 people creating the record at the same time
        # in _increment_index_in_pages, so could end up creating 2 records
        # but it's not a problem.

        base_kwargs = dict(page_index=self._index_in_pages, session_id=self._session_pk)

        if self.wait_for_all_groups:
            CompletedSubsessionWaitPage.objects.create(**base_kwargs)
            obj = self.subsession
        elif self.group_by_arrival_time:
            CompletedGBATWaitPage.objects.create(
                **base_kwargs, id_in_subsession=group.id_in_subsession
            )
            obj = group
        else:
            CompletedGroupWaitPage.objects.create(**base_kwargs, group_id=group.id)
            obj = group

        player_values = obj.player_set.values(
            'participant__id_in_session', 'participant__code', 'participant__pk'
        )

        self._mark_page_completions(player_values)

        # this can cause messages to get wrongly enqueued in the botworker
        if otree.common.USE_REDIS and not self.participant.is_browser_bot:
            participant_pks = [p['participant__pk'] for p in player_values]
            # 2016-11-15: we used to only ensure the next page is visited
            # if the next page has a timeout, or if it's a wait page
            # but this is not reliable because next page might be skipped anyway,
            # and we don't know what page will actually be shown next to the user.
            otree.timeout.tasks.ensure_pages_visited.schedule(
                kwargs=dict(
                    participant_pks=participant_pks,
                    base_url=self.request.build_absolute_uri('/'),
                ),
                delay=10,
            )

        if self.group_by_arrival_time:
            channel_utils.sync_group_send_wrapper(
                type='gbat_ready',
                group=channel_utils.gbat_group_name(**base_kwargs),
                event={},
            )
        else:
            if self.wait_for_all_groups:
                channels_group_name = channel_utils.subsession_wait_page_name(
                    **base_kwargs
                )
            else:
                channels_group_name = channel_utils.group_wait_page_name(
                    **base_kwargs, group_id=group.id
                )

            channel_utils.sync_group_send_wrapper(
                type='wait_page_ready', group=channels_group_name, event={}
            )
예제 #2
0
    def pre_disconnect(self, room_name, participant_label, tab_unique_id):
        if room_name in ROOM_DICT:
            room = ROOM_DICT[room_name]
        else:
            # doesn't get shown because not yet localized
            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
        ParticipantRoomVisit.objects.filter(
            participant_label=participant_label,
            room_name=room_name,
            tab_unique_id=tab_unique_id).delete()

        event = {
            'status': 'remove_participant',
        }
        if room.has_participant_labels():
            if ParticipantRoomVisit.objects.filter(
                    participant_label=participant_label,
                    room_name=room_name).exists():
                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)
        channel_utils.sync_group_send_wrapper(group=admin_group,
                                              type='roomadmin_update',
                                              event=event)
예제 #3
0
 def post_connect(self, room_name, participant_label, tab_unique_id):
     if room_name in ROOM_DICT:
         room = ROOM_DICT[room_name]
     else:
         # doesn't get shown because not yet localized
         self.send_json(
             {'error': 'Invalid room name "{}".'.format(room_name)})
         return
     if room.has_session():
         self.room_session_ready()
     else:
         try:
             ParticipantRoomVisit.objects.create(
                 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
         channel_utils.sync_group_send_wrapper(
             type='roomadmin.update',
             group=channel_utils.room_admin_group_name(room_name),
             event={
                 'status': 'add_participant',
                 'participant': participant_label
             })
예제 #4
0
def send_completion_message(*, session_code, participant_code):
    group_name = channel_utils.browser_bots_launcher_group(session_code)

    channel_utils.sync_group_send_wrapper(
        group=group_name,
        type='send_completion_message',
        event={'text': participant_code},
    )
예제 #5
0
 def inner_post(self, **kwargs):
     session = create_session(**kwargs)
     room_name = kwargs.get('room_name')
     if room_name:
         channel_utils.sync_group_send_wrapper(
             type='room_session_ready',
             group=channel_utils.room_participants_group_name(room_name),
             event={},
         )
     return HttpResponse(session.code)
예제 #6
0
 def send_response_to_browser(self, event: dict):
     '''
     Send to a group instead of the channel only,
     because if the websocket disconnects during creation of a large session,
     (due to temporary network error, etc, or Heroku H15, 55 seconds without ping)
     the user could be stuck on "please wait" forever.
     the downside is that if two admins create sessions around the same time,
     your page could automatically redirect to the other admin's session.
     '''
     [group] = self.groups
     channel_utils.sync_group_send_wrapper(type='session_created',
                                           group=group,
                                           event=event)
예제 #7
0
    def inner_post(self, num_participants, session_config_name, case_number,
                   pre_create_id):
        session = create_session(session_config_name=session_config_name,
                                 num_participants=num_participants)
        otree.bots.browser.initialize_session(session_pk=session.pk,
                                              case_number=case_number)
        BrowserBotsLauncherSessionCode.objects.update_or_create(
            # i don't know why the update_or_create arg is called 'defaults'
            # because it will update even if the instance already exists
            # maybe for consistency with get_or_create
            defaults=dict(code=session.code, pre_create_id=pre_create_id))
        channel_utils.sync_group_send_wrapper(type='browserbot_sessionready',
                                              group='browser_bot_wait',
                                              event={})

        return HttpResponse(session.code)
예제 #8
0
 def inner_post(self, **kwargs):
     '''
     Notes:
     - This allows you to pass parameters that did not exist in the original config,
     as well as params that are blocked from editing in the UI,
     either because of datatype.
     I can't see any specific problem with this.
     '''
     session = create_session(**kwargs)
     room_name = kwargs.get('room_name')
     if room_name:
         channel_utils.sync_group_send_wrapper(
             type='room_session_ready',
             group=channel_utils.room_participants_group_name(room_name),
             event={},
         )
     return HttpResponse(session.code)
예제 #9
0
파일: admin.py 프로젝트: yatsky/otree-core
    def post(self, request):
        num_participants = int(request.POST['num_participants'])
        session_config_name = request.POST['session_config_name']
        case_number = int(request.POST['case_number'])
        session = create_session(session_config_name=session_config_name,
                                 num_participants=num_participants)
        otree.bots.browser.initialize_session(session_pk=session.pk,
                                              case_number=case_number)
        BrowserBotsLauncherSessionCode.objects.update_or_create(
            # i don't know why the update_or_create arg is called 'defaults'
            # because it will update even if the instance already exists
            # maybe for consistency with get_or_create
            defaults={'code': session.code})
        channel_utils.sync_group_send_wrapper(type='browserbot_sessionready',
                                              group='browser_bot_wait',
                                              event={})

        return HttpResponse(session.code)
예제 #10
0
    def post_receive_json(self, content, channel, participant_id):
        # in the Channels docs, the example has a separate msg_consumer
        # channel, so this can be done asynchronously.
        # but i think the perf is probably good enough.
        # moving into here for simplicity, especially for testing.
        nickname_signed = content['nickname_signed']
        nickname = Signer().unsign(nickname_signed)
        body = content['body']

        chat_message = dict(nickname=nickname,
                            body=body,
                            participant_id=participant_id)

        [group] = self.groups
        channel_utils.sync_group_send_wrapper(type='chat_sendmessages',
                                              group=group,
                                              event={'chats': [chat_message]})

        ChatMessage.objects.create(participant_id=participant_id,
                                   channel=channel,
                                   body=body,
                                   nickname=nickname)
예제 #11
0
def _live_send_back(session_code, page_index, pcode_retval):
    '''separate function for easier patching'''
    group_name = channel_utils.live_group(session_code, page_index)
    channel_utils.sync_group_send_wrapper(
        group=group_name, type='send_back_to_client', event=pcode_retval
    )
예제 #12
0
    def post_receive_json(self, form_data: dict):
        form = CreateSessionForm(data=form_data)
        if not form.is_valid():
            self.send_json({'validation_errors': form.errors})
            return

        session_config_name = form.cleaned_data['session_config']
        is_mturk = form.cleaned_data['is_mturk']

        config = SESSION_CONFIGS_DICT[session_config_name]

        num_participants = form.cleaned_data['num_participants']
        if is_mturk:
            num_participants *= settings.MTURK_NUM_PARTICIPANTS_MULTIPLE

        edited_session_config_fields = {}

        for field in config.editable_fields():
            html_field_name = config.html_field_name(field)
            old_value = config[field]

            # to allow concise unit tests, we can simply omit any fields we don't
            # want to change. this allows us to write more concise
            # unit tests.
            # EXCEPT for boolean fields -- omitting
            # it means we turn it off.
            # ideally we could interpret omitted boolean fields as unchanged
            # and False as unchecked, but HTML & serializeArray omits
            # unchecked checkboxes from form data.

            if isinstance(old_value, bool):
                new_value = bool(form_data.get(html_field_name))
                if old_value != new_value:
                    edited_session_config_fields[field] = new_value
            else:
                new_value_raw = form_data.get(html_field_name, '')
                if new_value_raw != '':
                    # don't use isinstance because that will catch bool also
                    if type(old_value) is int:
                        # in case someone enters 1.0 instead of 1
                        new_value = int(float(new_value_raw))
                    else:
                        new_value = type(old_value)(new_value_raw)
                    if old_value != new_value:
                        edited_session_config_fields[field] = new_value

        use_browser_bots = edited_session_config_fields.get(
            'use_browser_bots', config.get('use_browser_bots', False))

        # if room_name is missing, it will be empty string
        room_name = form.cleaned_data['room_name'] or None

        self.create_session_then_send_start_link(
            session_config_name=session_config_name,
            num_participants=num_participants,
            is_demo=False,
            is_mturk=is_mturk,
            edited_session_config_fields=edited_session_config_fields,
            use_browser_bots=use_browser_bots,
            room_name=room_name,
        )

        if room_name:
            channel_utils.sync_group_send_wrapper(
                type='room_session_ready',
                group=channel_utils.room_participants_group_name(room_name),
                event={})