Page methods¶
Page methods are @classmethods that receive page (the page class) and usually player (the current participant) as arguments. Standard page hooks can be sync or async; wait-page callbacks such as after_grouping and all_here are sync-only.
Lifecycle overview¶
For a forward visit to a page, the lifecycle is:
show: Decide whether the page is displayed.early: Run before rendering.before_always_once: Run once when this page position is reached.before_once: Run once per player, on first visit only.fields: Determine form fields.templatevarsandjsvars: Prepare template and JS data.- (The page is rendered and displayed.)
- (The participant submits.)
validate: Check submitted data.before_form_save: Run before ordinary form fields are saved.stealth_fieldsandhandle_stealth_fields: Handle manual fields.may_proceed: Gate before advancing.after_once: Run once per player, on first successful submission only.after_always_once: Run once after this page position is submitted.
The timeout method configures the deadline while the page is rendered. The browser submits when the deadline is reached. If the server receives a request after the deadline, timeout_reached handles that request before normal form processing, may_proceed is skipped, and the page advances. Valid form data is saved; invalid data is discarded without an error message. uproot counts a request up to upd.TIMEOUT_TOLERANCE seconds (default: 1) before the deadline as timed out, too, but only if may_proceed allows the page to advance. See Timeouts and may_proceed. A direct visit to the current page can omit early, because that hook runs when entering a new page position.
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 when this page position is reached. Use this for setup that should also happen for internal or skipped pages.
@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, or None |
templatevars may return None instead of a dict. This is treated the same as returning {}.
jsvars¶
Returns variables available in JavaScript as uproot.vars.
@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 are:
str: single error displayed at the top of the form;list[str]: multiple errors displayed at the top;dict[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
before_form_save¶
Runs after built-in and custom validation passes, immediately before ordinary
form fields are saved to the player. The submitted data mapping is read-only;
use this hook for checks or side effects that need the validated values before
they are persisted.
| Parameter | Description |
|---|---|
page |
The page class |
player |
The current player |
data |
Read-only mapping of validated form values |
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 |
Note
A page timeout overrides may_proceed once the deadline has passed. See Timeouts and may_proceed.
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 this page position is submitted in the forward direction.
| 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.
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 |
Matching app .html file, then .md |
Custom template path relative to the project root |
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 |