Example #1
0
    def get(self, *args, **kwargs):

        anonymous_code = kwargs['anonymous_code']
        session = get_object_or_404(
            otree.models.Session, _anonymous_code=anonymous_code
        )
        with lock_on_this_code_path():
            try:
                participant = (
                    Participant.objects.select_for_update().filter(
                        session=session,
                        visited=False
                    )
                ).order_by('start_order')[0]
            except IndexError:
                return HttpResponseNotFound(NO_PARTICIPANTS_LEFT_MSG)

            # 2014-10-17: needs to be here even if it's also set in
            # the next view to prevent race conditions
            participant.visited = True
            participant.label = (
                self.request.GET.get('participant_label') or participant.label
            )
            participant.save()
        return HttpResponseRedirect(participant._start_url())
Example #2
0
    def get(self, *args, **kwargs):

        participant_label = self.request.GET.get(
            'participant_label'
        )
        if not participant_label:
            return HttpResponseNotFound(
                'Missing or empty participant label'
            )

        access_code_for_default_session = self.request.GET.get(
            'access_code_for_default_session'
        )
        if not access_code_for_default_session:
            return HttpResponseNotFound(
                'Missing or empty access code for default session'
            )

        cond = (
            access_code_for_default_session ==
            settings.ACCESS_CODE_FOR_DEFAULT_SESSION
        )
        if not cond:
            return HttpResponseNotFound(
                'Incorrect access code for default session'
            )

        global_singleton = GlobalSingleton.objects.get()
        default_session = global_singleton.default_session

        if not default_session:
            return HttpResponseNotFound(
                'No session is currently open. Make sure to create '
                'a session and set is as default.'
            )

        try:
            participant = Participant.objects.get(
                session=default_session,
                label=participant_label
            )
        except Participant.DoesNotExist:
            with lock_on_this_code_path():
                try:
                    participant = (
                        Participant.objects.select_for_update().filter(
                            session=default_session,
                            visited=False)
                    ).order_by('start_order')[0]
                except IndexError:
                    return HttpResponseNotFound(NO_PARTICIPANTS_LEFT_MSG)

                participant.label = participant_label
                # 2014-10-17: needs to be here even if it's also set in
                # the next view to prevent race conditions
                participant.visited = True
                participant.save()

        return HttpResponseRedirect(participant._start_url())
Example #3
0
 def _is_ready(self):
     if bool(self.group):
         return not self.group._is_missing_players
     # if grouping by arrival time,
     # and the player has not yet been assigned to a group,
     # we assign them.
     elif self.session.config.get('group_by_arrival_time'):
         with lock_on_this_code_path():
             # need to check again to prevent race conditions
             if bool(self.group):
                 return not self.group._is_missing_players
             if self.subsession.round_number == 1:
                 open_group = self.subsession._get_open_group()
                 group_players = open_group.get_players()
                 group_players.append(self.player)
                 open_group.set_players(group_players)
                 group_size_obj = GroupSize.objects.filter(
                     app_label=self.subsession._meta.app_config.name,
                     subsession_pk=self.subsession.pk,
                 ).order_by('group_index')[0]
                 group_quota = group_size_obj.group_size
                 if len(group_players) == group_quota:
                     open_group._is_missing_players = False
                     group_size_obj.delete()
                     open_group.save()
                     return True
                 else:
                     open_group.save()
                     return False
             else:
                 # 2015-06-11: just running
                 # self.subsession._create_groups() doesn't work
                 # because what if some participants didn't start round 1?
                 # following code only gets executed once
                 # (because of self.group check above)
                 # and doesn't get executed if ppg == None
                 # (because if ppg == None we preassign)
                 # get_players() is guaranteed to return a complete group
                 # (because a player can't start round 1 before
                 # being assigned to a complete group)
                 group_players = [
                     p._in_next_round() for p in
                     self.player._in_previous_round().group.get_players()
                 ]
                 open_group = self.subsession._get_open_group()
                 open_group.set_players(group_players)
                 open_group._is_missing_players = False
                 open_group.save()
                 return True
     # if not grouping by arrival time, but the session was just created
     # and the code to assign to groups has not executed yet
     return False
