FormFields: remove placeholders duplicating labels (#5135)

This commit is contained in:
Richard Schreiber
2025-07-10 16:06:36 +02:00
committed by GitHub
parent 415bff5c72
commit 14d6013292
17 changed files with 52 additions and 51 deletions

View File

@@ -28,6 +28,9 @@ from dateutil import parser
from django import forms from django import forms
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.db import models from django.db import models
from django.utils.formats import get_format
from django.utils.functional import lazy
from django.utils.timezone import now
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from rest_framework import serializers from rest_framework import serializers
@@ -206,14 +209,27 @@ class RelativeDateTimeWidget(forms.MultiWidget):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
self.status_choices = kwargs.pop('status_choices') self.status_choices = kwargs.pop('status_choices')
base_choices = kwargs.pop('base_choices') base_choices = kwargs.pop('base_choices')
def placeholder_datetime_format():
df = get_format('DATETIME_INPUT_FORMATS')[0]
return now().replace(
year=2000, month=12, day=31, hour=18, minute=0, second=0, microsecond=0
).strftime(df)
def placeholder_time_format():
tf = get_format('TIME_INPUT_FORMATS')[0]
return datetime.time(8, 30, 0).strftime(tf)
widgets = reldatetimeparts( widgets = reldatetimeparts(
status=forms.RadioSelect(choices=self.status_choices), status=forms.RadioSelect(choices=self.status_choices),
absolute=forms.DateTimeInput( absolute=forms.DateTimeInput(
attrs={'class': 'datetimepicker'} attrs={'placeholder': lazy(placeholder_datetime_format, str), 'class': 'datetimepicker'}
), ),
rel_days_number=forms.NumberInput(), rel_days_number=forms.NumberInput(),
rel_mins_relationto=forms.Select(choices=base_choices), rel_mins_relationto=forms.Select(choices=base_choices),
rel_days_timeofday=forms.TimeInput(attrs={'placeholder': _('Time'), 'class': 'timepickerfield'}), rel_days_timeofday=forms.TimeInput(
attrs={'placeholder': lazy(placeholder_time_format, str), 'class': 'timepickerfield'}
),
rel_mins_number=forms.NumberInput(), rel_mins_number=forms.NumberInput(),
rel_days_relationto=forms.Select(choices=base_choices), rel_days_relationto=forms.Select(choices=base_choices),
rel_mins_relation=forms.Select(choices=BEFORE_AFTER_CHOICE), rel_mins_relation=forms.Select(choices=BEFORE_AFTER_CHOICE),

View File

@@ -113,7 +113,6 @@ class EventWizardFoundationForm(forms.Form):
attrs={ attrs={
'data-model-select2': 'generic', 'data-model-select2': 'generic',
'data-select2-url': reverse('control:organizers.select2') + '?can_create=1', 'data-select2-url': reverse('control:organizers.select2') + '?can_create=1',
'data-placeholder': _('Organizer')
} }
), ),
empty_label=None, empty_label=None,

View File

@@ -1246,9 +1246,7 @@ class SubEventFilterForm(FilterForm):
) )
query = forms.CharField( query = forms.CharField(
label=_('Event name'), label=_('Event name'),
widget=forms.TextInput(attrs={ widget=forms.TextInput(),
'placeholder': _('Event name'),
}),
required=False required=False
) )
@@ -1693,9 +1691,7 @@ class EventFilterForm(FilterForm):
) )
query = forms.CharField( query = forms.CharField(
label=_('Event name'), label=_('Event name'),
widget=forms.TextInput(attrs={ widget=forms.TextInput(),
'placeholder': _('Event name'),
}),
required=False required=False
) )
date_from = forms.DateField( date_from = forms.DateField(
@@ -2448,7 +2444,7 @@ class CheckinFilterForm(FilterForm):
'event': self.event.slug, 'event': self.event.slug,
'organizer': self.event.organizer.slug, 'organizer': self.event.organizer.slug,
}), }),
'data-placeholder': _('Check-in list'), 'data-placeholder': _('All check-in lists'),
} }
) )
self.fields['checkin_list'].widget.choices = self.fields['checkin_list'].choices self.fields['checkin_list'].widget.choices = self.fields['checkin_list'].choices

View File

