Add name scheme with salutation (#1779)

This commit is contained in:
Felix Rindt
2020-09-28 11:41:59 +02:00
committed by GitHub
parent 2384478b45
commit 4fb0b948ec
4 changed files with 57 additions and 22 deletions

View File

@@ -222,6 +222,7 @@ class SimpleFunctionalMailTextPlaceholder(BaseMailTextPlaceholder):
def get_available_placeholders(event, base_parameters): def get_available_placeholders(event, base_parameters):
if 'order' in base_parameters: if 'order' in base_parameters:
base_parameters.append('invoice_address') base_parameters.append('invoice_address')
base_parameters.append('position_or_address')
params = {} params = {}
for r, val in register_mail_placeholders.send(sender=event): for r, val in register_mail_placeholders.send(sender=event):
if not isinstance(val, (list, tuple)): if not isinstance(val, (list, tuple)):
@@ -241,6 +242,8 @@ def get_email_context(**kwargs):
kwargs['invoice_address'] = kwargs['order'].invoice_address kwargs['invoice_address'] = kwargs['order'].invoice_address
except InvoiceAddress.DoesNotExist: except InvoiceAddress.DoesNotExist:
kwargs['invoice_address'] = InvoiceAddress() kwargs['invoice_address'] = InvoiceAddress()
finally:
kwargs.setdefault("position_or_address", kwargs['invoice_address'])
ctx = {} ctx = {}
for r, val in register_mail_placeholders.send(sender=event): for r, val in register_mail_placeholders.send(sender=event):
if not isinstance(val, (list, tuple)): if not isinstance(val, (list, tuple)):
@@ -268,7 +271,8 @@ def get_best_name(position_or_address, parts=False):
if isinstance(position_or_address, InvoiceAddress): if isinstance(position_or_address, InvoiceAddress):
if position_or_address.name: if position_or_address.name:
return position_or_address.name_parts if parts else position_or_address.name return position_or_address.name_parts if parts else position_or_address.name
position_or_address = position_or_address.order.positions.exclude(attendee_name_cached="").exclude(attendee_name_cached__isnull=True).first() elif position_or_address.order:
position_or_address = position_or_address.order.positions.exclude(attendee_name_cached="").exclude(attendee_name_cached__isnull=True).first()
if isinstance(position_or_address, OrderPosition): if isinstance(position_or_address, OrderPosition):
if position_or_address.attendee_name: if position_or_address.attendee_name:

View File

@@ -36,8 +36,8 @@ from pretix.base.i18n import language
from pretix.base.models import InvoiceAddress, Question, QuestionOption from pretix.base.models import InvoiceAddress, Question, QuestionOption
from pretix.base.models.tax import EU_COUNTRIES, cc_to_vat_prefix from pretix.base.models.tax import EU_COUNTRIES, cc_to_vat_prefix
from pretix.base.settings import ( from pretix.base.settings import (
COUNTRIES_WITH_STATE_IN_ADDRESS, PERSON_NAME_SCHEMES, COUNTRIES_WITH_STATE_IN_ADDRESS, PERSON_NAME_SALUTATIONS,
PERSON_NAME_TITLE_GROUPS, PERSON_NAME_SCHEMES, PERSON_NAME_TITLE_GROUPS,
) )
from pretix.base.templatetags.rich_text import rich_text from pretix.base.templatetags.rich_text import rich_text
from pretix.control.forms import ExtFileField, SplitDateTimeField from pretix.control.forms import ExtFileField, SplitDateTimeField
@@ -49,7 +49,7 @@ from pretix.presale.signals import question_form_fields
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
REQUIRED_NAME_PARTS = ['given_name', 'family_name', 'full_name'] REQUIRED_NAME_PARTS = ['salutation', 'given_name', 'family_name', 'full_name']
class NamePartsWidget(forms.MultiWidget): class NamePartsWidget(forms.MultiWidget):
@@ -73,6 +73,8 @@ class NamePartsWidget(forms.MultiWidget):
a['data-fname'] = fname a['data-fname'] = fname
if fname == 'title' and self.titles: if fname == 'title' and self.titles:
widgets.append(Select(attrs=a, choices=[('', '')] + [(d, d) for d in self.titles[1]])) widgets.append(Select(attrs=a, choices=[('', '')] + [(d, d) for d in self.titles[1]]))
elif fname == 'salutation':
widgets.append(Select(attrs=a, choices=[('', '---')] + [(s, s) for s in PERSON_NAME_SALUTATIONS]))
else: else:
widgets.append(self.widget(attrs=a)) widgets.append(self.widget(attrs=a))
super().__init__(widgets, attrs) super().__init__(widgets, attrs)
@@ -162,12 +164,18 @@ class NamePartsFormField(forms.MultiValueField):
**d, **d,
choices=[('', '')] + [(d, d) for d in self.scheme_titles[1]] choices=[('', '')] + [(d, d) for d in self.scheme_titles[1]]
) )
field.part_name = fname
fields.append(field) elif fname == 'salutation':
d = dict(defaults)
d.pop('max_length', None)
field = forms.ChoiceField(
**d,
choices=[('', '---')] + [(s, s) for s in PERSON_NAME_SALUTATIONS]
)
else: else:
field = forms.CharField(**defaults) field = forms.CharField(**defaults)
field.part_name = fname field.part_name = fname
fields.append(field) fields.append(field)
super().__init__( super().__init__(
fields=fields, require_all_fields=False, *args, **kwargs fields=fields, require_all_fields=False, *args, **kwargs
) )

