from __future__ import annotations from dataclasses import dataclass from typing import Union, List import otree from django.conf import settings from django.utils import translation from otree.views import abstract, Page @dataclass class ProgressItem: title: str width: int css_class: str = 'col' class SurveyPage(Page): show_progress = True def get_context_data(self, **context): context['show_back'] = self.allow_back() context['progress'] = self._progress() response = abstract.FormPageOrInGameWaitPage.get_context_data(self, **context) return response def get(self): # activate user-specific language translation.activate(self.participant.vars.get('lang', settings.LANGUAGE_CODE)) return super(SurveyPage, self).get() def post(self): # evaluate form and save object response = super(SurveyPage, self).post() # go back if necessary if 'back' in self.request.POST: self._go_back() return self._redirect_to_page_the_user_should_be_on() return response def _get_page(self, page_index: int) -> abstract.FormPageOrInGameWaitPage: url = otree.lookup.url_i_should_be_on( self.participant.code, self.participant._session_code, page_index ) return abstract.get_view_from_url(url)() def _go_to_page(self, page_index: int) -> abstract.FormPageOrInGameWaitPage: # sanity checks assert 1 <= page_index <= self.participant._max_page_index, 'page_index out of range' page = self._get_page(page_index) # assert type(page) == abstract.WaitPage, 'cannot go back to a wait page' # TODO assert players_in_group == None # take participant to page_index self.participant._index_in_pages = page_index return page def _go_back(self): assert self._index_in_pages > 1, 'participant is on first page, cannot go back' try: # loop over pages backwards, break if displayed # similar to self._increment_index_in_pages() for page_index in range(self._index_in_pages-1, 0, -1): page = self._go_to_page(page_index) page.set_attributes(self.participant) if page.is_displayed(): break self._go_to_page(page_index) except: # reset the page index if something went wrong self.participant._index_in_pages = self._index_in_pages raise def allow_back(self) -> bool: return False # return self.participant._index_in_pages > 1 def _progress(self) -> Union[List[ProgressItem], None]: """If `show_progress` attribute is True, use the user-set `progress()` progress bars or (if unavailable) show the progress in terms of oTree page numbers (incl. hidden pages)""" if not self.show_progress: return None if user_progress := self.progress(): if type(user_progress) == int: user_progress = [ProgressItem('', user_progress)] return user_progress default_progress = int(100 * (self.participant._index_in_pages / self.participant._max_page_index)) return [ProgressItem('', default_progress)] def progress(self) -> Union[List[ProgressItem], int, None]: return None # list of fields we do not want to iterate over PLAYER_INTERNAL_FIELDS = ['id', 'id_in_group', '_payoff', 'participant', 'session', 'round_number', 'subsession', 'group'] @property def all_player_fields(self): """All but internal fields, in order of declaration""" return [f.name for f in self.player._meta.fields if f.name not in self.PLAYER_INTERNAL_FIELDS] class WaitPage(Page): show_progress = True def get_context_data(self, **context): context['show_back'] = False context['progress'] = self._progress() response = abstract.FormPageOrInGameWaitPage.get_context_data(self, **context) return response def get(self): # activate user-specific language translation.activate(self.participant.vars.get('lang', settings.LANGUAGE_CODE)) return super(SurveyPage, self).get() def post(self): # evaluate form and save object response = super(SurveyPage, self).post() # go back if necessary if 'back' in self.request.POST: self._go_back() return self._redirect_to_page_the_user_should_be_on() return response def _get_page(self, page_index: int) -> abstract.FormPageOrInGameWaitPage: url = otree.lookup.url_i_should_be_on( self.participant.code, self.participant._session_code, page_index ) return abstract.get_view_from_url(url)() def _go_to_page(self, page_index: int) -> abstract.FormPageOrInGameWaitPage: # sanity checks assert 1 <= page_index <= self.participant._max_page_index, 'page_index out of range' page = self._get_page(page_index) # assert type(page) == abstract.WaitPage, 'cannot go back to a wait page' # TODO assert players_in_group == None # take participant to page_index self.participant._index_in_pages = page_index return page def _go_back(self): assert self._index_in_pages > 1, 'participant is on first page, cannot go back' try: # loop over pages backwards, break if displayed # similar to self._increment_index_in_pages() for page_index in range(self._index_in_pages-1, 0, -1): page = self._go_to_page(page_index) page.set_attributes(self.participant) if page.is_displayed(): break self._go_to_page(page_index) except: # reset the page index if something went wrong self.participant._index_in_pages = self._index_in_pages raise def allow_back(self) -> bool: return False # return self.participant._index_in_pages > 1 def _progress(self) -> Union[List[ProgressItem], None]: """If `show_progress` attribute is True, use the user-set `progress()` progress bars or (if unavailable) show the progress in terms of oTree page numbers (incl. hidden pages)""" if not self.show_progress: return None if user_progress := self.progress(): if type(user_progress) == int: user_progress = [ProgressItem('', user_progress)] return user_progress default_progress = int(100 * (self.participant._index_in_pages / self.participant._max_page_index)) return [ProgressItem('', default_progress)] def progress(self) -> Union[List[ProgressItem], int, None]: return None # list of fields we do not want to iterate over PLAYER_INTERNAL_FIELDS = ['id', 'id_in_group', '_payoff', 'participant', 'session', 'round_number', 'subsession', 'group'] @property def all_player_fields(self): """All but internal fields, in order of declaration""" return [f.name for f in self.player._meta.fields if f.name not in self.PLAYER_INTERNAL_FIELDS] class AssessmentPage(Page): show_progress = True def get_context_data(self, **context): context['show_back'] = self.allow_back() context['progress'] = self._progress() response = abstract.FormPageOrInGameWaitPage.get_context_data(self, **context) return response def get(self): # activate user-specific language translation.activate(self.participant.vars.get('lang', settings.LANGUAGE_CODE)) return super(SurveyPage, self).get() def post(self): # evaluate form and save object response = super(SurveyPage, self).post() # go back if necessary if 'back' in self.request.POST: self._go_back() return self._redirect_to_page_the_user_should_be_on() return response def _get_page(self, page_index: int) -> abstract.FormPageOrInGameWaitPage: url = otree.lookup.url_i_should_be_on( self.participant.code, self.participant._session_code, page_index ) return abstract.get_view_from_url(url)() def _go_to_page(self, page_index: int) -> abstract.FormPageOrInGameWaitPage: # sanity checks assert 1 <= page_index <= self.participant._max_page_index, 'page_index out of range' page = self._get_page(page_index) # assert type(page) == abstract.WaitPage, 'cannot go back to a wait page' # TODO assert players_in_group == None # take participant to page_index self.participant._index_in_pages = page_index return page def _go_back(self): assert self._index_in_pages > 1, 'participant is on first page, cannot go back' try: # loop over pages backwards, break if displayed # similar to self._increment_index_in_pages() for page_index in range(self._index_in_pages-1, 0, -1): page = self._go_to_page(page_index) page.set_attributes(self.participant) if page.is_displayed(): break self._go_to_page(page_index) except: # reset the page index if something went wrong self.participant._index_in_pages = self._index_in_pages raise def allow_back(self) -> bool: return False # return self.participant._index_in_pages > 1 def _progress(self) -> Union[List[ProgressItem], None]: """If `show_progress` attribute is True, use the user-set `progress()` progress bars or (if unavailable) show the progress in terms of oTree page numbers (incl. hidden pages)""" if not self.show_progress: return None if user_progress := self.progress(): if type(user_progress) == int: user_progress = [ProgressItem('', user_progress)] return user_progress default_progress = int(100 * (self.participant._index_in_pages / self.participant._max_page_index)) return [ProgressItem('', default_progress)] def progress(self) -> Union[List[ProgressItem], int, None]: return None # list of fields we do not want to iterate over PLAYER_INTERNAL_FIELDS = ['id', 'id_in_group', '_payoff', 'participant', 'session', 'round_number', 'subsession', 'group'] @property def all_player_fields(self): """All but internal fields, in order of declaration""" return [f.name for f in self.player._meta.fields if f.name not in self.PLAYER_INTERNAL_FIELDS]