@@ -47,9 +47,7 @@ from django.urls import reverse
from django.utils.functional import cached_property from django.utils.functional import cached_property
from django.utils.html import escape, format_html from django.utils.html import escape, format_html
from django.utils.safestring import mark_safe from django.utils.safestring import mark_safe
from django.utils.translation import ( from django.utils.translation import gettext as __, gettext_lazy as _
gettext as __, gettext_lazy as _, pgettext_lazy,
)
from django_scopes.forms import ( from django_scopes.forms import (
SafeModelChoiceField, SafeModelMultipleChoiceField, SafeModelChoiceField, SafeModelMultipleChoiceField,
) )
@@ -330,7 +328,6 @@ class QuotaForm(I18nModelForm):
'event': self.event.slug, 'event': self.event.slug,
'organizer': self.event.organizer.slug, 'organizer': self.event.organizer.slug,
}), }),
'data-placeholder': pgettext_lazy('subevent', 'Date')
} }
) )
self.fields['subevent'].widget.choices = self.fields['subevent'].choices self.fields['subevent'].widget.choices = self.fields['subevent'].choices
@@ -352,6 +349,9 @@ class QuotaForm(I18nModelForm):
field_classes = { field_classes = {
'subevent': SafeModelChoiceField, 'subevent': SafeModelChoiceField,
} }
widgets = {
'size': forms.NumberInput(attrs={'placeholder': _('Unlimited')})
}
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
creating = not self.instance.pk creating = not self.instance.pk

View File

@@ -409,7 +409,6 @@ class OrderPositionAddForm(forms.Form):
'event': order.event.slug, 'event': order.event.slug,
'organizer': order.event.organizer.slug, 'organizer': order.event.organizer.slug,
}), }),
'data-placeholder': pgettext_lazy('subevent', 'Date')
} }
) )
self.fields['subevent'].widget.choices = self.fields['subevent'].choices self.fields['subevent'].widget.choices = self.fields['subevent'].choices
@@ -705,7 +704,6 @@ class OrderContactForm(forms.ModelForm):
'data-select2-url': reverse('control:organizer.customers.select2', kwargs={ 'data-select2-url': reverse('control:organizer.customers.select2', kwargs={
'organizer': self.instance.event.organizer.slug, 'organizer': self.instance.event.organizer.slug,
}), }),
'data-placeholder': _('Customer')
} }
) )
self.fields['customer'].widget.choices = self.fields['customer'].choices self.fields['customer'].widget.choices = self.fields['customer'].choices

View File

@@ -781,7 +781,6 @@ class GiftCardUpdateForm(forms.ModelForm):
'data-select2-url': reverse('control:organizer.ticket_select2', kwargs={ 'data-select2-url': reverse('control:organizer.ticket_select2', kwargs={
'organizer': organizer.slug, 'organizer': organizer.slug,
}), }),
'data-placeholder': _('Ticket')
} }
) )
self.fields['owner_ticket'].widget.choices = self.fields['owner_ticket'].choices self.fields['owner_ticket'].widget.choices = self.fields['owner_ticket'].choices
@@ -817,7 +816,6 @@ class ReusableMediumUpdateForm(forms.ModelForm):
'data-select2-url': reverse('control:organizer.ticket_select2', kwargs={ 'data-select2-url': reverse('control:organizer.ticket_select2', kwargs={
'organizer': organizer.slug, 'organizer': organizer.slug,
}), }),
'data-placeholder': _('Ticket')
} }
) )
self.fields['linked_orderposition'].widget.choices = self.fields['linked_orderposition'].choices self.fields['linked_orderposition'].widget.choices = self.fields['linked_orderposition'].choices
@@ -830,7 +828,6 @@ class ReusableMediumUpdateForm(forms.ModelForm):
'data-select2-url': reverse('control:organizer.giftcards.select2', kwargs={ 'data-select2-url': reverse('control:organizer.giftcards.select2', kwargs={
'organizer': organizer.slug, 'organizer': organizer.slug,
}), }),
'data-placeholder': _('Gift card')
} }
) )
self.fields['linked_giftcard'].widget.choices = self.fields['linked_giftcard'].choices self.fields['linked_giftcard'].widget.choices = self.fields['linked_giftcard'].choices
@@ -844,7 +841,6 @@ class ReusableMediumUpdateForm(forms.ModelForm):
'data-select2-url': reverse('control:organizer.customers.select2', kwargs={ 'data-select2-url': reverse('control:organizer.customers.select2', kwargs={
'organizer': organizer.slug, 'organizer': organizer.slug,
}), }),
'data-placeholder': _('Customer')
} }
) )
self.fields['customer'].widget.choices = self.fields['customer'].choices self.fields['customer'].widget.choices = self.fields['customer'].choices

