下面开始具体的实验编写介绍,包括一个简单的调查问卷以及两个常见实验的快速实现,并介绍如何测试程序以及进阶的程序修改。这三个例子可以看作分别对应三种最基础的实验形式:单人决策、多人同时决策、多人序贯决策
简单问卷
我们首先实现一个简单的问卷,了解oTree的数据类型、前端的输入方式与显示方式等基础内容
首先将工作路径转移至上文中创建的程序文件夹(firstexp),然后输入以下命令:
otree startapp questionnaire
在问卷调查中,每个人填写的都是自己的情况,因此只需要在Player
类下添加字段即可。首先,如果想要参加者填写就读的学校信息,可以按如下方式添加字段:
class Player(BasePlayer):
school = models.StringField(
label = '您就读的学校是',
)
这里的school
是字段名,StringField
表明这个字段记录的是字符型数据,label
属性则是参加者在填写问卷时时可以看到的题目引导标签
第二个问题我们想知道参加者的年龄,可以这样添加字段:
age = models.IntegerField(
min = 0,
max = 99,
label = '您的年龄是',
)
IntegerField
表明age
记录的是整数型的数据,这里进一步设定了该字段的最小值和最大值,当参加者填写的数据不在允许范围中时将无法提交并出现错误提示。这是第一种对输入进行验证给出错误提示的方法,这种验证方法不经过服务器。
接下来我们还想知道参加者的性别、年级以及是否主修经济类专业,与前面的问题不同的是,这些问题的答案可以从有限的选项中选择,因此我们可以事先给定一些选项,让参加者从选项中选择,这里使用了三种不同的常见选项展示方式:
#dropdown menu
gender = models.IntegerField(
choices = [
[0,'女'], [1,'男'],
],
label = '您的性别是',
)
#horizontal radio buttons
grade = models.IntegerField(
choices = [
[1,'大一'],
[2,'大二'],
[3,'大三'],
[4,'大四'],
[5,'硕士生'],
[6,'博士生'],
],
widget = widgets.RadioSelectHorizontal,
label = '您的年级'
)
#vertical radio buttons
major = models.BooleanField(
choice = [[False,'否'], [True,'是'],],
widget = widgets.RadioSelect,
label = '您是否主修经济类专业?',
)
这三个字段中,都事先设定了choice
参数,choice
参数是一个列表,列表的元素还是列表,每个列表里面的第一个元素是数据的取值,比如整数型的1、2、3,布尔型的Tru
e和False
,第二个元素是与这个数据取值绑定的选项文本,也就是参加者可以在前端看到的选项字样。widget
参数控制了选项的展示形式,gender
字段中没有指定widget
参数,默认会以下拉菜单的形式展示;grade
字段制定widget
为widgets.RadioSelectHorizontal
,则六个选项水平排列,而major
字段指定widgets.RadioSelect
则选项垂直排列
在问卷中有一些问题不要求所有人都回答,这个时候可以指定参数blank
:
suggest = models.LongStringField(
blank = True,
label = '如果您有意见或建议,请填写在下框中',
)
blank=True
表明在填写时该字段可以留空,需要被试填入数据的字段默认是不允许留空的,另外这里使用的LongStringField
在输入时会显示更大的文本框
另外,有的时候我们也想要对参加者输入的数据进行验证(比如测试性问题检验答案是否正确),上面提到的设定最大最小值参数的方法不适用,我们在这里添加一个attention
字段用于举例说明如何进行输入的验证,注意这个验证需要在PAGES部分指定,将在下面讲解
attention = models.IntegerField(
label = '请在下框内填入1234以提交本页'
)
上面完成了后端MODELS
部分的设定,下面是有关问卷页面的PAGES部分设定,这一部分内容的主要功能是管理前端页面的逻辑、数据输入以及运算等等。在问卷调查这个例子中,我们需要两个页面,第一个页面是参加者填写问卷,第二个页面是展示参加者刚刚填写的信息。
第一个页面完成输入任务,如果参加者需要在页面上填入数据,那么这个页面需要设定form_model
和form_fields
,以说明当前页面需要填写哪些字段以及这些字段来自哪一类,这里参加者需要填写的是player
类下的一些字段:
class Questionnaire(Page):
form_model = 'player'
form_fields = ['school','age','gender','grade','major','suggest','attention']
@staticmethod
def error_message(player,values):
if values['attention'] != 1234:
return '输入有误!'
注意这一页面进行了上面提到的输入验证,这个验证方法定义了一个staticmethod
(静态方法,这是一个python面向对象编程里面比较专业的概念,只需要知道要这样写就可以,下面的例子中出现的静态方法同理),这个方法的名字叫error_message
,定义了这个方法后oTree会在运行的时候自动调用这个方法,因此可以理解为一个内置的方法叫error_message
,我们只需要填写需要验证的内容,oTree会根据填写的内容进行验证。
这里需要判断的是attention的输入值是否是1234,values参数(这里出现的values只是这个参数的一个记号,统一改成别的名字也可以)的数据类型是字典,表示执行的时候传入的是一个字典,这个字典包含了在页面上输入的所有字段。根据attention这个字典的key(键)可以获取相应的value(值),并判断是否等于1234,如果不等于,则返回return后面的错误提示,这个错误提示会显示在页面上方。
class Questionnaire(Page):
form_model = 'player'
form_fields = ['school','age','gender','grade','major','suggest','attention']
@staticmethod
def error_message(player,values):
if values['attention'] != 1234:
return '输入有误!'
第一个页面的html文件内容非常简单,由于我们已经设定好了字段以及相应的题目标签作为引导,在html文件中只需要写入如下内容即可:
{{ block title }}
问卷调查
{{ endblock }}
{{ block content }}
{{ formfields }}
{{ next_button }}
{{ endblock }}
oTree在生成网页时会自行解释花括号中的内容,转换为标准的html文件供浏览器显示。其中会自动生成form_fields中设定的待填写字段,会生成一个提交按键。
这里的内置的错误提示形式需要通过服务器进行验证,可以理解为学生交卷,老师改卷。另一种可行的方法是在前端借助JavaScript进行验证,即学生自己对答案并订正后才交回给老师。由于涉及到不少JavaScript的内容,不进行展开,具体的代码可以参考附带的代码示例。
为什么还需要这样一种前端验证方法?一个直接的原因是减轻服务器的运算负担,这个时候传回的数据服务器不需要再进行验证了;第二个原因是,如果在生成待输入字段的时候没有使用oTree自带的花括号形式(除外的其他形式参考官方文档Forms一节的内容),而是用原生HTML语言的<input>标签等生成输入框,这个时候生成的输入框在样式和逻辑上都会有很大不一样。
使用生成的输入框在页面字段经验证存在错误后不会清空先前已填写的内容;假如一个页面上有这么一个输入框使用了原生HTML标签,并且参加者已经填入了某个值。这个页面经后端验证后存在错误需要重新填写,这个时候参加者那边重新显示的页面上不会保留先前已填写的值,需要重新填写。为了避免这种情况出现,所以采用前端验证的方式而不经过服务器;这也可以看出oTree自带的元素或功能其实包含很多细微的特点和方便之处。
显示的页面如下:

Questionnaire
第二个页面中我们想要展示被试刚刚输入的内容,一般来说可以在前端中直接调用相应的字段,但有的时候我们想要展示的是一些简单计算过得到的数据或者是依据某个条件进行展示。参考下面的例子:
class Results(Page):
@staticmethod
def vars_for_template(player):
if player.gender == 0:
gender_text = '女'
else:
gender_text = '男'
if player.grade <= 4:
grade_text = '本科生'
else:
grade_text = '研究生'
return dict(
gender_text = gender_text,
grade_text = grade_text
)
这里使用的静态方法是vars_for_template,这个方法可以在后端进行一些计算或赋值得到需要在前端进行展示的变量,这个方法返回的值是一个字典,前端页面可以直接调用字典的key(键)显示相应的值。在上面的例子中,dict是生成字典的一个方法,等号左边是字典的键,右边是字典的值,前端调用的是字典的键。例子中是根据先前输入的数据得到不同的文本,并用于展示。
相应的html文件:
{{ block title }}
问卷内容展示
{{ endblock }}
{{ block content }}
<p> 您就读的学校是 {{ player.school }}</p>
<p> 您的年龄是 {{ player.age }}</p>
<p> 您的性别是 {{ gender_text }}</p>
<p> 您现在是 {{ grade_text }}</p>
{{ if player.major == True}}
<p> 您<b>主修</b>经济类专业</p>
{{ else }}
<p> 您<b>不主修</b>经济类专业</p>
{{ endif }}
{{ endblock }}
这个例子中,学校和年龄是直接调用显示输入的数据,性别和年级则是调用vars_for_template中返回的字典中键,主修专业这里使用了花括号的条件语句。
显示的页面如下:注意下面出现的Debug info是用于程序调试的,每一页都会有,正式实验的时候需要关闭(参考正式实验操作下面的程序准备)
在设定好页面之后,记得在py文件中的page_sequence中设定好页面顺序
page_sequence = [Questionnaire, Results]
至此完成简单问卷的例子编写。


Questionnaire Result
原文地址: https://github.com/MarvinLuoGS/otree-crash-tutorial 和 https://slides-otree-tutorial.netlify.app/1 感谢 罗干松 (ZJU) 范徐航(Duke) 同学,如果内容涉及侵权,告知我后会立即删除。
No responses yet