Example #4
0
 def _is_ready(self):
     if bool(self.group):
         return not self.group._is_missing_players
     # if grouping by arrival time,
     # and the player has not yet been assigned to a group,
     # we assign them.
     elif self.session.config.get('group_by_arrival_time'):
         with lock_on_this_code_path():
             # need to check again to prevent race conditions
             if bool(self.group):
                 return not self.group._is_missing_players
             if self.subsession.round_number == 1:
                 open_group = self.subsession._get_open_group()
                 group_players = open_group.get_players()
                 group_players.append(self.player)
                 open_group.set_players(group_players)
                 group_size_obj = GroupSize.objects.filter(
                     app_label=self.subsession._meta.app_config.name,
                     subsession_pk=self.subsession.pk,
                 ).order_by('group_index')[0]
                 group_quota = group_size_obj.group_size
                 if len(group_players) == group_quota:
                     open_group._is_missing_players = False
                     group_size_obj.delete()
                     open_group.save()
                     return True
                 else:
                     open_group.save()
                     return False
             else:
                 # 2015-06-11: just running
                 # self.subsession._create_groups() doesn't work
                 # because what if some participants didn't start round 1?
                 # following code only gets executed once
                 # (because of self.group check above)
                 # and doesn't get executed if ppg == None
                 # (because if ppg == None we preassign)
                 # get_players() is guaranteed to return a complete group
                 # (because a player can't start round 1 before
                 # being assigned to a complete group)
                 group_players = [
                     p._in_next_round() for p in
                     self.player._in_previous_round().group.get_players()
                 ]
                 open_group = self.subsession._get_open_group()
                 open_group.set_players(group_players)
                 open_group._is_missing_players = False
                 open_group.save()
                 return True
     # if not grouping by arrival time, but the session was just created
     # and the code to assign to groups has not executed yet
     return False
Example #5
0
    def get(self, *args, **kwargs):
        assignment_id = self.request.GET['assignmentId']
        worker_id = self.request.GET['workerId']
        if self.session.mturk_qualification_type_id:
            with MTurkConnection(
                self.request, self.session.mturk_sandbox
            ) as mturk_connection:
                try:
                    mturk_connection.assign_qualification(
                        self.session.mturk_qualification_type_id,
                        worker_id
                    )
                except MTurkRequestError as e:
                    if (
                        e.error_code ==
                        'AWS.MechanicalTurk.QualificationAlreadyExists'
                    ):
                        pass
                    else:
                        raise
        try:
            participant = Participant.objects.get(
                session=self.session,
                mturk_worker_id=worker_id,
                mturk_assignment_id=assignment_id)
        except Participant.DoesNotExist:
            with lock_on_this_code_path():
                try:
                    participant = (
                        Participant.objects.select_for_update().filter(
                            session=self.session,
                            visited=False
                        )
                    ).order_by('start_order')[0]
                except IndexError:
                    return HttpResponseNotFound(NO_PARTICIPANTS_LEFT_MSG)

            # 2014-10-17: needs to be here even if it's also set in
            # the next view to prevent race conditions
            participant.visited = True
            participant.mturk_worker_id = worker_id
            participant.mturk_assignment_id = assignment_id
            participant.save()
        return HttpResponseRedirect(participant._start_url())
Example #6
0
    def get(self, *args, **kwargs):
        assignment_id = self.request.GET['assignmentId']
        worker_id = self.request.GET['workerId']
        if self.session.mturk_qualification_type_id:
            with MTurkConnection(
                self.request, self.session.mturk_sandbox
            ) as mturk_connection:
                try:
                    mturk_connection.assign_qualification(
                        self.session.mturk_qualification_type_id,
                        worker_id
                    )
                except MTurkRequestError as e:
                    if (
                        e.error_code ==
                        'AWS.MechanicalTurk.QualificationAlreadyExists'
                    ):
                        pass
                    else:
                        raise
        try:
            participant = self.session.participant_set.get(
                mturk_worker_id=worker_id,
                mturk_assignment_id=assignment_id)
        except Participant.DoesNotExist:
            with lock_on_this_code_path():
                try:
                    participant = (
                        Participant.objects.select_for_update().filter(
                            session=self.session,
                            visited=False
                        )
                    ).order_by('start_order')[0]
                except IndexError:
                    return HttpResponseNotFound(NO_PARTICIPANTS_LEFT_MSG)

            # 2014-10-17: needs to be here even if it's also set in
            # the next view to prevent race conditions
            participant.visited = True
            participant.mturk_worker_id = worker_id
            participant.mturk_assignment_id = assignment_id
            participant.save()
        return HttpResponseRedirect(participant._start_url())