View File

@@ -133,16 +133,12 @@ class SubEventBulkEditForm(I18nModelForm):
# i18n fields # i18n fields
if k in self.mixed_values: if k in self.mixed_values:
self.fields[k].widget.attrs['placeholder'] = '[{}]'.format(_('Selection contains various values')) self.fields[k].widget.attrs['placeholder'] = '[{}]'.format(_('Selection contains various values'))
else:
self.fields[k].widget.attrs['placeholder'] = ''
self.fields[k].one_required = False self.fields[k].one_required = False
for k in ('geo_lat', 'geo_lon', 'comment'): for k in ('geo_lat', 'geo_lon', 'comment'):
# scalar fields # scalar fields
if k in self.mixed_values: if k in self.mixed_values:
self.fields[k].widget.attrs['placeholder'] = '[{}]'.format(_('Selection contains various values')) self.fields[k].widget.attrs['placeholder'] = '[{}]'.format(_('Selection contains various values'))
else:
self.fields[k].widget.attrs['placeholder'] = ''
self.fields[k].widget.is_required = False self.fields[k].widget.is_required = False
self.fields[k].required = False self.fields[k].required = False

View File

@@ -41,7 +41,7 @@ from django.core.exceptions import ObjectDoesNotExist, ValidationError
from django.core.validators import EmailValidator from django.core.validators import EmailValidator
from django.db.models.functions import Upper from django.db.models.functions import Upper
from django.urls import reverse from django.urls import reverse
from django.utils.translation import gettext_lazy as _, pgettext_lazy from django.utils.translation import gettext_lazy as _
from django_scopes.forms import SafeModelChoiceField from django_scopes.forms import SafeModelChoiceField
from pretix.base.email import get_available_placeholders from pretix.base.email import get_available_placeholders
@@ -115,7 +115,6 @@ class VoucherForm(I18nModelForm):
'event': instance.event.slug, 'event': instance.event.slug,
'organizer': instance.event.organizer.slug, 'organizer': instance.event.organizer.slug,
}), }),
'data-placeholder': pgettext_lazy('subevent', 'Date')
} }
) )
self.fields['subevent'].widget.choices = self.fields['subevent'].choices self.fields['subevent'].widget.choices = self.fields['subevent'].choices

View File

@@ -15,7 +15,7 @@
<span class="optional">{% trans "Optional" %}</span> <span class="optional">{% trans "Optional" %}</span>
</label> </label>
<div class="col-md-4"> <div class="col-md-4">
{% bootstrap_field form.geo_lat layout="inline" %} {% bootstrap_field form.geo_lat layout="inline" placeholder=_("Latitude") %}
{% if global_settings.opencagedata_apikey %} {% if global_settings.opencagedata_apikey %}
<p class="attrib"> <p class="attrib">
<a href="https://openstreetmap.org/" target="_blank"> <a href="https://openstreetmap.org/" target="_blank">
@@ -25,7 +25,7 @@
{% endif %} {% endif %}
</div> </div>
<div class="col-md-4"> <div class="col-md-4">
{% bootstrap_field form.geo_lon layout="inline" %} {% bootstrap_field form.geo_lon layout="inline" placeholder=_("Longitude") %}
</div> </div>
<div class="col-md-1"> <div class="col-md-1">
</div> </div>

View File

