Models

models.py is where you define your app’s data models:

  • Subsession
  • Group
  • Player

A player is part of a group, which is part of a subsession. See Conceptual overview.

Model fields

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:

name age is_student
John 30 False
Alice 22 True
Bob 35 False
...    

Here is how to define the above table structure:

class Player(BasePlayer):
    ...
    name = models.CharField()
    age = models.PositiveIntegerField()
    is_student = models.BooleanField()

When you run otree resetdb, it will scan your models.py and create your database tables accordingly. (Therefore, you need to run resetdb if you have added, removed, or changed a field in models.py.)

The full list of available fields is in the Django documentation here. The most commonly used ones are CharField/TextField (for text), FloatField (for real numbers), BooleanField (for true/false values), IntegerField, and PositiveIntegerField. Don’t use DecimalField unless you understand how it is different from 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 None. If you want to give it an initial value, you can use initial=:

class Player(BasePlayer):
    some_number = models.IntegerField(initial=0)

min, max, choices

For info on how to set a field’s min, max, or choices, see Simple form field validation.

Constants

The Constants class is the recommended place to put your app’s parameters and constants that do not vary from player to 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:

    http://otree-demo.herokuapp.com/p/zuzepona/public_goods/Introduction/1/

  • players_per_group (described in Groups)

  • num_rounds (described in Rounds)

Subsession

Here is a list of attributes and methods for subsession objects.

session

The session this subsession belongs to. See What is “self”?.

round_number

Gives the current round number. Only relevant if the app has multiple rounds (set in Constants.num_rounds). See Rounds.

before_session_starts

before_session_starts has been renamed to creating_session. since otree-core 1.3.2 (June 2017). However, new versions of oTree still execute before_session_starts, for backwards compatibility.

creating_session

Note

This method used to be called before_session_starts. See before_session_starts.

This method is executed when the admin clicks “create session”:

_images/creating-session.png

creating_session allows you to initialize the round, by setting initial values on fields on players, groups, participants, or the subsession. For example:

class Subsession(BaseSubsession):

    def creating_session(self):
        for p in self.get_players():
            p.some_field = some_value

More info on the section on treatments and group shuffling.

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.

Note

This method does NOT run at the beginning of each round. For that, you should use a wait page with after_all_players_arrive().

group_randomly()

See Group matching.

group_like_round()

See Group matching.

get_group_matrix()

See Group matching.

set_group_matrix()

See Group matching.

get_groups()

Returns a list of all the groups in the subsession.

get_players()

Returns a list of all the players in the subsession.

in_previous_rounds()

See Passing data between rounds or apps.

in_round(round_number)

See Passing data between rounds or apps.

in_rounds(self, first, last)

See Passing data between rounds or apps.

Group

Here is a list of attributes and methods for group objects.

session/subsession

The session/subsession this group belongs to. See What is “self”?.

get_players()

See Groups.

get_player_by_role(role)

See Groups.

get_player_by_id(id_in_group)

See Groups.

set_players(players_list)

See Group matching.

in_previous_rounds()

See Passing data between rounds or apps.

in_round(round_number)

See Passing data between rounds or apps.

in_rounds(self, first, last)

See Passing data between rounds or apps.

Player

Here is a list of attributes and methods for player objects.

id_in_group

Integer starting from 1. In multiplayer games, indicates whether this is player 1, player 2, etc.

payoff

The player’s payoff in this round. See payoffs.

session/subsession/group/participant

The session/subsession/group/participant this player belongs to. See What is “self”?.

get_others_in_group()

See Groups.

get_others_in_subsession()

See Groups.

role()

You can define this method to return a string label of the player’s role, usually depending on the player’s id_in_group.

For example:

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. See Groups.

Also, the player’s role will be displayed in the oTree admin interface, in the “results” tab.

in_previous_rounds()

See Passing data between rounds or apps.

in_round(round_number)

See Passing data between rounds or apps.

in_rounds(self, first, last)

See Passing data between rounds or apps.

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

Because 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.

Be careful with lists and dicts in Constants

Here is a common error I see. Let’s say you have a list in Constants, like this:

class Constants(BaseConstants):
    foo = [1, 2, 3]

Then somewhere in your code you want to randomly shuffle this list:

# wrong
foo = Constants.foo
random.shuffle(foo)

Because you shuffled foo, its value will be different, like [3, 1, 2]. But Constants.foo is still [1, 2, 3], right? Wrong: it is [3, 1, 2] also. foo and Constants.foo are references to the same object.

This error often manifests itself when you randomly shuffle the list for each participant in the session, but then notice everyone somehow ended up with the same “random” value.

The solution is to make a copy of Constants.foo:

foo = Constants.foo.copy()
random.shuffle(foo)

An even safer technique is to store foo as a tuple (use () instead of []):

class Constants(BaseConstants):
    foo = (1, 2, 3)

This way, you will not be able to modify Constants.foo at all until you copy it into a new list:

foo = list(Constants.foo)
random.shuffle(foo)

This error can also occur with dictionaries:

class Constants(BaseConstants):
    foo = {'a': 1, 'b': 2}

Before modifying this dictionary, you should do Constants.foo.copy().