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())
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())
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
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())
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())
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())
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())
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()
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})} )
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()
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()