@@ -289,13 +289,13 @@
{% bootstrap_field f.DELETE form_group_class="" layout="inline" %} {% bootstrap_field f.DELETE form_group_class="" layout="inline" %}
</div> </div>
<div class="col-sm-4"> <div class="col-sm-4">
{% bootstrap_field f.time_from layout="inline" %} {% bootstrap_field f.time_from layout="inline" placeholder=time_begin_sample %}
</div> </div>
<div class="col-sm-4"> <div class="col-sm-4">
{% bootstrap_field f.time_to layout="inline" %} {% bootstrap_field f.time_to layout="inline" placeholder=time_end_sample %}
</div> </div>
<div class="col-sm-3"> <div class="col-sm-3">
{% bootstrap_field f.time_admission layout="inline" %} {% bootstrap_field f.time_admission layout="inline" placeholder=time_admission_sample %}
</div> </div>
<div class="col-sm-1 text-right flip"> <div class="col-sm-1 text-right flip">
<button type="button" class="btn btn-danger btn-block" <button type="button" class="btn btn-danger btn-block"
@@ -315,13 +315,13 @@
{% bootstrap_field time_formset.empty_form.DELETE form_group_class="" layout="inline" %} {% bootstrap_field time_formset.empty_form.DELETE form_group_class="" layout="inline" %}
</div> </div>
<div class="col-sm-4"> <div class="col-sm-4">
{% bootstrap_field time_formset.empty_form.time_from layout="inline" %} {% bootstrap_field time_formset.empty_form.time_from layout="inline" placeholder=time_begin_sample %}
</div> </div>
<div class="col-sm-4"> <div class="col-sm-4">
{% bootstrap_field time_formset.empty_form.time_to layout="inline" %} {% bootstrap_field time_formset.empty_form.time_to layout="inline" placeholder=time_end_sample %}
</div> </div>
<div class="col-sm-3"> <div class="col-sm-3">
{% bootstrap_field time_formset.empty_form.time_admission layout="inline" %} {% bootstrap_field time_formset.empty_form.time_admission layout="inline" placeholder=time_admission_sample %}
</div> </div>
<div class="col-sm-1 text-right flip"> <div class="col-sm-1 text-right flip">
<button type="button" class="btn btn-danger btn-block" <button type="button" class="btn btn-danger btn-block"
@@ -338,13 +338,13 @@
<label for="subevent_add_many_slots_first"> <label for="subevent_add_many_slots_first">
<strong>{% trans "Start of first slot" %}</strong> <strong>{% trans "Start of first slot" %}</strong>
</label> </label>
<input class="form-control timepickerfield" id="subevent_add_many_slots_first" value="{{ time_begin_sample }}"> <input class="form-control timepickerfield" id="subevent_add_many_slots_first" value="{{ time_begin_sample }}" placeholder="{{ time_begin_sample }}">
</div> </div>
<div class="col-md-2 col-sm-12"> <div class="col-md-2 col-sm-12">
<label for="subevent_add_many_slots_end"> <label for="subevent_add_many_slots_end">
<strong>{% trans "End of time slots" %}</strong> <strong>{% trans "End of time slots" %}</strong>
</label> </label>
<input class="form-control timepickerfield" id="subevent_add_many_slots_end" value="{{ time_end_sample }}"> <input class="form-control timepickerfield" id="subevent_add_many_slots_end" value="{{ time_end_sample }}" placeholder="{{ time_end_sample }}">
</div> </div>
<div class="col-md-3 col-sm-12"> <div class="col-md-3 col-sm-12">
<label for="subevent_add_many_slots_length"> <label for="subevent_add_many_slots_length">
@@ -409,8 +409,8 @@
</fieldset> </fieldset>
<fieldset> <fieldset>
<legend>{% trans "Timeline" %}</legend> <legend>{% trans "Timeline" %}</legend>
{% bootstrap_field form.rel_presale_start layout="control" %} {% bootstrap_field form.rel_presale_start layout="control" placeholder=datetime_sample %}
{% bootstrap_field form.rel_presale_end layout="control" %} {% bootstrap_field form.rel_presale_end layout="control" placeholder=datetime_sample %}
</fieldset> </fieldset>
<fieldset> <fieldset>
<legend>{% trans "Quotas" %}</legend> <legend>{% trans "Quotas" %}</legend>

View File

@@ -51,7 +51,7 @@
<div class="field-content"> <div class="field-content">
<div class="row"> <div class="row">
<div class="col-md-6"> <div class="col-md-6">
{% bootstrap_field form.geo_lat layout="inline" %} {% bootstrap_field form.geo_lat layout="inline" placeholder=_("Latitude") %}
{% if global_settings.opencagedata_apikey %} {% if global_settings.opencagedata_apikey %}
<p class="attrib"> <p class="attrib">
<a href="https://openstreetmap.org/" target="_blank" tabindex="-1"> <a href="https://openstreetmap.org/" target="_blank" tabindex="-1">
@@ -61,7 +61,7 @@
{% endif %} {% endif %}
</div> </div>
<div class="col-md-6"> <div class="col-md-6">
{% bootstrap_field form.geo_lon layout="inline" %} {% bootstrap_field form.geo_lon layout="inline" placeholder=_("Longitude") %}
</div> </div>
</div> </div>
</div> </div>

View File

@@ -49,7 +49,7 @@ from django.shortcuts import redirect, render
from django.urls import reverse from django.urls import reverse
from django.utils.formats import get_format from django.utils.formats import get_format
from django.utils.functional import cached_property from django.utils.functional import cached_property
from django.utils.timezone import make_aware from django.utils.timezone import make_aware, now
from django.utils.translation import gettext_lazy as _, pgettext_lazy from django.utils.translation import gettext_lazy as _, pgettext_lazy
from django.views import View from django.views import View
from django.views.generic import CreateView, FormView, ListView, UpdateView from django.views.generic import CreateView, FormView, ListView, UpdateView
@@ -768,8 +768,15 @@ class SubEventBulkCreate(SubEventEditorMixin, EventPermissionRequiredMixin, Asyn
ctx['time_formset'] = self.time_formset ctx['time_formset'] = self.time_formset
tf = get_format('TIME_INPUT_FORMATS')[0] tf = get_format('TIME_INPUT_FORMATS')[0]
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)
df = get_format('DATETIME_INPUT_FORMATS')[0]
ctx['datetime_sample'] = now().replace(
year=2000, month=12, day=31, hour=18, minute=0, second=0, microsecond=0
).strftime(df)
return ctx return ctx
@cached_property @cached_property

View File

@@ -176,7 +176,6 @@ class AutoCheckinRuleForm(forms.ModelForm):
"organizer": self.event.organizer.slug, "organizer": self.event.organizer.slug,
}, },
), ),
"data-placeholder": _("Check-in list"),
} }
) )
self.fields["list"].widget.choices = self.fields["list"].choices self.fields["list"].widget.choices = self.fields["list"].choices

View File

@@ -2,7 +2,7 @@
{% load ibanformat %} {% load ibanformat %}
{% load bootstrap3 %} {% load bootstrap3 %}
{% bootstrap_field form.payer layout="inline" %} {% bootstrap_field form.payer layout="inline" placeholder=_("Account holder") %}
{% bootstrap_field form.iban layout="inline" %} {% bootstrap_field form.iban layout="inline" placeholder=_("IBAN") %}
{% bootstrap_field form.bic layout="inline" %} {% bootstrap_field form.bic layout="inline" placeholder=_("BIC (optional)") %}
{% bootstrap_form_errors form error_types="all" %} {% bootstrap_form_errors form error_types="all" %}

View File

@@ -279,7 +279,6 @@ class OrderMailForm(BaseMailForm):
'event': event.slug, 'event': event.slug,
'organizer': event.organizer.slug, 'organizer': event.organizer.slug,
}), }),
'data-placeholder': pgettext_lazy('subevent', 'Date')
} }
) )
self.fields['subevent'].widget.choices = self.fields['subevent'].choices self.fields['subevent'].widget.choices = self.fields['subevent'].choices
@@ -360,7 +359,6 @@ class RuleForm(FormPlaceholderMixin, I18nModelForm):
'event': self.event.slug, 'event': self.event.slug,
'organizer': self.event.organizer.slug, 'organizer': self.event.organizer.slug,
}), }),
'data-placeholder': pgettext_lazy('subevent', 'Date')
} }
) )
self.fields['subevent'].widget.choices = self.fields['subevent'].choices self.fields['subevent'].widget.choices = self.fields['subevent'].choices

View File

@@ -711,6 +711,7 @@ BOOTSTRAP3 = {
'bulkedit_inline': 'pretix.control.forms.renderers.InlineBulkEditFieldRenderer', 'bulkedit_inline': 'pretix.control.forms.renderers.InlineBulkEditFieldRenderer',
'checkout': 'pretix.presale.forms.renderers.CheckoutFieldRenderer', 'checkout': 'pretix.presale.forms.renderers.CheckoutFieldRenderer',
}, },
'set_placeholder': False,
} }
PASSWORD_HASHERS = [ PASSWORD_HASHERS = [

View File

@@ -34,10 +34,6 @@ footer {
margin: auto; margin: auto;
padding-bottom: 0; padding-bottom: 0;
.control-label {
display: none;
}
.buttons { .buttons {
text-align: right; text-align: right;
} }