forked from CGM_Public/pretix_original
Add company and address fields to attendees (#1633)
* Add company and address fields to attendees * Update src/pretix/control/templates/pretixcontrol/event/settings.html Co-Authored-By: Martin Gross <gross@rami.io> Co-authored-by: Martin Gross <gross@rami.io>
This commit is contained in:
@@ -558,6 +558,10 @@ class EventSettingsSerializer(serializers.Serializer):
|
||||
'attendee_names_required',
|
||||
'attendee_emails_asked',
|
||||
'attendee_emails_required',
|
||||
'attendee_addresses_asked',
|
||||
'attendee_addresses_required',
|
||||
'attendee_company_asked',
|
||||
'attendee_company_required',
|
||||
'confirm_text',
|
||||
'order_email_asked_twice',
|
||||
'payment_term_days',
|
||||
|
||||
@@ -39,7 +39,7 @@ class CompatibleCountryField(serializers.Field):
|
||||
def to_representation(self, instance: InvoiceAddress):
|
||||
if instance.country:
|
||||
return str(instance.country)
|
||||
else:
|
||||
elif hasattr(instance, 'country_old'):
|
||||
return instance.country_old
|
||||
|
||||
|
||||
@@ -211,10 +211,12 @@ class OrderPositionSerializer(I18nAwareModelSerializer):
|
||||
order = serializers.SlugRelatedField(slug_field='code', read_only=True)
|
||||
pdf_data = PdfDataSerializer(source='*')
|
||||
seat = InlineSeatSerializer(read_only=True)
|
||||
country = CompatibleCountryField(source='*')
|
||||
|
||||
class Meta:
|
||||
model = OrderPosition
|
||||
fields = ('id', 'order', 'positionid', 'item', 'variation', 'price', 'attendee_name', 'attendee_name_parts',
|
||||
'company', 'street', 'zipcode', 'city', 'country', 'state',
|
||||
'attendee_email', 'voucher', 'tax_rate', 'tax_value', 'secret', 'addon_to', 'subevent', 'checkins',
|
||||
'downloads', 'answers', 'tax_rule', 'pseudonymization_id', 'pdf_data', 'seat', 'canceled')
|
||||
|
||||
@@ -516,12 +518,22 @@ class OrderPositionCreateSerializer(I18nAwareModelSerializer):
|
||||
max_digits=10)
|
||||
voucher = serializers.SlugRelatedField(slug_field='code', queryset=Voucher.objects.none(),
|
||||
required=False, allow_null=True)
|
||||
country = CompatibleCountryField(source='*')
|
||||
|
||||
class Meta:
|
||||
model = OrderPosition
|
||||
fields = ('positionid', 'item', 'variation', 'price', 'attendee_name', 'attendee_name_parts', 'attendee_email',
|
||||
'company', 'street', 'zipcode', 'city', 'country', 'state',
|
||||
'secret', 'addon_to', 'subevent', 'answers', 'seat', 'voucher')
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
for k, v in self.fields.items():
|
||||
if k in ('company', 'street', 'zipcode', 'city', 'country', 'state'):
|
||||
v.required = False
|
||||
v.allow_blank = True
|
||||
v.allow_null = True
|
||||
|
||||
def validate_secret(self, secret):
|
||||
if secret and OrderPosition.all.filter(order__event=self.context['event'], secret=secret).exists():
|
||||
raise ValidationError(
|
||||
@@ -576,6 +588,24 @@ class OrderPositionCreateSerializer(I18nAwareModelSerializer):
|
||||
)
|
||||
if data.get('attendee_name_parts') and '_scheme' not in data.get('attendee_name_parts'):
|
||||
data['attendee_name_parts']['_scheme'] = self.context['request'].event.settings.name_scheme
|
||||
|
||||
if data.get('country'):
|
||||
if not pycountry.countries.get(alpha_2=data.get('country')):
|
||||
raise ValidationError(
|
||||
{'country': ['Invalid country code.']}
|
||||
)
|
||||
|
||||
if data.get('state'):
|
||||
cc = str(data.get('country') or self.instance.country or '')
|
||||
if cc not in COUNTRIES_WITH_STATE_IN_ADDRESS:
|
||||
raise ValidationError(
|
||||
{'state': ['States are not supported in country "{}".'.format(cc)]}
|
||||
)
|
||||
if not pycountry.subdivisions.get(code=cc + '-' + data.get('state')):
|
||||
raise ValidationError(
|
||||
{'state': ['"{}" is not a known subdivision of the country "{}".'.format(data.get('state'), cc)]}
|
||||
)
|
||||
|
||||
return data
|
||||
|
||||
|
||||
|
||||
@@ -305,6 +305,12 @@ class OrderListExporter(MultiSheetListExporter):
|
||||
headers.append(_('Attendee name') + ': ' + str(label))
|
||||
headers += [
|
||||
_('Attendee email'),
|
||||
_('Company'),
|
||||
_('Address'),
|
||||
_('ZIP code'),
|
||||
_('City'),
|
||||
_('Country'),
|
||||
pgettext('address', 'State'),
|
||||
_('Voucher'),
|
||||
_('Pseudonymization ID'),
|
||||
]
|
||||
@@ -364,6 +370,12 @@ class OrderListExporter(MultiSheetListExporter):
|
||||
)
|
||||
row += [
|
||||
op.attendee_email,
|
||||
op.company or '',
|
||||
op.street or '',
|
||||
op.zipcode or '',
|
||||
op.city or '',
|
||||
op.country if op.country else '',
|
||||
op.state or '',
|
||||
op.voucher.code if op.voucher else '',
|
||||
op.pseudonymization_id,
|
||||
]
|
||||
|
||||
@@ -245,7 +245,7 @@ class BaseQuestionsForm(forms.Form):
|
||||
if item.admission and event.settings.attendee_names_asked:
|
||||
self.fields['attendee_name_parts'] = NamePartsFormField(
|
||||
max_length=255,
|
||||
required=event.settings.attendee_names_required,
|
||||
required=event.settings.attendee_names_required and not self.all_optional,
|
||||
scheme=event.settings.name_scheme,
|
||||
titles=event.settings.name_scheme_titles,
|
||||
label=_('Attendee name'),
|
||||
@@ -253,7 +253,7 @@ class BaseQuestionsForm(forms.Form):
|
||||
)
|
||||
if item.admission and event.settings.attendee_emails_asked:
|
||||
self.fields['attendee_email'] = forms.EmailField(
|
||||
required=event.settings.attendee_emails_required,
|
||||
required=event.settings.attendee_emails_required and not self.all_optional,
|
||||
label=_('Attendee email'),
|
||||
initial=(cartpos.attendee_email if cartpos else orderpos.attendee_email),
|
||||
widget=forms.EmailInput(
|
||||
@@ -262,6 +262,73 @@ class BaseQuestionsForm(forms.Form):
|
||||
}
|
||||
)
|
||||
)
|
||||
if item.admission and event.settings.attendee_company_asked:
|
||||
self.fields['company'] = forms.CharField(
|
||||
required=event.settings.attendee_company_required and not self.all_optional,
|
||||
label=_('Company'),
|
||||
initial=(cartpos.company if cartpos else orderpos.company),
|
||||
)
|
||||
|
||||
if item.admission and event.settings.attendee_addresses_asked:
|
||||
self.fields['street'] = forms.CharField(
|
||||
required=event.settings.attendee_addresses_required and not self.all_optional,
|
||||
label=_('Address'),
|
||||
widget=forms.Textarea(attrs={
|
||||
'rows': 2,
|
||||
'placeholder': _('Street and Number'),
|
||||
'autocomplete': 'street-address'
|
||||
}),
|
||||
initial=(cartpos.street if cartpos else orderpos.street),
|
||||
)
|
||||
self.fields['zipcode'] = forms.CharField(
|
||||
required=event.settings.attendee_addresses_required and not self.all_optional,
|
||||
label=_('ZIP code'),
|
||||
initial=(cartpos.zipcode if cartpos else orderpos.zipcode),
|
||||
widget=forms.TextInput(attrs={
|
||||
'autocomplete': 'postal-code',
|
||||
}),
|
||||
)
|
||||
self.fields['city'] = forms.CharField(
|
||||
required=event.settings.attendee_addresses_required and not self.all_optional,
|
||||
label=_('City'),
|
||||
initial=(cartpos.city if cartpos else orderpos.city),
|
||||
widget=forms.TextInput(attrs={
|
||||
'autocomplete': 'address-level2',
|
||||
}),
|
||||
)
|
||||
country = (cartpos.country if cartpos else orderpos.country) or guess_country(event)
|
||||
self.fields['country'] = CountryField().formfield(
|
||||
required=event.settings.attendee_addresses_required and not self.all_optional,
|
||||
label=_('Country'),
|
||||
initial=country,
|
||||
widget=forms.Select(attrs={
|
||||
'autocomplete': 'country',
|
||||
}),
|
||||
)
|
||||
c = [('', pgettext_lazy('address', 'Select state'))]
|
||||
fprefix = str(self.prefix) + '-' if self.prefix is not None and self.prefix != '-' else ''
|
||||
cc = None
|
||||
if fprefix + 'country' in self.data:
|
||||
cc = str(self.data[fprefix + 'country'])
|
||||
elif country:
|
||||
cc = str(country)
|
||||
if cc and cc in COUNTRIES_WITH_STATE_IN_ADDRESS:
|
||||
types, form = COUNTRIES_WITH_STATE_IN_ADDRESS[cc]
|
||||
statelist = [s for s in pycountry.subdivisions.get(country_code=cc) if s.type in types]
|
||||
c += sorted([(s.code[3:], s.name) for s in statelist], key=lambda s: s[1])
|
||||
elif fprefix + 'state' in self.data:
|
||||
self.data = self.data.copy()
|
||||
del self.data[fprefix + 'state']
|
||||
|
||||
self.fields['state'] = forms.ChoiceField(
|
||||
label=pgettext_lazy('address', 'State'),
|
||||
required=False,
|
||||
choices=c,
|
||||
widget=forms.Select(attrs={
|
||||
'autocomplete': 'address-level1',
|
||||
}),
|
||||
)
|
||||
self.fields['state'].widget.is_required = True
|
||||
|
||||
for q in questions:
|
||||
# Do we already have an answer? Provide it as the initial value
|
||||
@@ -423,6 +490,10 @@ class BaseQuestionsForm(forms.Form):
|
||||
def clean(self):
|
||||
d = super().clean()
|
||||
|
||||
if d.get('city') and d.get('country') and str(d['country']) in COUNTRIES_WITH_STATE_IN_ADDRESS:
|
||||
if not d.get('state'):
|
||||
self.add_error('state', _('This field is required.'))
|
||||
|
||||
question_cache = {f.question.pk: f.question for f in self.fields.values() if getattr(f, 'question', None)}
|
||||
|
||||
def question_is_visible(parentid, qvals):
|
||||
|
||||
74
src/pretix/base/migrations/0150_auto_20200401_1123.py
Normal file
74
src/pretix/base/migrations/0150_auto_20200401_1123.py
Normal file
@@ -0,0 +1,74 @@
|
||||
# Generated by Django 3.0.4 on 2020-04-01 11:24
|
||||
|
||||
import django_countries.fields
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('pretixbase', '0149_order_cancellation_date'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='cartposition',
|
||||
name='city',
|
||||
field=models.CharField(max_length=255, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='cartposition',
|
||||
name='company',
|
||||
field=models.CharField(max_length=255, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='cartposition',
|
||||
name='country',
|
||||
field=django_countries.fields.CountryField(max_length=2, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='cartposition',
|
||||
name='state',
|
||||
field=models.CharField(max_length=255, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='cartposition',
|
||||
name='street',
|
||||
field=models.TextField(null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='cartposition',
|
||||
name='zipcode',
|
||||
field=models.CharField(max_length=30, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='orderposition',
|
||||
name='city',
|
||||
field=models.CharField(max_length=255, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='orderposition',
|
||||
name='company',
|
||||
field=models.CharField(max_length=255, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='orderposition',
|
||||
name='country',
|
||||
field=django_countries.fields.CountryField(max_length=2, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='orderposition',
|
||||
name='state',
|
||||
field=models.CharField(max_length=255, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='orderposition',
|
||||
name='street',
|
||||
field=models.TextField(null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='orderposition',
|
||||
name='zipcode',
|
||||
field=models.CharField(max_length=30, null=True),
|
||||
),
|
||||
]
|
||||
@@ -1065,6 +1065,13 @@ class AbstractPosition(models.Model):
|
||||
'Seat', null=True, blank=True, on_delete=models.PROTECT
|
||||
)
|
||||
|
||||
company = models.CharField(max_length=255, blank=True, verbose_name=_('Company name'), null=True)
|
||||
street = models.TextField(verbose_name=_('Address'), blank=True, null=True)
|
||||
zipcode = models.CharField(max_length=30, verbose_name=_('ZIP code'), blank=True, null=True)
|
||||
city = models.CharField(max_length=255, verbose_name=_('City'), blank=True, null=True)
|
||||
country = CountryField(verbose_name=_('Country'), blank=True, blank_label=_('Select country'), null=True)
|
||||
state = models.CharField(max_length=255, verbose_name=pgettext_lazy('address', 'State'), blank=True, null=True)
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
||||
|
||||
@@ -398,6 +398,99 @@ class AttendeeEmail(ImportColumn):
|
||||
position.attendee_email = value
|
||||
|
||||
|
||||
class AttendeeCompany(ImportColumn):
|
||||
identifier = 'attendee_company'
|
||||
|
||||
@property
|
||||
def verbose_name(self):
|
||||
return _('Attendee address') + ': ' + _('Company')
|
||||
|
||||
def assign(self, value, order, position, invoice_address, **kwargs):
|
||||
position.company = value or ''
|
||||
|
||||
|
||||
class AttendeeStreet(ImportColumn):
|
||||
identifier = 'attendee_street'
|
||||
|
||||
@property
|
||||
def verbose_name(self):
|
||||
return _('Attendee address') + ': ' + _('Address')
|
||||
|
||||
def assign(self, value, order, position, invoice_address, **kwargs):
|
||||
position.address = value or ''
|
||||
|
||||
|
||||
class AttendeeZip(ImportColumn):
|
||||
identifier = 'attendee_zipcode'
|
||||
|
||||
@property
|
||||
def verbose_name(self):
|
||||
return _('Attendee address') + ': ' + _('ZIP code')
|
||||
|
||||
def assign(self, value, order, position, invoice_address, **kwargs):
|
||||
position.zipcode = value or ''
|
||||
|
||||
|
||||
class AttendeeCity(ImportColumn):
|
||||
identifier = 'attendee_city'
|
||||
|
||||
@property
|
||||
def verbose_name(self):
|
||||
return _('Attendee address') + ': ' + _('City')
|
||||
|
||||
def assign(self, value, order, position, invoice_address, **kwargs):
|
||||
position.city = value or ''
|
||||
|
||||
|
||||
class AttendeeCountry(ImportColumn):
|
||||
identifier = 'attendee_country'
|
||||
default_value = None
|
||||
|
||||
@property
|
||||
def initial(self):
|
||||
return 'static:' + str(guess_country(self.event))
|
||||
|
||||
@property
|
||||
def verbose_name(self):
|
||||
return _('Attendee address') + ': ' + _('Country')
|
||||
|
||||
def static_choices(self):
|
||||
return list(countries)
|
||||
|
||||
def clean(self, value, previous_values):
|
||||
if value and not Country(value).numeric:
|
||||
raise ValidationError(_("Please enter a valid country code."))
|
||||
return value
|
||||
|
||||
def assign(self, value, order, position, invoice_address, **kwargs):
|
||||
position.country = value or ''
|
||||
|
||||
|
||||
class AttendeeState(ImportColumn):
|
||||
identifier = 'attendee_state'
|
||||
|
||||
@property
|
||||
def verbose_name(self):
|
||||
return _('Attendee address') + ': ' + _('State')
|
||||
|
||||
def clean(self, value, previous_values):
|
||||
if value:
|
||||
if previous_values.get('attendee_country') not in COUNTRIES_WITH_STATE_IN_ADDRESS:
|
||||
raise ValidationError(_("States are not supported for this country."))
|
||||
|
||||
types, form = COUNTRIES_WITH_STATE_IN_ADDRESS[previous_values.get('attendee_country')]
|
||||
match = [
|
||||
s for s in pycountry.subdivisions.get(country_code=previous_values.get('attendee_country'))
|
||||
if s.type in types and (s.code[3:] == value or s.name == value)
|
||||
]
|
||||
if len(match) == 0:
|
||||
raise ValidationError(_("Please enter a valid state."))
|
||||
return match[0].code[3:]
|
||||
|
||||
def assign(self, value, order, position, invoice_address, **kwargs):
|
||||
position.state = value or ''
|
||||
|
||||
|
||||
class Price(ImportColumn):
|
||||
identifier = 'price'
|
||||
verbose_name = gettext_lazy('Price')
|
||||
@@ -596,6 +689,12 @@ def get_all_columns(event):
|
||||
default.append(AttendeeNamePart(event, n, l))
|
||||
default += [
|
||||
AttendeeEmail(event),
|
||||
AttendeeCompany(event),
|
||||
AttendeeStreet(event),
|
||||
AttendeeZip(event),
|
||||
AttendeeCity(event),
|
||||
AttendeeCountry(event),
|
||||
AttendeeState(event),
|
||||
Price(event),
|
||||
Secret(event),
|
||||
Locale(event),
|
||||
|
||||
@@ -205,6 +205,11 @@ DEFAULT_VARIABLES = OrderedDict((
|
||||
"editor_sample": _("Sample city"),
|
||||
"evaluate": lambda op, order, ev: order.invoice_address.city if getattr(order, 'invoice_address', None) else ''
|
||||
}),
|
||||
("attendee_company", {
|
||||
"label": _("Attendee company"),
|
||||
"editor_sample": _("Sample company"),
|
||||
"evaluate": lambda op, order, ev: op.company or (op.addon_to.company if op.addon_to else '')
|
||||
}),
|
||||
("addons", {
|
||||
"label": _("List of Add-Ons"),
|
||||
"editor_sample": _("Addon 1\nAddon 2"),
|
||||
|
||||
@@ -105,6 +105,44 @@ DEFAULTS = {
|
||||
widget=forms.CheckboxInput(attrs={'data-checkbox-dependency': '#id_settings-attendee_emails_asked'}),
|
||||
)
|
||||
},
|
||||
'attendee_company_asked': {
|
||||
'default': 'False',
|
||||
'type': bool,
|
||||
'form_class': forms.BooleanField,
|
||||
'serializer_class': serializers.BooleanField,
|
||||
'form_kwargs': dict(
|
||||
label=_("Ask for company per ticket"),
|
||||
)
|
||||
},
|
||||
'attendee_company_required': {
|
||||
'default': 'False',
|
||||
'type': bool,
|
||||
'form_class': forms.BooleanField,
|
||||
'serializer_class': serializers.BooleanField,
|
||||
'form_kwargs': dict(
|
||||
label=_("Require company per ticket"),
|
||||
widget=forms.CheckboxInput(attrs={'data-checkbox-dependency': '#id_settings-attendee_company_asked'}),
|
||||
)
|
||||
},
|
||||
'attendee_addresses_asked': {
|
||||
'default': 'False',
|
||||
'type': bool,
|
||||
'form_class': forms.BooleanField,
|
||||
'serializer_class': serializers.BooleanField,
|
||||
'form_kwargs': dict(
|
||||
label=_("Ask for postal addresses per ticket"),
|
||||
)
|
||||
},
|
||||
'attendee_addresses_required': {
|
||||
'default': 'False',
|
||||
'type': bool,
|
||||
'form_class': forms.BooleanField,
|
||||
'serializer_class': serializers.BooleanField,
|
||||
'form_kwargs': dict(
|
||||
label=_("Require postal addresses per ticket"),
|
||||
widget=forms.CheckboxInput(attrs={'data-checkbox-dependency': '#id_settings-attendee_addresses_asked'}),
|
||||
)
|
||||
},
|
||||
'order_email_asked_twice': {
|
||||
'default': 'False',
|
||||
'type': bool,
|
||||
|
||||
@@ -194,15 +194,23 @@ class WaitingListShredder(BaseDataShredder):
|
||||
le.save(update_fields=['data', 'shredded'])
|
||||
|
||||
|
||||
class AttendeeNameShredder(BaseDataShredder):
|
||||
verbose_name = _('Attendee names')
|
||||
identifier = 'attendee_names'
|
||||
description = _('This will remove all attendee names from order positions, as well as logged changes to them.')
|
||||
class AttendeeInfoShredder(BaseDataShredder):
|
||||
verbose_name = _('Attendee info')
|
||||
identifier = 'attendee_info'
|
||||
description = _('This will remove all attendee names and postal addresses from order positions, as well as logged '
|
||||
'changes to them.')
|
||||
|
||||
def generate_files(self) -> List[Tuple[str, str, str]]:
|
||||
yield 'attendee-names.json', 'application/json', json.dumps({
|
||||
'{}-{}'.format(op.order.code, op.positionid): op.attendee_name
|
||||
for op in OrderPosition.all.filter(
|
||||
yield 'attendee-info.json', 'application/json', json.dumps({
|
||||
'{}-{}'.format(op.order.code, op.positionid): {
|
||||
'name': op.attendee_name,
|
||||
'company': op.company,
|
||||
'street': op.street,
|
||||
'zipcode': op.zipcode,
|
||||
'city': op.city,
|
||||
'country': str(op.country) if op.country else None,
|
||||
'state': op.state
|
||||
} for op in OrderPosition.all.filter(
|
||||
order__event=self.event
|
||||
).filter(
|
||||
Q(Q(attendee_name_cached__isnull=False) | Q(attendee_name_parts__isnull=False))
|
||||
@@ -214,8 +222,10 @@ class AttendeeNameShredder(BaseDataShredder):
|
||||
OrderPosition.all.filter(
|
||||
order__event=self.event
|
||||
).filter(
|
||||
Q(Q(attendee_name_cached__isnull=False) | Q(attendee_name_parts__isnull=False))
|
||||
).update(attendee_name_cached=None, attendee_name_parts={'_shredded': True})
|
||||
Q(attendee_name_cached__isnull=False) | Q(attendee_name_parts__isnull=False) |
|
||||
Q(company__isnull=False) | Q(street__isnull=False) | Q(zipcode__isnull=False) | Q(city__isnull=False)
|
||||
).update(attendee_name_cached=None, attendee_name_parts={'_shredded': True}, company=None, street=None,
|
||||
zipcode=None, city=None)
|
||||
|
||||
for le in self.event.logentry_set.filter(action_type="pretix.event.order.modified").exclude(data=""):
|
||||
d = le.parsed_data
|
||||
@@ -227,6 +237,14 @@ class AttendeeNameShredder(BaseDataShredder):
|
||||
d['data'][i]['attendee_name_parts'] = {
|
||||
'_legacy': '█'
|
||||
}
|
||||
if 'company' in row:
|
||||
d['data'][i]['company'] = '█'
|
||||
if 'street' in row:
|
||||
d['data'][i]['street'] = '█'
|
||||
if 'zipcode' in row:
|
||||
d['data'][i]['zipcode'] = '█'
|
||||
if 'city' in row:
|
||||
d['data'][i]['city'] = '█'
|
||||
le.data = json.dumps(d)
|
||||
le.shredded = True
|
||||
le.save(update_fields=['data', 'shredded'])
|
||||
@@ -357,7 +375,7 @@ class PaymentInfoShredder(BaseDataShredder):
|
||||
def register_payment_provider(sender, **kwargs):
|
||||
return [
|
||||
EmailAddressShredder,
|
||||
AttendeeNameShredder,
|
||||
AttendeeInfoShredder,
|
||||
InvoiceAddressShredder,
|
||||
QuestionAnswerShredder,
|
||||
InvoiceShredder,
|
||||
|
||||
@@ -85,10 +85,20 @@ class BaseQuestionsViewMixin:
|
||||
for k, v in form.cleaned_data.items():
|
||||
if k == 'attendee_name_parts':
|
||||
form.pos.attendee_name_parts = v if v else None
|
||||
form.pos.save()
|
||||
elif k == 'attendee_email':
|
||||
form.pos.attendee_email = v if v != '' else None
|
||||
form.pos.save()
|
||||
elif k == 'company':
|
||||
form.pos.company = v if v != '' else None
|
||||
elif k == 'street':
|
||||
form.pos.street = v if v != '' else None
|
||||
elif k == 'zipcode':
|
||||
form.pos.zipcode = v if v != '' else None
|
||||
elif k == 'city':
|
||||
form.pos.city = v if v != '' else None
|
||||
elif k == 'country':
|
||||
form.pos.country = v if v != '' else None
|
||||
elif k == 'state':
|
||||
form.pos.state = v if v != '' else None
|
||||
elif k.startswith('question_'):
|
||||
field = form.fields[k]
|
||||
if hasattr(field, 'answer'):
|
||||
@@ -119,7 +129,7 @@ class BaseQuestionsViewMixin:
|
||||
meta_info['question_form_data'][k] = v
|
||||
|
||||
form.pos.meta_info = json.dumps(meta_info)
|
||||
form.pos.save(update_fields=['meta_info'])
|
||||
form.pos.save()
|
||||
return not failed
|
||||
|
||||
def _save_to_answer(self, field, answer, value):
|
||||
|
||||
@@ -515,6 +515,10 @@ class EventSettingsForm(SettingsForm):
|
||||
'attendee_names_required',
|
||||
'attendee_emails_asked',
|
||||
'attendee_emails_required',
|
||||
'attendee_company_asked',
|
||||
'attendee_company_required',
|
||||
'attendee_addresses_asked',
|
||||
'attendee_addresses_required',
|
||||
'confirm_text',
|
||||
'order_email_asked_twice',
|
||||
'last_order_modification_date',
|
||||
|
||||
@@ -89,6 +89,10 @@
|
||||
{% 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" %}
|
||||
{% bootstrap_field sform.attendee_company_asked layout="control" %}
|
||||
{% bootstrap_field sform.attendee_company_required layout="control" %}
|
||||
{% bootstrap_field sform.attendee_addresses_asked layout="control" %}
|
||||
{% bootstrap_field sform.attendee_addresses_required layout="control" %}
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<legend>{% trans "Texts" %}</legend>
|
||||
|
||||
@@ -388,6 +388,29 @@
|
||||
{% endif %}
|
||||
</dd>
|
||||
{% endif %}
|
||||
{% if line.item.admission and event.settings.attendee_company_asked %}
|
||||
<dt>
|
||||
{% trans "Attendee company" %}
|
||||
</dt>
|
||||
<dd>
|
||||
{% if line.company %}{{ line.company }}{% else %}<em>{% trans "not answered" %}</em>{% endif %}
|
||||
</dd>
|
||||
{% endif %}
|
||||
{% if line.item.admission and event.settings.attendee_addresses_asked %}
|
||||
<dt>
|
||||
{% trans "Attendee address" %}
|
||||
</dt>
|
||||
<dd>
|
||||
{% if line.street or line.zipcode or line.city or line.country %}
|
||||
{{ line.street|linebreaksbr }}<br>
|
||||
{{ line.zipcode }} {{ line.city }}<br>
|
||||
{{ line.country.name }}
|
||||
{% if line.state %}<br>{{ line.state }}{% endif %}
|
||||
{% else %}
|
||||
<em>{% trans "not answered" %}</em>
|
||||
{% endif %}
|
||||
</dd>
|
||||
{% endif %}
|
||||
{% for q in line.questions %}
|
||||
<dt>
|
||||
{{ q.question }}
|
||||
|
||||
@@ -428,7 +428,7 @@ class CSVCheckinList(CheckInListMixin, ListExporter):
|
||||
for q in questions:
|
||||
row.append(acache.get(q.pk, ''))
|
||||
|
||||
row.append(ia.company)
|
||||
row.append(op.company or ia.company)
|
||||
row.append(op.voucher.code if op.voucher else "")
|
||||
row.append(op.order.datetime.astimezone(self.event.timezone).strftime('%Y-%m-%d'))
|
||||
row.append(_('Yes') if op.order.checkin_attention or op.item.checkin_attention else _('No'))
|
||||
|
||||
@@ -488,6 +488,16 @@ class QuestionsStep(QuestionsViewMixin, CartMixin, TemplateFlowStep):
|
||||
if warn:
|
||||
messages.warning(request, _('Please fill in answers to all required questions.'))
|
||||
return False
|
||||
if cp.item.admission and self.request.event.settings.get('attendee_company_required', as_type=bool) \
|
||||
and cp.company is None:
|
||||
if warn:
|
||||
messages.warning(request, _('Please fill in answers to all required questions.'))
|
||||
return False
|
||||
if cp.item.admission and self.request.event.settings.get('attendee_attendees_required', as_type=bool) \
|
||||
and (cp.street is None or cp.city is None or cp.country is None):
|
||||
if warn:
|
||||
messages.warning(request, _('Please fill in answers to all required questions.'))
|
||||
return False
|
||||
|
||||
responses = question_form_fields.send(sender=self.request.event, position=cp)
|
||||
form_data = cp.meta_info_data.get('question_form_data', {})
|
||||
|
||||
@@ -44,12 +44,51 @@
|
||||
{% if line.has_questions %}
|
||||
<dl>
|
||||
{% if line.item.admission and event.settings.attendee_names_asked %}
|
||||
<dt>{% trans "Attendee name" %}</dt>
|
||||
<dd>{% if line.attendee_name %}{{ line.attendee_name }}{% else %}<em>{% trans "not answered" %}</em>{% endif %}</dd>
|
||||
<dt class="sr-only">
|
||||
{% trans "Attendee name" %}
|
||||
</dt>
|
||||
<dd class="toplevel">
|
||||
<span data-toggle="tooltip" title="{% trans "Attendee name" %}">
|
||||
{% if line.attendee_name %}{{ line.attendee_name }}{% else %}<em>{% trans "not answered" %}</em>{% endif %}
|
||||
</span>
|
||||
</dd>
|
||||
{% endif %}
|
||||
{% if line.item.admission and event.settings.attendee_emails_asked %}
|
||||
<dt>{% trans "Attendee email" %}</dt>
|
||||
<dd>{% if line.attendee_email %}{{ line.attendee_email }}{% else %}<em>{% trans "not answered" %}</em>{% endif %}</dd>
|
||||
<dt class="sr-only">
|
||||
{% trans "Attendee email" %}
|
||||
</dt>
|
||||
<dd class="toplevel">
|
||||
<span data-toggle="tooltip" title="{% trans "Attendee email" %}">
|
||||
{% if line.attendee_email %}{{ line.attendee_email }}{% else %}<em>{% trans "not answered" %}</em>{% endif %}
|
||||
</span>
|
||||
</dd>
|
||||
{% endif %}
|
||||
{% if line.item.admission and event.settings.attendee_company_asked %}
|
||||
<dt class="sr-only">
|
||||
{% trans "Attendee company" %}
|
||||
</dt>
|
||||
<dd class="toplevel">
|
||||
<span data-toggle="tooltip" title="{% trans "Attendee company" %}">
|
||||
{% if line.company %}{{ line.company }}{% else %}<em>{% trans "not answered" %}</em>{% endif %}
|
||||
</span>
|
||||
</dd>
|
||||
{% endif %}
|
||||
{% if line.item.admission and event.settings.attendee_addresses_asked %}
|
||||
<dt class="sr-only">
|
||||
{% trans "Attendee address" %}
|
||||
</dt>
|
||||
<dd class="toplevel">
|
||||
<span data-toggle="tooltip" title="{% trans "Attendee address" %}">
|
||||
{% if line.street or line.zipcode or line.city or line.country %}
|
||||
{{ line.street|linebreaksbr }}<br>
|
||||
{{ line.zipcode }} {{ line.city }}<br>
|
||||
{{ line.country.name }}
|
||||
{% if line.state %}<br>{{ line.state }}{% endif %}
|
||||
{% else %}
|
||||
<em>{% trans "not answered" %}</em>
|
||||
{% endif %}
|
||||
</span>
|
||||
</dd>
|
||||
{% endif %}
|
||||
{% for q in line.questions %}
|
||||
<dt>{{ q.question }}</dt>
|
||||
|
||||
@@ -167,22 +167,35 @@ $(function () {
|
||||
return false;
|
||||
});
|
||||
var copy_to_first_ticket = true;
|
||||
$("input[id*=attendee_name_parts_], input[id*=attendee_email]").each(function () {
|
||||
var attendee_address_fields = $("input[id*=attendee_name_parts_], input[id*=attendee_email], .questions-form" +
|
||||
" input[id$=company], .questions-form[id$=street], .questions-form input[id$=zipcode], .questions-form" +
|
||||
" input[id$=city]");
|
||||
attendee_address_fields.each(function () {
|
||||
if ($(this).val()) {
|
||||
copy_to_first_ticket = false;
|
||||
}
|
||||
})
|
||||
$("input[id^=id_name_parts_], #id_email").change(function () {
|
||||
$("select[id^=id_name_parts], input[id^=id_name_parts_], #id_email, #id_street, #id_company, #id_zipcode," +
|
||||
" #id_city, #id_country, #id_state").change(function () {
|
||||
if (copy_to_first_ticket) {
|
||||
$(".questions-form").first().find("input[id*=attendee_email]").val($("#id_email").val());
|
||||
$(".questions-form").first().find("input[id*=attendee_name_parts]").each(function () {
|
||||
$(".questions-form").first().find("input[id$=company]").val($("#id_company").val());
|
||||
$(".questions-form").first().find("textarea[id$=street]").val($("#id_street").val());
|
||||
$(".questions-form").first().find("input[id$=zipcode]").val($("#id_zipcode").val());
|
||||
$(".questions-form").first().find("input[id$=city]").val($("#id_city").val());
|
||||
|
||||
$(".questions-form").first().find("select[id$=state]").val($("#id_state").val());
|
||||
if ($(".questions-form").first().find("select[id$=country]").val() !== $("#id_country").val()) {
|
||||
$(".questions-form").first().find("select[id$=country]").val($("#id_country").val()).trigger('change');
|
||||
}
|
||||
$(".questions-form").first().find("[id*=attendee_name_parts]").each(function () {
|
||||
var parts = $(this).attr("id").split("_");
|
||||
var num = parts[parts.length - 1];
|
||||
$(this).val($("#id_name_parts_" + num).val());
|
||||
});
|
||||
}
|
||||
});
|
||||
$("input[id*=attendee_name_parts_], input[id*=attendee_email]").change(function () {
|
||||
attendee_address_fields.change(function () {
|
||||
copy_to_first_ticket = false;
|
||||
});
|
||||
|
||||
@@ -273,7 +286,7 @@ $(function () {
|
||||
$("select[name$=state]").each(function () {
|
||||
var dependent = $(this),
|
||||
counter = 0,
|
||||
dependency = $(this).closest("form").find('select[name$=country]'),
|
||||
dependency = $(this).closest(".panel-body, form").find('select[name$=country]'),
|
||||
update = function (ev) {
|
||||
counter++;
|
||||
var curCounter = counter;
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
padding-left: 20px;
|
||||
margin-bottom: 0;
|
||||
|
||||
dd {
|
||||
dd:not(.toplevel) {
|
||||
padding-left: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user