diff --git a/doc/api/resources/questions.rst b/doc/api/resources/questions.rst index 4c795b6c80..a54974c6e6 100644 --- a/doc/api/resources/questions.rst +++ b/doc/api/resources/questions.rst @@ -23,6 +23,9 @@ type string The expected ty * ``C`` – choice from a list * ``M`` – multiple choice from a list * ``F`` – file upload + * ``D`` – date + * ``H`` – time + * ``W`` – date and time required boolean If ``True``, the question needs to be filled out. position integer An integer, used for sorting items list of integers List of item IDs this question is assigned to. @@ -32,6 +35,9 @@ options list of objects In case of ques └ answer multi-lingual string The displayed value of this option ===================================== ========================== ======================================================= +.. versionchanged:: 1.12 + + The values ``D``, ``H``, and ``W`` for the field ``type`` are now allowed. Endpoints --------- diff --git a/src/pretix/base/models/items.py b/src/pretix/base/models/items.py index 0b1db553d6..0cbc0603b8 100644 --- a/src/pretix/base/models/items.py +++ b/src/pretix/base/models/items.py @@ -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( diff --git a/src/pretix/base/models/orders.py b/src/pretix/base/models/orders.py index 60beb7157f..612a8cb833 100644 --- a/src/pretix/base/models/orders.py +++ b/src/pretix/base/models/orders.py @@ -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(_("")) + 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 diff --git a/src/pretix/base/templates/pretixbase/forms/widgets/splitdatetime.html b/src/pretix/base/templates/pretixbase/forms/widgets/splitdatetime.html new file mode 100644 index 0000000000..46e6c78d5d --- /dev/null +++ b/src/pretix/base/templates/pretixbase/forms/widgets/splitdatetime.html @@ -0,0 +1,3 @@ +
+ {% include 'django/forms/widgets/multiwidget.html' %} +
diff --git a/src/pretix/control/forms/__init__.py b/src/pretix/control/forms/__init__.py index f2be3727db..43c2deeda1 100644 --- a/src/pretix/control/forms/__init__.py +++ b/src/pretix/control/forms/__init__.py @@ -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) diff --git a/src/pretix/control/templates/pretixcontrol/event/settings.html b/src/pretix/control/templates/pretixcontrol/event/settings.html index 0d7dd89635..9d3bc99c62 100644 --- a/src/pretix/control/templates/pretixcontrol/event/settings.html +++ b/src/pretix/control/templates/pretixcontrol/event/settings.html @@ -9,10 +9,10 @@ {% trans "General information" %} {% 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 @@
{% trans "Timeline" %} - {% 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" %}
diff --git a/src/pretix/control/templates/pretixcontrol/events/create_basics.html b/src/pretix/control/templates/pretixcontrol/events/create_basics.html index 0098eacaeb..dde74d8fee 100644 --- a/src/pretix/control/templates/pretixcontrol/events/create_basics.html +++ b/src/pretix/control/templates/pretixcontrol/events/create_basics.html @@ -29,8 +29,8 @@ - {% 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 %}
{% trans "Timeline" %} - {% 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" %}
{% endif %} {% endblock %} diff --git a/src/pretix/control/templates/pretixcontrol/item/index.html b/src/pretix/control/templates/pretixcontrol/item/index.html index cbe3de15b1..4db4fd933e 100644 --- a/src/pretix/control/templates/pretixcontrol/item/index.html +++ b/src/pretix/control/templates/pretixcontrol/item/index.html @@ -23,8 +23,8 @@
{% trans "Availability" %} - {% 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" %} diff --git a/src/pretix/control/templates/pretixcontrol/subevents/detail.html b/src/pretix/control/templates/pretixcontrol/subevents/detail.html index 6119482f91..9eb13e8672 100644 --- a/src/pretix/control/templates/pretixcontrol/subevents/detail.html +++ b/src/pretix/control/templates/pretixcontrol/subevents/detail.html @@ -22,10 +22,10 @@ {% trans "General information" %} {% 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 %}
{% trans "Timeline" %} - {% 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" %}
{% trans "Quotas" %} diff --git a/src/pretix/control/templates/pretixcontrol/vouchers/bulk.html b/src/pretix/control/templates/pretixcontrol/vouchers/bulk.html index b3f3bd7bd0..6e38637a31 100644 --- a/src/pretix/control/templates/pretixcontrol/vouchers/bulk.html +++ b/src/pretix/control/templates/pretixcontrol/vouchers/bulk.html @@ -38,7 +38,7 @@
{% trans "Voucher details" %} - {% 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" %}
diff --git a/src/pretix/control/templates/pretixcontrol/vouchers/detail.html b/src/pretix/control/templates/pretixcontrol/vouchers/detail.html index a7b6bd6f8d..fa5461382f 100644 --- a/src/pretix/control/templates/pretixcontrol/vouchers/detail.html +++ b/src/pretix/control/templates/pretixcontrol/vouchers/detail.html @@ -26,7 +26,7 @@ {% trans "Voucher details" %} {% 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" %}
diff --git a/src/pretix/control/utils/i18n.py b/src/pretix/control/utils/i18n.py index 3f527d1c93..f94beb12a4 100644 --- a/src/pretix/control/utils/i18n.py +++ b/src/pretix/control/utils/i18n.py @@ -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: diff --git a/src/pretix/control/views/item.py b/src/pretix/control/views/item.py index 6a4417bc9a..47cc9adb3f 100644 --- a/src/pretix/control/views/item.py +++ b/src/pretix/control/views/item.py @@ -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') diff --git a/src/pretix/presale/context.py b/src/pretix/presale/context.py index 6bdd7ba3d9..2904298e5a 100644 --- a/src/pretix/presale/context.py +++ b/src/pretix/presale/context.py @@ -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 diff --git a/src/pretix/presale/forms/__init__.py b/src/pretix/presale/forms/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/src/pretix/presale/forms/checkout.py b/src/pretix/presale/forms/checkout.py index 3abe0277d5..83b1587327 100644 --- a/src/pretix/presale/forms/checkout.py +++ b/src/pretix/presale/forms/checkout.py @@ -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: diff --git a/src/pretix/presale/templates/pretixpresale/base.html b/src/pretix/presale/templates/pretixpresale/base.html index 53d0ca9b3f..c0785bf91e 100644 --- a/src/pretix/presale/templates/pretixpresale/base.html +++ b/src/pretix/presale/templates/pretixpresale/base.html @@ -23,6 +23,7 @@ + @@ -35,7 +36,7 @@ {% block custom_header %}{% endblock %} - + {% block above %} {% endblock %}
diff --git a/src/pretix/static/pretixpresale/js/ui/main.js b/src/pretix/static/pretixpresale/js/ui/main.js index c54fa4518e..17a1645401 100644 --- a/src/pretix/static/pretixpresale/js/ui/main.js +++ b/src/pretix/static/pretixpresale/js/ui/main.js @@ -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(); }); diff --git a/src/pretix/static/pretixpresale/scss/_forms.scss b/src/pretix/static/pretixpresale/scss/_forms.scss index c30d1f2954..e6a8e7a1a6 100644 --- a/src/pretix/static/pretixpresale/scss/_forms.scss +++ b/src/pretix/static/pretixpresale/scss/_forms.scss @@ -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; + } +} diff --git a/src/pretix/static/pretixpresale/scss/main.scss b/src/pretix/static/pretixpresale/scss/main.scss index 8a4401daa4..b605c076ea 100644 --- a/src/pretix/static/pretixpresale/scss/main.scss +++ b/src/pretix/static/pretixpresale/scss/main.scss @@ -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"; diff --git a/src/tests/presale/test_checkout.py b/src/tests/presale/test_checkout.py index 6cf617bfa7..44a987bfb5 100644 --- a/src/tests/presale/test_checkout.py +++ b/src/tests/presale/test_checkout.py @@ -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,