Fix #732 -- Add date and time question types (#732)

* [WIP] add date/time question type

* Date/time questions python classes, types and form handling

* use own timepicker

* Fix argument naming

* Add css and js for datetimepickers

* remove not needed str call

* seperate splitdatetime widget template and fix date/time questions

* change date placeholder to dec 31

* do not show seconds in presale time pickers

* improve codestyle

* add new question types to api doc

* add test

* expand test to datetime question

* add new questiontypes to changelog

remove duplicate parens

* remove timezone from time only question answers

* improve codestyle

* Fix date and time formatting in control question overview
This commit is contained in:
Felix Rindt
2018-01-14 14:29:38 +01:00
committed by Raphael Michel
parent b8c041d0d6
commit 251d62f3c4
21 changed files with 304 additions and 25 deletions

View File

@@ -543,7 +543,10 @@ class Question(LoggedModel):
* a multi-line string (``TYPE_TEXT``)
* a boolean (``TYPE_BOOLEAN``)
* a multiple choice option (``TYPE_CHOICE`` and ``TYPE_CHOICE_MULTIPLE``)
* a file upload (``TYPE_FILE``))
* a file upload (``TYPE_FILE``)
* a date (``TYPE_DATE``)
* a time (``TYPE_TIME``)
* a date and a time (``TYPE_DATETIME``)
:param event: The event this question belongs to
:type event: Event
@@ -562,6 +565,9 @@ class Question(LoggedModel):
TYPE_CHOICE = "C"
TYPE_CHOICE_MULTIPLE = "M"
TYPE_FILE = "F"
TYPE_DATE = "D"
TYPE_TIME = "H"
TYPE_DATETIME = "W"
TYPE_CHOICES = (
(TYPE_NUMBER, _("Number")),
(TYPE_STRING, _("Text (one line)")),
@@ -570,6 +576,9 @@ class Question(LoggedModel):
(TYPE_CHOICE, _("Choose one from a list")),
(TYPE_CHOICE_MULTIPLE, _("Choose multiple from a list")),
(TYPE_FILE, _("File upload")),
(TYPE_DATE, _("Date")),
(TYPE_TIME, _("Time")),
(TYPE_DATETIME, _("Date and time")),
)
event = models.ForeignKey(

View File

@@ -6,6 +6,7 @@ from datetime import datetime, time
from decimal import Decimal
from typing import Any, Dict, List, Union
import dateutil
import pytz
from django.conf import settings
from django.db import models
@@ -15,6 +16,7 @@ from django.dispatch import receiver
from django.urls import reverse
from django.utils.crypto import get_random_string
from django.utils.encoding import escape_uri_path
from django.utils.formats import date_format
from django.utils.functional import cached_property
from django.utils.timezone import make_aware, now
from django.utils.translation import pgettext_lazy, ugettext_lazy as _
@@ -498,6 +500,18 @@ class QuestionAnswer(models.Model):
return str(_("No"))
elif self.question.type == Question.TYPE_FILE:
return str(_("<file>"))
elif self.question.type == Question.TYPE_DATETIME:
d = dateutil.parser.parse(self.answer)
if self.orderposition:
tz = pytz.timezone(self.orderposition.order.event.settings.timezone)
d = d.astimezone(tz)
return date_format(d, "SHORT_DATETIME_FORMAT")
elif self.question.type == Question.TYPE_DATE:
d = dateutil.parser.parse(self.answer)
return date_format(d, "SHORT_DATE_FORMAT")
elif self.question.type == Question.TYPE_TIME:
d = dateutil.parser.parse(self.answer)
return date_format(d, "TIME_FORMAT")
else:
return self.answer

View File

@@ -0,0 +1,3 @@
<div class="splitdatetimerow">
{% include 'django/forms/widgets/multiwidget.html' %}
</div>

View File

@@ -103,6 +103,7 @@ class SlugWidget(forms.TextInput):
class SplitDateTimePickerWidget(forms.SplitDateTimeWidget):
template_name = 'pretixbase/forms/widgets/splitdatetime.html'
def __init__(self, attrs=None, date_format=None, time_format=None):
attrs = attrs or {}
@@ -114,11 +115,10 @@ class SplitDateTimePickerWidget(forms.SplitDateTimeWidget):
time_attrs.setdefault('class', 'form-control splitdatetimepart')
date_attrs['class'] += ' datepickerfield'
time_attrs['class'] += ' timepickerfield'
time_attrs['class'] += ' timepickerfield'
df = date_format or get_format('DATE_INPUT_FORMATS')[0]
date_attrs['placeholder'] = now().replace(
year=2000, month=1, day=1, hour=0, minute=0, second=0, microsecond=0
year=2000, month=12, day=31, hour=18, minute=0, second=0, microsecond=0
).strftime(df)
tf = time_format or get_format('TIME_INPUT_FORMATS')[0]
time_attrs['placeholder'] = now().replace(
@@ -131,3 +131,39 @@ class SplitDateTimePickerWidget(forms.SplitDateTimeWidget):
)
# Skip one hierarchy level
forms.MultiWidget.__init__(self, widgets, attrs)
class DatePickerWidget(forms.DateInput):
def __init__(self, attrs=None, date_format=None):
attrs = attrs or {}
if 'placeholder' in attrs:
del attrs['placeholder']
date_attrs = dict(attrs)
date_attrs.setdefault('class', 'form-control')
date_attrs['class'] += ' datepickerfield'
df = date_format or get_format('DATE_INPUT_FORMATS')[0]
date_attrs['placeholder'] = now().replace(
year=2000, month=12, day=31, hour=18, minute=0, second=0, microsecond=0
).strftime(df)
forms.DateInput.__init__(self, date_attrs, date_format)
class TimePickerWidget(forms.TimeInput):
def __init__(self, attrs=None, time_format=None):
attrs = attrs or {}
if 'placeholder' in attrs:
del attrs['placeholder']
time_attrs = dict(attrs)
time_attrs.setdefault('class', 'form-control')
time_attrs['class'] += ' timepickerfield'
tf = time_format or get_format('TIME_INPUT_FORMATS')[0]
time_attrs['placeholder'] = now().replace(
year=2000, month=12, day=31, hour=18, minute=0, second=0, microsecond=0
).strftime(tf)
forms.TimeInput.__init__(self, time_attrs, time_format)

View File

@@ -9,10 +9,10 @@
<legend>{% trans "General information" %}</legend>
{% bootstrap_field form.name layout="control" %}
{% bootstrap_field form.slug layout="control" %}
{% bootstrap_field form.date_from layout="control" horizontal_field_class="col-md-9 splitdatetimerow" %}
{% bootstrap_field form.date_to layout="control" horizontal_field_class="col-md-9 splitdatetimerow" %}
{% bootstrap_field form.date_from layout="control" %}
{% bootstrap_field form.date_to layout="control" %}
{% bootstrap_field form.location layout="control" %}
{% bootstrap_field form.date_admission layout="control" horizontal_field_class="col-md-9 splitdatetimerow" %}
{% bootstrap_field form.date_admission layout="control" %}
{% bootstrap_field form.currency layout="control" %}
{% bootstrap_field form.is_public layout="control" %}
@@ -51,9 +51,9 @@
</fieldset>
<fieldset>
<legend>{% trans "Timeline" %}</legend>
{% bootstrap_field form.presale_start layout="control" horizontal_field_class="col-md-9 splitdatetimerow" %}
{% bootstrap_field form.presale_start layout="control" %}
{% bootstrap_field sform.presale_start_show_date layout="control" %}
{% bootstrap_field form.presale_end layout="control" horizontal_field_class="col-md-9 splitdatetimerow" %}
{% bootstrap_field form.presale_end layout="control" %}
{% bootstrap_field sform.show_items_outside_presale_period layout="control" %}
{% bootstrap_field sform.last_order_modification_date layout="control" %}
</fieldset>

View File

@@ -29,8 +29,8 @@
</div>
</div>
</div>
{% bootstrap_field form.date_from layout="control" horizontal_field_class="col-md-9 splitdatetimerow" %}
{% bootstrap_field form.date_to layout="control" horizontal_field_class="col-md-9 splitdatetimerow" %}
{% bootstrap_field form.date_from layout="control" %}
{% bootstrap_field form.date_to layout="control" %}
{% bootstrap_field form.location layout="control" %}
{% bootstrap_field form.currency layout="control" %}
{% bootstrap_field form.tax_rate addon_after="%" layout="control" %}
@@ -43,8 +43,8 @@
{% if form.presale_start %}
<fieldset>
<legend>{% trans "Timeline" %}</legend>
{% bootstrap_field form.presale_start layout="control" horizontal_field_class="col-md-9 splitdatetimerow" %}
{% bootstrap_field form.presale_end layout="control" horizontal_field_class="col-md-9 splitdatetimerow" %}
{% bootstrap_field form.presale_start layout="control" %}
{% bootstrap_field form.presale_end layout="control" %}
</fieldset>
{% endif %}
{% endblock %}

View File

@@ -23,8 +23,8 @@
</fieldset>
<fieldset>
<legend>{% trans "Availability" %}</legend>
{% bootstrap_field form.available_from layout="control" horizontal_field_class="col-md-9 splitdatetimerow" %}
{% bootstrap_field form.available_until layout="control" horizontal_field_class="col-md-9 splitdatetimerow" %}
{% bootstrap_field form.available_from layout="control" %}
{% bootstrap_field form.available_until layout="control" %}
{% bootstrap_field form.max_per_order layout="control" %}
{% bootstrap_field form.min_per_order layout="control" %}
{% bootstrap_field form.require_voucher layout="control" %}

View File

@@ -22,10 +22,10 @@
<legend>{% trans "General information" %}</legend>
{% bootstrap_field form.name layout="control" %}
{% bootstrap_field form.active layout="control" %}
{% bootstrap_field form.date_from layout="control" horizontal_field_class="col-md-9 splitdatetimerow" %}
{% bootstrap_field form.date_to layout="control" horizontal_field_class="col-md-9 splitdatetimerow" %}
{% bootstrap_field form.date_from layout="control" %}
{% bootstrap_field form.date_to layout="control" %}
{% bootstrap_field form.location layout="control" %}
{% bootstrap_field form.date_admission layout="control" horizontal_field_class="col-md-9 splitdatetimerow" %}
{% bootstrap_field form.date_admission layout="control" %}
{% bootstrap_field form.frontpage_text layout="control" %}
{% if meta_forms %}
<div class="form-group metadata-group">
@@ -49,8 +49,8 @@
</fieldset>
<fieldset>
<legend>{% trans "Timeline" %}</legend>
{% bootstrap_field form.presale_start layout="control" horizontal_field_class="col-md-9 splitdatetimerow" %}
{% bootstrap_field form.presale_end layout="control" horizontal_field_class="col-md-9 splitdatetimerow" %}
{% bootstrap_field form.presale_start layout="control" %}
{% bootstrap_field form.presale_end layout="control" %}
</fieldset>
<fieldset>
<legend>{% trans "Quotas" %}</legend>

View File

@@ -38,7 +38,7 @@
</fieldset>
<fieldset>
<legend>{% trans "Voucher details" %}</legend>
{% bootstrap_field form.valid_until layout="control" horizontal_field_class="col-md-9 splitdatetimerow" %}
{% bootstrap_field form.valid_until layout="control" %}
{% bootstrap_field form.block_quota layout="control" %}
{% bootstrap_field form.allow_ignore_quota layout="control" %}
<div class="form-group">

View File

@@ -26,7 +26,7 @@
<legend>{% trans "Voucher details" %}</legend>
{% bootstrap_field form.code layout="control" %}
{% bootstrap_field form.max_usages layout="control" %}
{% bootstrap_field form.valid_until layout="control" horizontal_field_class="col-md-9 splitdatetimerow" %}
{% bootstrap_field form.valid_until layout="control" %}
{% bootstrap_field form.block_quota layout="control" %}
{% bootstrap_field form.allow_ignore_quota layout="control" %}
<div class="form-group">

View File

@@ -55,6 +55,20 @@ def get_javascript_format(format_name):
)
def get_format_without_seconds(format_name):
formats = get_format(format_name)
formats_no_seconds = [f for f in formats if '%S' not in f]
return formats_no_seconds[0] if formats_no_seconds else formats[0]
def get_javascript_format_without_seconds(format_name):
f = get_format_without_seconds(format_name)
return toJavascript_re.sub(
lambda x: date_conversion_to_moment[x.group()],
f
)
def get_moment_locale(locale=None):
cur_lang = locale or translation.get_language()
if cur_lang in moment_locales:

View File

@@ -430,6 +430,12 @@ class QuestionView(EventPermissionRequiredMixin, QuestionMixin, ChartContainingV
for a in qs:
a['answer'] = str(a['options__answer'])
del a['options__answer']
elif self.object.type in (Question.TYPE_TIME, Question.TYPE_DATE, Question.TYPE_DATETIME):
qs = qs.order_by('answer')
qs_model = qs
qs = qs.values('answer').annotate(count=Count('id')).order_by('-count')
for a, a_model in zip(qs, qs_model):
a['answer'] = str(a_model)
else:
qs = qs.order_by('answer').values('answer').annotate(count=Count('id')).order_by('-count')

View File

@@ -4,6 +4,7 @@ from django.utils.translation import get_language_info
from i18nfield.strings import LazyI18nString
from pretix.base.settings import GlobalSettingsObject
from pretix.control.utils.i18n import get_javascript_format_without_seconds, get_moment_locale
from .signals import footer_link, html_footer, html_head
@@ -71,4 +72,9 @@ def contextprocessor(request):
ctx['footer'] = _footer
ctx['site_url'] = settings.SITE_URL
ctx['js_datetime_format'] = get_javascript_format_without_seconds('DATETIME_INPUT_FORMATS')
ctx['js_date_format'] = get_javascript_format_without_seconds('DATE_INPUT_FORMATS')
ctx['js_time_format'] = get_javascript_format_without_seconds('TIME_INPUT_FORMATS')
ctx['js_locale'] = get_moment_locale()
return ctx

View File

@@ -3,6 +3,8 @@ import os
from decimal import Decimal
from itertools import chain
import dateutil
import pytz
import vat_moss.errors
import vat_moss.id
from django import forms
@@ -18,8 +20,12 @@ from pretix.base.models import ItemVariation, Question
from pretix.base.models.orders import InvoiceAddress, OrderPosition
from pretix.base.models.tax import EU_COUNTRIES, TAXED_ZERO
from pretix.base.templatetags.rich_text import rich_text
from pretix.control.forms import (
DatePickerWidget, SplitDateTimePickerWidget, TimePickerWidget,
)
from pretix.multidomain.urlreverse import eventreverse
from pretix.presale.signals import contact_form_fields, question_form_fields
from pretix.control.utils.i18n import get_format_without_seconds
logger = logging.getLogger(__name__)
@@ -235,6 +241,7 @@ class QuestionsForm(forms.Form):
initial = answers[0]
else:
initial = None
tz = pytz.timezone(event.settings.timezone)
if q.type == Question.TYPE_BOOLEAN:
if q.required:
# For some reason, django-bootstrap3 does not set the required attribute
@@ -258,7 +265,7 @@ class QuestionsForm(forms.Form):
label=q.question, required=q.required,
help_text=q.help_text,
initial=initial.answer if initial else None,
min_value=Decimal('0.00')
min_value=Decimal('0.00'),
)
elif q.type == Question.TYPE_STRING:
field = forms.CharField(
@@ -295,7 +302,28 @@ class QuestionsForm(forms.Form):
label=q.question, required=q.required,
help_text=q.help_text,
initial=initial.file if initial else None,
widget=UploadedFileWidget(position=pos, event=event, answer=initial)
widget=UploadedFileWidget(position=pos, event=event, answer=initial),
)
elif q.type == Question.TYPE_DATE:
field = forms.DateField(
label=q.question, required=q.required,
help_text=q.help_text,
initial=dateutil.parser.parse(initial.answer).date() if initial and initial.answer else None,
widget=DatePickerWidget(),
)
elif q.type == Question.TYPE_TIME:
field = forms.TimeField(
label=q.question, required=q.required,
help_text=q.help_text,
initial=dateutil.parser.parse(initial.answer).astimezone(tz).time() if initial and initial.answer else None,
widget=TimePickerWidget(time_format=get_format_without_seconds('TIME_INPUT_FORMATS')),
)
elif q.type == Question.TYPE_DATETIME:
field = forms.SplitDateTimeField(
label=q.question, required=q.required,
help_text=q.help_text,
initial=dateutil.parser.parse(initial.answer).astimezone(tz) if initial and initial.answer else None,
widget=SplitDateTimePickerWidget(time_format=get_format_without_seconds('TIME_INPUT_FORMATS')),
)
field.question = q
if answers:

View File

@@ -23,6 +23,7 @@
<script type="text/javascript" src="{% static "moment/moment-with-locales.js" %}"></script>
<script type="text/javascript" src="{% static "js/jquery.formset.js" %}"></script>
<script type="text/javascript" src="{% static "bootstrap/js/bootstrap.js" %}"></script>
<script type="text/javascript" src="{% static "datetimepicker/bootstrap-datetimepicker.js" %}"></script>
<script type="text/javascript" src="{% static "pretixpresale/js/ui/main.js" %}"></script>
<script type="text/javascript" src="{% static "pretixbase/js/asynctask.js" %}"></script>
<script type="text/javascript" src="{% static "pretixbase/js/asyncdownload.js" %}"></script>
@@ -35,7 +36,7 @@
<link rel="icon" href="{% static "pretixbase/img/favicon.ico" %}">
{% block custom_header %}{% endblock %}
</head>
<body data-locale="{{ request.LANGUAGE_CODE }}" data-now="{% now "U.u" %}">
<body data-locale="{{ request.LANGUAGE_CODE }}" data-now="{% now "U.u" %}" data-datetimeformat="{{ js_datetime_format }}" data-timeformat="{{ js_time_format }}" data-dateformat="{{ js_date_format }}" data-datetimelocale="{{ js_locale }}">
{% block above %}
{% endblock %}
<div class="container">

