Esempio n. 1
0
 def _get_current_player(self):
     lookup = get_page_lookup(self._session_code, self._index_in_pages)
     models_module = otree.common.get_models_module(lookup.app_name)
     PlayerClass = getattr(models_module, 'Player')
     return PlayerClass.objects.get(
         participant=self, round_number=lookup.round_number
     )
Esempio n. 2
0
    def set_attributes(self, participant):

        lookup = get_page_lookup(participant._session_code, participant._index_in_pages)
        self._lookup = lookup

        app_name = lookup.app_name

        models_module = otree.common.get_models_module(app_name)
        self._Constants = models_module.Constants
        self.PlayerClass = getattr(models_module, 'Player')
        self.GroupClass = getattr(models_module, 'Group')
        self.SubsessionClass = getattr(models_module, 'Subsession')
        self.player = self.PlayerClass.objects.get(
            participant=participant, round_number=lookup.round_number
        )
        self._subsession_pk = lookup.subsession_id
        self.round_number = lookup.round_number
        self._session_pk = lookup.session_pk
        # simpler if we set it directly so that we can do tests without idmap cache
        self._participant_pk = participant.pk
        # setting it directly makes testing easier (tests dont need to use cache)
        self.participant = participant

        # it's already validated that participant is on right page
        self._index_in_pages = participant._index_in_pages

        # for the participant changelist
        participant._current_app_name = app_name
        participant._current_page_name = self.__class__.__name__
        participant._last_request_timestamp = time.time()
        participant._round_number = lookup.round_number

        self._is_frozen = True
Esempio n. 3
0
    def _increment_index_in_pages(self):
        # when is this not the case?
        participant = self.participant
        assert self._index_in_pages == participant._index_in_pages

        # we should allow a user to move beyond the last page if it's mturk
        # also in general maybe we should show the 'out of sequence' page

        # we skip any page that is a sequence page where is_displayed
        # evaluates to False to eliminate unnecessary redirection

        page_index_to_skip_to = self._get_next_page_index_if_skipping_apps()
        is_skipping_apps = bool(page_index_to_skip_to)

        for page_index in range(
            # go to max_page_index+2 because range() skips the last index
            # and it's possible to go to max_page_index + 1 (OutOfRange)
            self._index_in_pages + 1,
            participant._max_page_index + 2,
        ):
            participant._index_in_pages = page_index
            if page_index == participant._max_page_index + 1:
                # break and go to OutOfRangeNotification
                break
            if is_skipping_apps and page_index == page_index_to_skip_to:
                break

            # scope, receive, send
            page = get_page_lookup(
                participant._session_code, page_index
            ).page_class.instantiate_without_request()

            page.set_attributes(self.participant)
            if not is_skipping_apps:
                if page._lookup.is_first_in_round:
                    # we have moved to a new round.
                    page.player.start()
                if page._is_displayed():
                    break

            # if it's a wait page, record that they visited
            if isinstance(page, WaitPage):

                if page.group_by_arrival_time:
                    # keep looping
                    # if 1 participant can skip the page,
                    # then all other participants should skip it also,
                    # as described in the docs
                    # so there is no need to mark as complete.
                    continue

                # save the participant, because tally_unvisited
                # queries index_in_pages directly from the DB
                db.commit()

                is_last, someone_waiting = page._tally_unvisited()
                if is_last and someone_waiting:
                    page._run_aapa_and_notify(page._group_or_subsession)
Esempio n. 4
0
def live_payload_function(participant_code, page_name, payload):

    participant = Participant.objects.get(code=participant_code)
    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
    assert page_name == PageClass.__name__
    method_name = PageClass.live_method

    with otree.db.idmap.use_cache():
        player = models_module.Player.objects.get(
            round_number=lookup.round_number, participant=participant
        )
        group = player.group
        method = getattr(group, method_name)
        retval = method(player.id_in_group, payload)
        otree.db.idmap.save_objects()

    if not retval:
        return
    if not isinstance(retval, dict):
        msg = f'{method_name} must return a dict'
        raise LiveMethodBadReturnValue(msg)

    pcodes_dict = {
        d['id_in_group']: d['participant__code']
        for d in models_module.Player.objects.filter(group=group).values(
            'participant__code', 'id_in_group'
        )
    }

    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

    _live_send_back(
        participant._session_code, participant._index_in_pages, pcode_retval
    )