View File

@@ -1845,7 +1845,7 @@ PERSON_NAME_TITLE_GROUPS = OrderedDict([
'Mx', 'Mx',
'Dr', 'Dr',
'Professor', 'Professor',
'Sir' 'Sir',
))), ))),
('german_common', (_('Most common German titles'), ( ('german_common', (_('Most common German titles'), (
'Dr.', 'Dr.',
@@ -1853,9 +1853,16 @@ PERSON_NAME_TITLE_GROUPS = OrderedDict([
'Prof. Dr.', 'Prof. Dr.',
))) )))
]) ])
PERSON_NAME_SALUTATIONS = [
pgettext_lazy("person_name_salutation", "Ms"),
pgettext_lazy("person_name_salutation", "Mr"),
]
PERSON_NAME_SCHEMES = OrderedDict([ PERSON_NAME_SCHEMES = OrderedDict([
('given_family', { ('given_family', {
'fields': ( 'fields': (
# field_name, label, weight for widget width
('given_name', _('Given name'), 1), ('given_name', _('Given name'), 1),
('family_name', _('Family name'), 1), ('family_name', _('Family name'), 1),
), ),
@@ -2010,6 +2017,24 @@ PERSON_NAME_SCHEMES = OrderedDict([
'_scheme': 'full_transcription', '_scheme': 'full_transcription',
}, },
}), }),
('salutation_title_given_family', {
'fields': (
('salutation', pgettext_lazy('person_name', 'Salutation'), 1),
('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(key, '') for key in ["title", "given_name", "family_name"]) if p
),
'sample': {
'salutation': pgettext_lazy('person_name_sample', 'Mr'),
'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_salutation_given_family',
},
}),
]) ])
COUNTRIES_WITH_STATE_IN_ADDRESS = { COUNTRIES_WITH_STATE_IN_ADDRESS = {
# Source: http://www.bitboost.com/ref/international-address-formats.html # Source: http://www.bitboost.com/ref/international-address-formats.html
@@ -2025,7 +2050,6 @@ COUNTRIES_WITH_STATE_IN_ADDRESS = {
'US': (['State', 'Outlying area', 'District'], 'short'), 'US': (['State', 'Outlying area', 'District'], 'short'),
} }
settings_hierarkey = Hierarkey(attribute_name='settings') settings_hierarkey = Hierarkey(attribute_name='settings')
for k, v in DEFAULTS.items(): for k, v in DEFAULTS.items():
@@ -2095,7 +2119,7 @@ class SettingsSandbox:
def __delattr__(self, key: str) -> None: def __delattr__(self, key: str) -> None:
del self._event.settings[self._convert_key(key)] del self._event.settings[self._convert_key(key)]
def get(self, key: str, default: Any=None, as_type: type=str): def get(self, key: str, default: Any = None, as_type: type = str):
return self._event.settings.get(self._convert_key(key), default=default, as_type=as_type) return self._event.settings.get(self._convert_key(key), default=default, as_type=as_type)
def set(self, key: str, value: Any): def set(self, key: str, value: Any):

View File

@@ -604,7 +604,7 @@ class CheckoutTestCase(BaseCheckoutTestCase, TestCase):
def test_attendee_name_scheme(self): def test_attendee_name_scheme(self):
self.event.settings.set('attendee_names_asked', True) self.event.settings.set('attendee_names_asked', True)
self.event.settings.set('attendee_names_required', True) self.event.settings.set('attendee_names_required', True)
self.event.settings.set('name_scheme', 'title_given_middle_family') self.event.settings.set('name_scheme', 'salutation_title_given_family')
with scopes_disabled(): with scopes_disabled():
cr1 = CartPosition.objects.create( cr1 = CartPosition.objects.create(
event=self.event, cart_id=self.session_key, item=self.ticket, event=self.event, cart_id=self.session_key, item=self.ticket,
@@ -612,17 +612,16 @@ class CheckoutTestCase(BaseCheckoutTestCase, TestCase):
) )
response = self.client.get('/%s/%s/checkout/questions/' % (self.orga.slug, self.event.slug), follow=True) response = self.client.get('/%s/%s/checkout/questions/' % (self.orga.slug, self.event.slug), follow=True)
doc = BeautifulSoup(response.rendered_content, "lxml") doc = BeautifulSoup(response.rendered_content, "lxml")
self.assertEqual(len(doc.select('input[name="%s-attendee_name_parts_0"]' % cr1.id)), 1) self.assertEqual(len(doc.select('select[name="%s-attendee_name_parts_0"]' % cr1.id)), 1)
self.assertEqual(len(doc.select('input[name="%s-attendee_name_parts_1"]' % cr1.id)), 1) self.assertEqual(len(doc.select('input[name="%s-attendee_name_parts_1"]' % cr1.id)), 1)
self.assertEqual(len(doc.select('input[name="%s-attendee_name_parts_2"]' % cr1.id)), 1) self.assertEqual(len(doc.select('input[name="%s-attendee_name_parts_2"]' % cr1.id)), 1)
self.assertEqual(len(doc.select('input[name="%s-attendee_name_parts_3"]' % cr1.id)), 1) self.assertEqual(len(doc.select('input[name="%s-attendee_name_parts_3"]' % cr1.id)), 1)
# Not all required fields filled out, expect failure # Not all required fields filled out, expect failure
response = self.client.post('/%s/%s/checkout/questions/' % (self.orga.slug, self.event.slug), { response = self.client.post('/%s/%s/checkout/questions/' % (self.orga.slug, self.event.slug), {
'%s-attendee_name_parts_0' % cr1.id: 'Mr', '%s-attendee_name_parts_0' % cr1.id: 'Mr',
'%s-attendee_name_parts_1' % cr1.id: 'John', '%s-attendee_name_parts_1' % cr1.id: '',
'%s-attendee_name_parts_2' % cr1.id: 'F', '%s-attendee_name_parts_2' % cr1.id: 'John',
'%s-attendee_name_parts_3' % cr1.id: 'Kennedy', '%s-attendee_name_parts_3' % cr1.id: 'Doe',
'email': 'admin@localhost' 'email': 'admin@localhost'
}) })
self.assertRedirects(response, '/%s/%s/checkout/payment/' % (self.orga.slug, self.event.slug), self.assertRedirects(response, '/%s/%s/checkout/payment/' % (self.orga.slug, self.event.slug),
@@ -630,13 +629,13 @@ class CheckoutTestCase(BaseCheckoutTestCase, TestCase):
with scopes_disabled(): with scopes_disabled():
cr1 = CartPosition.objects.get(id=cr1.id) cr1 = CartPosition.objects.get(id=cr1.id)
self.assertEqual(cr1.attendee_name, 'Mr John F Kennedy') self.assertEqual(cr1.attendee_name, 'John Doe')
self.assertEqual(cr1.attendee_name_parts, { self.assertEqual(cr1.attendee_name_parts, {
'salutation': 'Mr',
'title': '',
'given_name': 'John', 'given_name': 'John',
'title': 'Mr', 'family_name': 'Doe',
'middle_name': 'F', "_scheme": "salutation_title_given_family"
'family_name': 'Kennedy',
"_scheme": "title_given_middle_family"
}) })
def test_attendee_name_optional(self): def test_attendee_name_optional(self):