from otree.api import * import os import smtplib from email.message import EmailMessage doc = """ Pre-experiment sociodemographic and music personalization survey. """ ENABLE_EMAIL = False # ========================= # SMTP helper # ========================= def send_gmail_smtp(subject: str, body: str, to_email: str): gmail_user = os.environ.get("GMAIL_USER") gmail_app_pw = (os.environ.get("GMAIL_APP_PASSWORD") or "").replace(" ", "") if not gmail_user or not gmail_app_pw: raise RuntimeError("Missing GMAIL_USER / GMAIL_APP_PASSWORD env vars") msg = EmailMessage() msg["Subject"] = subject msg["From"] = gmail_user msg["To"] = to_email msg.set_content(body) with smtplib.SMTP("smtp.gmail.com", 587) as smtp: smtp.ehlo() smtp.starttls() smtp.login(gmail_user, gmail_app_pw) smtp.send_message(msg) class C(BaseConstants): NAME_IN_URL = 'thesis_pre_survey' PLAYERS_PER_GROUP = None NUM_ROUNDS = 1 class Subsession(BaseSubsession): pass class Group(BaseGroup): pass class Player(BasePlayer): # Basic Demographics q1_age = models.IntegerField(label="Age:", min=18, max=99) q2_gender = models.StringField( label="Gender:", choices=[ "Male", "Female", "Non-binary", "Other", "Prefer not to say", ], widget=widgets.RadioSelect, ) q3_nationality = models.StringField(label="Nationality:") # Educational Background q4_status = models.StringField( label="Current Status:", choices=[ "Student", "Working full-time", "Working part-time", "Not working / not studying", "Other", ], widget=widgets.RadioSelect, ) q5_degree = models.StringField( label="Highest completed degree:", choices=[ "No degree yet", "High school / Abitur", "Bachelor's degree", "Master's degree", "PhD / Doctorate", "Other", ], widget=widgets.RadioSelect, ) q6_field_of_study = models.StringField( label="Field of Study or Profession (if applicable):" ) # Familiarity with Tasks q7_prior_decision_experiment = models.BooleanField( label="Have you participated in decision-making experiments before?", widget=widgets.RadioSelect, ) q8_fin_task_familiarity = models.IntegerField( label="How familiar are you with financial decision-making tasks (e.g., lottery games)?", choices=[ [1, "Not at all familiar"], [2, "Slightly familiar"], [3, "Moderately familiar"], [4, "Very familiar"], ], widget=widgets.RadioSelect, ) # Numerical Task Experience q9_arithmetic_comfort = models.IntegerField( label="How comfortable are you with solving basic arithmetic problems (e.g., quick addition or subtraction)?", choices=[ [1, "Not confident"], [2, "Slightly confident"], [3, "Moderately confident"], [4, "Very confident"], ], widget=widgets.RadioSelect, ) # Financial Experience q10_investing_experience = models.IntegerField( label="Do you have experience with investing?", choices=[ [1, "No experience"], [2, "Rarely (e.g., small investments, beginner-level knowledge)"], [3, "Sometimes (e.g., occasional stock trading, mutual funds)"], [4, "Often (e.g., active investor, frequent stock or crypto trading)"], ], widget=widgets.RadioSelect, ) q11_overdrawn_frequency = models.IntegerField( label="How often is your bank account overdrawn (i.e., your balance goes below zero)?", choices=[ [1, "Never"], [2, "Rarely"], [3, "Frequently"], [4, "Always have a negative balance"], ], widget=widgets.RadioSelect, ) q12_gambling_experience = models.IntegerField( label="Do you have experience with gambling?", choices=[ [1, "No experience"], [2, "Rarely (e.g., casual betting, lottery tickets)"], [3, "Sometimes (e.g., occasional casino visits, sports betting)"], [4, "Often (e.g., regular gambling, poker, or high-risk betting)"], ], widget=widgets.RadioSelect, ) # Music Personalization – Q13 q13_music_frequency = models.IntegerField( label="How often do you listen to music while concentrating on cognitive or analytical tasks?", choices=[ [1, "Never"], [2, "Rarely"], [3, "Sometimes"], [4, "Often"], [5, "Always"], ], widget=widgets.RadioSelect, ) # Music Personalization – Q14 (lyrics vs instrumental) q14_lyrics = models.StringField( label="What kind of music would you prefer during cognitive or analytical tasks?", choices=[ "Only instrumental (no lyrics)", "Songs with lyrics", ], widget=widgets.RadioSelect, ) # Music Personalization – Q15 (genres, multi-select) q15_classical = models.BooleanField(label="Classical", blank=True) q15_lofi = models.BooleanField(label="Lo-fi / chill beats", blank=True) q15_ambient = models.BooleanField(label="Ambient / background", blank=True) q15_soundtrack = models.BooleanField(label="Film or game soundtracks", blank=True) q15_pop = models.BooleanField(label="Pop", blank=True) q15_rock = models.BooleanField(label="Rock", blank=True) q15_electronic = models.BooleanField(label="Electronic", blank=True) q15_jazz = models.BooleanField(label="Jazz", blank=True) q15_other = models.StringField(label="Other (optional):", blank=True) # Music Personalization – Q16 q16_music_effect = models.StringField( label="What type of music helps you concentrate during cognitive or analytical tasks?", choices=["Relaxing", "Neutral", "Energizing"], widget=widgets.RadioSelect, ) # Music Personalization – Q17 q17_tempo = models.StringField( label="Preferred music tempo while concentrating on cognitive or analytical tasks:", choices=["Slow", "Medium", "Fast"], widget=widgets.RadioSelect, ) # Music Personalization – Q18 (instruments, multi-select) q18_piano = models.BooleanField(label="Piano", blank=True) q18_guitar = models.BooleanField(label="Guitar", blank=True) q18_strings = models.BooleanField(label="Violin / strings", blank=True) q18_synth = models.BooleanField(label="Synthesizer / electronic sounds", blank=True) q18_drums = models.BooleanField(label="Drums / percussion", blank=True) q18_wind = models.BooleanField( label="Wind instruments (e.g. flute, saxophone)", blank=True ) q18_voice = models.BooleanField( label="Human voice (e.g. humming, choir)", blank=True ) q18_other = models.StringField(label="Other (optional):", blank=True) # Q19: Experiment code: assigned by the system (A01/B01/C01...) exp_code = models.StringField(blank=True) # Q20: Email address email = models.StringField(label="Please enter your e-mail address to receive your experiment code.") class PreSurvey(Page): template_name = 'thesis_pre_survey/PreSurvey.html' form_model = 'player' form_fields = [ 'q1_age', 'q2_gender', 'q3_nationality', 'q4_status', 'q5_degree', 'q6_field_of_study', 'q7_prior_decision_experiment', 'q8_fin_task_familiarity', 'q9_arithmetic_comfort', 'q10_investing_experience', 'q11_overdrawn_frequency', 'q12_gambling_experience', 'q13_music_frequency', 'q14_lyrics', 'q15_classical', 'q15_lofi', 'q15_ambient', 'q15_soundtrack', 'q15_pop', 'q15_rock', 'q15_electronic', 'q15_jazz', 'q15_other', 'q16_music_effect', 'q17_tempo', 'q18_piano', 'q18_guitar', 'q18_strings', 'q18_synth', 'q18_drums', 'q18_wind', 'q18_voice', 'q18_other', 'email' ] @staticmethod def before_next_page(player, timeout_happened): # 1) Assign experiment code idx = player.session.vars.get('code_index', 0) letter = ['A', 'B', 'C'][idx % 3] num = (idx // 3) + 1 code = f"{letter}{num:02d}" player.session.vars['code_index'] = idx + 1 player.exp_code = code # store for later apps (lab session) player.participant.vars['exp_code'] = code player.participant.vars['lab_code'] = code player.participant.vars['treatment'] = { 'A': 'no_music', 'B': 'slow_music', 'C': 'personalized_music', }[letter] # store email for cross-app access + monitor player.participant.vars['email'] = player.email # 2) Send email via SMTP — ONLY ONCE if ENABLE_EMAIL and not player.participant.vars.get('code_email_sent'): subject = "Your personal code for the lab experiment" body = ( "Hi!\n\n" "Thanks for completing the online pre-survey.\n\n" f"Your personal code is:\n\n" f" {player.exp_code}\n\n" "Please keep it safe — you will need it for the lab session.\n\n" "Best regards,\n" "Your thesis guide\n" "Gülfem" ) send_gmail_smtp( subject=subject, body=body, to_email=player.email, ) # 🔒 mark as sent so refresh/back does NOT resend player.participant.vars['code_email_sent'] = True class PreSurveyDone(Page): template_name = 'thesis_pre_survey/PreSurveyDone.html' pass page_sequence = [PreSurvey, PreSurveyDone]