An oTree app has 3 data models:
A player is part of a group, which is part of a subsession. See Conceptual overview.
Let’s say you want your experiment to generate data that looks like this:
Here is how to define the above table structure:
class Player(BasePlayer): name = models.StringField() age = models.IntegerField() is_student = models.BooleanField()
So, a model is essentially a database table. And a field is a column in a table.
BooleanField(for true/false and yes/no values)
CurrencyFieldfor currency amounts; see Currency.
FloatField(for real numbers)
StringField(for text strings)
LongStringField(for long text strings; its form widget is a multi-line textarea)
Your field’s initial value will be
None, unless you set
class Player(BasePlayer): some_number = models.IntegerField(initial=0)
Built-in fields and methods¶
Player, group, and subsession already have some predefined fields.
Player has fields called
id_in_group, as well as methods like
These built-in fields and methods are listed below.
Gives the current round number.
Only relevant if the app has multiple rounds
creating_session allows you to set initial values on fields on
players, groups, participants, or the subsession.
class Subsession(BaseSubsession): def creating_session(self): for p in self.get_players(): p.payoff = c(10)
Unlike most other built-in subsession methods, this method is one you must define yourself.
creating_session is not run at the beginning of each round.
It is run when you click the “create session” button, i.e. before anybody starts playing.
If your app has multiple rounds,
creating_session gets run multiple
class Subsession(BaseSubsession): def creating_session(self): print('in creating_session', self.round_number)
Will output all at once:
in creating_session 1 in creating_session 2 in creating_session 3
Returns a list of all the groups in the subsession.
Returns a list of all the players in the subsession.
Automatically assigned integer starting from 1. In multiplayer games, indicates whether this is player 1, player 2, etc.
The session/subsession/group/participant this player belongs to. See What is “self”?.
Unlike most other built-in player methods, this is one you define yourself.
This method should return a label with the player’s role,
usually depending on
def role(self): if self.id_in_group == 1: return 'buyer' if self.id_in_group == 2: return 'seller'
Then you can use
get_player_by_role('seller') to get player 2.
Also, the player’s role will be displayed in the oTree admin interface, in the “results” tab.
The number of participants in the session.
The participant’s ID in the session. This is the same as the player’s
Constants is the recommended place to put your app’s
parameters and constants that do not vary from player
Here are the built-in constants:
if you don’t want your app’s real name
to be displayed in URLs,
define a string constant
name_in_url with your desired name.
Constants can be numbers, strings, booleans, lists, etc.
But for more complex data types like dicts, lists of dicts, etc,
you should instead define it in a subsession method. For example,
instead of defining a Constant called
my_dict, do this:
class Subsession(BaseSubsession): def my_dict(self): return dict(a=[1,2], b=[3,4])
Defining your own methods¶
In addition to the methods listed on this page,
you can define your own.
Just remember to use them somewhere!
Just defining them with
def has no effect.
class Group(BaseGroup): def set_payoffs(self): print('in set_payoffs') # etc ...
Then call it:
class MyWaitPage(WaitPage): after_all_players_arrive = 'set_payoffs'
About using random()¶
Never generate random values outside of a method. For example, don’t do this:
class Constants(BaseConstants): p = random.randint(1, 10) # wrong
If it changes randomly, it isn’t a constant.
class Player(BasePlayer): p = models.FloatField( # wrong initial=random.randint(1, 10) )
These won’t work because they will change every time
the server launches a new process.
It may appear to work during testing but will eventually break.
Instead, you should generate the random variables inside a method,
such as creating_session() (and preferably not
which gets re-executed if the user refreshes the page).
If you want to set your own random seed, don’t use the
Instead, generate an instance of
random.Random as described here