# pages.py from otree.api import Page, WaitPage # modern import path import time, random from .models import Constants from django.utils.translation import gettext as _ # ugettext is deprecated # --------------------------------------------------------------------- # Helper – call this from every page that needs the shared variables # --------------------------------------------------------------------- def shared_vars(page): return { 'total_letters': Constants.total_letters, 'd_total': Constants.d_total, 'b_total': Constants.b_total, 'num_rounds': Constants.num_rounds, 'treatment': page.player.participant.vars.get('treatment', ''), 'completion_url': Constants.completion_url, } # --------------------------------------------------------------------- # Base class: any page that doesn’t override vars_for_template inherits this # --------------------------------------------------------------------- class BasePage(Page): def vars_for_template(self): # Only the common variables return shared_vars(self) # --------------------------------------------------------------------- # Pages # --------------------------------------------------------------------- class Choice(BasePage): form_model = 'player' form_fields = ['tool'] def is_displayed(self): return self.round_number == 1 def vars_for_template(self): self.player.get_treatment() # store the time the page is first rendered self.participant.vars['choice_start_ts'] = time.time() return { 'time': time.time() + self.player.my_page_timeout_seconds, **shared_vars(self), } def before_next_page(self): start = self.participant.vars.pop('choice_start_ts', None) if start: self.player.choice_seconds = time.time() - start class Affect(BasePage): form_model = 'player' form_fields = ['affect'] def is_displayed(self): return self.round_number == 1 class FrQ(BasePage): form_model = 'player' form_fields = ['fair_1', 'fair_2', 'fair_3'] def is_displayed(self): return self.round_number == 1 class Quality(BasePage): form_model = 'player' form_fields = ['quality_1', 'quality_2'] def is_displayed(self): return self.round_number == 1 class Start_task(BasePage): form_model = 'player' def is_displayed(self): return self.round_number == 1 def before_next_page(self): self.participant.vars['tool'] = self.player.tool self.participant.vars['expiry_timestamp'] = time.time() + self.player.task_timer class TaskPaidPremiumTool(BasePage): form_model = 'player' form_fields = [] # ← make sure this is empty timer_text = 'Time left to complete as many matrices as possible:' def get_timeout_seconds(self): return self.participant.vars['expiry_timestamp'] - time.time() def is_displayed(self): return ( not should_end_task(self.player) and self.participant.vars['tool'] == 1 ) def get_form_fields(self): # letters_choices = [(idx, "choice_17", "d"), …] random.shuffle(self.participant.vars['letters_choices']) return [field for _, field, _ in self.participant.vars['letters_choices']] def vars_for_template(self): return { 'choices': self.participant.vars['letters_choices'], 'round_number': self.round_number, **shared_vars(self), } def before_next_page(self): # store answers back into participant vars for idx, field, _ in self.participant.vars['letters_choices']: self.participant.vars['letters_choices_made'][idx - 1] = getattr(self.player, field) self.player.count_effort() self.player.check_correctness() if self.round_number == 1: self.player.total_correct = self.player.correct else: prev_total = self.player.in_round(self.round_number - 1).total_correct self.player.total_correct = prev_total + self.player.correct class TaskPaidNoTool(TaskPaidPremiumTool): def is_displayed(self): return ( not should_end_task(self.player) and self.participant.vars['tool'] == 0 ) class ResultsPaid(BasePage): def is_displayed(self): return should_end_task(self.player) and not self.participant.vars.get('endgame_second') def before_next_page(self): self.participant.vars['total_correct'] = self.player.total_correct self.player.get_treatment() self.player.calculate_payoff() self.participant.vars['payoff'] = self.player.payoff self.participant.vars['reward_task'] = ( self.participant.vars['total_correct'] * Constants.matrix_rate ) self.player.participant_vars_dump = str(self.participant.vars) def vars_for_template(self): rows = [ dict( round_number=p.round_number, d_crossed=p.d_crossed, b_crossed=p.b_crossed, is_correct=p.is_correct, ) for p in self.player.in_all_rounds() ] self.participant.vars['t1_results'] = rows return { 'table_rows': rows, **shared_vars(self), } class AdditionalQuestions(BasePage): form_model = 'player' form_fields = ['mot_alert1', 'mot_alert2', 'mot_alert3'] def is_displayed(self): return should_end_task(self.player) and not self.participant.vars.get('endgame_second') class RecAver(BasePage): form_model = 'player' form_fields = [ 'reciprocity_1', 'reciprocity_2', 'reciprocity_3', 'reciprocity_4', 'reciprocity_5', 'reciprocity_6', 'reciprocity_7', 'reciprocity_8', 'reciprocity_9', 'reciprocity_10', 'reciprocity_11', ] def is_displayed(self): return should_end_task(self.player) and not self.participant.vars.get('endgame_second') class Suspicious(BasePage): form_model = 'player' form_fields = [ 'suspicious_1', 'suspicious_2', 'suspicious_3', 'suspicious_4', 'suspicious_5', 'suspicious_6', 'suspicious_7', 'suspicious_8', 'suspicious_9', ] def is_displayed(self): return should_end_task(self.player) and not self.participant.vars.get('endgame_second') class Reasons(BasePage): form_model = 'player' form_fields = ['reasons'] def is_displayed(self): return should_end_task(self.player) and not self.participant.vars.get('endgame_second') class Comments(BasePage): form_model = 'player' form_fields = ['comments'] def is_displayed(self): return should_end_task(self.player) and not self.participant.vars.get('endgame_second') class FinalScreen(BasePage): def is_displayed(self): return should_end_task(self.player) and not self.participant.vars.get('endgame_second') def vars_for_template(self): self.player.get_treatment() return { 'final_payoff': self.participant.vars['payoff'], 'final_total_correct': self.participant.vars['total_correct'], 'final_reward_task': self.participant.vars['reward_task'], **shared_vars(self), } class RoundKiller(Page): # no shared vars needed here def is_displayed(self): return True def before_next_page(self): self.player.update_endgame_second() # --------------------------------------------------------------------- # Utility # --------------------------------------------------------------------- def should_end_task(player): """Task ends once the master timer expires.""" return player.participant.vars.get('expiry_timestamp', 0) - time.time() < 0 # --------------------------------------------------------------------- # Page sequence # --------------------------------------------------------------------- page_sequence = [ Choice, Affect, FrQ, Quality, Start_task, TaskPaidPremiumTool, TaskPaidNoTool, ResultsPaid, RecAver, Suspicious, AdditionalQuestions, Reasons, Comments, FinalScreen, ]