I want to use oTree as an alternative for conducting experiments. For this purpose I am looking for a possibility to include mandatory slider questions in forms, i. e. sliders you are required to move before you are able to proceed to the next question. As a start I tried to modify oTrees survey template to achieve a solution for future usage but wasn't able to integrate common approaches like a fieldtracker into the project.
Here are two modified (yet currently after a number of unsuccessful try-outs not really functioning) versions of the models.py and views.py files which give a hint in which direction I want to go. Is there a way to get this to work?
# -*- coding: utf-8 -*-
## models.py
# <standard imports>
from __future__ import division
from django.db import models
from django_countries.fields import CountryField
from model_utils import FieldTracker,
from otree import widgets
from otree.constants import BaseConstants
from otree.db import models
from otree.models import BaseSubsession, BaseGroup, BasePlayer
class Constants(BaseConstants):
name_in_url = 'survey'
players_per_group = None
num_rounds = 1
class Subsession(BaseSubsession):
pass
class Group(BaseGroup):
pass
class Player(BasePlayer):
def set_payoff(self):
"""Calculate payoff, which is zero for the survey"""
self.payoff = 0
q_country = CountryField(
verbose_name='What is your country of citizenship?')
q_age = IntegerFielder(verbose_name='What is your age?',
min=13, max=125,
initial=25,
widget=widgets.SliderInput())
q_gender = models.CharField(initial=None,
choices=['Male', 'Female'],
verbose_name='What is your gender?',
widget=widgets.RadioSelect())
tracker = FieldTracker()
crt_bat = models.PositiveIntegerField()
crt_widget = models.PositiveIntegerField()
crt_lake = models.PositiveIntegerField()
Here comes the second file:
# -*- coding: utf-8 -*-
##views.py
from __future__ import division
from . import models
from ._builtin import Page, WaitPage
from otree.common import Currency as c, currency_range
from .models import Constants, integerfieldcustom
class Demographics(Page):
form_model = models.Player
form_fields = ['q_country',
'q_age',
'q_gender']
check_age = q_age.tracker.has_changed()
def q_age_error_message(self, ):
if Demographics.check_age == False:
return 'You must move the slider before you can continue'
class CognitiveReflectionTest(Page):
form_model = models.Player
form_fields = ['crt_bat',
'crt_widget',
'crt_lake']
def before_next_page(self):
self.player.set_payoff()
page_sequence = [
Demographics,
CognitiveReflectionTest
]
Thanks in advance!
There are two ways of doing it: by using JS only, on the client's side, and by using Django at the server side.
The simple JS solution: in the template add:
{% block scripts %}
<script>
var SliderTouched = false;
var selector = $('[data-slider] input[type="range"]');
selector.change(function() {
SliderTouched = true;
});
$( ".form" ).submit(function( event ) {
if (!SliderTouched){
event.preventDefault();}
});
</script>
{% endblock %}
So until the user triggers change
event, the SliderTOuched
var is set to False
which prevents a form to be submitted. It is a compact way, but you have to deal with showing an error message to the user yourself.
=================
The longer server-side solution is the following:
in models.py
define an additional field:
class Player(BasePlayer):
checkslider = models.IntegerField(blank=True)
in views.py
in addition to your slider field pass also this extra field that will check that the slider was changed:
class MyPage(Page):
form_model = models.Player
form_fields = ['q_age', 'checkslider']
def checkslider_error_message(self, value):
if not value:
return 'Please make your decision using slider'
in template insert this hidden extra field to html:
<input type="hidden" name="checkslider" value="" id="id_checkslider"/>
and set this field to current slider value as soon as slider is changed:
{% block scripts %}
<script>
var selector = $('[data-slider] input[type="range"]');
selector.change(function() {
$('#id_checkslider').val(selector.val());
});
</script>
{% endblock %}
By default, Django assumes an input is required.
I think that means if you just remove the initial value, it will self-validate.
Also, you called something named "IntegerFielder()." Did you mean models.IntegerField() or is there an import that we're not seeing?
I suggest a slight modification to Philipp's answer.
The code above still triggers the error message if the participant touches the slider, but returns the slider to the default starting position.
To fix this, I used the following script:
{% block scripts %}
<script>
$('input[name=q_age]').on('input', function(){
$('#id_checkslider').val(1);
});
</script>
{% endblock %}
The code changes checkslider
from None
to 1
when the slider is touched, even if the participant sets the slider to the default starting position.
来源:https://stackoverflow.com/questions/39113641/mandatory-slider-in-otree-django