View File

@@ -12,6 +12,88 @@ function ngettext(singular, plural, count) {
}
return plural;
}
var form_handlers = function (el) {
el.find(".datetimepicker").each(function() {
$(this).datetimepicker({
format: $("body").attr("data-datetimeformat"),
locale: $("body").attr("data-datetimelocale"),
useCurrent: false,
showClear: !$(this).prop("required"),
icons: {
time: 'fa fa-clock-o',
date: 'fa fa-calendar',
up: 'fa fa-chevron-up',
down: 'fa fa-chevron-down',
previous: 'fa fa-chevron-left',
next: 'fa fa-chevron-right',
today: 'fa fa-screenshot',
clear: 'fa fa-trash',
close: 'fa fa-remove'
}
});
if (!$(this).val()) {
$(this).data("DateTimePicker").viewDate(moment().hour(0).minute(0).second(0));
}
});
el.find(".datepickerfield").each(function() {
var opts = {
format: $("body").attr("data-dateformat"),
locale: $("body").attr("data-datetimelocale"),
useCurrent: false,
showClear: !$(this).prop("required"),
icons: {
time: 'fa fa-clock-o',
date: 'fa fa-calendar',
up: 'fa fa-chevron-up',
down: 'fa fa-chevron-down',
previous: 'fa fa-chevron-left',
next: 'fa fa-chevron-right',
today: 'fa fa-screenshot',
clear: 'fa fa-trash',
close: 'fa fa-remove'
},
};
$(this).datetimepicker(opts);
if ($(this).parent().is('.splitdatetimerow')) {
$(this).on("dp.change", function (ev) {
var $timepicker = $(this).closest(".splitdatetimerow").find(".timepickerfield");
var date = $(this).data('DateTimePicker').date();
if (date === null) {
return;
}
if ($timepicker.val() === "") {
date.set({'hour': 0, 'minute': 0, 'second': 0});
$timepicker.data('DateTimePicker').date(date);
}
});
}
});
el.find(".timepickerfield").each(function() {
var opts = {
format: $("body").attr("data-timeformat"),
locale: $("body").attr("data-datetimelocale"),
useCurrent: false,
showClear: !$(this).prop("required"),
icons: {
time: 'fa fa-clock-o',
date: 'fa fa-calendar',
up: 'fa fa-chevron-up',
down: 'fa fa-chevron-down',
previous: 'fa fa-chevron-left',
next: 'fa fa-chevron-right',
today: 'fa fa-screenshot',
clear: 'fa fa-trash',
close: 'fa fa-remove'
}
};
$(this).datetimepicker(opts);
});
}
$(function () {
"use strict";
@@ -119,6 +201,8 @@ $(function () {
dependency.closest('.form-group').find('input[name=' + dependency.attr("name") + ']').on("dp.change", update);
});
form_handlers($("body"));
// Lightbox
lightbox.init();
});

