フォーム¶
oTreeの各ページはフォームを持ち、プレイヤーはフォームに入力し、 "Next" ボタンを押すことでフォームに入力したデータを送信できます。フォームを作成するためにまず、Playerクラスにフィールドを作成する必要があります。例えば、
class Player(BasePlayer):
name = models.StringField(label="Your name:")
age = models.IntegerField(label="Your age:")
次に、Pageクラスで form_model
と form_fields
を設定します。
class Page1(Page):
form_model = 'player'
form_fields = ['name', 'age'] # this means player.name, player.age
ユーザがフォームデータを送信したとき、送信されたデータは、Playerクラス内の対応するフィールドに自動的に保存されます。
シンプルなフォームの検証¶
最大値、最小値¶
例: 12から24までの整数が必要な時
offer = models.IntegerField(min=12, max=24)
フォームで設定される最大/最小値を動的に設定したい場合は、 {field_name}_max() を使用する必要があります。
選択肢¶
フォームを選択肢付きのドロップダウンメニューにしたい場合は、 choices=
を次のように設定します。
level = models.IntegerField(
choices=[1, 2, 3],
)
ダ六腑ダウンの代わりにラジオボタンを使いたい場合は、 widget
に RadioSelect
か RadioSelectHorizontal
を設定して下さい。:
level = models.IntegerField(
choices=[1, 2, 3],
widget=widgets.RadioSelect
)
選択肢を動的に設定したい場合は、 {field_name}_choices() を使う必要があります。
[value, display]のペアをリストに設定することで、選択肢に対応する表示名を設定することができます。:
level = models.IntegerField(
choices=[
[1, 'Low'],
[2, 'Medium'],
[3, 'High'],
]
)
上記のコードを記述した場合、 "Low" , "Medium, "High" と共にメニューが表示されます。ただし、1,2,3として選択は保存されます。
BooleanField
や StringField
等を使いたい場合は、下記のように記述します。:
cooperated = models.BooleanField(
choices=[
[False, 'Defect'],
[True, 'Cooperate'],
]
)
次のようにすることで、人間にとって読みやすい形でユーザの選択を取得することができます。
player.cooperated # returns e.g. False
player.field_display('cooperated') # returns e.g. 'Defect'
注釈
field_display
はoTree 5 (2021年3月) からの新機能です。
動的なフォームの検証¶
min
、 max
、 choices
は固定値限定です。
動的に検証したい場合(例えば、プレイヤーごとに入力できる値の範囲が異なる)は、代わりに下記の関数を定義してください。
{field_name}_choices()¶
choices=
で設定することで、フォームの選択肢を設定できます。
例:
class Player(BasePlayer):
fruit = models.StringField()
def fruit_choices(player):
import random
choices = ['apple', 'kiwi', 'mango']
random.shuffle(choices)
return choices
{field_name}_max()¶
max=
を動的に設定したい場合の手段として用いることができます。例:
class Player(BasePlayer):
offer = models.CurrencyField()
budget = models.CurrencyField()
def offer_max(player):
return player.budget
{field_name}_min()¶
min=
を動的に設定したい場合の手段として用いることができます。
{field_name}_error_message()¶
これは最も柔軟にフォームの検証ができる方法です。
class Player(BasePlayer):
offer = models.CurrencyField()
budget = models.CurrencyField()
def offer_error_message(player, value):
print('value is', value)
if value > player.budget:
return 'Cannot offer more than your remaining budget'
複数フォームの検証¶
3つの数値を入力するフォームがあり、その値の合計が100にならなければならないとします。その場合、ページクラスに error_message
関数を下記のように設定することで検証できます。
class MyPage(Page):
form_model = 'player'
form_fields = ['int1', 'int2', 'int3']
@staticmethod
def error_message(player, values):
print('values is', values)
if values['int1'] + values['int2'] + values['int3'] != 100:
return 'The numbers must add up to 100'
注意
- フォームが空欄でもよい場合(
blank=True
に設定した場合)、値はNone
となります。 - この関数はフォームに他のエラーがない場合にのみ実行されます。
- エラーメッセージにフィールドをマップすることもでき、FIELD_error_message methods メソッドを このように 繰り返す必要がありません。
動的なフォームフィールドの決定¶
動的なフォームフィールドのリストが必要な時、 form_fields
の代わりに get_form_fields
関数を定義できます。
@staticmethod
def get_form_fields(player):
if player.num_bids == 3:
return ['bid_1', 'bid_2', 'bid_3']
else:
return ['bid_1', 'bid_2']
Widgets¶
ドロップダウンメニューの代わりに、ラジオボタンを使いたい場合は、modelの widget
を RadioSelect
または RadioSelectHorizontal
に設定して下さい。
{{ formfield }}¶
フォームを個別に配置したい場合は、 {{ formfields }}
の代わりに、 {{ formfield }}
を使うことができます。:
{{ formfield 'bid' }}
テンプレート内で直接 label
を配置することもできます。
{{ formfield 'bid' label="How much do you want to contribute?" }}
過去のバージョンでの構文 {% formfield player.bid %}
は引き続きサポートされます。
フォームの外観のカスタマイズ¶
自動的にフォームフィールドに必要なすべてのパーツ(入力、ラベル、エラーメッセージ)を出力できるので、 {{ formfields }}
と {{ formfield }}
が使いやすいです。
ただし、外観やレイアウトを変更したい場合、手動で設定したフォームのレンダリングを使用できます。{{ formfield 'my_field' }}
の代わりに、 {{ form.my_field }}
を実行することで、入力要素のみを取得することができます。また、 {{ formfield_errors 'my_field' }}
を含むことも忘れないでください。
Example: Radio buttons arranged like a slider¶
pizza = models.IntegerField(
widget=widgets.RadioSelect,
choices=[-3, -2, -1, 0, 1, 2, 3]
)
<p>Choose the point on the scale that represents how much you like pizza:</p>
<p>
Least
{{ for choice in form.pizza }}
{{ choice }}
{{ endfor }}
Most
</p>
例: 表やそのほかのカスタムレイアウト中のラジオボタン¶
model 内に IntegerField
のセットがあるとする。
class Player(BasePlayer):
offer_1 = models.IntegerField(widget=widgets.RadioSelect, choices=[1,2,3])
offer_2 = models.IntegerField(widget=widgets.RadioSelect, choices=[1,2,3])
offer_3 = models.IntegerField(widget=widgets.RadioSelect, choices=[1,2,3])
offer_4 = models.IntegerField(widget=widgets.RadioSelect, choices=[1,2,3])
offer_5 = models.IntegerField(widget=widgets.RadioSelect, choices=[1,2,3])
各選択肢が別々の列にある、リッカート尺度としてそれらを提示します。
(まず、 多くのフィールドを作成する方法 の手順に従い、model内のコードの重複を減らしてください。)
選択肢は表のセルで区切られており、通常の RadioSelectHorizontal
は機能しません。
代わりに、次のようにフィールドの選択肢をループさせてください。
<tr>
<td>{{ form.offer_1.label }}</td>
{{ for choice in form.offer_1 }}
<td>{{ choice }}</td>
{{ endfor }}
</tr>
同数の選択肢を持つフィールドが多くある場合は、それらを表に配置できます。
<table class="table">
{{ for field in form }}
<tr>
<th>{{ field.label }}</th>
{{ for choice in field }}
<td>{{ choice }}</td>
{{ endfor }}
</tr>
{{ endfor }}
</table>
独自のHTMLウィジェット¶
{{ formfields }}
や 手動のフィールドレンダリング を利用しても理想の外観にならないとき、HTMLの生コード上でウィジェットを記述できます。ただし、oTreeによって提供される便利な機能を利用することはできません。例えば、フォームにエラーが発生したり、ページがリロードされたときに、ユーザが入力した情報は消去されてしまう可能性があります。
まず、 <input>
を加えます。例えば、 form_fields
が my_field
を含むのであれば、 <input name="my_field" type="checkbox" />
を実行することができます。(他にも、 radio
、 text
、 number
、 range
がああります。)
次に、参加者が誤った値を入力してしまった場合に、エラーメッセージが表示されるように、 {{ formfield_errors 'xyz' }}
を含める必要があります。
HTMLコードの例: JavaScriptを用いたカスタムユーザインタフェース¶
ユーザがフォームフィールに入力するのではなく、チャートのクリックやグラフィカルなゲームの操作等、ある種のビジュアルアプリを操作するとします。または、ページの一部に費やした時間やクリックした回数等、データを追加で記録したいとします。
まず、HTMLやJavaScriptを利用するインタフェースを構成してください。次に、JavaScriptを利用して非表示のフォームに結果を書き出します。例:
# Player class
contribution = models.IntegerField()
# page
form_fields = ['contribution']
# HTML
<input type="hidden" name="contribution" id="contribution" />
# JavaScript
document.getElementById('contribution').value = 42;
ページが送信されると、非表示のフォームに書き出された値が、ほかのフォームのようにoTreeに記録されます。
これが機能しない場合は、利用しているブラウザのJavaScriptコンソールを開き、エラーが出ていないか確認してください。そして、 console.log()
( print()
に相当する命令)を利用して、コードの実行をトレースすることをおすすめします。
ボタン¶
フォームを送信するボタン¶
{{ next_button }}
を省略し、代わりにいくつかのボタンの一つをクリックして、次のページへ遷移させることができます。
例えば、Playerクラスが offer_accepted = models.BooleanField()
を持っており、ラジオボタンではなく、下記のようなボタンを表示したいとします。

まず、ページの form_fields
に offer_accepted
を設定して下さい。次に、テンプレートにこのコードを記入してください。
<p>Do you wish to accept the offer?</p>
<button name="offer_accepted" value="True">Yes</button>
<button name="offer_accepted" value="False">No</button>
この手法は BooleanField
だけでなく、任意のタイプのフィールドに利用できます。
フォームの送信以外の目的で利用するボタン¶
フォームの送信以外の目的があるとき、 type="button"
を追加します。
<button>
Clicking this will submit the form
</button>
<button type="button">
Clicking this will not submit the form
</button>
その他、高度な設定¶
動的ラベル付きフォームフィールド¶
"ラベルに変数が含まれる場合、ページに文字列を作成できます
class Contribute(Page):
form_model = 'player'
form_fields = ['contribution']
@staticmethod
def vars_for_template(player):
return dict(
contribution_label='How much of your {} do you want to contribute?'.format(player.endowment)
)
次に、テンプレートで:
{{ formfield 'contribution' label=contribution_label }}
この手法を利用する場合は、 動的なフォームの検証 の利用もおすすめします。
JavaScript access to form inputs¶
注釈
New beta feature as of oTree 5.9 (July 2022)
In your JavaScript code you can use forminputs.xyz
to access the <input>
element of form field xyz
. For example, you can do:
// get the value of an input
forminputs.xyz.value; // returns '42' or '' etc.
// set the value of a field.
forminputs.xyz.value = '42';
// dynamically set a field's properties -- readonly, size, step, pattern, etc.
forminputs.xyz.minlength = '10';
// do live calculations on inputs
function myFunction() {
let sum = parseInt(forminputs.aaa.value) + parseInt(forminputs.bbb.value);
alert(`Your total is ${sum}`);
}
// set an event handler (for oninput/onchange/etc)
forminputs.aaa.oninput = myFunction;
// call methods on an input
forminputs.xyz.focus();
forminputs.xyz.reportValidity();
Radio widgets work a bit differently:
my_radio = models.IntegerField(
widget=widgets.RadioSelect,
choices=[1, 2, 3]
)
// forminputs.my_radio is a RadioNodeList, not a single <input>
// so you need to loop over all 3 options:
for (let radio of forminputs.my_radio) {
radio.required = false;
}
for (let radio of forminputs.my_radio) {
radio.onclick = function() { alert("radio button changed"); };
}
// but the 'value' attribute works the same way as non-radio widgets
forminputs.my_radio.value = 2;