Example #7
0
    def get(self, *args, **kwargs):
        cond = (
            self.request.GET[constants.access_code_for_default_session] ==
            settings.ACCESS_CODE_FOR_DEFAULT_SESSION)
        if not cond:
            return HttpResponseNotFound(
                'Incorrect access code for default session'
            )

        global_singleton = GlobalSingleton.objects.get()
        default_session = global_singleton.default_session

        if not default_session:
            return HttpResponseNotFound(
                'No session is currently open. Make sure to create '
                'a session and set is as default.'
            )
        if not self.url_has_correct_parameters():
            return HttpResponseNotFound(
                self.incorrect_parameters_in_url_message()
            )
        try:
            participant = (
                self.retrieve_existing_participant_with_these_params(
                    default_session))
        except Participant.DoesNotExist:
            with lock_on_this_code_path():
                try:
                    participant = (
                        Participant.objects.select_for_update().filter(
                            session=default_session,
                            visited=False)).order_by('start_order')[0]
                except IndexError:
                    return HttpResponseNotFound(NO_PARTICIPANTS_LEFT_MSG)

            self.set_external_params_on_participant(participant)
            # 2014-10-17: needs to be here even if it's also set in
            # the next view to prevent race conditions
            participant.visited = True
            participant.save()

        return HttpResponseRedirect(participant._start_url())
Example #8
0
    def get(self, *args, **kwargs):
        cond = (self.request.GET[constants.access_code_for_default_session] ==
                settings.ACCESS_CODE_FOR_DEFAULT_SESSION)
        if not cond:
            return HttpResponseNotFound(
                'Incorrect access code for default session')

        global_singleton = GlobalSingleton.objects.get()
        default_session = global_singleton.default_session

        if not default_session:
            return HttpResponseNotFound(
                'No session is currently open. Make sure to create '
                'a session and set is as default.')
        if not self.url_has_correct_parameters():
            return HttpResponseNotFound(
                self.incorrect_parameters_in_url_message())
        try:
            participant = (
                self.retrieve_existing_participant_with_these_params(
                    default_session))
        except Participant.DoesNotExist:
            with lock_on_this_code_path():
                try:
                    participant = (
                        Participant.objects.select_for_update().filter(
                            session=default_session,
                            visited=False)).order_by('start_order')[0]
                except IndexError:
                    return HttpResponseNotFound(NO_PARTICIPANTS_LEFT_MSG)

            self.set_external_params_on_participant(participant)
            # 2014-10-17: needs to be here even if it's also set in
            # the next view to prevent race conditions
            participant.visited = True
            participant.save()

        return HttpResponseRedirect(participant._start_url())
Example #9
0
    def dispatch(self, request, *args, **kwargs):
        if self.wait_for_all_groups:
            self._group_or_subsession = self.subsession
        else:
            self._group_or_subsession = self.group
        if self._is_ready():
            return self._response_when_ready()
        # take a lock because we set "waiting for" list here
        with lock_on_this_code_path():
            unvisited_participants = self._tally_unvisited()
        if unvisited_participants:
            # only skip the wait page if there are still
            # unvisited participants. otherwise, you need to
            # mark the page as completed.
            # see _increment_index_in_pages, which needs to
            # handle this case also
            if not self.is_displayed():
                return self._response_when_ready()
            self.participant.is_on_wait_page = True
            return self._get_wait_page()
        else:
            # on SQLite, transaction.atomic causes database to lock,
            # so we use no-op context manager instead
            with lock_on_this_code_path():
                if self.wait_for_all_groups:
                    _c = CompletedSubsessionWaitPage.objects.get_or_create(
                        page_index=self._index_in_pages,
                        session_pk=self.session.pk)
                    completion, created = _c
                else:
                    _c = CompletedGroupWaitPage.objects.get_or_create(
                        page_index=self._index_in_pages,
                        group_pk=self.group.pk,
                        session_pk=self.session.pk)
                    completion, created = _c

                # run the action inside the context manager, so that the
                # action is completed before the next thread does a
                # get_or_create and sees that the action has been completed
                if created:

                    # need to check this before deleting
                    # reference to self.player
                    is_displayed = self.is_displayed()

                    # block users from accessing self.player inside
                    # after_all_players_arrive, because conceptually
                    # there is no single player in this context
                    # (method is executed once for the whole group)

                    player = self.player
                    del self.player

                    # make sure we get the most up-to-date player objects
                    # e.g. if they were queried in is_displayed(),
                    # then they could be out of date
                    # but don't delete the current player from cache
                    # because we need it to be saved at the end
                    import idmap.tls
                    cache = getattr(idmap.tls._tls, 'idmap_cache', {})
                    for p in list(cache.get(self.PlayerClass, {}).values()):
                        if p != player:
                            self.PlayerClass.flush_cached_instance(p)

                    # if any player can skip the wait page,
                    # then we shouldn't run after_all_players_arrive
                    # because if some players are able to proceed to the next page
                    # before after_all_players_arrive is run,
                    # then after_all_players_arrive is probably not essential.
                    # often, there are some wait pages that all players skip,
                    # because they should only be shown in certain rounds.
                    # maybe the fields that after_all_players_arrive depends on
                    # are null
                    # something to think about: ideally, should we check if
                    # all players skipped, or any player skipped?
                    # as a shortcut, we just check if is_displayed is true
                    # for the last player.
                    if is_displayed:
                        self.after_all_players_arrive()
                    self.player = player

                    completion.after_all_players_arrive_run = True
                    completion.save()

                    # send a message to the channel to move forward
                    channels.Group(self.channels_group_name()).send(
                        {'text': json.dumps(
                            {'status': 'ready'})}
                    )

                    # in case there is a timeout on the next page, we
                    # should ensure the next pages are visited promptly
                    # TODO: can we make this run only if next page is a
                    # timeout page?
                    # or if a player is auto playing.
                    # we could instead make this request the current page
                    # URL, but it's different for each player

                    # 2015-07-27:
                    #   why not check if the next page has_timeout?

                    participant_pk_set = set([
                        p.participant.pk
                        for p in self._group_or_subsession.player_set.all()
                    ])

                    otree.timeout.tasks.ensure_pages_visited.apply_async(
                        kwargs={
                            'participant_pk_set': participant_pk_set,
                            'wait_page_index': self._index_in_pages,
                        }, countdown=10)
                # we can assume it's ready because
                # even if it wasn't created, that means someone else
                # created it, and therefore that whole code block
                # finished executing (including the after_all_players_arrive)
                # inside the transaction
            return self._response_when_ready()
