diff --git a/src/pretix/base/forms/questions.py b/src/pretix/base/forms/questions.py index 80a633834c..6c07201131 100644 --- a/src/pretix/base/forms/questions.py +++ b/src/pretix/base/forms/questions.py @@ -12,6 +12,7 @@ from django import forms from django.contrib import messages from django.core.exceptions import ValidationError from django.db.models import QuerySet +from django.forms import Select from django.utils.html import escape from django.utils.safestring import mark_safe from django.utils.translation import get_language, ugettext_lazy as _ @@ -24,7 +25,7 @@ from pretix.base.forms.widgets import ( ) from pretix.base.models import InvoiceAddress, Question, QuestionOption from pretix.base.models.tax import EU_COUNTRIES -from pretix.base.settings import PERSON_NAME_SCHEMES +from pretix.base.settings import PERSON_NAME_SCHEMES, PERSON_NAME_TITLE_GROUPS from pretix.base.templatetags.rich_text import rich_text from pretix.control.forms import SplitDateTimeField from pretix.helpers.escapejson import escapejson_attr @@ -37,14 +38,18 @@ logger = logging.getLogger(__name__) class NamePartsWidget(forms.MultiWidget): widget = forms.TextInput - def __init__(self, scheme: dict, field: forms.Field, attrs=None): + def __init__(self, scheme: dict, field: forms.Field, attrs=None, titles: list=None): widgets = [] self.scheme = scheme self.field = field + self.titles = titles for fname, label, size in self.scheme['fields']: a = copy.copy(attrs) or {} a['data-fname'] = fname - widgets.append(self.widget(attrs=a)) + if fname == 'title' and self.titles: + widgets.append(Select(attrs=a, choices=[('', '')] + [(d, d) for d in self.titles[1]])) + else: + widgets.append(self.widget(attrs=a)) super().__init__(widgets, attrs) def decompress(self, value): @@ -103,19 +108,34 @@ class NamePartsFormField(forms.MultiValueField): 'max_length': kwargs.pop('max_length', None), } self.scheme_name = kwargs.pop('scheme') + self.titles = kwargs.pop('titles') self.scheme = PERSON_NAME_SCHEMES.get(self.scheme_name) + if self.titles: + self.scheme_titles = PERSON_NAME_TITLE_GROUPS.get(self.titles) + else: + self.scheme_titles = None self.one_required = kwargs.get('required', True) require_all_fields = kwargs.pop('require_all_fields', False) kwargs['required'] = False kwargs['widget'] = (kwargs.get('widget') or self.widget)( - scheme=self.scheme, field=self, **kwargs.pop('widget_kwargs', {}) + scheme=self.scheme, titles=self.scheme_titles, field=self, **kwargs.pop('widget_kwargs', {}) ) defaults.update(**kwargs) for fname, label, size in self.scheme['fields']: defaults['label'] = label - field = forms.CharField(**defaults) - field.part_name = fname - fields.append(field) + if fname == 'title' and self.scheme_titles: + d = dict(defaults) + d.pop('max_length', None) + field = forms.ChoiceField( + **d, + choices=[('', '')] + [(d, d) for d in self.scheme_titles[1]] + ) + field.part_name = fname + fields.append(field) + else: + field = forms.CharField(**defaults) + field.part_name = fname + fields.append(field) super().__init__( fields=fields, require_all_fields=False, *args, **kwargs ) @@ -160,6 +180,7 @@ class BaseQuestionsForm(forms.Form): max_length=255, required=event.settings.attendee_names_required, scheme=event.settings.name_scheme, + titles=event.settings.name_scheme_titles, label=_('Attendee name'), initial=(cartpos.attendee_name_parts if cartpos else orderpos.attendee_name_parts), ) @@ -400,6 +421,7 @@ class BaseInvoiceAddressForm(forms.ModelForm): max_length=255, required=event.settings.invoice_name_required and not self.all_optional, scheme=event.settings.name_scheme, + titles=event.settings.name_scheme_titles, label=_('Name'), initial=(self.instance.name_parts if self.instance else self.instance.name_parts), ) diff --git a/src/pretix/base/settings.py b/src/pretix/base/settings.py index 6f3282e4c4..c7471b76fc 100644 --- a/src/pretix/base/settings.py +++ b/src/pretix/base/settings.py @@ -705,6 +705,23 @@ Your {event} team""")) 'type': str } } +PERSON_NAME_TITLE_GROUPS = OrderedDict([ + ('english_common', (_('Most common English titles'), ( + 'Mr', + 'Ms', + 'Mrs', + 'Miss', + 'Mx', + 'Dr', + 'Professor', + 'Sir' + ))), + ('german_common', (_('Most common German titles'), ( + 'Dr.', + 'Prof.', + 'Prof. Dr.', + ))) +]) PERSON_NAME_SCHEMES = OrderedDict([ ('given_family', { 'fields': ( @@ -734,6 +751,22 @@ PERSON_NAME_SCHEMES = OrderedDict([ '_scheme': 'title_given_family', }, }), + ('title_given_family', { + 'fields': ( + ('title', pgettext_lazy('person_name', 'Title'), 1), + ('given_name', _('Given name'), 2), + ('family_name', _('Family name'), 2), + ), + 'concatenation': lambda d: ' '.join( + str(p) for p in [d.get('title', ''), d.get('given_name', ''), d.get('family_name', '')] if p + ), + 'sample': { + 'title': pgettext_lazy('person_name_sample', 'Dr'), + 'given_name': pgettext_lazy('person_name_sample', 'John'), + 'family_name': pgettext_lazy('person_name_sample', 'Doe'), + '_scheme': 'title_given_family', + }, + }), ('given_middle_family', { 'fields': ( ('given_name', _('First name'), 2), diff --git a/src/pretix/control/forms/event.py b/src/pretix/control/forms/event.py index ffcde5be4e..6fd3a11fca 100644 --- a/src/pretix/control/forms/event.py +++ b/src/pretix/control/forms/event.py @@ -24,7 +24,7 @@ from pretix.base.forms import I18nModelForm, PlaceholderValidator, SettingsForm from pretix.base.models import Event, Organizer, TaxRule from pretix.base.models.event import EventMetaValue, SubEvent from pretix.base.reldate import RelativeDateField, RelativeDateTimeField -from pretix.base.settings import PERSON_NAME_SCHEMES +from pretix.base.settings import PERSON_NAME_SCHEMES, PERSON_NAME_TITLE_GROUPS from pretix.control.forms import ( ExtFileField, FontSelect, MultipleLanguagesWidget, SingleLanguageWidget, SlugWidget, SplitDateTimeField, SplitDateTimePickerWidget, @@ -383,6 +383,12 @@ class EventSettingsForm(SettingsForm): "orders might lead to unexpected behaviour when sorting or changing names."), required=True, ) + name_scheme_titles = forms.ChoiceField( + label=_("Allowed titles"), + help_text=_("If the naming scheme you defined above allows users to input a title, you can use this to " + "restrict the set of selectable titles."), + required=False, + ) attendee_emails_asked = forms.BooleanField( label=_("Ask for email addresses per ticket"), help_text=_("Normally, pretix asks for one email address per order and the order confirmation will be sent " @@ -466,6 +472,13 @@ class EventSettingsForm(SettingsForm): )) for k, v in PERSON_NAME_SCHEMES.items() ) + self.fields['name_scheme_titles'].choices = [('', _('Free text input'))] + [ + (k, '{scheme}: {samples}'.format( + scheme=v[0], + samples=', '.join(v[1]) + )) + for k, v in PERSON_NAME_TITLE_GROUPS.items() + ] class CancelSettingsForm(SettingsForm): diff --git a/src/pretix/control/templates/pretixcontrol/event/settings.html b/src/pretix/control/templates/pretixcontrol/event/settings.html index 8c9b6caf22..64bb3dabb1 100644 --- a/src/pretix/control/templates/pretixcontrol/event/settings.html +++ b/src/pretix/control/templates/pretixcontrol/event/settings.html @@ -65,6 +65,7 @@ {% bootstrap_field sform.attendee_names_asked layout="control" %} {% bootstrap_field sform.attendee_names_required layout="control" %} {% bootstrap_field sform.name_scheme layout="control" %} + {% bootstrap_field sform.name_scheme_titles layout="control" %} {% bootstrap_field sform.order_email_asked_twice layout="control" %} {% bootstrap_field sform.attendee_emails_asked layout="control" %} {% bootstrap_field sform.attendee_emails_required layout="control" %} diff --git a/src/pretix/static/pretixcontrol/scss/_forms.scss b/src/pretix/static/pretixcontrol/scss/_forms.scss index ecf34376e7..d581ecb43b 100644 --- a/src/pretix/static/pretixcontrol/scss/_forms.scss +++ b/src/pretix/static/pretixcontrol/scss/_forms.scss @@ -416,11 +416,11 @@ table td > .checkbox input[type="checkbox"] { @media(max-width: $screen-xs-max) { .nameparts-form-group { display: block; - input:not(:first-child) { + input:not(:first-child), select:not(:first-child) { border-top-right-radius: 0; border-top-left-radius: 0; } - input:not(:last-child) { + input:not(:last-child), select:not(:last-child) { border-bottom-right-radius: 0; border-bottom-left-radius: 0; } @@ -430,32 +430,32 @@ table td > .checkbox input[type="checkbox"] { .nameparts-form-group { display: flex; flex-direction: row; - input { + input, select { width: auto; flex-basis: 0; flex-grow: 1; } - input:not(:first-child) { + input:not(:first-child), select:not(:first-child) { border-bottom-left-radius: 0; border-top-left-radius: 0; } - input:not(:last-child) { + input:not(:last-child), select:not(:last-child) { border-bottom-right-radius: 0; border-top-right-radius: 0; } - input[data-size="1"] { + [data-size="1"] { flex-grow: 1; flex-shrink: 4; } - input[data-size="2"] { + [data-size="2"] { flex-grow: 2; flex-shrink: 3; } - input[data-size="3"] { + [data-size="3"] { flex-grow: 3; flex-shrink: 2; } - input[data-size="4"] { + [data-size="4"] { flex-grow: 4; flex-shrink: 1; } diff --git a/src/pretix/static/pretixpresale/scss/_forms.scss b/src/pretix/static/pretixpresale/scss/_forms.scss index e898334366..0feb77c207 100644 --- a/src/pretix/static/pretixpresale/scss/_forms.scss +++ b/src/pretix/static/pretixpresale/scss/_forms.scss @@ -96,11 +96,11 @@ @media(max-width: $screen-xs-max) { .nameparts-form-group { display: block; - input:not(:first-child) { + input:not(:first-child), select:not(:first-child) { border-top-right-radius: 0; border-top-left-radius: 0; } - input:not(:last-child) { + input:not(:last-child), select:not(:last-child) { border-bottom-right-radius: 0; border-bottom-left-radius: 0; } @@ -110,32 +110,32 @@ .nameparts-form-group { display: flex; flex-direction: row; - input { + input, select { width: auto; flex-basis: 0; flex-grow: 1; } - input:not(:first-child) { + input:not(:first-child), select:not(:first-child) { border-bottom-left-radius: 0; border-top-left-radius: 0; } - input:not(:last-child) { + input:not(:last-child), select:not(:last-child) { border-bottom-right-radius: 0; border-top-right-radius: 0; } - input[data-size="1"] { + [data-size="1"] { flex-grow: 1; flex-shrink: 4; } - input[data-size="2"] { + [data-size="2"] { flex-grow: 2; flex-shrink: 3; } - input[data-size="3"] { + [data-size="3"] { flex-grow: 3; flex-shrink: 2; } - input[data-size="4"] { + [data-size="4"] { flex-grow: 4; flex-shrink: 1; }