forked from CGM_Public/pretix_original
* [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:
committed by
Raphael Michel
parent
b8c041d0d6
commit
251d62f3c4
@@ -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(
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
<div class="splitdatetimerow">
|
||||
{% include 'django/forms/widgets/multiwidget.html' %}
|
||||
</div>
|
||||
@@ -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)
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 %}
|
||||
|
||||
@@ -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" %}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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')
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user