From 4c987ac7b3e82bb0f76289badd08d548cc81eddd Mon Sep 17 00:00:00 2001 From: Raphael Michel Date: Tue, 5 May 2026 11:35:29 +0200 Subject: [PATCH] Drop second component from many time picker fields --- src/pretix/base/forms/questions.py | 2 +- src/pretix/base/forms/widgets.py | 55 +++++++++++++++++-- src/pretix/control/forms/event.py | 18 +++--- src/pretix/control/forms/filter.py | 2 +- src/pretix/control/forms/item.py | 8 +-- src/pretix/control/forms/subevents.py | 19 +++++-- src/pretix/control/views/subevents.py | 3 +- src/pretix/static/pretixcontrol/js/ui/main.js | 6 +- .../static/pretixcontrol/scss/main.scss | 2 + src/pretix/static/pretixpresale/js/ui/main.js | 6 +- 10 files changed, 88 insertions(+), 33 deletions(-) diff --git a/src/pretix/base/forms/questions.py b/src/pretix/base/forms/questions.py index 488e684b7a..778ff09bbe 100644 --- a/src/pretix/base/forms/questions.py +++ b/src/pretix/base/forms/questions.py @@ -939,7 +939,7 @@ class BaseQuestionsForm(forms.Form): label=label, required=required, help_text=help_text, initial=_initial, - widget=TimePickerWidget(time_format=get_format_without_seconds('TIME_INPUT_FORMATS')), + widget=TimePickerWidget(without_seconds=True), ) elif q.type == Question.TYPE_DATETIME: if not help_text: diff --git a/src/pretix/base/forms/widgets.py b/src/pretix/base/forms/widgets.py index 45eb48d872..598527a16d 100644 --- a/src/pretix/base/forms/widgets.py +++ b/src/pretix/base/forms/widgets.py @@ -43,6 +43,10 @@ from django.utils.timezone import get_current_timezone, now from django.utils.translation import gettext_lazy as _ from pretix.helpers.format import PlainHtmlAlternativeString +from pretix.helpers.i18n import ( + get_format_without_seconds, get_javascript_format, + get_javascript_format_without_seconds, +) def replace_arabic_numbers(inp): @@ -108,7 +112,7 @@ class DatePickerWidget(forms.DateInput): class TimePickerWidget(forms.TimeInput): - def __init__(self, attrs=None, time_format=None): + def __init__(self, attrs=None, time_format=None, without_seconds=False): attrs = attrs or {} if 'placeholder' in attrs: del attrs['placeholder'] @@ -117,8 +121,27 @@ class TimePickerWidget(forms.TimeInput): time_attrs['class'] += ' timepickerfield' time_attrs['autocomplete'] = 'off' + if time_format or without_seconds: + # Explicitly set data-format attributes for the JS layer instead of relying on the body-wide config + def time_format_attr(): + if without_seconds: + return get_javascript_format_without_seconds(time_format or "TIME_INPUT_FORMATS") + return get_javascript_format(time_format or "TIME_INPUT_FORMATS") + + time_attrs['data-format'] = lazy(time_format_attr, str) + + def time_format_attr(): + if without_seconds: + return get_javascript_format_without_seconds(time_format or "TIME_INPUT_FORMATS") + return get_javascript_format(time_format or "TIME_INPUT_FORMATS") + + time_attrs['data-format'] = lazy(time_format_attr, str) + def placeholder(): - tf = time_format or get_format('TIME_INPUT_FORMATS')[0] + if without_seconds: + tf = time_format or get_format_without_seconds('TIME_INPUT_FORMATS') + else: + tf = time_format or get_format('TIME_INPUT_FORMATS')[0] return now().replace( year=2000, month=1, day=1, hour=0, minute=0, second=0, microsecond=0 ).strftime(tf) @@ -182,7 +205,7 @@ class UploadedFileWidget(forms.ClearableFileInput): class SplitDateTimePickerWidget(forms.SplitDateTimeWidget): template_name = 'pretixbase/forms/widgets/splitdatetime.html' - def __init__(self, attrs=None, date_format=None, time_format=None, min_date=None, max_date=None): + def __init__(self, attrs=None, date_format=None, time_format=None, min_date=None, max_date=None, without_seconds=False): attrs = attrs or {} if 'placeholder' in attrs: del attrs['placeholder'] @@ -205,14 +228,36 @@ class SplitDateTimePickerWidget(forms.SplitDateTimeWidget): max_date if not isinstance(max_date, datetime) else max_date.astimezone(get_current_timezone()).date() ).isoformat() + if date_format or time_format or without_seconds: + # Explicitly set data-format attributes for the JS layer instead of relying on the body-wide config + def date_format_attr(): + if without_seconds: + return get_javascript_format_without_seconds(date_format or "DATE_INPUT_FORMATS") + return get_javascript_format(date_format or "DATE_INPUT_FORMATS") + + date_attrs['data-format'] = lazy(date_format_attr, str) + + def time_format_attr(): + if without_seconds: + return get_javascript_format_without_seconds(time_format or "TIME_INPUT_FORMATS") + return get_javascript_format(time_format or "TIME_INPUT_FORMATS") + + time_attrs['data-format'] = lazy(time_format_attr, str) + def date_placeholder(): - df = date_format or get_format('DATE_INPUT_FORMATS')[0] + if without_seconds: + df = date_format or get_format_without_seconds('DATE_INPUT_FORMATS') + else: + df = date_format or get_format('DATE_INPUT_FORMATS')[0] return now().replace( year=2000, month=12, day=31, hour=18, minute=0, second=0, microsecond=0 ).strftime(df) def time_placeholder(): - tf = time_format or get_format('TIME_INPUT_FORMATS')[0] + if without_seconds: + tf = time_format or get_format_without_seconds('TIME_INPUT_FORMATS') + else: + tf = time_format or get_format('TIME_INPUT_FORMATS')[0] return now().replace( year=2000, month=1, day=1, hour=0, minute=0, second=0, microsecond=0 ).strftime(tf) diff --git a/src/pretix/control/forms/event.py b/src/pretix/control/forms/event.py index 6d672f204e..d4509f844d 100644 --- a/src/pretix/control/forms/event.py +++ b/src/pretix/control/forms/event.py @@ -197,10 +197,10 @@ class EventWizardBasicsForm(I18nModelForm): 'presale_end': SplitDateTimeField, } widgets = { - 'date_from': SplitDateTimePickerWidget(), - 'date_to': SplitDateTimePickerWidget(attrs={'data-date-after': '#id_basics-date_from_0'}), - 'presale_start': SplitDateTimePickerWidget(), - 'presale_end': SplitDateTimePickerWidget(attrs={'data-date-after': '#id_basics-presale_start_0'}), + 'date_from': SplitDateTimePickerWidget(without_seconds=True), + 'date_to': SplitDateTimePickerWidget(attrs={'data-date-after': '#id_basics-date_from_0'}, without_seconds=True), + 'presale_start': SplitDateTimePickerWidget(without_seconds=True), + 'presale_end': SplitDateTimePickerWidget(attrs={'data-date-after': '#id_basics-presale_start_0'}, without_seconds=True), 'slug': SlugWidget, } @@ -521,11 +521,11 @@ class EventUpdateForm(I18nModelForm): 'limit_sales_channels': SafeModelMultipleChoiceField, } widgets = { - 'date_from': SplitDateTimePickerWidget(), - 'date_to': SplitDateTimePickerWidget(attrs={'data-date-after': '#id_date_from_0'}), - 'date_admission': SplitDateTimePickerWidget(attrs={'data-date-default': '#id_date_from_0'}), - 'presale_start': SplitDateTimePickerWidget(), - 'presale_end': SplitDateTimePickerWidget(attrs={'data-date-after': '#id_presale_start_0'}), + 'date_from': SplitDateTimePickerWidget(without_seconds=True), + 'date_to': SplitDateTimePickerWidget(attrs={'data-date-after': '#id_date_from_0'}, without_seconds=True), + 'date_admission': SplitDateTimePickerWidget(attrs={'data-date-default': '#id_date_from_0'}, without_seconds=True), + 'presale_start': SplitDateTimePickerWidget(without_seconds=True), + 'presale_end': SplitDateTimePickerWidget(attrs={'data-date-after': '#id_presale_start_0'}, without_seconds=True), } diff --git a/src/pretix/control/forms/filter.py b/src/pretix/control/forms/filter.py index 806c5e2ea2..fece494bbf 100644 --- a/src/pretix/control/forms/filter.py +++ b/src/pretix/control/forms/filter.py @@ -770,7 +770,7 @@ class EventOrderExpertFilterForm(EventOrderFilterForm): ) elif q.type == Question.TYPE_TIME: self.fields[fname] = forms.TimeField( - widget=TimePickerWidget(time_format=get_format_without_seconds('TIME_INPUT_FORMATS')), + widget=TimePickerWidget(without_seconds=True), help_text=_('Exact matches only'), **kwargs, ) diff --git a/src/pretix/control/forms/item.py b/src/pretix/control/forms/item.py index 6829907cd4..577a40393e 100644 --- a/src/pretix/control/forms/item.py +++ b/src/pretix/control/forms/item.py @@ -245,8 +245,8 @@ class QuestionForm(I18nModelForm): 'valid_string_length_max', ] widgets = { - 'valid_datetime_min': SplitDateTimePickerWidget(), - 'valid_datetime_max': SplitDateTimePickerWidget(), + 'valid_datetime_min': SplitDateTimePickerWidget(without_seconds=True), + 'valid_datetime_max': SplitDateTimePickerWidget(without_seconds=True), 'valid_date_min': DatePickerWidget(), 'valid_date_max': DatePickerWidget(), 'items': forms.CheckboxSelectMultiple( @@ -1372,6 +1372,6 @@ class ItemProgramTimeForm(I18nModelForm): 'end': forms.SplitDateTimeField, } widgets = { - 'start': SplitDateTimePickerWidget(), - 'end': SplitDateTimePickerWidget(), + 'start': SplitDateTimePickerWidget(without_seconds=True), + 'end': SplitDateTimePickerWidget(without_seconds=True), } diff --git a/src/pretix/control/forms/subevents.py b/src/pretix/control/forms/subevents.py index 05607956e3..b8e004c33a 100644 --- a/src/pretix/control/forms/subevents.py +++ b/src/pretix/control/forms/subevents.py @@ -39,6 +39,7 @@ from pretix.base.reldate import RelativeDateTimeField, RelativeDateWrapper from pretix.base.templatetags.money import money_filter from pretix.control.forms import SplitDateTimeField, SplitDateTimePickerWidget from pretix.control.forms.rrule import RRuleForm +from pretix.helpers.i18n import get_javascript_format_without_seconds from pretix.helpers.money import change_decimal_field @@ -80,11 +81,11 @@ class SubEventForm(I18nModelForm): 'presale_end': SplitDateTimeField, } widgets = { - 'date_from': SplitDateTimePickerWidget(), - 'date_to': SplitDateTimePickerWidget(attrs={'data-date-after': '#id_date_from_0'}), - 'date_admission': SplitDateTimePickerWidget(attrs={'data-date-after': '#id_date_from_0'}), - 'presale_start': SplitDateTimePickerWidget(), - 'presale_end': SplitDateTimePickerWidget(attrs={'data-date-after': '#id_presale_start_0'}), + 'date_from': SplitDateTimePickerWidget(without_seconds=True), + 'date_to': SplitDateTimePickerWidget(attrs={'data-date-after': '#id_date_from_0'}, without_seconds=True), + 'date_admission': SplitDateTimePickerWidget(attrs={'data-date-after': '#id_date_from_0'}, without_seconds=True), + 'presale_start': SplitDateTimePickerWidget(without_seconds=True), + 'presale_end': SplitDateTimePickerWidget(attrs={'data-date-after': '#id_presale_start_0'}, without_seconds=True), } @@ -162,7 +163,7 @@ class SubEventBulkEditForm(I18nModelForm): self.fields[k + '_time'] = forms.TimeField( label=self._meta.model._meta.get_field(k).verbose_name, help_text=self._meta.model._meta.get_field(k).help_text, - widget=TimePickerWidget(), + widget=TimePickerWidget(without_seconds=True), required=False, ) @@ -506,6 +507,12 @@ class TimeForm(forms.Form): required=False ) + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.fields['time_from'].widget.attrs['data-format'] = get_javascript_format_without_seconds("TIME_INPUT_FORMATS") + self.fields['time_to'].widget.attrs['data-format'] = get_javascript_format_without_seconds("TIME_INPUT_FORMATS") + self.fields['time_admission'].widget.attrs['data-format'] = get_javascript_format_without_seconds("TIME_INPUT_FORMATS") + TimeFormSet = formset_factory( TimeForm, diff --git a/src/pretix/control/views/subevents.py b/src/pretix/control/views/subevents.py index 4d5fbbc0c3..6605fc17d5 100644 --- a/src/pretix/control/views/subevents.py +++ b/src/pretix/control/views/subevents.py @@ -79,6 +79,7 @@ from pretix.control.views import PaginationMixin from pretix.control.views.event import MetaDataEditorMixin from pretix.helpers import GroupConcat from pretix.helpers.compat import CompatDeleteView +from pretix.helpers.i18n import get_format_without_seconds from pretix.helpers.models import modelcopy @@ -803,7 +804,7 @@ class SubEventBulkCreate(SubEventEditorMixin, EventPermissionRequiredMixin, Asyn ctx['rrule_formset'] = self.rrule_formset ctx['time_formset'] = self.time_formset - tf = get_format('TIME_INPUT_FORMATS')[0] + tf = get_format_without_seconds('TIME_INPUT_FORMATS') ctx['time_admission_sample'] = time(8, 30, 0).strftime(tf) ctx['time_begin_sample'] = time(9, 0, 0).strftime(tf) ctx['time_end_sample'] = time(18, 0, 0).strftime(tf) diff --git a/src/pretix/static/pretixcontrol/js/ui/main.js b/src/pretix/static/pretixcontrol/js/ui/main.js index 06c436192b..2b865eec9c 100644 --- a/src/pretix/static/pretixcontrol/js/ui/main.js +++ b/src/pretix/static/pretixcontrol/js/ui/main.js @@ -123,7 +123,7 @@ var form_handlers = function (el) { el.find(".datetimepicker").each(function () { $(this).datetimepicker({ - format: $("body").attr("data-datetimeformat"), + format: $(this).attr("data-format") ? $(this).attr("data-format") : $("body").attr("data-datetimeformat"), locale: $("body").attr("data-datetimelocale"), useCurrent: false, showClear: !$(this).prop("required"), @@ -146,7 +146,7 @@ var form_handlers = function (el) { el.find(".datepickerfield").each(function () { var opts = { - format: $("body").attr("data-dateformat"), + format: $(this).attr("data-format") ? $(this).attr("data-format") : $("body").attr("data-dateformat"), locale: $("body").attr("data-datetimelocale"), useCurrent: false, showClear: !$(this).prop("required"), @@ -204,7 +204,7 @@ var form_handlers = function (el) { el.find(".timepickerfield").each(function () { var opts = { - format: $("body").attr("data-timeformat"), + format: $(this).attr("data-format") ? $(this).attr("data-format") : $("body").attr("data-timeformat"), locale: $("body").attr("data-datetimelocale"), useCurrent: false, showClear: !$(this).prop("required"), diff --git a/src/pretix/static/pretixcontrol/scss/main.scss b/src/pretix/static/pretixcontrol/scss/main.scss index 67b3f22b76..6971c5a170 100644 --- a/src/pretix/static/pretixcontrol/scss/main.scss +++ b/src/pretix/static/pretixcontrol/scss/main.scss @@ -464,6 +464,8 @@ details.details-open .panel-title::before { .alert > dl:last-child, td > p:last-child, .panel-body > dl:last-child, +.panel-body > ul:last-child, +.panel-body > ol:last-child, .panel-body > .table:last-child, .panel-body > .table-responsive:last-child > .table:last-child, table td ul:last-child { diff --git a/src/pretix/static/pretixpresale/js/ui/main.js b/src/pretix/static/pretixpresale/js/ui/main.js index f4f66f70c4..e026a219cd 100644 --- a/src/pretix/static/pretixpresale/js/ui/main.js +++ b/src/pretix/static/pretixpresale/js/ui/main.js @@ -11,7 +11,7 @@ var form_handlers = function (el) { el.find(".datetimepicker").each(function () { $(this).datetimepicker({ - format: $("body").attr("data-datetimeformat"), + format: $(this).attr("data-format") ? $(this).attr("data-format") : $("body").attr("data-datetimeformat"), locale: $("body").attr("data-datetimelocale"), useCurrent: false, showClear: !$(this).prop("required"), @@ -34,7 +34,7 @@ var form_handlers = function (el) { el.find(".datepickerfield").each(function () { var opts = { - format: $("body").attr("data-dateformat"), + format: $(this).attr("data-format") ? $(this).attr("data-format") : $("body").attr("data-dateformat"), locale: $("body").attr("data-datetimelocale"), useCurrent: false, showClear: !$(this).prop("required"), @@ -91,7 +91,7 @@ var form_handlers = function (el) { el.find(".timepickerfield").each(function () { var opts = { - format: $("body").attr("data-timeformat"), + format: $(this).attr("data-format") ? $(this).attr("data-format") : $("body").attr("data-timeformat"), locale: $("body").attr("data-datetimelocale"), useCurrent: false, showClear: !$(this).prop("required"),