mirror of
https://github.com/pretix/pretix.git
synced 2026-05-06 15:24:02 +00:00
Customer accounts & Memberships (#2024)
This commit is contained in:
@@ -45,7 +45,7 @@ from django.urls import reverse
|
||||
from django.utils.timezone import now
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from ...base.forms import I18nModelForm
|
||||
from ...base.forms import I18nModelForm, SecretKeySettingsField
|
||||
|
||||
# Import for backwards compatibility with okd import paths
|
||||
from ...base.forms.widgets import ( # noqa
|
||||
@@ -368,3 +368,46 @@ class SplitDateTimeField(forms.SplitDateTimeField):
|
||||
|
||||
class FontSelect(forms.RadioSelect):
|
||||
option_template_name = 'pretixcontrol/font_option.html'
|
||||
|
||||
|
||||
class SMTPSettingsMixin(forms.Form):
|
||||
smtp_use_custom = forms.BooleanField(
|
||||
label=_("Use custom SMTP server"),
|
||||
help_text=_("All mail related to your event will be sent over the smtp server specified by you."),
|
||||
required=False
|
||||
)
|
||||
smtp_host = forms.CharField(
|
||||
label=_("Hostname"),
|
||||
required=False,
|
||||
widget=forms.TextInput(attrs={'placeholder': 'mail.example.org'})
|
||||
)
|
||||
smtp_port = forms.IntegerField(
|
||||
label=_("Port"),
|
||||
required=False,
|
||||
widget=forms.TextInput(attrs={'placeholder': 'e.g. 587, 465, 25, ...'})
|
||||
)
|
||||
smtp_username = forms.CharField(
|
||||
label=_("Username"),
|
||||
widget=forms.TextInput(attrs={'placeholder': 'myuser@example.org'}),
|
||||
required=False
|
||||
)
|
||||
smtp_password = SecretKeySettingsField(
|
||||
label=_("Password"),
|
||||
required=False,
|
||||
)
|
||||
smtp_use_tls = forms.BooleanField(
|
||||
label=_("Use STARTTLS"),
|
||||
help_text=_("Commonly enabled on port 587."),
|
||||
required=False
|
||||
)
|
||||
smtp_use_ssl = forms.BooleanField(
|
||||
label=_("Use SSL"),
|
||||
help_text=_("Commonly enabled on port 465."),
|
||||
required=False
|
||||
)
|
||||
|
||||
def clean(self):
|
||||
data = super().clean()
|
||||
if data.get('smtp_use_tls') and data.get('smtp_use_ssl'):
|
||||
raise ValidationError(_('You can activate either SSL or STARTTLS security, but not both at the same time.'))
|
||||
return data
|
||||
|
||||
@@ -63,7 +63,7 @@ from pretix.base.settings import (
|
||||
PERSON_NAME_SCHEMES, PERSON_NAME_TITLE_GROUPS, validate_event_settings,
|
||||
)
|
||||
from pretix.control.forms import (
|
||||
MultipleLanguagesWidget, SlugWidget, SplitDateTimeField,
|
||||
MultipleLanguagesWidget, SlugWidget, SMTPSettingsMixin, SplitDateTimeField,
|
||||
SplitDateTimePickerWidget,
|
||||
)
|
||||
from pretix.control.forms.widgets import Select2
|
||||
@@ -825,7 +825,7 @@ def contains_web_channel_validate(val):
|
||||
raise ValidationError(_("The online shop must be selected to receive these emails."))
|
||||
|
||||
|
||||
class MailSettingsForm(SettingsForm):
|
||||
class MailSettingsForm(SMTPSettingsMixin, SettingsForm):
|
||||
auto_fields = [
|
||||
'mail_prefix',
|
||||
'mail_from',
|
||||
@@ -1020,43 +1020,6 @@ class MailSettingsForm(SettingsForm):
|
||||
required=False,
|
||||
widget=I18nTextarea,
|
||||
)
|
||||
smtp_use_custom = forms.BooleanField(
|
||||
label=_("Use custom SMTP server"),
|
||||
help_text=_("All mail related to your event will be sent over the smtp server specified by you."),
|
||||
required=False
|
||||
)
|
||||
smtp_host = forms.CharField(
|
||||
label=_("Hostname"),
|
||||
required=False,
|
||||
widget=forms.TextInput(attrs={'placeholder': 'mail.example.org'})
|
||||
)
|
||||
smtp_port = forms.IntegerField(
|
||||
label=_("Port"),
|
||||
required=False,
|
||||
widget=forms.TextInput(attrs={'placeholder': 'e.g. 587, 465, 25, ...'})
|
||||
)
|
||||
smtp_username = forms.CharField(
|
||||
label=_("Username"),
|
||||
widget=forms.TextInput(attrs={'placeholder': 'myuser@example.org'}),
|
||||
required=False
|
||||
)
|
||||
smtp_password = forms.CharField(
|
||||
label=_("Password"),
|
||||
required=False,
|
||||
widget=forms.PasswordInput(attrs={
|
||||
'autocomplete': 'new-password' # see https://bugs.chromium.org/p/chromium/issues/detail?id=370363#c7
|
||||
}),
|
||||
)
|
||||
smtp_use_tls = forms.BooleanField(
|
||||
label=_("Use STARTTLS"),
|
||||
help_text=_("Commonly enabled on port 587."),
|
||||
required=False
|
||||
)
|
||||
smtp_use_ssl = forms.BooleanField(
|
||||
label=_("Use SSL"),
|
||||
help_text=_("Commonly enabled on port 465."),
|
||||
required=False
|
||||
)
|
||||
base_context = {
|
||||
'mail_text_order_placed': ['event', 'order', 'payment'],
|
||||
'mail_text_order_placed_attendee': ['event', 'order', 'position'],
|
||||
@@ -1110,17 +1073,6 @@ class MailSettingsForm(SettingsForm):
|
||||
# the user interface with it
|
||||
del self.fields[k]
|
||||
|
||||
def clean(self):
|
||||
data = self.cleaned_data
|
||||
if not data.get('smtp_password') and data.get('smtp_username'):
|
||||
# Leave password unchanged if the username is set and the password field is empty.
|
||||
# This makes it impossible to set an empty password as long as a username is set, but
|
||||
# Python's smtplib does not support password-less schemes anyway.
|
||||
data['smtp_password'] = self.initial.get('smtp_password')
|
||||
|
||||
if data.get('smtp_use_tls') and data.get('smtp_use_ssl'):
|
||||
raise ValidationError(_('You can activate either SSL or STARTTLS security, but not both at the same time.'))
|
||||
|
||||
|
||||
class TicketSettingsForm(SettingsForm):
|
||||
auto_fields = [
|
||||
|
||||
@@ -1022,6 +1022,44 @@ class GiftCardFilterForm(FilterForm):
|
||||
return qs.distinct()
|
||||
|
||||
|
||||
class CustomerFilterForm(FilterForm):
|
||||
orders = {
|
||||
'email': 'email',
|
||||
'identifier': 'identifier',
|
||||
'name_cached': 'name_cached',
|
||||
}
|
||||
query = forms.CharField(
|
||||
label=_('Search query'),
|
||||
widget=forms.TextInput(attrs={
|
||||
'placeholder': _('Search query'),
|
||||
'autofocus': 'autofocus'
|
||||
}),
|
||||
required=False
|
||||
)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
kwargs.pop('request')
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
def filter_qs(self, qs):
|
||||
fdata = self.cleaned_data
|
||||
|
||||
if fdata.get('query'):
|
||||
query = fdata.get('query')
|
||||
qs = qs.filter(
|
||||
Q(email__icontains=query)
|
||||
| Q(name_cached__icontains=query)
|
||||
| Q(identifier__istartswith=query)
|
||||
)
|
||||
|
||||
if fdata.get('ordering'):
|
||||
qs = qs.order_by(self.get_order_by())
|
||||
else:
|
||||
qs = qs.order_by('-email')
|
||||
|
||||
return qs
|
||||
|
||||
|
||||
class TeamFilterForm(FilterForm):
|
||||
orders = {
|
||||
'name': 'name',
|
||||
|
||||
@@ -368,6 +368,11 @@ class ItemCreateForm(I18nModelForm):
|
||||
'hidden_if_available',
|
||||
'require_bundling',
|
||||
'checkin_attention',
|
||||
'require_membership',
|
||||
'grant_membership_type',
|
||||
'grant_membership_duration_like_event',
|
||||
'grant_membership_duration_days',
|
||||
'grant_membership_duration_months',
|
||||
)
|
||||
for f in fields:
|
||||
setattr(self.instance, f, getattr(self.cleaned_data['copy_from'], f))
|
||||
@@ -399,6 +404,10 @@ class ItemCreateForm(I18nModelForm):
|
||||
'items': [self.instance.pk]
|
||||
})
|
||||
|
||||
if self.cleaned_data.get('copy_from'):
|
||||
self.instance.require_membership_types.set(
|
||||
self.cleaned_data['copy_from'].require_membership_types.all()
|
||||
)
|
||||
if self.cleaned_data.get('has_variations'):
|
||||
if self.cleaned_data.get('copy_from') and self.cleaned_data.get('copy_from').has_variations:
|
||||
for variation in self.cleaned_data['copy_from'].variations.all():
|
||||
@@ -523,6 +532,19 @@ class ItemUpdateForm(I18nModelForm):
|
||||
)
|
||||
self.fields['category'].widget.choices = self.fields['category'].choices
|
||||
|
||||
qs = self.event.organizer.membership_types.all()
|
||||
if qs:
|
||||
self.fields['require_membership_types'].queryset = qs
|
||||
self.fields['grant_membership_type'].queryset = qs
|
||||
self.fields['grant_membership_type'].empty_label = _('No membership granted')
|
||||
else:
|
||||
del self.fields['require_membership']
|
||||
del self.fields['require_membership_types']
|
||||
del self.fields['grant_membership_type']
|
||||
del self.fields['grant_membership_duration_like_event']
|
||||
del self.fields['grant_membership_duration_days']
|
||||
del self.fields['grant_membership_duration_months']
|
||||
|
||||
def clean(self):
|
||||
d = super().clean()
|
||||
if d['issue_giftcard']:
|
||||
@@ -571,15 +593,26 @@ class ItemUpdateForm(I18nModelForm):
|
||||
'show_quota_left',
|
||||
'hidden_if_available',
|
||||
'issue_giftcard',
|
||||
'require_membership',
|
||||
'require_membership_types',
|
||||
'grant_membership_type',
|
||||
'grant_membership_duration_like_event',
|
||||
'grant_membership_duration_days',
|
||||
'grant_membership_duration_months',
|
||||
]
|
||||
field_classes = {
|
||||
'available_from': SplitDateTimeField,
|
||||
'available_until': SplitDateTimeField,
|
||||
'hidden_if_available': SafeModelChoiceField,
|
||||
'grant_membership_type': SafeModelChoiceField,
|
||||
'require_membership_types': SafeModelMultipleChoiceField,
|
||||
}
|
||||
widgets = {
|
||||
'available_from': SplitDateTimePickerWidget(),
|
||||
'available_until': SplitDateTimePickerWidget(attrs={'data-date-after': '#id_available_from_0'}),
|
||||
'require_membership_types': forms.CheckboxSelectMultiple(attrs={
|
||||
'class': 'scrolling-multiple-choice'
|
||||
}),
|
||||
'generate_tickets': TicketNullBooleanSelect(),
|
||||
'show_quota_left': ShowQuotaNullBooleanSelect()
|
||||
}
|
||||
@@ -632,6 +665,13 @@ class ItemVariationForm(I18nModelForm):
|
||||
super().__init__(*args, **kwargs)
|
||||
change_decimal_field(self.fields['default_price'], self.event.currency)
|
||||
|
||||
qs = self.event.organizer.membership_types.all()
|
||||
if qs:
|
||||
self.fields['require_membership_types'].queryset = qs
|
||||
else:
|
||||
del self.fields['require_membership']
|
||||
del self.fields['require_membership_types']
|
||||
|
||||
class Meta:
|
||||
model = ItemVariation
|
||||
localized_fields = '__all__'
|
||||
@@ -641,7 +681,14 @@ class ItemVariationForm(I18nModelForm):
|
||||
'default_price',
|
||||
'original_price',
|
||||
'description',
|
||||
'require_membership',
|
||||
'require_membership_types'
|
||||
]
|
||||
widgets = {
|
||||
'require_membership_types': forms.CheckboxSelectMultiple(attrs={
|
||||
'class': 'scrolling-multiple-choice'
|
||||
}),
|
||||
}
|
||||
|
||||
|
||||
class ItemAddOnsFormSet(I18nFormSet):
|
||||
|
||||
@@ -46,6 +46,7 @@ from django.utils.timezone import make_aware, now
|
||||
from django.utils.translation import (
|
||||
gettext_lazy as _, gettext_noop, pgettext_lazy,
|
||||
)
|
||||
from django_scopes.forms import SafeModelChoiceField
|
||||
from i18nfield.forms import I18nFormField, I18nTextarea, I18nTextInput
|
||||
from i18nfield.strings import LazyI18nString
|
||||
|
||||
@@ -290,6 +291,10 @@ class OrderPositionAddForm(forms.Form):
|
||||
widget=forms.TextInput(attrs={'placeholder': _('General admission'), 'data-seat-guid-field': 'true'}),
|
||||
label=_('Seat')
|
||||
)
|
||||
used_membership = forms.ChoiceField(
|
||||
label=_('Membership'),
|
||||
required=False,
|
||||
)
|
||||
price = forms.DecimalField(
|
||||
required=False,
|
||||
max_digits=10, decimal_places=2,
|
||||
@@ -360,6 +365,23 @@ class OrderPositionAddForm(forms.Form):
|
||||
del self.fields['subevent']
|
||||
change_decimal_field(self.fields['price'], order.event.currency)
|
||||
|
||||
choices = [
|
||||
('', ''),
|
||||
]
|
||||
if order.customer:
|
||||
self.memberships = list(order.customer.memberships.all())
|
||||
for m in self.memberships:
|
||||
choices.append((str(m.pk), str(m)))
|
||||
self.fields['used_membership'].choices = choices
|
||||
|
||||
def clean(self):
|
||||
d = super().clean()
|
||||
if d['used_membership']:
|
||||
d['used_membership'] = [m for m in self.memberships if str(m.pk) == d['used_membership']][0]
|
||||
else:
|
||||
d['used_membership'] = None
|
||||
return d
|
||||
|
||||
|
||||
class OrderPositionAddFormset(forms.BaseFormSet):
|
||||
def __init__(self, *args, **kwargs):
|
||||
@@ -405,6 +427,9 @@ class OrderPositionChangeForm(forms.Form):
|
||||
localize=True,
|
||||
label=_('New price (gross)')
|
||||
)
|
||||
used_membership = forms.ChoiceField(
|
||||
required=False,
|
||||
)
|
||||
tax_rule = forms.ModelChoiceField(
|
||||
TaxRule.objects.none(),
|
||||
required=False,
|
||||
@@ -478,6 +503,24 @@ class OrderPositionChangeForm(forms.Form):
|
||||
self.fields['itemvar'].choices = choices
|
||||
change_decimal_field(self.fields['price'], instance.order.event.currency)
|
||||
|
||||
choices = [
|
||||
('', _('(Unchanged)')),
|
||||
('CLEAR', _('(No membership)')),
|
||||
]
|
||||
if instance.order.customer:
|
||||
self.memberships = list(instance.order.customer.memberships.all())
|
||||
for m in self.memberships:
|
||||
choices.append((str(m.pk), str(m)))
|
||||
self.fields['used_membership'].choices = choices
|
||||
|
||||
def clean(self):
|
||||
d = super().clean()
|
||||
if d['used_membership'] and d['used_membership'] != 'CLEAR':
|
||||
d['used_membership'] = [m for m in self.memberships if str(m.pk) == d['used_membership']][0]
|
||||
elif not d['used_membership']:
|
||||
d['used_membership'] = None
|
||||
return d
|
||||
|
||||
|
||||
class OrderFeeChangeForm(forms.Form):
|
||||
value = forms.DecimalField(
|
||||
@@ -516,16 +559,36 @@ class OrderContactForm(forms.ModelForm):
|
||||
|
||||
class Meta:
|
||||
model = Order
|
||||
fields = ['email', 'email_known_to_work', 'phone']
|
||||
fields = ['customer', 'email', 'email_known_to_work', 'phone']
|
||||
widgets = {
|
||||
'phone': WrappedPhoneNumberPrefixWidget()
|
||||
'phone': WrappedPhoneNumberPrefixWidget(),
|
||||
}
|
||||
field_classes = {
|
||||
'customer': SafeModelChoiceField,
|
||||
}
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
customers = kwargs.pop('customers')
|
||||
super().__init__(*args, **kwargs)
|
||||
if not self.instance.event.settings.order_phone_asked and not self.instance.phone:
|
||||
del self.fields['phone']
|
||||
|
||||
if customers:
|
||||
self.fields['customer'].queryset = self.instance.event.organizer.customers.all()
|
||||
self.fields['customer'].widget = Select2(
|
||||
attrs={
|
||||
'data-model-select2': 'generic',
|
||||
'data-select2-url': reverse('control:organizer.customers.select2', kwargs={
|
||||
'organizer': self.instance.event.organizer.slug,
|
||||
}),
|
||||
'data-placeholder': _('Customer')
|
||||
}
|
||||
)
|
||||
self.fields['customer'].widget.choices = self.fields['customer'].choices
|
||||
self.fields['customer'].required = False
|
||||
else:
|
||||
del self.fields['customer']
|
||||
|
||||
|
||||
class OrderLocaleForm(forms.ModelForm):
|
||||
locale = forms.ChoiceField()
|
||||
|
||||
@@ -39,20 +39,31 @@ from django import forms
|
||||
from django.conf import settings
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.db.models import Q
|
||||
from django.utils.crypto import get_random_string
|
||||
from django.utils.safestring import mark_safe
|
||||
from django.utils.translation import gettext_lazy as _, pgettext_lazy
|
||||
from django_scopes.forms import SafeModelMultipleChoiceField
|
||||
from i18nfield.forms import I18nFormField, I18nTextarea
|
||||
from pytz import common_timezones
|
||||
|
||||
from pretix.api.models import WebHook
|
||||
from pretix.api.webhooks import get_all_webhook_events
|
||||
from pretix.base.forms import I18nModelForm, SettingsForm
|
||||
from pretix.base.forms import I18nModelForm, PlaceholderValidator, SettingsForm
|
||||
from pretix.base.forms.questions import NamePartsFormField
|
||||
from pretix.base.forms.widgets import SplitDateTimePickerWidget
|
||||
from pretix.base.models import (
|
||||
Device, EventMetaProperty, Gate, GiftCard, Organizer, Team,
|
||||
Customer, Device, EventMetaProperty, Gate, GiftCard, Membership,
|
||||
MembershipType, Organizer, Team,
|
||||
)
|
||||
from pretix.base.settings import PERSON_NAME_SCHEMES, PERSON_NAME_TITLE_GROUPS
|
||||
from pretix.control.forms import (
|
||||
ExtFileField, SMTPSettingsMixin, SplitDateTimeField,
|
||||
)
|
||||
from pretix.control.forms.event import (
|
||||
SafeEventMultipleChoiceField, multimail_validate,
|
||||
)
|
||||
from pretix.control.forms import ExtFileField, SplitDateTimeField
|
||||
from pretix.control.forms.event import SafeEventMultipleChoiceField
|
||||
from pretix.multidomain.models import KnownDomain
|
||||
from pretix.multidomain.urlreverse import build_absolute_uri
|
||||
|
||||
|
||||
class OrganizerForm(I18nModelForm):
|
||||
@@ -168,6 +179,12 @@ class EventMetaPropertyForm(forms.ModelForm):
|
||||
}
|
||||
|
||||
|
||||
class MembershipTypeForm(I18nModelForm):
|
||||
class Meta:
|
||||
model = MembershipType
|
||||
fields = ['name', 'transferable', 'allow_parallel_usage', 'max_usages']
|
||||
|
||||
|
||||
class TeamForm(forms.ModelForm):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
@@ -181,7 +198,7 @@ class TeamForm(forms.ModelForm):
|
||||
model = Team
|
||||
fields = ['name', 'all_events', 'limit_events', 'can_create_events',
|
||||
'can_change_teams', 'can_change_organizer_settings',
|
||||
'can_manage_gift_cards',
|
||||
'can_manage_gift_cards', 'can_manage_customers',
|
||||
'can_change_event_settings', 'can_change_items',
|
||||
'can_view_orders', 'can_change_orders', 'can_checkin_orders',
|
||||
'can_view_vouchers', 'can_change_vouchers']
|
||||
@@ -250,7 +267,24 @@ class DeviceForm(forms.ModelForm):
|
||||
|
||||
|
||||
class OrganizerSettingsForm(SettingsForm):
|
||||
timezone = forms.ChoiceField(
|
||||
choices=((a, a) for a in common_timezones),
|
||||
label=_("Default timezone"),
|
||||
)
|
||||
name_scheme = forms.ChoiceField(
|
||||
label=_("Name format"),
|
||||
help_text=_("This defines how pretix will ask for human names. Changing this after you already received "
|
||||
"orders might lead to unexpected behavior 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,
|
||||
)
|
||||
auto_fields = [
|
||||
'customer_accounts',
|
||||
'contact_mail',
|
||||
'imprint_url',
|
||||
'organizer_info_text',
|
||||
@@ -292,6 +326,115 @@ class OrganizerSettingsForm(SettingsForm):
|
||||
'We recommend a size of at least 200x200px to accommodate most devices.')
|
||||
)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.fields['name_scheme'].choices = (
|
||||
(k, _('Ask for {fields}, display like {example}').format(
|
||||
fields=' + '.join(str(vv[1]) for vv in v['fields']),
|
||||
example=v['concatenation'](v['sample'])
|
||||
))
|
||||
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 MailSettingsForm(SMTPSettingsMixin, SettingsForm):
|
||||
auto_fields = [
|
||||
'mail_from',
|
||||
'mail_from_name',
|
||||
]
|
||||
|
||||
mail_bcc = forms.CharField(
|
||||
label=_("Bcc address"),
|
||||
help_text=_("All emails will be sent to this address as a Bcc copy"),
|
||||
validators=[multimail_validate],
|
||||
required=False,
|
||||
max_length=255
|
||||
)
|
||||
mail_text_signature = I18nFormField(
|
||||
label=_("Signature"),
|
||||
required=False,
|
||||
widget=I18nTextarea,
|
||||
help_text=_("This will be attached to every email."),
|
||||
validators=[PlaceholderValidator([])],
|
||||
widget_kwargs={'attrs': {
|
||||
'rows': '4',
|
||||
'placeholder': _(
|
||||
'e.g. your contact details'
|
||||
)
|
||||
}}
|
||||
)
|
||||
|
||||
mail_text_customer_registration = I18nFormField(
|
||||
label=_("Text"),
|
||||
required=False,
|
||||
widget=I18nTextarea,
|
||||
)
|
||||
mail_text_customer_email_change = I18nFormField(
|
||||
label=_("Text"),
|
||||
required=False,
|
||||
widget=I18nTextarea,
|
||||
)
|
||||
mail_text_customer_reset = I18nFormField(
|
||||
label=_("Text"),
|
||||
required=False,
|
||||
widget=I18nTextarea,
|
||||
)
|
||||
|
||||
base_context = {
|
||||
'mail_text_customer_registration': ['customer', 'url'],
|
||||
'mail_text_customer_email_change': ['customer', 'url'],
|
||||
'mail_text_customer_reset': ['customer', 'url'],
|
||||
}
|
||||
|
||||
def _get_sample_context(self, base_parameters):
|
||||
placeholders = {
|
||||
'organizer': self.organizer.name
|
||||
}
|
||||
|
||||
if 'url' in base_parameters:
|
||||
placeholders['url'] = build_absolute_uri(
|
||||
self.organizer,
|
||||
'presale:organizer.customer.activate'
|
||||
) + '?token=' + get_random_string(30)
|
||||
|
||||
if 'customer' in base_parameters:
|
||||
placeholders['name'] = pgettext_lazy('person_name_sample', 'John Doe')
|
||||
name_scheme = PERSON_NAME_SCHEMES[self.organizer.settings.name_scheme]
|
||||
for f, l, w in name_scheme['fields']:
|
||||
if f == 'full_name':
|
||||
continue
|
||||
placeholders['name_%s' % f] = name_scheme['sample'][f]
|
||||
return placeholders
|
||||
|
||||
def _set_field_placeholders(self, fn, base_parameters):
|
||||
phs = [
|
||||
'{%s}' % p
|
||||
for p in sorted(self._get_sample_context(base_parameters).keys())
|
||||
]
|
||||
ht = _('Available placeholders: {list}').format(
|
||||
list=', '.join(phs)
|
||||
)
|
||||
if self.fields[fn].help_text:
|
||||
self.fields[fn].help_text += ' ' + str(ht)
|
||||
else:
|
||||
self.fields[fn].help_text = ht
|
||||
self.fields[fn].validators.append(
|
||||
PlaceholderValidator(phs)
|
||||
)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.organizer = kwargs.get('obj')
|
||||
super().__init__(*args, **kwargs)
|
||||
for k, v in self.base_context.items():
|
||||
self._set_field_placeholders(k, v)
|
||||
|
||||
|
||||
class WebHookForm(forms.ModelForm):
|
||||
events = forms.MultipleChoiceField(
|
||||
@@ -373,3 +516,67 @@ class GiftCardUpdateForm(forms.ModelForm):
|
||||
'expires': SplitDateTimePickerWidget,
|
||||
'conditions': forms.Textarea(attrs={"rows": 2})
|
||||
}
|
||||
|
||||
|
||||
class CustomerUpdateForm(forms.ModelForm):
|
||||
error_messages = {
|
||||
'duplicate': _("An account with this email address is already registered."),
|
||||
}
|
||||
|
||||
class Meta:
|
||||
model = Customer
|
||||
fields = ['is_active', 'name_parts', 'email', 'is_verified', 'locale']
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
self.fields['name_parts'] = NamePartsFormField(
|
||||
max_length=255,
|
||||
required=False,
|
||||
scheme=self.instance.organizer.settings.name_scheme,
|
||||
titles=self.instance.organizer.settings.name_scheme_titles,
|
||||
label=_('Name'),
|
||||
)
|
||||
|
||||
def clean(self):
|
||||
email = self.cleaned_data.get('email')
|
||||
|
||||
if email is not None:
|
||||
try:
|
||||
self.instance.organizer.customers.exclude(pk=self.instance.pk).get(email=email)
|
||||
except Customer.DoesNotExist:
|
||||
pass
|
||||
else:
|
||||
raise forms.ValidationError(
|
||||
self.error_messages['duplicate'],
|
||||
code='duplicate',
|
||||
)
|
||||
|
||||
return self.cleaned_data
|
||||
|
||||
|
||||
class MembershipUpdateForm(forms.ModelForm):
|
||||
|
||||
class Meta:
|
||||
model = Membership
|
||||
fields = ['membership_type', 'date_start', 'date_end', 'attendee_name_parts']
|
||||
field_classes = {
|
||||
'date_start': SplitDateTimeField,
|
||||
'date_end': SplitDateTimeField,
|
||||
}
|
||||
widgets = {
|
||||
'date_start': SplitDateTimePickerWidget(),
|
||||
'date_end': SplitDateTimePickerWidget(attrs={'data-date-after': '#id_date_Start'}),
|
||||
}
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
self.fields['membership_type'].queryset = self.instance.customer.organizer.membership_types.all()
|
||||
self.fields['attendee_name_parts'] = NamePartsFormField(
|
||||
max_length=255,
|
||||
required=False,
|
||||
scheme=self.instance.customer.organizer.settings.name_scheme,
|
||||
titles=self.instance.customer.organizer.settings.name_scheme_titles,
|
||||
label=_('Attendee name'),
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user