Tips and tricks

Preventing code duplication

As much as possible, it’s good to avoid copy-pasting the same code in multiple places. Although it sometimes takes a bit of thinking to figure out how to avoid copy-pasting code, you will see that having your code in only one place usually saves you a lot of effort later when you need to change the design of your code or fix bugs.

Below are some techniques to achieve code reuse.

Don’t make multiple copies of your app

If possible, you should avoid copying an app’s folder to make a slightly different version, because then you have duplicated code that is harder to maintain.

If you need multiple rounds, set num_rounds. If you need slightly different versions (e.g. different treatments), then you should use the techniques described in Treatments, such as making 2 session configs that have a different 'treatment' parameter, and then checking for self.session.config['treatment'] in your app’s code.

Templates: prevent code duplication by using a base template

If you are copy-pasting the same JavaScript or CSS to multiple templates, you should instead put it in one of the following blocks in a base template:

  • {% block global_styles %}
  • {% block global_scripts %}
  • {% block app_styles %}
  • {% block app_scripts %}

Read more in JavaScript and CSS.

views.py: prevent code duplication by using multiple rounds

If your views.py has many pages that are almost the same, consider just having 1 page and looping it for multiple rounds. One sign that your code can be simplified is if it looks something like this:

# [pages 1 through 7....]

class Decision8(Page):
    form_model = models.Player
    form_fields = ['decision8']

class Decision9(Page):
    form_model = models.Player
    form_fields = ['decision9']

# etc...

See the quiz or real effort sample games for examples of how to just have 1 page that gets looped over many rounds, varying the question that gets displayed with each round.

views.py: prevent code duplication by using inheritance

If you can’t merge your code into 1 Page as suggested above, but your code still has a lot of repetition, you can use Python inheritance to define the common code on a base class.

Basic example

For example, let’s say that your page classes all repeat some of the code. For example, you use is_displayed to skip the rest of the app once a certain participant var is set:

class Page1(Page):
    def is_displayed(self):
        return not self.participant.vars.get('finished')

class Page2(Page):
    def is_displayed(self):
        return not self.participant.vars.get('finished')

class Page3(Page):
    def is_displayed(self):
        return not self.participant.vars.get('finished')

page_sequence = [
    Page1,
    Page2,
    Page3,
]

You can eliminate this repetition as follows:

class SkipIfFinished(Page):
    def is_displayed(self):
        return not self.participant.vars.get('finished')

class Page1(SkipIfFinished):
    pass

class Page2(SkipIfFinished):
    pass

class Page3(SkipIfFinished):
    pass

page_sequence = [
    Page1,
    Page2,
    Page3,
]

(This is not a special oTree feature; it is simply using Python class inheritance.)

Let’s say you have a page that has its own special display condition:

class Player1Page(Page):
    def is_displayed(self):
        return self.player.id_in_group == 1

To combine it with is_displayed() of the base class, use inheritance and Python’s super():

class Player1Page(SkipIfFinished):
    def is_displayed(self):
        return super().is_displayed() and self.player.id_in_group == 1

More complex example

Let’s say you’ve got the following code (note that Page1 passes an extra variable 'd'):

class Page1(Page):
    def vars_for_template(self):
        return {
            'a': 1,
            'b': 2,
            'c': 3,
            'd': 4
        }

class Page2(Page):
    def vars_for_template(self):
        return {
            'a': 1,
            'b': 2,
            'c': 3
        }

class Page3(Page):
    def vars_for_template(self):
        return {
            'a': 1,
            'b': 2,
            'c': 3
        }

You can refactor this as follows:

class VarsPage(Page):
    def vars_for_template(self):
        v = {
            'a': 1,
            'b': 2,
            'c': 3
        }
        v.update(self.extra_vars_for_template())
        return v

    def extra_vars_for_template(self):
        return {}


class Page1(VarsPage):
    def extra_vars_for_template(self):
        return {'d': 4}

class Page2(VarsPage):
    pass

class Page3(VarsPage):
    pass

(Or, if you prefer, use super().vars_for_template(), etc.)

Improving code performance

You should avoid redundant use of get_players(), get_player_by_id(), in_*_rounds(), get_others_in_group(), or any other methods that return a player or list of players. These methods all require a database query, which can be slow.

For example, this code has a redundant query because it asks the database 5 times for the exact same player:

class MyPage(Page):
    def vars_for_template(self):
        return {
            'a': self.player.in_round(1).a,
            'b': self.player.in_round(1).b,
            'c': self.player.in_round(1).c,
            'd': self.player.in_round(1).d,
            'e': self.player.in_round(1).e,
        }

It should be simplified to this:

class MyPage(Page):
    def vars_for_template(self):
        round_1_player = self.player.in_round(1)
        return {
            'a': round_1_player.a,
            'b': round_1_player.b,
            'c': round_1_player.c,
            'd': round_1_player.d,
            'e': round_1_player.e,
        }

As an added benefit, this usually makes the code more readable.