Example #10
0
    def _increment_index_in_pages(self):
        # when is this not the case?
        assert self._index_in_pages == self.participant._index_in_pages

        self._record_page_completion_time()
        # 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

        # the timeout record is irrelevant at this point, delete it
        # wait pages don't have a has_timeout attribute
        if hasattr(self, 'has_timeout') and self.has_timeout():
            PageTimeout.objects.filter(
                participant_pk=self.participant.pk,
                page_index=self.participant._index_in_pages).delete()
        # this is causing crashes because of the weird DB issue
        # ParticipantToPlayerLookup.objects.filter(
        #    participant_pk=self.participant.pk,
        #    page_index=self.participant._index_in_pages).delete()

        # performance optimization:
        # we skip any page that is a sequence page where is_displayed
        # evaluates to False to eliminate unnecessary redirection
        views_module = otree.common_internal.get_views_module(
            self.subsession._meta.app_config.name)
        pages = views_module.page_sequence

        assert self.__class__ in pages
        pages_to_jump_by = 1
        indexes = list(range(self.player._index_in_game_pages + 1,
                             len(pages)))
        for target_index in indexes:
            Page = pages[target_index]

            # FIXME: are there other attributes? should i do As_view,
            # or simulate the
            # request?
            page = Page()
            page.player = self.player
            page.group = self.group
            page.subsession = self.subsession
            page.session = self.session

            # don't skip wait pages
            # because the user has to pass through them
            # so we record that they visited
            if hasattr(Page, 'is_displayed') and not page.is_displayed():
                if hasattr(Page, '_tally_unvisited'):
                    page._index_in_pages = self._index_in_pages + pages_to_jump_by
                    if page.wait_for_all_groups:
                        page._group_or_subsession = self.subsession
                    else:
                        page._group_or_subsession = self.group
                    with lock_on_this_code_path():
                        unvisited_participants = page._tally_unvisited()
                    # don't count myself; i only need to visit this page
                    # if everybody else already passed it
                    unvisited_participants.discard(self.participant.code)
                    if not unvisited_participants:
                        # if it's the last person
                        # because they need to complete the wait page
                        # don't skip past the wait page
                        break
                pages_to_jump_by += 1
            else:
                break

        self.player._index_in_game_pages += pages_to_jump_by
        self.participant._index_in_pages += pages_to_jump_by

        channels.Group(
            'auto-advance-{}'.format(self.participant.code)
        ).send(
            {'text': json.dumps(
                {'new_index_in_pages': self.participant._index_in_pages})}
        )