View File

@@ -68,3 +68,27 @@
.panel-default>.accordion-radio+.panel-collapse>.panel-body {
border-top: 1px solid #ddd;
}
.splitdatetimerow {
display: flex;
flex-direction: row;
flex-wrap: wrap;
.help-block {
width: 100%;
}
}
.splitdatetimepart {
width: 50%;
display: inline-block;
&.datepickerfield {
border-bottom-right-radius: 0;
border-top-right-radius: 0;
}
&.timepickerfield {
border-bottom-left-radius: 0;
border-top-left-radius: 0;
border-left: 0;
}
}

View File

@@ -1,6 +1,7 @@
@import "_variables.scss";
@import "../../pretixbase/scss/colors.scss";
@import "../../bootstrap/scss/_bootstrap.scss";
@import "../../datetimepicker/_bootstrap-datetimepicker.scss";
@import "../../fontawesome/scss/font-awesome.scss";
@import "_event.scss";

View File

@@ -14,7 +14,7 @@ from django_countries.fields import Country
from pretix.base.decimal import round_decimal
from pretix.base.models import (
CartPosition, Event, InvoiceAddress, Item, ItemCategory, Order,
OrderPosition, Organizer, Question, Quota, Voucher,
OrderPosition, Organizer, Question, QuestionAnswer, Quota, Voucher,
)
from pretix.base.models.items import ItemAddOn, ItemVariation, SubEventItem
from pretix.testutils.sessions import get_cart_session_key
@@ -37,6 +37,7 @@ class CheckoutTestCase(TestCase):
category=self.category, default_price=23, admission=True,
tax_rule=self.tr19)
self.quota_tickets.items.add(self.ticket)
self.event.settings.set('timezone', 'UTC')
self.event.settings.set('attendee_names_asked', False)
self.event.settings.set('payment_banktransfer__enabled', True)
@@ -73,6 +74,52 @@ class CheckoutTestCase(TestCase):
self.assertRedirects(response, '/%s/%s/' % (self.orga.slug, self.event.slug),
target_status_code=200)
def test_timezone(self):
""" Test basic timezone change handling by date and time questions """
q1 = Question.objects.create(
event=self.event, question='When did you wake up today?', type=Question.TYPE_TIME,
required=True
)
q2 = Question.objects.create(
event=self.event, question='When was your last haircut?', type=Question.TYPE_DATE,
required=True
)
q3 = Question.objects.create(
event=self.event, question='When are you going to arrive?', type=Question.TYPE_DATETIME,
required=True
)
self.ticket.questions.add(q1)
self.ticket.questions.add(q2)
self.ticket.questions.add(q3)
cr = CartPosition.objects.create(
event=self.event, cart_id=self.session_key, item=self.ticket,
price=23, expires=now() + timedelta(minutes=10)
)
response = self.client.post('/%s/%s/checkout/questions/' % (self.orga.slug, self.event.slug), {
'%s-question_%s' % (cr.id, q1.id): '06:30',
'%s-question_%s' % (cr.id, q2.id): '2005-12-31',
'%s-question_%s_0' % (cr.id, q3.id): '2018-01-01',
'%s-question_%s_1' % (cr.id, q3.id): '5:23',
'email': 'admin@localhost',
}, follow=True)
self.assertRedirects(response, '/%s/%s/checkout/payment/' % (self.orga.slug, self.event.slug), target_status_code=200)
self.event.settings.set('timezone', 'US/Central')
o1 = QuestionAnswer.objects.get(question=q1)
o2 = QuestionAnswer.objects.get(question=q2)
o3 = QuestionAnswer.objects.get(question=q3)
order = Order.objects.create(event=self.event, status=Order.STATUS_PAID,
expires=now() + timedelta(days=3),
total=4)
op = OrderPosition.objects.create(order=order, item=self.ticket, price=42)
o1.cartposition, o2.cartposition, o3.cartposition = None, None, None
o1.orderposition, o2.orderposition, o3.orderposition = op, op, op
# only time and date answers should be unaffected by timezone change
self.assertEqual(str(o1), '06:30')
self.assertEqual(str(o2), '2005-12-31')
o3date, o3time = str(o3).split(' ')
self.assertEqual(o3date, '2017-12-31')
self.assertEqual(o3time, '23:23')
def test_addon_questions(self):
q1 = Question.objects.create(
event=self.event, question='Age', type=Question.TYPE_NUMBER,