Mac/Linux:
# Mac with Homebrew
brew install python3
# Ubuntu/Debian
sudo apt install python3 python3-pip
Option 1: venv
python3 -m venv otree_env
source otree_env/bin/activate
Option 2: conda
# Download & install Miniconda from:
# https://docs.conda.io/en/latest/miniconda.html
conda create -n otree_env python=3.10
conda activate otree_env
pip install otree
Windows:
Install Python
Create and activate virtual environment
Option 1: venv
python -m venv otree_env
.\otree_env\Scripts\activate
Option 2: conda
# Download & install Miniconda from:
# https://docs.conda.io/en/latest/miniconda.html
conda create -n otree_env python=3.10
conda activate otree_env
pip install otree
otree startproject guessing_game
cd guessing_game
otree startapp guessing_app
otree devserver
Database Configuration
Blueprint | Guess Game |
---|---|
1. How many players? | 1 player |
2. Real-time interaction? | No interaction |
3. Number of rounds? | 1 round (or more) |
4. Different roles? | No roles |
5. Main stages? | Generating random number, letting player guess, show results |
6. When are payoffs calculated? | After the guess is made |
7. Info needed from other players? | No info needed |
8. External information needed? | Pre-generated random number |
9. Treatment variations? | No treatments |
SESSION_CONFIGS = [
dict(
name='guessing_game',
app_sequence=['guessing_app'],
num_demo_participants=1,
),
]
File location:
otree_workshop/
└── guessing_game/
├── _static/
├── _templates/
├── guessing_app/
│ ├── __init__.py
│ ├── Instructions.html
│ ├── MyPage.html
│ └── Results.html
└── settings.py ⭐
Models: where the data is stored
Field types
Field properties: (doc, verbose_name, max/min, choices, and other field properties)
class C(BaseConstants):
NAME_IN_URL = 'guessing_app'
PLAYERS_PER_GROUP = None
NUM_ROUNDS = 2
ENDOWMENT = cu(100)
MIN_NUMBER = 0
MAX_NUMBER = 90
File location:
otree_workshop/
└── guessing_game/
├── _static/
├── _templates/
├── guessing_app/
│ ├── __init__.py ⭐
│ ├── Instructions.html
│ ├── MyPage.html
│ └── Results.html
└── settings.py
class Player(BasePlayer):
computer_number = models.IntegerField()
guess = models.IntegerField(
min=C.MIN_NUMBER,
max=C.MAX_NUMBER,
label="Please, insert any number from {} to {}"
)
File location:
otree_workshop/
└── guessing_game/
├── _static/
├── _templates/
├── guessing_app/
│ ├── __init__.py ⭐
│ ├── Instructions.html
│ ├── MyPage.html
│ └── Results.html
└── settings.py
Page creation:
When/if/for whom it is
is_displayed
What is shown:
vars_for_template
What to do next:
before_next_page()
class NameOfThePage(Page):
pass
page_sequence = [NameOfThePage]
NameOfThePage.html
__init__.py
File location:
otree_workshop/
└── guessing_game/
├── _static/
├── _templates/
├── guessing_app/
│ ├── __init__.py ⭐
├── ├── Instructions.html
│ ├── NameOfThePage.html ⭐
│ └── Results.html
└── settings.py
class Instructions(Page):
@staticmethod
def before_next_page(player, timeout_happened):
player.computer_number = random.randint(
C.MIN_NUMBER, C.MAX_NUMBER
)
class MyPage(Page):
form_model = 'player'
form_fields = ['guess']
@staticmethod
def before_next_page(player, timeout_happened):
difference = abs(player.computer_number - player.guess)
player.payoff = C.ENDOWMENT - difference
class Results(Page):
@staticmethod
def vars_for_template(player):
guess = player.field_maybe_none('guess')
difference = abs(player.computer_number - guess) if guess is not None else 0
return {
'difference': difference
}
Page sequence:
page_sequence = [Instructions, MyPage, Results]
vars_for_template
:@staticmethod
def vars_for_template(player):
return {
'my_var': 42,
'my_list': [1, 2, 3],
'my_dict': {'key': 'value'}
}
<p>Variable: </p>
<p>List item: </p>
<p>Dict value: </p>
<!-- Constants -->
<p>Endowment: </p>
<!-- Player variables -->
<p>Payoff: </p>
<p>ID in group: </p>
<!-- Group variables -->
<p>Group ID: </p>
<!-- Subsession variables -->
<p>Round number: </p>
<!-- Include entire file -->
<!-- Include specific blocks -->
<!-- Submission buttons -->
<button>
Clicking this will submit the form
</button>
<button type="button">
Clicking this will not submit the form
</button>
<!-- Form elements -->
<!-- Blocks -->
Page Title
Instructions.html:
Instructions
<div class="card">
<div class="card-body">
<p>You have an endowment of .</p>
<p>The computer will generate a number from
to .</p>
<p>You have to guess this number.</p>
<p>The closer is your guess the more you will earn.</p>
</div>
</div>
MyPage.html (Decision Page):
Make your guess
<div class="card">
<div class="card-body">
<p>You have an endowment of .</p>
<p>The computer will generate a number
from to .</p>
<p>You have to guess this number.</p>
<p>The closer is your guess the more you will earn.</p>
</div>
</div>
Results.html:
Results
<div class="card">
<div class="card-body">
<p>The number that computer generated was:
</p>
<p>Your guess was: </p>
<p>The difference is: </p>
<p>Your final earning is:
- points = </p>
</div>
</div>
git init
git add . # Stage all files
git commit -m "Commit message" # Save changes
git remote add origin <REPO_URL>
git push origin master
# Rolling back
git checkout <COMMIT_ID> # Return to specific commit
git stash # Temporarily store changes
# Get updates
git pull origin master # Download & merge changes
1. Heroku/Cloud Services (up to 500)
2. oTree Hub (10-50)
3. Local + ngrok (10-30)
4. Own Server (Advanced) (40-200)
1. Setup ngrok
pip install pyngrok
2. Configure Environment
# Set environment variables within environment
OTREE_AUTH_LEVEL=STUDY
OTREE_ADMIN_PASSWORD=mypassword
DATABASE_URL=postgresql://...
3. Start oTree Server
# Start production server
otree prodserver 8000
4. Install pyngrok
# In activated environment
pip install pyngrok
5. Create Tunnel
# In Jupyter via VS Code or
# via terminal run python commands
from pyngrok import ngrok
# Set your authtoken
ngrok.set_auth_token("your_token_here")
# Create tunnel
public_url = ngrok.connect(8000)
print(f"Public URL: {public_url}")
💡 Tips:
1. Get Authtoken:
2. Run Server & Tunnel:
ngrok.disconnect(public_url)
1. Local Preparation
# Inside your project directory
cd my_otree_app
# Create deployment package
otree zip
# Creates: my_otree_app.otreezip
2. Heroku Setup
3. oTree Hub Connection
💡 Tip: Free tier includes 2 Heroku sites
Recommended Setups:
Development & Testing
Load Testing Setup
Why Use oTree Hub? ↗
💡 Resources:
# Sign up for Heroku
# Install Heroku CLI
# Initialize Git repository (see previous slide)
# Create new Heroku app
heroku create <APPNAME>
# Deploy code
git push heroku master
# Reset database
heroku run otree resetdb
# Set config(environment variables, databaseurl, etc)
heroku config:set OTREE_PRODUCTION=1
heroku config:set OTREE_AUTH_LEVEL=DEMO
# Check logs if needed
heroku logs --tail
If needed: Add PostgreSQL:
Open Heroku Dashboard → Go to your app → Click "Resources" tab → Under "Add-ons" search for "Heroku Postgres" → Select plan:
Command line Database configuration:
# Add PostgreSQL addon (Essential-0 plan)
heroku addons:create heroku-postgresql:essential-0
# Check database URL
heroku config:get DATABASE_URL
oTree Studio
SQLite
PostgreSQL
Plan & Design
Setup Environment
Development
Local Testing
otree devserver
Version Control
git add .
git commit -m "Ready for deployment"
git push origin main
Deployment
Prolific ─────────────> oTree (url) 1-t page ───────> oTree (redirect) last page
↑ ↑__________________________|__________________________|
│ │
└ Experimenter (payoffs) ─————┘
Key Points:
💡 URLs:
`your-app.com/room/study?participant_label={PROLIFIC_PID}`
`app.prolific.co/submissions/complete?cc=CODE`
Requirements:
Development:
Deployment:
Integration:
Sides:
Prolific:
oTree:
Experimenter:
1. Room Configuration (settings.py
):
2. Store Prolific ID (early app):
3. Configure Completion (settings.py
):
4. Setup Redirect (final app):
💡 Important:
Guide with more details: Running oTree on Prolific for Beginners
1. Configure Study URL:
2. Set URL Parameters:
participant_label
as parameteryour-app.com/room/study?participant_label={PROLIFIC_PID%}
3. Completion Setup:
completionlink=
'https://app.prolific.co/submissions/complete?cc=XXXX'
💡 Important:
1. Data Verification:
2. Payment Processing:
3. Important Notes:
💡 Resources: