表单¶
oTree中的每一个页面均可包含表单,玩家填完表单之后点击 “Next” 按钮即提交了表单。为了创建一个表单,首先你需要在模型中添加字段,举例如下:
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
当用户提交表单时,所提交的数据自动保存到模型对应的字段中。
简单的表单验证¶
最小值与最大值¶
验证一个整数在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'],
]
)
动态表单验证¶
上述的 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方法(参考 这里)。
动态决定表单字段¶
如果你需要表单字段是动态的,你可以定义 get_form_fields
方法取代 form_fields
:
def get_form_fields(player):
if player.num_bids == 3:
return ['bid_1', 'bid_2', 'bid_3']
else:
return ['bid_1', 'bid_2']
控件¶
你可以设定一个模型字段的 widget
为 RadioSelect
或 RadioSelectHorizontal
如果你想让选项表现为单选按钮而不是下拉菜单的话。
{% formfield %}¶
如果你想分别设定字段的位置,可以使用 {% formfield %}
替代 {% formfields %}
:
{% formfield 'bid' %}
你也可以将 label
直接放在模板中:
{% formfield 'bid' label="How much do you want to contribute?" %}
注解
The previous syntax of {% formfield player.bid %}
is still supported.
定制字段的外观¶
{% formfields %}
与 {% formfield %}
很容易使用,因为它们自动输出了一个表单字段所必需的所有部分(输入框,标签,错误信息),并使用Bootstrap的样式。
然而,如果你想要自己更多地控制外观与布局,你可以使用Django手动字段渲染。使用 {{ form.my_field }}
,而不是 {% formfield player.my_field %}
来获取输入元素。只需记得包含 {% if form.my_field.errors %}{{ form.my_field.errors.0 }}{% endif %}
.
例子:表格中的单选按钮与其他定制布局¶
假定你在模型中有很多 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])
并且你想要它们表现得像李克特量表,每一个选项在独立的一列中。
(First, try to reduce the code duplication in your model by following the instructions in 如何创建多个字段.)
由于选项必须在独立的表格单元中,原生的 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 %}
与 manual field rendering 不能实现你想要的外观,你可以使用原始HTML写你自己的控件。然而,这样做会失去由oTree自动处理的一些便捷的特性。举例来说,如果表单有一个错误并且页面重载了,所有用户已经输入的条目可能被清除。
首先,添加一个 <input>
元素。例如,如果你的 form_fields
包含 my_field
,你可以这样写: <input name="my_field" type="checkbox" />
(其他一些常见的type为 radio
, text
, number
, 与 range
)。
其次,你通常应当包含 {% if form.my_field.errors %}{{ form.my_field.errors.0 }}{% endif %}
, 这样如果参与人提交了一个不正确的值或者缺少了某些值时,他们就可以看见报错信息。
原始HTML的例子:滑动条¶
如果你想使用滑动条,将下面这样的HTML放入模板中,而不使用 {% formfields %}
:
<label class="col-form-label">
Pizza is the best food:
</label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text">Disagree</span>
</div>
<input type="range" name="pizza" min="-2" max="2" step="1" class="form-range">
<div class="input-group-append">
<span class="input-group-text">Agree</span>
</div>
</div>
如果你想显示当前的数值,或者当滑动条未被点击时隐藏滑动块,你可以使用JavaScript实现,但是可以考虑使用 RadioSelectHorizontal
控件替代。
原始HTML的例子:使用JavaScript定制用户界面¶
假设你不想让用户填写表单,而是与某种可视化的应用交互,比如在图表上点击或者玩一个图形游戏等等。或者,你想要记录一些额外数据,比如用户在页面的某个部分花了多少时间,用户点击了多少次,诸如此类。
First, build your interface using HTML and JavaScript. Then use JavaScript to write the results into a hidden form field. For example:
# Player class
num_clicks = models.IntegerField()
# page
form_fields = ['num_clicks']
# HTML
<input type="hidden" name="num_clicks" id="num_clicks" />
# JavaScript
document.getElementById('num_clicks').value = 42;
当页面被提交的时候,隐藏输入中的值会被记录到oTree中,如其他表单字段一样。
按钮¶
提交表单的按钮¶
如果你的页面只包含一个问题,你可以忽略 {% next_button %}
,并让用户在一系列按钮中点击一个,并前往下一个页面。
举例来说,假定你的玩家模型有 offer_accepted = models.BooleanField()
, 并且你希望像下面这样展示按钮,而不是一个单选按钮:

首先,将 offer_accepted
放入你的页面的 form_fields
。然后将这样的代码放入模板中:
<p>Do you wish to accept the offer?</p>
<div>
<button name="offer_accepted" value="True">Yes</button>
<button name="offer_accepted" value="False">No</button>
</div>
你可以在任何类型的字段上使用这个技巧,不只是 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 player.contribution label=contribution_label %}
如果你想使用这个技巧,你可能也想用 动态表单验证.