Example #11
0
    def dispatch(self, request, *args, **kwargs):
        if self.wait_for_all_groups:
            self._group_or_subsession = self.subsession
        else:
            self._group_or_subsession = self.group
        if self.request_is_from_wait_page():
            unvisited_ids = self._get_unvisited_ids()
            self._record_unvisited_ids(unvisited_ids)
            return self._response_to_wait_page()
        else:
            if self._is_ready():
                return self._response_when_ready()
            self._session_user.is_on_wait_page = True
            self._record_visit()
            if not self.is_displayed():
                self._increment_index_in_pages()
                return self._redirect_to_page_the_user_should_be_on()
            unvisited_ids = self._get_unvisited_ids()
            self._record_unvisited_ids(unvisited_ids)
            if len(unvisited_ids) == 0:

                # on SQLite, transaction.atomic causes database to lock,
                # so we use no-op context manager instead
                with lock_on_this_code_path():
                    if self.wait_for_all_groups:
                        _c = CompletedSubsessionWaitPage.objects.get_or_create(
                            page_index=self._index_in_pages,
                            session_pk=self.session.pk)
                        _, created = _c
                    else:
                        _c = CompletedGroupWaitPage.objects.get_or_create(
                            page_index=self._index_in_pages,
                            group_pk=self.group.pk,
                            session_pk=self.session.pk)
                        _, created = _c

                    # run the action inside the context manager, so that the
                    # action is completed before the next thread does a
                    # get_or_create and sees that the action has been completed
                    if created:
                        self._action()

                        # in case there is a timeout on the next page, we
                        # should ensure the next pages are visited promptly
                        # TODO: can we make this run only if next page is a
                        # timeout page?
                        # we could instead make this request the current page
                        # URL, but it's different for each player

                        # 2015-07-27:
                        #   why not check if the next page has_timeout?

                        participant_pk_set = set([
                            p.participant.pk for p in
                            self._group_or_subsession.player_set.all()
                        ])

                        otree.timeout.tasks.ensure_pages_visited.apply_async(
                            kwargs={
                                'participant_pk_set': participant_pk_set,
                                'wait_page_index': self._index_in_pages,
                            },
                            countdown=10)
                        return self._response_when_ready()
            return self._get_wait_page()
Example #12
0
    def dispatch(self, request, *args, **kwargs):
        if self.wait_for_all_groups:
            self._group_or_subsession = self.subsession
        else:
            self._group_or_subsession = self.group
        if self.request_is_from_wait_page():
            unvisited_ids = self._get_unvisited_ids()
            self._record_unvisited_ids(unvisited_ids)
            return self._response_to_wait_page()
        else:
            if self._is_ready():
                return self._response_when_ready()
            self._participant.is_on_wait_page = True
            self._record_visit()
            if not self.is_displayed():
                self._increment_index_in_pages()
                return self._redirect_to_page_the_user_should_be_on()
            unvisited_ids = self._get_unvisited_ids()
            self._record_unvisited_ids(unvisited_ids)
            if len(unvisited_ids) == 0:

                # on SQLite, transaction.atomic causes database to lock,
                # so we use no-op context manager instead
                with lock_on_this_code_path():
                    if self.wait_for_all_groups:
                        _c = CompletedSubsessionWaitPage.objects.get_or_create(
                            page_index=self._index_in_pages,
                            session_pk=self.session.pk)
                        _, created = _c
                    else:
                        _c = CompletedGroupWaitPage.objects.get_or_create(
                            page_index=self._index_in_pages,
                            group_pk=self.group.pk,
                            session_pk=self.session.pk)
                        _, created = _c

                    # run the action inside the context manager, so that the
                    # action is completed before the next thread does a
                    # get_or_create and sees that the action has been completed
                    if created:

                        # block users from accessing self.player inside
                        # after_all_players_arrive, because conceptually
                        # there is no single player in this context
                        # (method is executed once for the whole group)

                        player = self.player
                        del self.player
                        self.after_all_players_arrive()
                        self.player = player

                        # in case there is a timeout on the next page, we
                        # should ensure the next pages are visited promptly
                        # TODO: can we make this run only if next page is a
                        # timeout page?
                        # we could instead make this request the current page
                        # URL, but it's different for each player

                        # 2015-07-27:
                        #   why not check if the next page has_timeout?

                        participant_pk_set = set([
                            p.participant.pk
                            for p in self._group_or_subsession.player_set.all()
                        ])

                        otree.timeout.tasks.ensure_pages_visited.apply_async(
                            kwargs={
                                'participant_pk_set': participant_pk_set,
                                'wait_page_index': self._index_in_pages,
                            }, countdown=10)
                        return self._response_when_ready()
            return self._get_wait_page()