Page methods¶
All page methods are @classmethods that receive page (the page class) and player (the current participant) as arguments. They can be sync or async.
Lifecycle overview¶
When a participant navigates to a page, methods run in this order:
show— Should the page be displayed?early— Earliest hook (before any rendering)before_always_once— Runs once per page displaybefore_once— Runs once per player, on first visit onlyfields— Determine form fieldstemplatevars/jsvars— Prepare template and JS data- (Page is rendered and displayed)
- (Participant submits)
validate— Check submitted datamay_proceed— Gate before advancingstealth_fields/handle_stealth_fields— Manual field handlingafter_always_once— Runs once after each submissionafter_once— Runs once per player, on first submission onlybefore_next— Last hook before advancing to the next pagetimeout/timeout_reached— Handle page timeouts
show¶
Decides whether the page should be displayed. Return False to skip the page entirely.
| Parameter | Description |
|---|---|
page |
The page class |
player |
The current player |
| Returns | bool — True to show (default), False to skip |
early¶
The earliest hook in the page lifecycle. Runs before before_always_once. Receives the HTTP request as an additional keyword argument.
@classmethod
def early(page, player, request):
player.user_agent = request.headers.get("user-agent", "")
| Parameter | Description |
|---|---|
page |
The page class |
player |
The current player |
request |
The Starlette Request object |
before_once¶
Runs once per player, the first time they see this page. Does not run again if the player navigates back and returns.
| Parameter | Description |
|---|---|
page |
The page class |
player |
The current player |
before_always_once¶
Runs once each time the page is displayed (including on back navigation). Use this for per-visit initialization.
@classmethod
def before_always_once(page, player):
player.visit_count = getattr(player, "visit_count", 0) + 1
| Parameter | Description |
|---|---|
page |
The page class |
player |
The current player |
fields¶
Returns the form fields for this page. Can be a dictionary (static) or a method (dynamic).
Static fields¶
Dynamic fields¶
@classmethod
def fields(page, player):
max_offer = player.other_in_group.endowment
return dict(
offer=IntegerField(label="Your offer", min=0, max=max_offer),
)
| Parameter | Description |
|---|---|
page |
The page class |
player |
The current player |
| Returns | dict mapping field names to field instances |
templatevars¶
Returns variables available in the page template.
@classmethod
def templatevars(page, player):
return dict(
partner=player.other_in_group,
total=sum(p.contribution for p in player.group.players),
)
| Parameter | Description |
|---|---|
page |
The page class |
player |
The current player |
| Returns | dict of template variables |
jsvars¶
Returns variables available in JavaScript as _uproot_js.
@classmethod
def jsvars(page, player):
return dict(
initial_price=player.price,
max_trades=C.MAX_TRADES,
)
Access in JavaScript:
| Parameter | Description |
|---|---|
page |
The page class |
player |
The current player |
| Returns | dict of JavaScript variables |
validate¶
Custom validation of submitted form data. Called after built-in field validation passes.
@classmethod
def validate(page, player, data):
if data["give_a"] + data["give_b"] > 100:
return "Total cannot exceed 100"
| Parameter | Description |
|---|---|
page |
The page class |
player |
The current player |
data |
dict of submitted values |
| Returns | Error message(s), or nothing if valid |
Return types:
str— Single error displayed at the top of the formlist[str]— Multiple errors displayed at the topdict[str, str | list[str]]— Per-field errors displayed next to each field
@classmethod
def validate(page, player, data):
errors = {}
if data["min_price"] > data["max_price"]:
errors["min_price"] = "Must be less than max price"
errors["max_price"] = "Must be greater than min price"
return errors or None
See the input_validation example
may_proceed¶
Gate that controls whether the player can advance. Return False to keep the player on the page.
@classmethod
def may_proceed(page, player):
from time import time
return time() >= player.session.detection_period_until
| Parameter | Description |
|---|---|
page |
The page class |
player |
The current player |
| Returns | bool — True to allow proceeding (default), False to block |
after_once¶
Runs once per player, after the first forward submission. Does not run on back navigation or revisits.
| Parameter | Description |
|---|---|
page |
The page class |
player |
The current player |
after_always_once¶
Runs once after each forward submission (including revisits after back navigation).
| Parameter | Description |
|---|---|
page |
The page class |
player |
The current player |
Note
after_once and after_always_once are not available on GroupCreatingWait or SynchronizingWait pages.
before_next¶
Runs just before the player advances to the next page. This is the last hook before navigation.
| Parameter | Description |
|---|---|
page |
The page class |
player |
The current player |
timeout¶
Sets a page timeout in seconds. Can be a static value or a method.
Static timeout¶
Dynamic timeout¶
| Parameter | Description |
|---|---|
page |
The page class |
player |
The current player |
| Returns | float seconds until timeout, or None to disable |
timeout_reached¶
Called when the page timeout expires, before the page auto-advances.
| Parameter | Description |
|---|---|
page |
The page class |
player |
The current player |
stealth_fields¶
Specifies which fields should not be saved automatically. Can be a list or a method.
Static stealth fields¶
Dynamic stealth fields¶
@classmethod
def stealth_fields(page, player):
return ["response"] if player.condition == "special" else []
| Parameter | Description |
|---|---|
page |
The page class |
player |
The current player |
| Returns | list[str] of field names to handle manually |
handle_stealth_fields¶
Processes stealth fields. Called after validation. Return error strings to reject the submission.
@classmethod
async def handle_stealth_fields(page, player, data):
code = data["code"]
if code != "secret123":
return "Invalid access code"
player.verified = True
| Parameter | Description |
|---|---|
page |
The page class |
player |
The current player |
data |
dict of stealth field values |
| Returns | Error string or list of error strings, or nothing if valid |
See the payment_data example · input_validation example
Page class attributes¶
| Attribute | Default | Description |
|---|---|---|
allow_back |
False |
Show a "Back" button |
template |
{AppName}/{ClassName}.html |
Custom template path |
keep_values |
False |
Re-populate form from player data on re-render |
Wait page methods¶
GroupCreatingWait¶
| Attribute/Method | Description |
|---|---|
group_size |
Required. Number of players per group |
after_grouping(page, group) |
Called once when the group forms |
SynchronizingWait¶
| Attribute/Method | Description |
|---|---|
synchronize |
"group" (default) or "session" |
all_here(page, group) |
Called when all group members arrive |
all_here(page, session) |
Called when all session members arrive (if synchronize = "session") |
wait_for(page, player) |
Override to customize who to wait for |