from otree.api import * import settings import random import time from .models import C, DecisionRow, Player, choice_payoff, _sync_identity_state_to_participant_vars def _is_identity_disqualified(participant): return participant.vars.get( 'identity_disqualified', participant.vars.get('ethnicity_disqualified', False), ) def _is_identity_eligible(participant): return not _is_identity_disqualified(participant) class IdentityScreening(Page): form_model = 'player' def is_displayed(self): return self.player.treatment in ('gender', 'ethnicity') def get_form_fields(self): if self.player.treatment == 'gender': return ['gender'] if self.player.treatment == 'ethnicity': return ['ethnicity'] return [] def before_next_page(self): if self.player.treatment not in ('gender', 'ethnicity'): return gender = self.player.field_maybe_none('gender') ethnicity = self.player.field_maybe_none('ethnicity') disqualified = False needs_nonbinary_ethnicity = False if self.player.treatment == 'gender': normalized_gender = (gender or '').strip().lower() if normalized_gender in ('male', 'female'): disqualified = False elif normalized_gender in ('non-binary', 'nonbinary'): needs_nonbinary_ethnicity = True self.participant.vars['needs_nonbinary_ethnicity']=True self.player.treatment = 'ethnicity' elif self.player.treatment == 'ethnicity': disqualified = ethnicity not in C.ETHNICITIES self.participant.vars['identity_disqualified'] = disqualified self.participant.vars['ethnicity_disqualified'] = disqualified self.participant.vars['needs_nonbinary_ethnicity'] = needs_nonbinary_ethnicity self.participant.vars['identity_screening_gender'] = gender self.participant.vars['identity_screening_ethnicity'] = ethnicity if not needs_nonbinary_ethnicity: self.participant.vars['qualified'] = not disqualified class NonbinaryEthnicityScreen(Page): form_model = 'player' form_fields = ['ethnicity'] def is_displayed(self): return ( self.participant.vars.get('needs_nonbinary_ethnicity', False) ) def before_next_page(self): ethnicity = self.player.ethnicity disqualified = ethnicity not in C.ETHNICITIES self.participant.vars['identity_disqualified'] = disqualified self.participant.vars['ethnicity_disqualified'] = disqualified self.participant.vars['identity_screening_ethnicity'] = ethnicity self.participant.vars['needs_nonbinary_ethnicity'] = False self.participant.vars['qualified'] = not disqualified if disqualified: return self.player.treatment = 'ethnicity' self.participant.vars['treatment'] = 'ethnicity' rows = [r for r in DecisionRow.filter(player=self.player)] for row in rows: row.treatment = 'ethnicity' class IdentityDisqualify(Page): def is_displayed(self): return self.player.treatment in ('gender', 'ethnicity') and _is_identity_disqualified(self.participant) def vars_for_template(self): return dict( treatment=self.player.treatment, ) class BasicInstructions(Page): def vars_for_template(self): real_world_currency_per_point = self.session.config.get( 'real_world_currency_per_point', settings.SESSION_CONFIG_DEFAULTS.get('real_world_currency_per_point', 1), ) points_per_dollar = int( 1 / real_world_currency_per_point ) return dict( experimental_currency=getattr(settings, 'POINTS_CUSTOM_NAME', 'points'), points_per_dollar=points_per_dollar, ) class GameIntro(Page): def is_displayed(self): return _is_identity_eligible(self.participant) def vars_for_template(self): return dict( experimental_currency=getattr(settings, 'POINTS_CUSTOM_NAME', 'points'), ) def before_next_page(self): self.participant.vars['quiz_started_at'] = time.time() class ComprehensionQuiz(Page): form_model = 'player' form_fields = [ 'quiz_q1', 'quiz_q2', 'quiz_q3', 'quiz_q4', 'quiz_q5', ] timeout_seconds = C.QUIZ_MINUTES * 60 SOLUTIONS = dict( quiz_q1=1, # True quiz_q2=3, # 60 quiz_q3=2, # 40 quiz_q4=3, # 0 quiz_q5=3, # 40 ) def error_message(self, values): if self.player.quiz_failed: return None wrong = [k for k, v in self.SOLUTIONS.items() if values.get(k) != v] if not wrong: return None self.player.quiz_attempts += 1 self.player.quiz_wrong_total += len(wrong) if self.player.quiz_attempts >= C.MAX_ATTEMPTS: self.player.quiz_failed = True return None return {k: 'Incorrect.' for k in wrong} def before_next_page(self): if self.participant.vars.get('quiz_started_at',0): elapsed = time.time() - self.participant.vars.get('quiz_started_at',0) self.player.quiz_duration_sec = int(round(elapsed)) if self.timeout_happened: self.player.quiz_failed = True self.participant.vars['qualified'] = False else: self.participant.vars['qualified'] = True def vars_for_template(self): return dict( experimental_currency=getattr(settings, 'POINTS_CUSTOM_NAME', 'points'), debug_autofill=self.session.config.get('debug', False), quiz_solutions=self.SOLUTIONS, ) class QuizFailed(Page): def is_displayed(self): return _is_identity_eligible(self.participant) and self.player.quiz_failed class Priming(Page): form_model = 'player' def get_form_fields(self): if self.player.quiz_failed: return [] t = self.player.treatment if t in ('ethnicity', 'race'): return [ 'ethnic_generations', 'ethnic_identify_strength', # 'ethnicity', # 'ethnic_group_other', 'ethnic_roommate_pref', 'ethnic_adv_same_1', 'ethnic_adv_same_2', 'ethnic_adv_diff_1', 'ethnic_adv_diff_2', ] if t == 'gender': return [ # 'gender', 'gender_identify_strength', 'gender_importance', 'gender_living_pref', 'gender_adv_coed_1', 'gender_adv_coed_2', 'gender_adv_single_1', 'gender_adv_single_2', ] return [ 'control_cable_tv', 'control_roommate', 'control_roommate_pref', 'control_adv_quiet_1', 'control_adv_quiet_2', 'control_adv_social_1', 'control_adv_social_2', ] def before_next_page(self): if self.player.quiz_failed: return if self.player.treatment in ('ethnicity', 'race'): ethnicity = self.player.field_maybe_none('ethnicity') allowed = C.ETHNICITIES self.participant.vars['identity_disqualified'] = ( bool(ethnicity) and ethnicity not in allowed ) self.participant.vars['ethnicity_disqualified'] = self.participant.vars['identity_disqualified'] if self.participant.vars['identity_disqualified']: self.participant.vars['qualified'] = False def is_displayed(self): return _is_identity_eligible(self.participant) and not self.player.quiz_failed def vars_for_template(self): return dict( experimental_currency=getattr(settings, 'POINTS_CUSTOM_NAME', 'points'), debug_autofill=self.session.config.get('debug', False), debug_auto_submit=self.session.config.get('auto-submit', False) and self.session.config.get('debug', False) , ) class Decision(Page): live_method = 'live_method' def is_displayed(self): return _is_identity_eligible(self.participant) and not self.player.quiz_failed def before_next_page(self): _sync_identity_state_to_participant_vars(self.player) class BeliefIntro(Page): def is_displayed(self): return _is_identity_eligible(self.participant) and not self.player.quiz_failed def vars_for_template(self): return dict( experimental_currency=getattr(settings, 'POINTS_CUSTOM_NAME', 'points'), belief_payoffs=C.BELIEF_PAYOFFS, ) class BeliefQuiz(Page): form_model = 'player' def get_form_fields(self): fields = [ 'belief_q1', 'belief_q3', ] if self.player.treatment != 'baseline': fields.append('belief_q2') return fields def is_displayed(self): return not self.player.quiz_failed def error_message(self, values): solutions = dict( belief_q1=4, # 0 belief_q3=3, # 200 belief_q2=1, # one belief answer ) wrong = [k for k, v in solutions.items() if k in values and values.get(k) != v] if not wrong: return None return {k: 'Incorrect.' for k in wrong} def vars_for_template(self): return dict( experimental_currency=getattr(settings, 'POINTS_CUSTOM_NAME', 'points'), ) class Beliefs(Page): live_method = 'live_method' def is_displayed(self): return _is_identity_eligible(self.participant) and not self.player.quiz_failed def before_next_page(self): # Persist latest rows to participant.vars for payment app payoff logic. _sync_identity_state_to_participant_vars(self.player) def vars_for_template(self): return dict( experimental_currency=getattr(settings, 'POINTS_CUSTOM_NAME', 'points'), belief_payoffs=C.BELIEF_PAYOFFS, ) page_sequence = [ IdentityScreening, NonbinaryEthnicityScreen, IdentityDisqualify, GameIntro, ComprehensionQuiz, QuizFailed, Priming, Decision, BeliefIntro, # BeliefQuiz, Beliefs, ]