from otree.api import *
import random
import cpo_shared
from collections import OrderedDict
doc = """
LIONESS SURVEY SECTION
"""
def read_csv(filename):
import csv, os
path = os.path.join(os.path.dirname(__file__), filename)
f = open(path, encoding='utf-8-sig')
return list(csv.DictReader(f))
def order_items(rows, shuffle_blocks=True):
"""Shuffle within each subscale, then (optionally) shuffle the blocks.
Rows with empty/absent subscale collapse to one block -> plain shuffle."""
groups = OrderedDict()
for r in rows:
groups.setdefault(r.get('subscale', '') or '', []).append(r['var_name'])
for items in groups.values():
random.shuffle(items)
keys = list(groups.keys())
if shuffle_blocks:
random.shuffle(keys)
return [v for k in keys for v in groups[k]]
# ---------------------------------------------------------------------------
# Sezione 3C - list experiment statements
# ---------------------------------------------------------------------------
LIST_BASELINE = [
"Le aziende dovrebbero essere obbligate a pubblicare i dati sui salari per genere.",
"I congedi parentali dovrebbero essere obbligatori anche per i padri.",
"Le politiche sulle quote di genere nelle assunzioni sono ingiuste nei confronti degli uomini.",
"Il merito dovrebbe essere l'unico criterio nella selezione del personale, indipendentemente dal genere.",
"Le differenze di carriera tra uomini e donne dipendono principalmente dalle loro scelte individuali."
]
# One sensitive item per treatment arm. The control arm (1) gets none.
# arm 2 = approval framing (matches the direct item opinione_titolo)
# arm 3 = harm framing, arm4 new
LIST_SENSITIVE = {
2: "Sarebbe più appropriato che una donna usasse il titolo professionale declinato al maschile (es. avvocato, ingegnere).",
3: "In alcune professioni, l’utilizzo del titolo declinato al femminile (es. avvocata, ingegnera) rischia di penalizzare le donne che svolgono quella professione, anche quelle che non lo usano.",
4: "Una donna che utilizza il titolo professionale declinato al femminile (es. avvocata, ingegnera) trasmette un'immagine di sé più vicina alle posizioni femministe."
}
LIST_ARMS = [1, 2, 3, 4] # 1 = controllo, 2 = TG approvazione, 3 = TG danno, 4 = segnalazione
# Likert scale anchor labels (header row of each table) and the
# corresponding (value, label) choices used by the IntegerFields.
HEADERS_L6 = [
'completamente in disaccordo', 'in disaccordo', 'abbastanza in disaccordo',
"abbastanza d'accordo", "d'accordo", "completamente d'accordo",
]
CHOICES_ASI = [[i, f'{i+1} - {lab}'] for i, lab in enumerate(HEADERS_L6)]
HEADERS_L7 = [
"completamente in disaccordo", "in disaccordo", "abbastanza in disaccordo",
"né d'accordo né in disaccordo", "abbastanza d'accordo", "d'accordo",
"completamente d'accordo"
]
CHOICES_MS = [[i + 1, f'{i + 1} - {lab}'] for i, lab in enumerate(HEADERS_L7)]
HEADERS_GFL1 = [
'molto contrario', 'contrario', "né contrario né d'accordo",
"d'accordo", "molto d'accordo",
]
CHOICES_GFL1 = [[i + 1, f'{i + 1} - {lab}'] for i, lab in enumerate(HEADERS_GFL1)]
HEADERS_GFL2 = [
'per nulla sessiste', 'probabilmente non sessiste',
'né sessiste né non sessiste', 'in qualche modo sessiste', 'molto sessiste',
]
CHOICES_GFL2 = [[i + 1, f'{i + 1} - {lab}'] for i, lab in enumerate(HEADERS_GFL2)]
# POLID (Likert 4)
HEADERS_L4 = [
"completamente in disaccordo", "in disaccordo", "d'accordo", "completamente d'accordo"
]
CHOICES_POLID = [[i + 1, f'{i + 1} - {lab}'] for i, lab in enumerate(HEADERS_L4)]
# Belief in Sexism Shift Scale (BSS) - LIKERT 7
CHOICES_BSS = [[i + 1, f'{i + 1} - {lab}'] for i, lab in enumerate(HEADERS_L7)]
# ATGIL - Attitude Towards Gender-Inclusive Language (Schwarz et al. (2026) - LIKERT 5
HEADERS_L5 = [
"completamente in disaccordo", "in disaccordo", "né d'accordo né in disaccordo", "d'accordo", "completamente d'accordo"
]
CHOICES_ATGIL = [[i + 1, f'{i + 1} - {lab}'] for i, lab in enumerate(HEADERS_L5)]
CHOICES_NORME = [[i + 1, f'{i + 1} - {lab}'] for i, lab in enumerate(HEADERS_L4)]
CHOICES_BY_SCALE = {
'norme_sociali': CHOICES_NORME,
'asi_hs': CHOICES_ASI,
'ms': CHOICES_MS,
'gfl_sez1': CHOICES_GFL1,
'gfl_sez2': CHOICES_GFL2,
'polid': CHOICES_POLID,
'bss': CHOICES_BSS,
'atgil': CHOICES_ATGIL
}
HEADERS_BY_SCALE = {
'norme_sociali': HEADERS_L4,
'asi_hs': HEADERS_L6,
'ms': HEADERS_L7,
'gfl_sez1': HEADERS_GFL1,
'gfl_sez2': HEADERS_GFL2,
'polid': HEADERS_L4,
'bss': HEADERS_L7,
'atgil': HEADERS_L5
}
class C(BaseConstants):
NAME_IN_URL = 'sezioneB'
PLAYERS_PER_GROUP = None
NUM_ROUNDS = 1
professions = read_csv('professions.csv')
scales = read_csv('scales.csv')
term_choices = read_csv('term_choices.csv')
N_PAGES = 10
not_mandatory = False
LIKERT_STYLE = 'b' # 'a' = vertical labelled · 'b' = single row · 'c' = compact vertical
# TEMPLATES
next_progress = 'cpo_survey/_templates/next_progress.html'
likert_css = 'cpo_survey/_templates/_likert_css.html'
class Subsession(BaseSubsession):
pass
def creating_session(subsession: Subsession):
for p in subsession.get_players():
# --- Sezione B (professions/sports): same mechanism as sparkle ---
p.prof_condition = random.choice([1, 2, 3])
col_order = [1, 2, 3]
random.shuffle(col_order)
p.prof_col_order = ','.join(map(str, col_order))
# --- Sezione C: randomized order of the 4 professions ---
belief_order = [prof['key'] for prof in cpo_shared.PROFESSIONS]
random.shuffle(belief_order)
p.participant.vars['cpo_survey_belief_order'] = belief_order
# Randomizzazione colonne Scenario Anna:
# Ordine codici interni: 1 = Maschile, 2 = Femminile. 3 (Entrambe) resta fisso alla fine.
anna_order = [1, 2]
random.shuffle(anna_order)
# anna_order.append(3) # 'Entrambe' rimane sempre l'ultimo elemento
p.anna_col_order = ','.join(map(str, anna_order))
# Trattamenti Scenario Anna
p.anna_treatment_pct = random.choice([25, 50])
p.anna_anchor = random.choice(['femminile', 'maschile'])
# --- Sezione 3C: list-experiment arm + this participant's statements ---
# (assigned INDEPENDENTLY of the cpo_shared T1-T4 title treatment)
arm = random.choices(LIST_ARMS, weights=[30, 30, 20, 20], k=1)[0]
p.list_arm = arm
statements = list(LIST_BASELINE)
if arm in LIST_SENSITIVE:
statements.append(LIST_SENSITIVE[arm])
random.shuffle(statements)
p.participant.vars['cpo_list_statements'] = statements
item_order = {}
for scale_name in CHOICES_BY_SCALE:
rows = [r for r in C.scales if r['scale'] == scale_name]
item_order[scale_name] = order_items(rows)
term_order = [r['var_name'] for r in C.term_choices]
random.shuffle(term_order)
item_order['gfl_sez3'] = term_order
# # --- Sezioni D/E/F: randomize item order within each scale, once ---
# item_order = {}
# for scale_name in CHOICES_BY_SCALE:
# var_names = [r['var_name'] for r in C.scales if r['scale'] == scale_name]
# random.shuffle(var_names)
# item_order[scale_name] = var_names
# term_order = [r['var_name'] for r in C.term_choices]
# random.shuffle(term_order)
# item_order['gfl_sez3'] = term_order
p.participant.vars['cpo_survey_item_order'] = item_order
class Group(BaseGroup):
pass
# FIELD CONSTRUCTORS ----------------------------------------------------------
def prof_scale(label):
return models.IntegerField(
choices=[
[1, 'Ad un uomo'],
[2, 'Ad una donna'],
[3, 'Ad entrambi'],
],
label=label,
widget=widgets.RadioSelect(),
blank = C.not_mandatory
)
def likert_scale(label, choices):
return models.IntegerField(
choices=choices,
label=label,
widget=widgets.RadioSelect(),
blank = C.not_mandatory
)
def term_choice(label, term1, term5):
return models.IntegerField(
choices=[[1, term1], [5, term5]],
label=label,
widget=widgets.RadioSelect(),
blank = C.not_mandatory
)
def belief_pct(label):
return models.IntegerField(
label=label,
min=0,
max=100,
blank = C.not_mandatory
)
def _choices_from(choice_list):
"""Turn a [[value, 'n - text'], ...] list into render-ready dicts."""
out = []
for v, lab in choice_list:
txt = lab.split(' - ', 1)[-1]
out.append({'value': v, 'num': v, 'text': txt})
return out
def consiglio_scale(label):
"""Sezione 3D - 3-way title-form advice for the Anna vignette.
codes: 1 = maschile, 2 = femminile, 3 = entrambe."""
return models.IntegerField(
choices=[
[1, 'La forma maschile'],
[2, 'La forma femminile']
],
label=label,
widget=widgets.RadioSelect(),
blank=C.not_mandatory,
)
def _choices_from(choice_list):
"""Turn a [[value, 'n - text'], ...] list into render-ready dicts."""
out = []
for v, lab in choice_list:
txt = lab.split(' - ', 1)[-1]
out.append({'value': v, 'num': v, 'text': txt})
return out
class Player(BasePlayer):
# --- Sezione B: professioni e sport ---
prof_condition = models.IntegerField()
prof_col_order = models.StringField()
for i in C.professions:
locals()[i['var_name']] = prof_scale(i['g2_label'])
del i
# --- Sezione C: beliefs su % donne / % titolo femminile ---
for _prof in cpo_shared.PROFESSIONS:
locals()[f"belief_pct_donna_{_prof['key']}"] = belief_pct(
f"Su 100 persone che esercitano la professione "
f"{_prof['ambito'].lower()} in Italia, quante pensa siano donne?"
)
locals()[f"belief_pct_titolo_{_prof['key']}"] = belief_pct(
f"Su 100 donne che esercitano questa professione, quante pensa "
f"che si presentino con il titolo \"{_prof['fem_title'].lower()}\" "
f"anziché \"{_prof['masc_title'].lower()}\"?"
)
del _prof
# --- Sezione 3C: list experiment + opinione diretta + credenze 2° ordine ---
list_arm = models.IntegerField()
list_count = models.IntegerField(
blank=C.not_mandatory,
label="Con quante di queste affermazioni è d'accordo?",
)
# Opinione diretta (solo gruppo di controllo) - stesso enunciato del
# list experiment (arm 2), così list-inferred vs diretto = wedge SDB.
opinione_titolo = models.IntegerField(
choices=[[1, "Sì, sono d'accordo"], [0, "No, non sono d'accordo"]],
widget=widgets.RadioSelect,
blank=C.not_mandatory,
label="“Sarebbe più appropriato che una donna usasse il titolo professionale declinato al maschile (es. avvocato, ingegnere).”",
)
opinione_danno = models.IntegerField(
choices=[[1, "Sì, sono d'accordo"], [0, "No, non sono d'accordo"]],
widget=widgets.RadioSelect,
blank=C.not_mandatory,
label="“In alcune professioni, l’utilizzo del titolo declinato al femminile (es. avvocata, ingegnera) rischia di penalizzare le donne che svolgono quella professione, anche quelle che non lo usano.”",
)
opinione_segnalazione = models.IntegerField(
choices=[[1, "Sì, sono d'accordo"], [0, "No, non sono d'accordo"]],
widget=widgets.RadioSelect,
blank=C.not_mandatory,
label="“Una donna che utilizza il titolo professionale declinato al femminile (es. avvocata, ingegnera) trasmette un'immagine di sé più vicina alle posizioni femministe.”",
)
# Credenze di secondo ordine (solo gruppo di controllo), per genere x
# Italia / regione / settore professionale. Il testo completo della
# domanda è iniettato nel template (vedi CredenzeTitolo.vars_for_template).
so_naz_uomini = belief_pct("")
so_naz_donne = belief_pct("")
so_reg_uomini = belief_pct("")
so_reg_donne = belief_pct("")
# --- Sezione 3D: scenario Anna (threshold / conformity) - TUTTI ---
anna_col_order = models.StringField() # Salva l'ordine delle colonne per questo partecipante
consiglio_anna_10 = consiglio_scale(
"Se meno di 1 donna su 10 nel suo settore usa la forma femminile")
consiglio_anna_30 = consiglio_scale(
"Se circa 3 donne su 10 usano la forma femminile")
consiglio_anna_50 = consiglio_scale(
"Se circa la metà delle donne usa la forma femminile")
consiglio_anna_70 = consiglio_scale(
"Se circa 7 donne su 10 usano la forma femminile")
consiglio_anna_90 = consiglio_scale(
"Se più di 9 donne su 10 nel suo settore usano la forma femminile")
anna_treatment_pct = models.IntegerField() # Salva il trattamento: 25 o 50
anna_anchor = models.StringField() # Salva l'ancoraggio: 'femminile' o 'maschile'
# Lasciamo le etichette vuote nel database perché verranno iniettate dinamicamente dalla pagina
consiglio_anna_10 = consiglio_scale("")
consiglio_anna_30 = consiglio_scale("")
consiglio_anna_50 = consiglio_scale("")
consiglio_anna_70 = consiglio_scale("")
consiglio_anna_90 = consiglio_scale("")
# --- Sezioni D, E, F (Sezione 1 e 2): from scales.csv ---
for _row in C.scales:
if _row["scale"] in ['polid', 'ms', 'atgil']:
locals()[_row['var_name']] = likert_scale(_row['label'], CHOICES_BY_SCALE[_row['scale']])
del _row
# --- Sezione F, Sezione 3: forced binary term-choice items ---
# for _row in C.term_choices:
# locals()[_row['var_name']] = term_choice(_row['label'], _row['term1'], _row['term5'])
# del _row
# === Dynamic per-player column order for the Sezione B radios (sparkle pattern) ===
def _prof_choices(player):
order = [int(x) for x in player.prof_col_order.split(",")]
labels = {
1: 'Ad un uomo',
2: 'Ad una donna',
3: 'Ad entrambi',
}
return [[i, labels[i]] for i in order]
for _r in C.professions:
globals()[f"{_r['var_name']}_choices"] = _prof_choices
del _r
# === Sezione 3C: dynamic 0..N choices for the list-experiment count ===
def list_count_choices(player):
"""RadioSelect 0..N where N = number of statements shown to this player
(4 for control, 5 for the treatment arms)."""
n = len(player.participant.vars['cpo_list_statements'])
return [[i, str(i)] for i in range(0, n + 1)]
# PAGES -------------------------------------------------------------------
class PS(Page):
template_name = 'cpo_survey/ProfessioniSport.html'
form_model = 'player'
@staticmethod
def get_form_fields(player: Player):
return [r['var_name'] for r in C.professions]
@staticmethod
def vars_for_template(player: Player):
non_sport = [r for r in C.professions if r['category'] != 'SPORT']
sport = [r for r in C.professions if r['category'] == 'SPORT']
random.shuffle(non_sport)
random.shuffle(sport)
ordered = non_sport + sport
labels = {
1: 'Ad un uomo',
2: 'Ad una donna',
3: 'Ad entrambi',
}
order = [int(x) for x in player.prof_col_order.split(",")]
column_headers = [labels[i] for i in order]
cond = player.prof_condition
label_key = {1: 'g1_label', 2: 'g2_label', 3: 'g3_label'}[cond]
items = [
{'var_name': r['var_name'], 'display_label': r[label_key]}
for r in ordered
]
return dict(
items=items,
column_headers=column_headers,
progress_pct=50 + round(50 * 1 / C.N_PAGES),
)
# ===========================================================================
# Sezione 3C - list experiment (everyone) + direct item & beliefs (control)
# ===========================================================================
class LA(Page):
template_name = 'cpo_survey/ListaAffermazioni.html'
form_model = 'player'
form_fields = ['list_count']
@staticmethod
def vars_for_template(player: Player):
statements = list(LIST_BASELINE)
if player.list_arm in LIST_SENSITIVE:
statements.append(LIST_SENSITIVE[player.list_arm])
random.shuffle(statements)
player.participant.vars['cpo_list_statements'] = statements
return dict(
statements=statements,
progress_pct=round(100 * 2 / C.N_PAGES),
)
class OpinioneTitolo(Page):
form_model = 'player'
@staticmethod
def get_form_fields(player: Player):
fields = []
if player.list_arm != 2:
fields.append('opinione_titolo')
if player.list_arm != 3:
fields.append('opinione_danno')
if player.list_arm != 4:
fields.append('opinione_segnalazione')
return fields
@staticmethod
def vars_for_template(player: Player):
return dict(
show_titolo=player.list_arm != 2,
show_danno=player.list_arm != 3,
show_segnalazione=player.list_arm != 4,
progress_pct=50 + round(50 * 3 / C.N_PAGES),
)
class CT(Page):
template_name = 'cpo_survey/CredenzeTitolo.html'
form_model = 'player'
@staticmethod
def is_displayed(player: Player):
return player.list_arm in (1, 3, 4)
@staticmethod
def get_form_fields(player: Player):
fields = ['so_naz_uomini', 'so_naz_donne', 'so_reg_uomini', 'so_reg_donne']
return fields
@staticmethod
def vars_for_template(player: Player):
status = player.participant.vars.get('cpo_stato_lavorativo', 1)
info = cpo_shared.STATO_INFO[status]
return dict(
reg_label=info['reg_belief'],
prof_label=info['prof_belief'],
show_settore=info['prof_belief'] is not None,
progress_pct=50 + round(50 * 4 / C.N_PAGES),
)
# class CredenzeTitolo(Page):
# """Second-order beliefs (CONTROL group only) - 3 cards style."""
# form_model = 'player'
# form_fields = [
# 'so_naz_uomini', 'so_naz_donne',
# 'so_reg_uomini', 'so_reg_donne',
# 'so_settore_uomini', 'so_settore_donne',
# ]
# @staticmethod
# def is_displayed(player: Player):
# return player.list_arm in (1, 3)
# @staticmethod
# def vars_for_template(player: Player):
# # Protezione campo 'regione' se vuoto
# reg = "REGIONE" # player.field_maybe_none('regione')
# if not reg or reg.startswith('Altro'):
# reg_txt = "nella sua regione di residenza"
# else:
# reg_txt = f"nella sua regione di residenza"
# # CORREZIONE: Protezione campo 'professione_attuale' usando field_maybe_none()
# prof = "PROFESSIONE" #player.field_maybe_none('professione_attuale')
# if not prof:
# prof_txt = "che svolgono la sua stessa professione"
# else:
# prof_txt = f"che svolgono la sua stessa professione"
# return dict(
# reg_label=reg_txt,
# prof_label=prof_txt,
# progress_pct=round(100 * 5 / C.N_PAGES)
# )
# class CredenzeTitolo(Page):
# """Second-order beliefs (CONTROL group only): for each of 6 reference
# groups (gender x {Italia / own region / own professional field}), the
# perceived share who think it is right for a woman to use the feminine
# title. The question text is built here so the region and profession the
# participant reported in Demografia can be injected."""
# form_model = 'player'
# form_fields = [
# 'so_naz_uomini', 'so_naz_donne',
# 'so_reg_uomini', 'so_reg_donne',
# 'so_settore_uomini', 'so_settore_donne',
# ]
# @staticmethod
# def is_displayed(player: Player):
# return player.list_arm == 1
# @staticmethod
# def vars_for_template(player: Player):
# reg = player.field_maybe_none('regione')
# if not reg or reg.startswith('Altro'):
# reg_txt = "che vivono nella sua regione di origine"
# else:
# reg_txt = f"che vivono nella sua regione ({reg})"
# prof = player.field_maybe_none('professione_attuale')
# prof_txt = "che svolgono la sua stessa professione"
# if prof:
# prof_txt += f" ({prof})"
# base = ("ritengono che sarebbe più appropriato che una donna usasse il titolo professionale declinato al femminile (es. avvocata, ingegnera)")
# items = [
# dict(field='so_naz_uomini',
# label=f"Su 100 UOMINI in Italia, quanti pensa {base}?"),
# dict(field='so_naz_donne',
# label=f"Su 100 DONNE in Italia, quante pensa {base}?"),
# dict(field='so_reg_uomini',
# label=f"Su 100 UOMINI {reg_txt}, quanti pensa {base}?"),
# dict(field='so_reg_donne',
# label=f"Su 100 DONNE {reg_txt}, quante pensa {base}?"),
# dict(field='so_settore_uomini',
# label=f"Su 100 UOMINI {prof_txt}, quanti pensa {base}?"),
# dict(field='so_settore_donne',
# label=f"Su 100 DONNE {prof_txt}, quante pensa {base}?"),
# ]
# return dict(items=items, progress_pct=round(100 * 5 / C.N_PAGES))
class SA(Page):
template_name = 'cpo_survey/ScenarioAnna.html'
form_model = 'player'
form_fields = [
'consiglio_anna_10',
'consiglio_anna_30',
'consiglio_anna_50',
'consiglio_anna_70',
'consiglio_anna_90'
]
@staticmethod
def vars_for_template(player: Player):
labels_map = {
1: 'La forma maschile',
2: 'La forma femminile'
}
order = [int(x) for x in player.anna_col_order.split(",") if int(x) in labels_map]
column_headers = [labels_map[i] for i in order]
# Recuperiamo l'ancoraggio assegnato al giocatore
anchor = player.anna_anchor # 'femminile' o 'maschile'
# Costruiamo il testo delle righe in modo grammaticalmente corretto e uniforme
if anchor == 'femminile':
rows = [
{'field_name': 'consiglio_anna_10', 'label': 'Se meno di 1 donna su 10 nella sua professione usa la forma femminile'},
{'field_name': 'consiglio_anna_30', 'label': 'Se circa 3 donne su 10 nella sua professione usano la forma femminile'},
{'field_name': 'consiglio_anna_50', 'label': 'Se circa 5 donne su 10 nella sua professione usano la forma femminile'},
{'field_name': 'consiglio_anna_70', 'label': 'Se circa 7 donne su 10 nella sua professione usano la forma femminile'},
{'field_name': 'consiglio_anna_90', 'label': 'Se più di 9 donne su 10 nella sua professione usano la forma femminile'},
]
else: # maschile
rows = [
{'field_name': 'consiglio_anna_10', 'label': 'Se meno di 1 donna su 10 nella sua professione usa la forma maschile'},
{'field_name': 'consiglio_anna_30', 'label': 'Se circa 3 donne su 10 nella sua professione usano la forma maschile'},
{'field_name': 'consiglio_anna_50', 'label': 'Se circa 5 donne su 10 nella sua professione usano la forma maschile'},
{'field_name': 'consiglio_anna_70', 'label': 'Se circa 7 donne su 10 nella sua professione usano la forma maschile'},
{'field_name': 'consiglio_anna_90', 'label': 'Se più di 9 donne su 10 nella sua professione usano la forma maschile'},
]
return dict(
column_headers=column_headers,
column_order=order,
rows=rows,
treatment_pct=player.anna_treatment_pct,
progress_pct=50 + round(50 * 5 / C.N_PAGES)
)
class BP(Page):
template_name = 'cpo_survey/BeliefsProfessioni.html'
form_model = 'player'
@staticmethod
def get_form_fields(player: Player):
order = player.participant.vars['cpo_survey_belief_order']
fields = []
for key in order:
fields.append(f'belief_pct_donna_{key}')
fields.append(f'belief_pct_titolo_{key}')
return fields
@staticmethod
def vars_for_template(player: Player):
order = player.participant.vars['cpo_survey_belief_order']
items = []
for key in order:
prof = cpo_shared.PROFESSION_BY_KEY[key]
items.append(dict(
profession=prof,
field_donna=f'belief_pct_donna_{key}',
field_titolo=f'belief_pct_titolo_{key}',
))
return dict(
items=items,
progress_pct=50 + round(50 * 6 / C.N_PAGES),
)
class NS(Page):
template_name = 'cpo_survey/NormeSociali.html'
form_model = 'player'
@staticmethod
def get_form_fields(player: Player):
order = player.participant.vars['cpo_survey_item_order']
return order['polid'] # + order['norme_sociali']
@staticmethod
def vars_for_template(player: Player):
order = player.participant.vars['cpo_survey_item_order']
by_var = {r['var_name']: r for r in C.scales}
def items_for(k):
return [{'var_name': v, 'label': by_var[v]['label']} for v in order[k]]
polid_choices = _choices_from(CHOICES_POLID)
# norme_choices = _choices_from(CHOICES_NORME)
return dict(
likert_style=C.LIKERT_STYLE,
polid_items=items_for('polid'),
polid_choices=polid_choices,
polid_low=polid_choices[0]['text'], polid_high=polid_choices[-1]['text'],
polid_headers=HEADERS_L4,
# norme_items=items_for('norme_sociali'),
# norme_choices=norme_choices,
# norme_low=norme_choices[0]['text'], norme_high=norme_choices[-1]['text'],
# norme_headers=HEADERS_L4,
progress_pct=50 + round(50 * 7 / C.N_PAGES),
)
# class NormeSociali(Page):
# form_model = 'player'
# @staticmethod
# def get_form_fields(player: Player):
# order = player.participant.vars['cpo_survey_item_order']
# return order['polid'] + order['norme_sociali']
# @staticmethod
# def vars_for_template(player: Player):
# order = player.participant.vars['cpo_survey_item_order']
# by_var = {r['var_name']: r for r in C.scales}
# polid_items = [{'var_name': v, 'label': by_var[v]['label']} for v in order['polid']]
# norme_items = [{'var_name': v, 'label': by_var[v]['label']} for v in order['norme_sociali']]
# return dict(
# polid_items = polid_items,
# polid_headers = HEADERS_L4,
# norme_items = norme_items,
# headers = HEADERS_NORME,
# progress_pct=round(100 * 4 / C.N_PAGES),
# )
class GA(Page):
template_name = 'cpo_survey/Sessismo.html'
form_model = 'player'
@staticmethod
def get_form_fields(player: Player):
order = player.participant.vars['cpo_survey_item_order']
return order['ms']
@staticmethod
def vars_for_template(player: Player):
order = player.participant.vars['cpo_survey_item_order']
by_var = {r['var_name']: r for r in C.scales}
def items_for(k):
return [{'var_name': v, 'label': by_var[v]['label']} for v in order[k]]
# asi_choices = _choices_from(CHOICES_ASI)
ms_choices = _choices_from(CHOICES_MS)
return dict(
likert_style=C.LIKERT_STYLE,
ms_items=items_for('ms'),
ms_choices=ms_choices,
ms_low=ms_choices[0]['text'], ms_high=ms_choices[-1]['text'],
ms_headers=HEADERS_L7,
progress_pct=50 + round(50 * 8 / C.N_PAGES),
)
# class Sessismo(Page):
# form_model = 'player'
# @staticmethod
# def get_form_fields(player: Player):
# order = player.participant.vars['cpo_survey_item_order']
# return order['asi_hs'] + order['ms'] + order['bss']
# @staticmethod
# def vars_for_template(player: Player):
# order = player.participant.vars['cpo_survey_item_order']
# by_var = {r['var_name']: r for r in C.scales}
# asi_items = [{'var_name': v, 'label': by_var[v]['label']} for v in order['asi_hs']]
# ms_items = [{'var_name': v, 'label': by_var[v]['label']} for v in order['ms']]
# bss_items = [{'var_name': v, 'label': by_var[v]['label']} for v in order['bss']]
# return dict(
# asi_items=asi_items,
# asi_headers=HEADERS_ASI,
# ms_items=ms_items,
# ms_headers=HEADERS_L7,
# bss_items=bss_items,
# bss_headers=HEADERS_L7,
# progress_pct=round(100 * 5 / C.N_PAGES),
# )
# TODO: ATGIL scale (Schwarz et al. 2026) - add once the Italian item text
# (full + shortened versions) is supplied. See LinguaggioGenere.html for the
# placeholder block where it should be inserted.
class LG(Page):
template_name = 'cpo_survey/LinguaggioGenere.html'
form_model = 'player'
@staticmethod
def get_form_fields(player: Player):
order = player.participant.vars['cpo_survey_item_order']
return order['atgil'] # + order['gfl_sez1'] + order['gfl_sez2'] + order['gfl_sez3']
@staticmethod
def vars_for_template(player: Player):
order = player.participant.vars['cpo_survey_item_order']
by_var = {r['var_name']: r for r in C.scales}
by_var_term = {r['var_name']: r for r in C.term_choices}
def items_for(k):
return [{'var_name': v, 'label': by_var[v]['label']} for v in order[k]]
atgil_choices = _choices_from(CHOICES_ATGIL)
# sez1_choices = _choices_from(CHOICES_GFL1)
# sez2_choices = _choices_from(CHOICES_GFL2)
return dict(
likert_style=C.LIKERT_STYLE,
atgil_items=items_for('atgil'),
atgil_choices=atgil_choices,
atgil_low=atgil_choices[0]['text'], atgil_high=atgil_choices[-1]['text'],
atgil_headers=HEADERS_L5,
# sez1_items=items_for('gfl_sez1'),
# sez1_choices=sez1_choices,
# sez1_low=sez1_choices[0]['text'], sez1_high=sez1_choices[-1]['text'],
# sez1_headers=HEADERS_GFL1,
# sez2_items=items_for('gfl_sez2'),
# sez2_choices=sez2_choices,
# sez2_low=sez2_choices[0]['text'], sez2_high=sez2_choices[-1]['text'],
# sez2_headers=HEADERS_GFL2,
# sez3_items=[{'var_name': v, 'label': by_var_term[v]['label']} for v in order['gfl_sez3']],
progress_pct=50 + round(50 * 9 / C.N_PAGES),
)
class Final(Page):
@staticmethod
def vars_for_template(player):
# Build the dynamic link for your survey platform (e.g., Qualtrics, Prolific, etc.)
participant_label = player.participant.label or "TEST_ID"
external_url = f"https://community.swg.it/access/questionari_return.php?qst=38778_417568&r=b6f0c89951&magic={participant_label}"
# external_url = f"https://www.yoursurveycompany.com/complete?id={player.participant.code}"
return dict(
external_url=external_url
)
# class LinguaggioGenere(Page):
# form_model = 'player'
# @staticmethod
# def get_form_fields(player: Player):
# order = player.participant.vars['cpo_survey_item_order']
# return order['atgil'] + order['gfl_sez1'] + order['gfl_sez2'] + order['gfl_sez3']
# @staticmethod
# def vars_for_template(player: Player):
# order = player.participant.vars['cpo_survey_item_order']
# by_var = {r['var_name']: r for r in C.scales}
# by_var_term = {r['var_name']: r for r in C.term_choices}
# atgil_items = [{'var_name': v, 'label': by_var[v]['label']} for v in order['atgil']]
# sez1_items = [{'var_name': v, 'label': by_var[v]['label']} for v in order['gfl_sez1']]
# sez2_items = [{'var_name': v, 'label': by_var[v]['label']} for v in order['gfl_sez2']]
# sez3_items = [{'var_name': v, 'label': by_var_term[v]['label']} for v in order['gfl_sez3']]
# return dict(
# atgil_items = atgil_items,
# atgil_headers = HEADERS_L5,
# sez1_items=sez1_items,
# sez1_headers=HEADERS_GFL1,
# sez2_items=sez2_items,
# sez2_headers=HEADERS_GFL2,
# sez3_items=sez3_items,
# progress_pct=round(100 * 6 / C.N_PAGES),
# )
page_sequence = [
PS,
LA, # list experiment (everyone) - BEFORE the
BP, # descriptive title belief, to avoid priming
OpinioneTitolo, # control only - direct binary (aligned w/ list)
CT, # control only - 2nd-order beliefs (gender x area)
SA, # everyone - threshold / conformity vignette
NS,
GA,
LG,
Final
]