Drop second component from many time picker fields

This commit is contained in:
Raphael Michel
2026-05-05 11:35:29 +02:00
parent 0acaed41be
commit 4c987ac7b3
10 changed files with 88 additions and 33 deletions

View File

@@ -939,7 +939,7 @@ class BaseQuestionsForm(forms.Form):
label=label, required=required, label=label, required=required,
help_text=help_text, help_text=help_text,
initial=_initial, initial=_initial,
widget=TimePickerWidget(time_format=get_format_without_seconds('TIME_INPUT_FORMATS')), widget=TimePickerWidget(without_seconds=True),
) )
elif q.type == Question.TYPE_DATETIME: elif q.type == Question.TYPE_DATETIME:
if not help_text: if not help_text:

View File

@@ -43,6 +43,10 @@ from django.utils.timezone import get_current_timezone, now
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from pretix.helpers.format import PlainHtmlAlternativeString 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): def replace_arabic_numbers(inp):
@@ -108,7 +112,7 @@ class DatePickerWidget(forms.DateInput):
class TimePickerWidget(forms.TimeInput): 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 {} attrs = attrs or {}
if 'placeholder' in attrs: if 'placeholder' in attrs:
del attrs['placeholder'] del attrs['placeholder']
@@ -117,8 +121,27 @@ class TimePickerWidget(forms.TimeInput):
time_attrs['class'] += ' timepickerfield' time_attrs['class'] += ' timepickerfield'
time_attrs['autocomplete'] = 'off' 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(): 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( return now().replace(
year=2000, month=1, day=1, hour=0, minute=0, second=0, microsecond=0 year=2000, month=1, day=1, hour=0, minute=0, second=0, microsecond=0
).strftime(tf) ).strftime(tf)
@@ -182,7 +205,7 @@ class UploadedFileWidget(forms.ClearableFileInput):
class SplitDateTimePickerWidget(forms.SplitDateTimeWidget): class SplitDateTimePickerWidget(forms.SplitDateTimeWidget):
template_name = 'pretixbase/forms/widgets/splitdatetime.html' 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 {} attrs = attrs or {}
if 'placeholder' in attrs: if 'placeholder' in attrs:
del attrs['placeholder'] 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() max_date if not isinstance(max_date, datetime) else max_date.astimezone(get_current_timezone()).date()
).isoformat() ).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(): 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( return now().replace(
year=2000, month=12, day=31, hour=18, minute=0, second=0, microsecond=0 year=2000, month=12, day=31, hour=18, minute=0, second=0, microsecond=0
).strftime(df) ).strftime(df)
def time_placeholder(): 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( return now().replace(
year=2000, month=1, day=1, hour=0, minute=0, second=0, microsecond=0 year=2000, month=1, day=1, hour=0, minute=0, second=0, microsecond=0
).strftime(tf) ).strftime(tf)

View File

@@ -197,10 +197,10 @@ class EventWizardBasicsForm(I18nModelForm):
'presale_end': SplitDateTimeField, 'presale_end': SplitDateTimeField,
} }
widgets = { widgets = {
'date_from': SplitDateTimePickerWidget(), 'date_from': SplitDateTimePickerWidget(without_seconds=True),
'date_to': SplitDateTimePickerWidget(attrs={'data-date-after': '#id_basics-date_from_0'}), 'date_to': SplitDateTimePickerWidget(attrs={'data-date-after': '#id_basics-date_from_0'}, without_seconds=True),
'presale_start': SplitDateTimePickerWidget(), 'presale_start': SplitDateTimePickerWidget(without_seconds=True),
'presale_end': SplitDateTimePickerWidget(attrs={'data-date-after': '#id_basics-presale_start_0'}), 'presale_end': SplitDateTimePickerWidget(attrs={'data-date-after': '#id_basics-presale_start_0'}, without_seconds=True),
'slug': SlugWidget, 'slug': SlugWidget,
} }
@@ -521,11 +521,11 @@ class EventUpdateForm(I18nModelForm):
'limit_sales_channels': SafeModelMultipleChoiceField, 'limit_sales_channels': SafeModelMultipleChoiceField,
} }
widgets = { widgets = {
'date_from': SplitDateTimePickerWidget(), 'date_from': SplitDateTimePickerWidget(without_seconds=True),
'date_to': SplitDateTimePickerWidget(attrs={'data-date-after': '#id_date_from_0'}), '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'}), 'date_admission': SplitDateTimePickerWidget(attrs={'data-date-default': '#id_date_from_0'}, without_seconds=True),
'presale_start': SplitDateTimePickerWidget(), 'presale_start': SplitDateTimePickerWidget(without_seconds=True),
'presale_end': SplitDateTimePickerWidget(attrs={'data-date-after': '#id_presale_start_0'}), 'presale_end': SplitDateTimePickerWidget(attrs={'data-date-after': '#id_presale_start_0'}, without_seconds=True),
} }

View File

@@ -770,7 +770,7 @@ class EventOrderExpertFilterForm(EventOrderFilterForm):
) )
elif q.type == Question.TYPE_TIME: elif q.type == Question.TYPE_TIME:
self.fields[fname] = forms.TimeField( 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'), help_text=_('Exact matches only'),
**kwargs, **kwargs,
) )

View File

@@ -245,8 +245,8 @@ class QuestionForm(I18nModelForm):
'valid_string_length_max', 'valid_string_length_max',
] ]
widgets = { widgets = {
'valid_datetime_min': SplitDateTimePickerWidget(), 'valid_datetime_min': SplitDateTimePickerWidget(without_seconds=True),
'valid_datetime_max': SplitDateTimePickerWidget(), 'valid_datetime_max': SplitDateTimePickerWidget(without_seconds=True),
'valid_date_min': DatePickerWidget(), 'valid_date_min': DatePickerWidget(),
'valid_date_max': DatePickerWidget(), 'valid_date_max': DatePickerWidget(),
'items': forms.CheckboxSelectMultiple( 'items': forms.CheckboxSelectMultiple(
@@ -1372,6 +1372,6 @@ class ItemProgramTimeForm(I18nModelForm):
'end': forms.SplitDateTimeField, 'end': forms.SplitDateTimeField,
} }
widgets = { widgets = {
'start': SplitDateTimePickerWidget(), 'start': SplitDateTimePickerWidget(without_seconds=True),
'end': SplitDateTimePickerWidget(), 'end': SplitDateTimePickerWidget(without_seconds=True),
} }

View File

@@ -39,6 +39,7 @@ from pretix.base.reldate import RelativeDateTimeField, RelativeDateWrapper
from pretix.base.templatetags.money import money_filter from pretix.base.templatetags.money import money_filter
from pretix.control.forms import SplitDateTimeField, SplitDateTimePickerWidget from pretix.control.forms import SplitDateTimeField, SplitDateTimePickerWidget
from pretix.control.forms.rrule import RRuleForm 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 from pretix.helpers.money import change_decimal_field
@@ -80,11 +81,11 @@ class SubEventForm(I18nModelForm):
'presale_end': SplitDateTimeField, 'presale_end': SplitDateTimeField,
} }
widgets = { widgets = {
'date_from': SplitDateTimePickerWidget(), 'date_from': SplitDateTimePickerWidget(without_seconds=True),
'date_to': SplitDateTimePickerWidget(attrs={'data-date-after': '#id_date_from_0'}), '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'}), 'date_admission': SplitDateTimePickerWidget(attrs={'data-date-after': '#id_date_from_0'}, without_seconds=True),
'presale_start': SplitDateTimePickerWidget(), 'presale_start': SplitDateTimePickerWidget(without_seconds=True),
'presale_end': SplitDateTimePickerWidget(attrs={'data-date-after': '#id_presale_start_0'}), '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( self.fields[k + '_time'] = forms.TimeField(
label=self._meta.model._meta.get_field(k).verbose_name, label=self._meta.model._meta.get_field(k).verbose_name,
help_text=self._meta.model._meta.get_field(k).help_text, help_text=self._meta.model._meta.get_field(k).help_text,
widget=TimePickerWidget(), widget=TimePickerWidget(without_seconds=True),
required=False, required=False,
) )
@@ -506,6 +507,12 @@ class TimeForm(forms.Form):
required=False 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( TimeFormSet = formset_factory(
TimeForm, TimeForm,

View File

@@ -79,6 +79,7 @@ from pretix.control.views import PaginationMixin
from pretix.control.views.event import MetaDataEditorMixin from pretix.control.views.event import MetaDataEditorMixin
from pretix.helpers import GroupConcat from pretix.helpers import GroupConcat
from pretix.helpers.compat import CompatDeleteView from pretix.helpers.compat import CompatDeleteView
from pretix.helpers.i18n import get_format_without_seconds
from pretix.helpers.models import modelcopy from pretix.helpers.models import modelcopy
@@ -803,7 +804,7 @@ class SubEventBulkCreate(SubEventEditorMixin, EventPermissionRequiredMixin, Asyn
ctx['rrule_formset'] = self.rrule_formset ctx['rrule_formset'] = self.rrule_formset
ctx['time_formset'] = self.time_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_admission_sample'] = time(8, 30, 0).strftime(tf)
ctx['time_begin_sample'] = time(9, 0, 0).strftime(tf) ctx['time_begin_sample'] = time(9, 0, 0).strftime(tf)
ctx['time_end_sample'] = time(18, 0, 0).strftime(tf) ctx['time_end_sample'] = time(18, 0, 0).strftime(tf)

View File

@@ -123,7 +123,7 @@ var form_handlers = function (el) {
el.find(".datetimepicker").each(function () { el.find(".datetimepicker").each(function () {
$(this).datetimepicker({ $(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"), locale: $("body").attr("data-datetimelocale"),
useCurrent: false, useCurrent: false,
showClear: !$(this).prop("required"), showClear: !$(this).prop("required"),
@@ -146,7 +146,7 @@ var form_handlers = function (el) {
el.find(".datepickerfield").each(function () { el.find(".datepickerfield").each(function () {
var opts = { 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"), locale: $("body").attr("data-datetimelocale"),
useCurrent: false, useCurrent: false,
showClear: !$(this).prop("required"), showClear: !$(this).prop("required"),
@@ -204,7 +204,7 @@ var form_handlers = function (el) {
el.find(".timepickerfield").each(function () { el.find(".timepickerfield").each(function () {
var opts = { 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"), locale: $("body").attr("data-datetimelocale"),
useCurrent: false, useCurrent: false,
showClear: !$(this).prop("required"), showClear: !$(this).prop("required"),

View File

@@ -464,6 +464,8 @@ details.details-open .panel-title::before {
.alert > dl:last-child, .alert > dl:last-child,
td > p:last-child, td > p:last-child,
.panel-body > dl: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:last-child,
.panel-body > .table-responsive:last-child > .table:last-child, .panel-body > .table-responsive:last-child > .table:last-child,
table td ul:last-child { table td ul:last-child {

View File

@@ -11,7 +11,7 @@ var form_handlers = function (el) {
el.find(".datetimepicker").each(function () { el.find(".datetimepicker").each(function () {
$(this).datetimepicker({ $(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"), locale: $("body").attr("data-datetimelocale"),
useCurrent: false, useCurrent: false,
showClear: !$(this).prop("required"), showClear: !$(this).prop("required"),
@@ -34,7 +34,7 @@ var form_handlers = function (el) {
el.find(".datepickerfield").each(function () { el.find(".datepickerfield").each(function () {
var opts = { 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"), locale: $("body").attr("data-datetimelocale"),
useCurrent: false, useCurrent: false,
showClear: !$(this).prop("required"), showClear: !$(this).prop("required"),
@@ -91,7 +91,7 @@ var form_handlers = function (el) {
el.find(".timepickerfield").each(function () { el.find(".timepickerfield").each(function () {
var opts = { 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"), locale: $("body").attr("data-datetimelocale"),
useCurrent: false, useCurrent: false,
showClear: !$(this).prop("required"), showClear: !$(this).prop("required"),