mirror of
https://github.com/pretix/pretix.git
synced 2026-05-09 15:54:03 +00:00
Add name scheme with salutation (#1779)
This commit is contained in:
@@ -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:
|
||||||
|
|||||||
@@ -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
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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):
|
||||||
|
|||||||
@@ -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):
|
||||||
|
|||||||
Reference in New Issue
Block a user