Esempio n. 5
0
 def post(self):
     r = super().post()
     current_app = self._lookup.app_name
     try:
         next_app = get_page_lookup(self.session.code, self.participant._index_in_pages).app_name
     except KeyError:
         # in this case it's apparently the last app in the original app_sequence
         next_app = None
     if current_app == next_app:
         return r
     seq_dict = self.participant.vars.get('_updated_seq_apps')
     if seq_dict:
         app_to_skip_to = seq_dict.get(current_app)
         if app_to_skip_to:
             where_to = get_min_idx_for_app(self.participant._session_code, app_to_skip_to)
         else:
             where_to = self.participant._max_page_index + 1
         self.participant._index_in_pages = where_to
         self._is_frozen = False
         self._index_in_pages = where_to
         return self._redirect_to_page_the_user_should_be_on()
     else:
         return r
Esempio n. 6
0
async def live_payload_function(participant_code, page_name, payload):

    participant = Participant.objects_get(code=participant_code)
    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
    live_method_name = PageClass.live_method

    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
    if hasattr(group, live_method_name):
        method = getattr(group, live_method_name)
        retval = method(player.id_in_group, payload)
    else:
        method = getattr(player, live_method_name)
        retval = method(payload)

    if not retval:
        return
    if not isinstance(retval, dict):
        msg = f'{live_method_name} 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)
Esempio n. 7
0
 def get_current_page_name(self):
     lookup = get_page_lookup(self.participant._session_code,
                              self.participant._index_in_pages)
     return lookup.page_class.__name__
Esempio n. 8
0
    def advance_last_place_participants(self):
        """the problem with using the test client to make get/post requests is
        (1) this request already has the global asyncio.lock
        (2) there are apparently some issues with async/await and event loops.
        """
        from otree.lookup import get_page_lookup
        from otree.api import WaitPage, Page

        participants = self.get_participants()

        # in case some participants haven't started
        unvisited_participants = False
        for p in participants:
            if p._index_in_pages == 0:
                p.initialize(None)

        if unvisited_participants:
            # that's it -- just visit the start URL, advancing by 1
            return

        last_place_page_index = min([p._index_in_pages for p in participants])
        last_place_participants = [
            p for p in participants
            if p._index_in_pages == last_place_page_index
        ]
        for p in last_place_participants:
            page_index = p._index_in_pages
            if page_index >= p._max_page_index:
                return
            page = get_page_lookup(
                self.code,
                page_index).page_class.instantiate_without_request()
            page.set_attributes(p)

            if isinstance(page, Page):

                from starlette.datastructures import FormData

                page._is_frozen = False
                page._form_data = FormData({
                    otree.constants.admin_secret_code:
                    ADMIN_SECRET_CODE,
                    otree.constants.timeout_happened:
                    '1',
                })
                # TODO: should we also call .get() so that _update_monitor_table will also get run?
                resp = page.post()
                if resp.status_code >= 400:
                    msg = (
                        f'Submitting page {p._current_form_page_url} failed, '
                        f'returned HTTP status code {resp.status_code}. '
                        'Check the logs')
                    raise AssertionError(msg)
            else:
                # it's possible that the slowest user is on a wait page,
                # especially if their browser is closed.
                # because they were waiting for another user who then
                # advanced past the wait page, but they were never
                # advanced themselves.
                resp = page.inner_dispatch(request=None)

            # do the auto-advancing here,
            # rather than in increment_index_in_pages,
            # because it's only needed here.
            otree.channels.utils.sync_group_send(group=auto_advance_group(
                p.code),
                                                 data={'auto_advanced': True})