models.py is where you define your app’s data models:
A player is part of a group, which is part of a subsession. See Conceptual overview.
The main purpose of
models.py is to define the columns of your
database tables. 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.CharField() age = models.IntegerField() is_student = models.BooleanField()
When you run
otree resetdb, it will scan your
and create your database tables accordingly.
(Therefore, you need to run
resetdb if you have added,
removed, or changed a field in
The full list of available fields is in the Django documentation
The most commonly used ones are
TextField (for text),
FloatField (for real numbers),
BooleanField (for true/false values),
DecimalField unless you understand how it is different
FloatField and have a specific need for it.
Additionally, oTree has
CurrencyField; see Money & Payoffs.
Setting a field’s initial/default value¶
Any field you define will have the initial value of
If you want to give it an initial value, you can use
class Player(BasePlayer): some_number = models.IntegerField(initial=0)
Constants class is the recommended place to put your app’s
parameters and constants that do not vary from player
Here are the required constants:
name_in_url: the name used to identify your app in the participant’s URL.
For example, if you set it to
public_goods, a participant’s URL might look like this:
players_per_group(described in Groups)
num_rounds(described in Rounds)
Here is a list of attributes and methods for subsession objects.
Gives the current round number.
Only relevant if the app has multiple rounds
before_session_starts has been renamed to creating_session.
since otree-core 1.3.2 (June 2017).
However, new versions of oTree still execute
for backwards compatibility.
This method used to be called
This method is executed when the admin clicks “create session”:
creating_session allows you to initialize the round,
by setting 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.some_field = some_value
If your app has 1 round,
creating_session will execute once.
If your app has N rounds, it will execute N times consecutively;
that is, once on each subsession instance.
This method does NOT run at the beginning of each round. For that, you should use a wait page with after_all_players_arrive().
Returns a list of all the groups in the subsession.
Returns a list of all the players in the subsession.
Here is a list of attributes and methods for group objects.
Here is a list of attributes and methods for player objects.
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”?.
You can define this method to return a string label of the player’s role,
usually depending on the player’s
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
How oTree executes your code¶
Any code that is not inside a method is basically global and will only be executed once – when the server starts.
Some people write code mistakenly thinking that it will be re-executed for each new session. For example, someone who wants to generate a random probability that a coin flip will come up “heads” might do this in models.py:
class Constants(BaseConstants): heads_probability = random.random() # wrong
When the server starts, it loads models.py,
and executes the
random.random() only once.
It will evaluate to some random number, for example “0.257291”.
This means you have basically written this:
class Constants(BaseConstants): heads_probability = 0.257291
Constants is a global variable, that value 0.257291 will now be shared
by all players in all sessions.
For the same reason, this will not work either:
class Player(BasePlayer): heads_probability = models.FloatField( # wrong initial=random.random() )
The solution is to generate the random variables inside a method, such as creating_session.