mirror of
https://github.com/pretix/pretix.git
synced 2026-03-08 13:22:27 +00:00
Add order-level telephone field to core (#1872)
Co-authored-by: Martin Gross <gross@rami.io>
This commit is contained in:
@@ -30,6 +30,7 @@ testmode boolean If ``true``, th
|
||||
test mode. Only orders in test mode can be deleted.
|
||||
secret string The secret contained in the link sent to the customer
|
||||
email string The customer email address
|
||||
phone string The customer phone number
|
||||
locale string The locale used for communication with this customer
|
||||
sales_channel string Channel this sale was created through, such as
|
||||
``"web"``.
|
||||
@@ -167,6 +168,10 @@ last_modified datetime Last modificati
|
||||
|
||||
The ``subevent_before`` query parameter has been added.
|
||||
|
||||
.. versionchanged:: 3.14
|
||||
|
||||
The ``phone`` attribute has been added.
|
||||
|
||||
|
||||
.. _order-position-resource:
|
||||
|
||||
@@ -372,6 +377,7 @@ List of all orders
|
||||
"secret": "k24fiuwvu8kxz3y1",
|
||||
"url": "https://test.pretix.eu/dummy/dummy/order/ABC12/k24fiuwvu8kxz3y1/",
|
||||
"email": "tester@example.org",
|
||||
"phone": "+491234567",
|
||||
"locale": "en",
|
||||
"sales_channel": "web",
|
||||
"datetime": "2017-12-01T10:00:00Z",
|
||||
@@ -539,6 +545,7 @@ Fetching individual orders
|
||||
"secret": "k24fiuwvu8kxz3y1",
|
||||
"url": "https://test.pretix.eu/dummy/dummy/order/ABC12/k24fiuwvu8kxz3y1/",
|
||||
"email": "tester@example.org",
|
||||
"phone": "+491234567",
|
||||
"locale": "en",
|
||||
"sales_channel": "web",
|
||||
"datetime": "2017-12-01T10:00:00Z",
|
||||
@@ -705,6 +712,8 @@ Updating order fields
|
||||
|
||||
* ``email``
|
||||
|
||||
* ``phone``
|
||||
|
||||
* ``checkin_attention``
|
||||
|
||||
* ``locale``
|
||||
|
||||
@@ -601,6 +601,9 @@ class EventSettingsSerializer(serializers.Serializer):
|
||||
'attendee_data_explanation_text',
|
||||
'confirm_texts',
|
||||
'order_email_asked_twice',
|
||||
'order_phone_asked',
|
||||
'order_phone_required',
|
||||
'checkout_phone_helptext',
|
||||
'payment_term_mode',
|
||||
'payment_term_days',
|
||||
'payment_term_weekdays',
|
||||
|
||||
@@ -361,7 +361,7 @@ class OrderSerializer(I18nAwareModelSerializer):
|
||||
class Meta:
|
||||
model = Order
|
||||
fields = (
|
||||
'code', 'status', 'testmode', 'secret', 'email', 'locale', 'datetime', 'expires', 'payment_date',
|
||||
'code', 'status', 'testmode', 'secret', 'email', 'phone', 'locale', 'datetime', 'expires', 'payment_date',
|
||||
'payment_provider', 'fees', 'total', 'comment', 'invoice_address', 'positions', 'downloads',
|
||||
'checkin_attention', 'last_modified', 'payments', 'refunds', 'require_approval', 'sales_channel',
|
||||
'url'
|
||||
@@ -393,7 +393,7 @@ class OrderSerializer(I18nAwareModelSerializer):
|
||||
def update(self, instance, validated_data):
|
||||
# Even though all fields that shouldn't be edited are marked as read_only in the serializer
|
||||
# (hopefully), we'll be extra careful here and be explicit about the model fields we update.
|
||||
update_fields = ['comment', 'checkin_attention', 'email', 'locale']
|
||||
update_fields = ['comment', 'checkin_attention', 'email', 'locale', 'phone']
|
||||
|
||||
if 'invoice_address' in validated_data:
|
||||
iadata = validated_data.pop('invoice_address')
|
||||
@@ -691,7 +691,7 @@ class OrderCreateSerializer(I18nAwareModelSerializer):
|
||||
|
||||
class Meta:
|
||||
model = Order
|
||||
fields = ('code', 'status', 'testmode', 'email', 'locale', 'payment_provider', 'fees', 'comment', 'sales_channel',
|
||||
fields = ('code', 'status', 'testmode', 'email', 'phone', 'locale', 'payment_provider', 'fees', 'comment', 'sales_channel',
|
||||
'invoice_address', 'positions', 'checkin_attention', 'payment_info', 'payment_date', 'consume_carts',
|
||||
'force', 'send_email', 'simulate')
|
||||
|
||||
|
||||
@@ -674,6 +674,17 @@ class OrderViewSet(viewsets.ModelViewSet):
|
||||
}
|
||||
)
|
||||
|
||||
if 'phone' in self.request.data and serializer.instance.phone != self.request.data.get('phone'):
|
||||
serializer.instance.log_action(
|
||||
'pretix.event.order.phone.changed',
|
||||
user=self.request.user,
|
||||
auth=self.request.auth,
|
||||
data={
|
||||
'old_phone': serializer.instance.phone,
|
||||
'new_phone': self.request.data.get('phone'),
|
||||
}
|
||||
)
|
||||
|
||||
if 'locale' in self.request.data and serializer.instance.locale != self.request.data.get('locale'):
|
||||
serializer.instance.log_action(
|
||||
'pretix.event.order.locale.changed',
|
||||
|
||||
@@ -139,7 +139,7 @@ class OrderListExporter(MultiSheetListExporter):
|
||||
tax_rates = self._get_all_tax_rates(qs)
|
||||
|
||||
headers = [
|
||||
_('Event slug'), _('Order code'), _('Order total'), _('Status'), _('Email'), _('Order date'),
|
||||
_('Event slug'), _('Order code'), _('Order total'), _('Status'), _('Email'), _('Phone number'), _('Order date'),
|
||||
_('Order time'), _('Company'), _('Name'),
|
||||
]
|
||||
name_scheme = PERSON_NAME_SCHEMES[self.event.settings.name_scheme] if not self.is_multievent else None
|
||||
@@ -215,6 +215,7 @@ class OrderListExporter(MultiSheetListExporter):
|
||||
order.total,
|
||||
order.get_status_display(),
|
||||
order.email,
|
||||
str(order.phone) if order.phone else '',
|
||||
order.datetime.astimezone(tz).strftime('%Y-%m-%d'),
|
||||
order.datetime.astimezone(tz).strftime('%H:%M:%S'),
|
||||
]
|
||||
@@ -303,6 +304,7 @@ class OrderListExporter(MultiSheetListExporter):
|
||||
_('Order code'),
|
||||
_('Status'),
|
||||
_('Email'),
|
||||
_('Phone number'),
|
||||
_('Order date'),
|
||||
_('Order time'),
|
||||
_('Fee type'),
|
||||
@@ -334,6 +336,7 @@ class OrderListExporter(MultiSheetListExporter):
|
||||
order.code,
|
||||
order.get_status_display(),
|
||||
order.email,
|
||||
str(order.phone) if order.phone else '',
|
||||
order.datetime.astimezone(tz).strftime('%Y-%m-%d'),
|
||||
order.datetime.astimezone(tz).strftime('%H:%M:%S'),
|
||||
op.get_fee_type_display(),
|
||||
@@ -402,6 +405,7 @@ class OrderListExporter(MultiSheetListExporter):
|
||||
_('Position ID'),
|
||||
_('Status'),
|
||||
_('Email'),
|
||||
_('Phone number'),
|
||||
_('Order date'),
|
||||
_('Order time'),
|
||||
]
|
||||
@@ -481,6 +485,7 @@ class OrderListExporter(MultiSheetListExporter):
|
||||
op.positionid,
|
||||
order.get_status_display(),
|
||||
order.email,
|
||||
str(order.phone) if order.phone else '',
|
||||
order.datetime.astimezone(tz).strftime('%Y-%m-%d'),
|
||||
order.datetime.astimezone(tz).strftime('%H:%M:%S'),
|
||||
]
|
||||
|
||||
@@ -28,7 +28,7 @@ from django_countries.fields import Country, CountryField
|
||||
from phonenumber_field.formfields import PhoneNumberField
|
||||
from phonenumber_field.phonenumber import PhoneNumber
|
||||
from phonenumber_field.widgets import PhoneNumberPrefixWidget
|
||||
from phonenumbers import NumberParseException
|
||||
from phonenumbers import NumberParseException, national_significant_number
|
||||
from phonenumbers.data import _COUNTRY_CODE_TO_REGION_CODE
|
||||
|
||||
from pretix.base.forms.widgets import (
|
||||
@@ -212,6 +212,38 @@ class WrappedPhoneNumberPrefixWidget(PhoneNumberPrefixWidget):
|
||||
def format_output(self, rendered_widgets) -> str:
|
||||
return '<div class="nameparts-form-group">%s</div>' % ''.join(rendered_widgets)
|
||||
|
||||
def decompress(self, value):
|
||||
"""
|
||||
If an incomplete phone number (e.g. without country prefix) is currently entered,
|
||||
the default implementation just discards the value and shows nothing at all.
|
||||
Let's rather show something invalid, so the user is prompted to fix it, instead of
|
||||
silently deleting data.
|
||||
"""
|
||||
if value:
|
||||
if type(value) == PhoneNumber:
|
||||
if value.country_code and value.national_number:
|
||||
return [
|
||||
"+%d" % value.country_code,
|
||||
national_significant_number(value),
|
||||
]
|
||||
return [
|
||||
None,
|
||||
str(value)
|
||||
]
|
||||
elif "." in value:
|
||||
return value.split(".")
|
||||
else:
|
||||
return [None, value]
|
||||
return [None, ""]
|
||||
|
||||
def value_from_datadict(self, data, files, name):
|
||||
# In contrast to defualt implementation, do not silently fail if a number without
|
||||
# country prefix is entered
|
||||
values = super(PhoneNumberPrefixWidget, self).value_from_datadict(data, files, name)
|
||||
if values[1]:
|
||||
return "%s.%s" % tuple(values)
|
||||
return ""
|
||||
|
||||
|
||||
def guess_country(event):
|
||||
# Try to guess the initial country from either the country of the merchant
|
||||
|
||||
51
src/pretix/base/migrations/0173_auto_20201211_1648.py
Normal file
51
src/pretix/base/migrations/0173_auto_20201211_1648.py
Normal file
@@ -0,0 +1,51 @@
|
||||
# Generated by Django 3.0.11 on 2020-12-11 16:48
|
||||
import json
|
||||
|
||||
import phonenumber_field.modelfields
|
||||
from django.db import migrations
|
||||
|
||||
import pretix.base.models.fields
|
||||
|
||||
|
||||
def migrate_settings(apps, schema_editor):
|
||||
Order = apps.get_model('pretixbase', 'Order')
|
||||
Event = apps.get_model('pretixbase', 'Event')
|
||||
Event_SettingsStore = apps.get_model('pretixbase', 'Event_SettingsStore')
|
||||
Event_SettingsStore.objects.filter(key='telephone_field_required').update(key='order_phone_required')
|
||||
Event_SettingsStore.objects.filter(key='telephone_field_help_text').update(key='checkout_phone_helptext')
|
||||
for e in Event.objects.filter(plugins__icontains="pretix_telephone"):
|
||||
plugins = e.plugins.split(",")
|
||||
plugins.remove("pretix_telephone")
|
||||
e.plugins = ",".join(plugins)
|
||||
e.save()
|
||||
Event_SettingsStore.objects.create(object=e, key='order_phone_asked', value='True')
|
||||
for o in Order.objects.filter(meta_info__icontains='"telephone"'):
|
||||
mi = json.loads(o.meta_info)
|
||||
if 'telephone' in mi.get('contact_form_data', {}):
|
||||
mi['phone'] = mi['contact_form_data'].pop('telephone')
|
||||
o.phone = mi['phone']
|
||||
o.meta_info = json.dumps(mi)
|
||||
o.save(update_fields=['meta_info', 'phone'])
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('pretixbase', '0172_event_sales_channels'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='order',
|
||||
name='phone',
|
||||
field=phonenumber_field.modelfields.PhoneNumberField(max_length=128, null=True, region=None),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='event',
|
||||
name='sales_channels',
|
||||
field=pretix.base.models.fields.MultiStringField(default=['web']),
|
||||
),
|
||||
migrations.RunPython(
|
||||
migrate_settings, migrations.RunPython.noop,
|
||||
)
|
||||
]
|
||||
@@ -31,6 +31,7 @@ from django_countries.fields import Country
|
||||
from django_scopes import ScopedManager, scopes_disabled
|
||||
from i18nfield.strings import LazyI18nString
|
||||
from jsonfallback.fields import FallbackJSONField
|
||||
from phonenumber_field.modelfields import PhoneNumberField
|
||||
from phonenumber_field.phonenumber import PhoneNumber
|
||||
from phonenumbers import NumberParseException
|
||||
|
||||
@@ -86,6 +87,8 @@ class Order(LockModel, LoggedModel):
|
||||
:type event: Event
|
||||
:param email: The email of the person who ordered this
|
||||
:type email: str
|
||||
:param phone: The phone number of the person who ordered this
|
||||
:type phone: str
|
||||
:param testmode: Whether this is a test mode order
|
||||
:type testmode: bool
|
||||
:param locale: The locale of this order
|
||||
@@ -144,6 +147,10 @@ class Order(LockModel, LoggedModel):
|
||||
null=True, blank=True,
|
||||
verbose_name=_('E-mail')
|
||||
)
|
||||
phone = PhoneNumberField(
|
||||
null=True, blank=True,
|
||||
verbose_name=_('Phone number'),
|
||||
)
|
||||
locale = models.CharField(
|
||||
null=True, blank=True, max_length=32,
|
||||
verbose_name=_('Locale')
|
||||
|
||||
@@ -39,6 +39,7 @@ from pretix.base.models import Order, OrderPosition
|
||||
from pretix.base.settings import PERSON_NAME_SCHEMES
|
||||
from pretix.base.signals import layout_text_variables
|
||||
from pretix.base.templatetags.money import money_filter
|
||||
from pretix.base.templatetags.phone_format import phone_format
|
||||
from pretix.presale.style import get_fonts
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -229,6 +230,11 @@ DEFAULT_VARIABLES = OrderedDict((
|
||||
"editor_sample": _("Random City"),
|
||||
"evaluate": lambda op, order, ev: str(ev.location)
|
||||
}),
|
||||
("telephone", {
|
||||
"label": _("Phone number"),
|
||||
"editor_sample": "+01 1234 567890",
|
||||
"evaluate": lambda op, order, ev: phone_format(order.phone)
|
||||
}),
|
||||
("invoice_name", {
|
||||
"label": _("Invoice address name"),
|
||||
"editor_sample": _("John Doe"),
|
||||
|
||||
@@ -778,6 +778,7 @@ def _create_order(event: Event, email: str, positions: List[CartPosition], now_d
|
||||
status=Order.STATUS_PENDING,
|
||||
event=event,
|
||||
email=email,
|
||||
phone=(meta_info or {}).get('contact_form_data', {}).get('phone'),
|
||||
datetime=now_dt,
|
||||
locale=get_language_without_region(locale),
|
||||
total=total,
|
||||
|
||||
@@ -193,6 +193,25 @@ DEFAULTS = {
|
||||
help_text=_("Require customers to fill in the primary email address twice to avoid errors."),
|
||||
)
|
||||
},
|
||||
'order_phone_asked': {
|
||||
'default': 'False',
|
||||
'type': bool,
|
||||
'form_class': forms.BooleanField,
|
||||
'serializer_class': serializers.BooleanField,
|
||||
'form_kwargs': dict(
|
||||
label=_("Ask for a phone number per order"),
|
||||
)
|
||||
},
|
||||
'order_phone_required': {
|
||||
'default': 'False',
|
||||
'type': bool,
|
||||
'form_class': forms.BooleanField,
|
||||
'serializer_class': serializers.BooleanField,
|
||||
'form_kwargs': dict(
|
||||
label=_("Require a phone number per order"),
|
||||
widget=forms.CheckboxInput(attrs={'data-checkbox-dependency': '#id_settings-order_phone_asked'}),
|
||||
)
|
||||
},
|
||||
'invoice_address_asked': {
|
||||
'default': 'True',
|
||||
'type': bool,
|
||||
@@ -1859,6 +1878,17 @@ Your {event} team"""))
|
||||
"why you need information from them.")
|
||||
)
|
||||
},
|
||||
'checkout_phone_helptext': {
|
||||
'default': '',
|
||||
'type': LazyI18nString,
|
||||
'serializer_class': I18nField,
|
||||
'form_class': I18nFormField,
|
||||
'form_kwargs': dict(
|
||||
label=_("Help text of the phone number field"),
|
||||
widget_kwargs={'attrs': {'rows': '2'}},
|
||||
widget=I18nTextarea
|
||||
)
|
||||
},
|
||||
'checkout_email_helptext': {
|
||||
'default': LazyI18nString.from_gettext(gettext_noop(
|
||||
'Make sure to enter a valid email address. We will send you an order '
|
||||
|
||||
@@ -20,6 +20,7 @@ from pretix.base.models import (
|
||||
)
|
||||
from pretix.base.services.invoices import invoice_pdf_task
|
||||
from pretix.base.signals import register_data_shredders
|
||||
from pretix.helpers.json import CustomJSONEncoder
|
||||
|
||||
|
||||
class ShredError(LazyLocaleException):
|
||||
@@ -121,6 +122,31 @@ def shred_log_fields(logentry, banlist=None, whitelist=None):
|
||||
logentry.save(update_fields=['data', 'shredded'])
|
||||
|
||||
|
||||
class PhoneNumberShredder(BaseDataShredder):
|
||||
verbose_name = _('Phone numbers')
|
||||
identifier = 'phone_numbers'
|
||||
description = _('This will remove all phone numbers from orders.')
|
||||
|
||||
def generate_files(self) -> List[Tuple[str, str, str]]:
|
||||
yield 'phone-by-order.json', 'application/json', json.dumps({
|
||||
o.code: o.phone for o in self.event.orders.filter(phone__isnull=False)
|
||||
}, cls=CustomJSONEncoder, indent=4)
|
||||
|
||||
@transaction.atomic
|
||||
def shred_data(self):
|
||||
for o in self.event.orders.all():
|
||||
o.phone = None
|
||||
d = o.meta_info_data
|
||||
if d:
|
||||
if 'contact_form_data' in d and 'phone' in d['contact_form_data']:
|
||||
del d['contact_form_data']['phone']
|
||||
o.meta_info = json.dumps(d)
|
||||
o.save(update_fields=['meta_info', 'phone'])
|
||||
|
||||
for le in self.event.logentry_set.filter(action_type="pretix.event.order.phone.changed"):
|
||||
shred_log_fields(le, banlist=['old_phone', 'new_phone'])
|
||||
|
||||
|
||||
class EmailAddressShredder(BaseDataShredder):
|
||||
verbose_name = _('E-mails')
|
||||
identifier = 'order_emails'
|
||||
@@ -372,9 +398,10 @@ class PaymentInfoShredder(BaseDataShredder):
|
||||
|
||||
|
||||
@receiver(register_data_shredders, dispatch_uid="shredders_builtin")
|
||||
def register_payment_provider(sender, **kwargs):
|
||||
def register_core_shredders(sender, **kwargs):
|
||||
return [
|
||||
EmailAddressShredder,
|
||||
PhoneNumberShredder,
|
||||
AttendeeInfoShredder,
|
||||
InvoiceAddressShredder,
|
||||
QuestionAnswerShredder,
|
||||
|
||||
19
src/pretix/base/templatetags/phone_format.py
Normal file
19
src/pretix/base/templatetags/phone_format.py
Normal file
@@ -0,0 +1,19 @@
|
||||
from django import template
|
||||
from phonenumber_field.phonenumber import PhoneNumber
|
||||
from phonenumbers import NumberParseException
|
||||
|
||||
register = template.Library()
|
||||
|
||||
|
||||
@register.filter("phone_format")
|
||||
def phone_format(value: str):
|
||||
if not value:
|
||||
return ""
|
||||
|
||||
if isinstance(value, PhoneNumber):
|
||||
return value.as_international
|
||||
|
||||
try:
|
||||
return PhoneNumber.from_string(value).as_international
|
||||
except NumberParseException:
|
||||
return value
|
||||
@@ -481,6 +481,9 @@ class EventSettingsForm(SettingsForm):
|
||||
'attendee_addresses_asked',
|
||||
'attendee_addresses_required',
|
||||
'attendee_data_explanation_text',
|
||||
'order_phone_asked',
|
||||
'order_phone_required',
|
||||
'checkout_phone_helptext',
|
||||
'banner_text',
|
||||
'banner_text_bottom',
|
||||
'order_email_asked_twice',
|
||||
|
||||
@@ -16,6 +16,7 @@ from i18nfield.strings import LazyI18nString
|
||||
|
||||
from pretix.base.email import get_available_placeholders
|
||||
from pretix.base.forms import I18nModelForm, PlaceholderValidator
|
||||
from pretix.base.forms.questions import WrappedPhoneNumberPrefixWidget
|
||||
from pretix.base.forms.widgets import (
|
||||
DatePickerWidget, SplitDateTimePickerWidget,
|
||||
)
|
||||
@@ -460,7 +461,15 @@ class OrderContactForm(forms.ModelForm):
|
||||
|
||||
class Meta:
|
||||
model = Order
|
||||
fields = ['email', 'email_known_to_work']
|
||||
fields = ['email', 'email_known_to_work', 'phone']
|
||||
widgets = {
|
||||
'phone': WrappedPhoneNumberPrefixWidget()
|
||||
}
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
if not self.instance.event.settings.order_phone_asked and not self.instance.phone:
|
||||
del self.fields['phone']
|
||||
|
||||
|
||||
class OrderLocaleForm(forms.ModelForm):
|
||||
|
||||
@@ -292,6 +292,8 @@ def pretixcontrol_logentry_display(sender: Event, logentry: LogEntry, **kwargs):
|
||||
'pretix.event.order.denied': _('The order has been denied.'),
|
||||
'pretix.event.order.contact.changed': _('The email address has been changed from "{old_email}" '
|
||||
'to "{new_email}".'),
|
||||
'pretix.event.order.phone.changed': _('The phone number has been changed from "{old_phone}" '
|
||||
'to "{new_phone}".'),
|
||||
'pretix.event.order.locale.changed': _('The order locale has been changed.'),
|
||||
'pretix.event.order.invoice.generated': _('The invoice has been generated.'),
|
||||
'pretix.event.order.invoice.regenerated': _('The invoice has been regenerated.'),
|
||||
|
||||
@@ -86,20 +86,113 @@
|
||||
{% bootstrap_field sform.region layout="control" %}
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<legend>{% trans "Attendee data" %}</legend>
|
||||
{% bootstrap_field sform.attendee_names_asked layout="control" %}
|
||||
{% bootstrap_field sform.attendee_names_required layout="control" %}
|
||||
<legend>{% trans "Customer and attendee data" %}</legend>
|
||||
{% trans "Asked" context "attendee_data" as asked %}
|
||||
{% trans "Required" context "attendee_data" as required %}
|
||||
<h4>{% trans "Customer data (once per order)" %}</h4>
|
||||
<div class="form-group">
|
||||
<label class="control-label col-md-3">
|
||||
{% trans "E-mail" %}
|
||||
</label>
|
||||
<div class="col-md-3">
|
||||
<div class="checkbox">
|
||||
<label><input type="checkbox" checked="checked" disabled="disabled"> {{ asked }}</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="checkbox">
|
||||
<label><input type="checkbox" checked="checked" disabled="disabled"> {{ required }}</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% bootstrap_field sform.order_email_asked_twice layout="control" %}
|
||||
<div class="form-group">
|
||||
<label class="control-label col-md-3">
|
||||
{% trans "Phone number" %}
|
||||
</label>
|
||||
<div class="col-md-3 form-field-boundary">
|
||||
{% bootstrap_field sform.order_phone_asked layout="inline" form_group_class="" label=asked %}
|
||||
</div>
|
||||
<div class="col-md-3 form-field-boundary">
|
||||
{% bootstrap_field sform.order_phone_required layout="inline" form_group_class="" label=required %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="control-label col-md-3">
|
||||
{% trans "Name and address" %}
|
||||
</label>
|
||||
<div class="col-md-9 static-form-row">
|
||||
<p>
|
||||
<a href="{% url "control:event.settings.invoice" event=request.event.slug organizer=request.organizer.slug %}#tab-0-1-open" target="_blank">
|
||||
{% trans "See invoice settings" %}
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h4>{% trans "Attendee data (once per admission ticket)" %}</h4>
|
||||
<div class="form-group">
|
||||
<label class="control-label col-md-3">
|
||||
{% trans "Name" %}
|
||||
</label>
|
||||
<div class="col-md-3 form-field-boundary">
|
||||
{% bootstrap_field sform.attendee_names_asked layout="inline" form_group_class="" label=asked %}
|
||||
</div>
|
||||
<div class="col-md-3 form-field-boundary">
|
||||
{% bootstrap_field sform.attendee_names_required layout="inline" form_group_class="" label=required %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="control-label col-md-3">
|
||||
{% trans "E-mail" %}
|
||||
</label>
|
||||
<div class="col-md-3 form-field-boundary">
|
||||
{% bootstrap_field sform.attendee_emails_asked layout="inline" form_group_class="" label=asked %}
|
||||
</div>
|
||||
<div class="col-md-3 form-field-boundary">
|
||||
{% bootstrap_field sform.attendee_emails_required layout="inline" form_group_class="" label=required %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="control-label col-md-3">
|
||||
{% trans "Company" %}
|
||||
</label>
|
||||
<div class="col-md-3 form-field-boundary">
|
||||
{% bootstrap_field sform.attendee_company_asked layout="inline" form_group_class="" label=asked %}
|
||||
</div>
|
||||
<div class="col-md-3 form-field-boundary">
|
||||
{% bootstrap_field sform.attendee_company_required layout="inline" form_group_class="" label=required %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="control-label col-md-3">
|
||||
{% trans "Address" %}
|
||||
</label>
|
||||
<div class="col-md-3 form-field-boundary">
|
||||
{% bootstrap_field sform.attendee_addresses_asked layout="inline" form_group_class="" label=asked %}
|
||||
</div>
|
||||
<div class="col-md-3 form-field-boundary">
|
||||
{% bootstrap_field sform.attendee_addresses_required layout="inline" form_group_class="" label=required %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="control-label col-md-3">
|
||||
{% trans "Custom fields" %}
|
||||
</label>
|
||||
<div class="col-md-9 static-form-row">
|
||||
<p>
|
||||
<a href="{% url "control:event.items.questions" event=request.event.slug organizer=request.organizer.slug %}" target="_blank">
|
||||
{% trans "Manage questions" %}
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
{% bootstrap_field sform.attendee_data_explanation_text layout="control" %}
|
||||
|
||||
<h4>{% trans "Other settings" %}</h4>
|
||||
{% 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" %}
|
||||
{% 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" %}
|
||||
{% bootstrap_field sform.checkout_show_copy_answers_button layout="control" %}
|
||||
{% bootstrap_field sform.attendee_data_explanation_text layout="control" %}
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<legend>{% trans "Texts" %}</legend>
|
||||
@@ -178,6 +271,7 @@
|
||||
</div>
|
||||
|
||||
{% bootstrap_field sform.checkout_email_helptext layout="control" %}
|
||||
{% bootstrap_field sform.checkout_phone_helptext layout="control" %}
|
||||
{% bootstrap_field sform.banner_text layout="control" %}
|
||||
{% bootstrap_field sform.banner_text_bottom layout="control" %}
|
||||
</fieldset>
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
{% load rich_text %}
|
||||
{% load safelink %}
|
||||
{% load eventsignal %}
|
||||
{% load phone_format %}
|
||||
{% block title %}
|
||||
{% blocktrans trimmed with code=order.code %}
|
||||
Order details: {{ code }}
|
||||
@@ -201,6 +202,15 @@
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</dd>
|
||||
{% if order.phone or request.event.settings.order_phone_asked %}
|
||||
<dt>{% trans "Phone number" %}</dt>
|
||||
<dd>
|
||||
{{ order.phone|default_if_none:""|phone_format }}
|
||||
<a href="{% url "control:event.order.contact" event=request.event.slug organizer=request.event.organizer.slug code=order.code %}" class="btn btn-default btn-xs">
|
||||
<span class="fa fa-edit"></span>
|
||||
</a>
|
||||
</dd>
|
||||
{% endif %}
|
||||
{% if invoices %}
|
||||
<dt>{% trans "Invoices" %}</dt>
|
||||
<dd>
|
||||
|
||||
@@ -1663,6 +1663,7 @@ class OrderContactChange(OrderView):
|
||||
|
||||
def post(self, *args, **kwargs):
|
||||
old_email = self.order.email
|
||||
old_phone = self.order.phone
|
||||
changed = False
|
||||
if self.form.is_valid():
|
||||
new_email = self.form.cleaned_data['email']
|
||||
@@ -1677,6 +1678,18 @@ class OrderContactChange(OrderView):
|
||||
user=self.request.user,
|
||||
)
|
||||
|
||||
new_phone = self.form.cleaned_data.get('phone')
|
||||
if new_phone != old_phone:
|
||||
changed = True
|
||||
self.order.log_action(
|
||||
'pretix.event.order.phone.changed',
|
||||
data={
|
||||
'old_phone': old_phone,
|
||||
'new_phone': self.form.cleaned_data['phone'],
|
||||
},
|
||||
user=self.request.user,
|
||||
)
|
||||
|
||||
if self.form.cleaned_data['regenerate_secrets']:
|
||||
changed = True
|
||||
self.order.secret = generate_secret()
|
||||
|
||||
@@ -442,7 +442,8 @@ class QuestionsStep(QuestionsViewMixin, CartMixin, TemplateFlowStep):
|
||||
'email': (
|
||||
self.cart_session.get('email', '') or
|
||||
wd.get('email', '')
|
||||
)
|
||||
),
|
||||
'phone': wd.get('phone', None)
|
||||
}
|
||||
initial.update(self.cart_session.get('contact_form_data', {}))
|
||||
|
||||
@@ -549,7 +550,10 @@ class QuestionsStep(QuestionsViewMixin, CartMixin, TemplateFlowStep):
|
||||
_("We had difficulties processing your input. Please review the errors below."))
|
||||
return self.render()
|
||||
self.cart_session['email'] = self.contact_form.cleaned_data['email']
|
||||
self.cart_session['contact_form_data'] = self.contact_form.cleaned_data
|
||||
d = dict(self.contact_form.cleaned_data)
|
||||
if d.get('phone'):
|
||||
d['phone'] = str(d['phone'])
|
||||
self.cart_session['contact_form_data'] = d
|
||||
if self.address_asked or self.request.event.settings.invoice_name_required:
|
||||
addr = self.invoice_form.save()
|
||||
try:
|
||||
@@ -820,6 +824,9 @@ class ConfirmStep(CartMixin, AsyncAction, TemplateFlowStep):
|
||||
]
|
||||
else:
|
||||
ctx['contact_info'] = []
|
||||
phone = self.cart_session.get('contact_form_data', {}).get('phone')
|
||||
if phone:
|
||||
ctx['contact_info'].append((_('Phone number'), phone))
|
||||
responses = contact_form_fields.send(self.event, request=self.request)
|
||||
for r, response in sorted(responses, key=lambda r: str(r[0])):
|
||||
for key, value in response.items():
|
||||
|
||||
@@ -1,13 +1,20 @@
|
||||
from itertools import chain
|
||||
|
||||
from babel import localedata
|
||||
from django import forms
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.utils.encoding import force_str
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django.utils.translation import get_language, gettext_lazy as _
|
||||
from phonenumber_field.formfields import PhoneNumberField
|
||||
from phonenumber_field.phonenumber import PhoneNumber
|
||||
from phonenumbers import NumberParseException
|
||||
from phonenumbers.data import _COUNTRY_CODE_TO_REGION_CODE
|
||||
|
||||
from pretix.base.forms.questions import (
|
||||
BaseInvoiceAddressForm, BaseQuestionsForm,
|
||||
BaseInvoiceAddressForm, BaseQuestionsForm, WrappedPhoneNumberPrefixWidget,
|
||||
guess_country,
|
||||
)
|
||||
from pretix.base.i18n import language
|
||||
from pretix.base.validators import EmailBanlistValidator
|
||||
from pretix.presale.signals import contact_form_fields
|
||||
|
||||
@@ -31,6 +38,35 @@ class ContactForm(forms.Form):
|
||||
help_text=_('Please enter the same email address again to make sure you typed it correctly.'),
|
||||
)
|
||||
|
||||
if self.event.settings.order_phone_asked:
|
||||
babel_locale = 'en'
|
||||
# Babel, and therefore django-phonenumberfield, do not support our custom locales such as de_Informal
|
||||
if localedata.exists(get_language()):
|
||||
babel_locale = get_language()
|
||||
elif localedata.exists(get_language()[:2]):
|
||||
babel_locale = get_language()[:2]
|
||||
with language(babel_locale):
|
||||
default_country = guess_country(self.event)
|
||||
default_prefix = None
|
||||
for prefix, values in _COUNTRY_CODE_TO_REGION_CODE.items():
|
||||
if str(default_country) in values:
|
||||
default_prefix = prefix
|
||||
try:
|
||||
initial = self.initial.pop('phone', None)
|
||||
initial = PhoneNumber().from_string(initial) if initial else "+{}.".format(default_prefix)
|
||||
except NumberParseException:
|
||||
initial = None
|
||||
self.fields['phone'] = PhoneNumberField(
|
||||
label=_('Phone number'),
|
||||
required=self.event.settings.order_phone_required,
|
||||
help_text=self.event.settings.checkout_phone_helptext,
|
||||
# We now exploit an implementation detail in PhoneNumberPrefixWidget to allow us to pass just
|
||||
# a country code but no number as an initial value. It's a bit hacky, but should be stable for
|
||||
# the future.
|
||||
initial=initial,
|
||||
widget=WrappedPhoneNumberPrefixWidget()
|
||||
)
|
||||
|
||||
if not self.request.session.get('iframe_session', False):
|
||||
# There is a browser quirk in Chrome that leads to incorrect initial scrolling in iframes if there
|
||||
# is an autofocus field. Who would have thought… See e.g. here:
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
{% load money %}
|
||||
{% load expiresformat %}
|
||||
{% load eventurl %}
|
||||
{% load phone_format %}
|
||||
{% block title %}{% trans "Order details" %}{% endblock %}
|
||||
{% block content %}
|
||||
{% if "thanks" in request.GET or "paid" in request.GET %}
|
||||
@@ -205,7 +206,7 @@
|
||||
{% eventsignal event "pretix.presale.signals.order_info" order=order request=request %}
|
||||
<div class="row">
|
||||
{% if invoices %}
|
||||
<div class="col-xs-12 {% if invoice_address_asked or request.event.settings.invoice_name_required %}col-md-6{% endif %}">
|
||||
<div class="col-xs-12 col-md-6">
|
||||
<div class="panel panel-primary">
|
||||
<div class="panel-heading">
|
||||
<h3 class="panel-title">
|
||||
@@ -226,7 +227,7 @@
|
||||
</div>
|
||||
</div>
|
||||
{% elif can_generate_invoice %}
|
||||
<div class="col-xs-12 {% if invoice_address_asked or request.event.settings.invoice_name_required %}col-md-6{% endif %}">
|
||||
<div class="col-xs-12 col-md-6">
|
||||
<div class="panel panel-primary">
|
||||
<div class="panel-heading">
|
||||
<h3 class="panel-title">
|
||||
@@ -256,10 +257,10 @@
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if invoice_address_asked or request.event.settings.invoice_name_required %}
|
||||
<div class="col-xs-12 {% if invoices or can_generate_invoice %}col-md-6{% endif %}">
|
||||
<div class="panel panel-primary">
|
||||
<div class="panel-heading">
|
||||
<div class="col-xs-12 {% if invoices or can_generate_invoice %}col-md-6{% endif %}">
|
||||
<div class="panel panel-primary">
|
||||
<div class="panel-heading">
|
||||
{% if invoice_address_asked or request.event.settings.invoice_name_required %}
|
||||
{% if order.can_modify_answers %}
|
||||
<div class="pull-right flip">
|
||||
<a href="{% eventurl event "presale:event.order.modify" secret=order.secret order=order.code %}">
|
||||
@@ -268,49 +269,53 @@
|
||||
</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
<h3 class="panel-title">
|
||||
{% if request.event.settings.invoice_address_asked %}
|
||||
{% trans "Invoice information" %}
|
||||
{% else %}
|
||||
{% trans "Contact information" %}
|
||||
{% endif %}
|
||||
<h3 class="panel-title">
|
||||
{% trans "Your information" %}
|
||||
</h3>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<dl class="dl-horizontal">
|
||||
{% if order.email %}
|
||||
<dt>{% trans "E-mail" %}</dt>
|
||||
<dd>{{ order.email }}</dd>
|
||||
{% endif %}
|
||||
{% if order.phone %}
|
||||
<dt>{% trans "Phone number" %}</dt>
|
||||
<dd>{{ order.phone|phone_format }}</dd>
|
||||
{% endif %}
|
||||
{% if invoice_address_asked %}
|
||||
<dt>{% trans "Company" %}</dt>
|
||||
<dd>{{ order.invoice_address.company }}</dd>
|
||||
{% endif %}
|
||||
<dt>{% trans "Name" %}</dt>
|
||||
<dd>{{ order.invoice_address.name }}</dd>
|
||||
{% if invoice_address_asked %}
|
||||
<dt>{% trans "Address" %}</dt>
|
||||
<dd>{{ order.invoice_address.street|linebreaksbr }}</dd>
|
||||
<dt>{% trans "ZIP code and city" %}</dt>
|
||||
<dd>{{ order.invoice_address.zipcode }} {{ order.invoice_address.city }}</dd>
|
||||
<dt>{% trans "Country" %}</dt>
|
||||
<dd>{{ order.invoice_address.country.name|default:order.invoice_address.country_old }}</dd>
|
||||
{% if order.invoice_address.state %}
|
||||
<dt>{% trans "State" context "address" %}</dt>
|
||||
<dd>{{ order.invoice_address.state_name }}</dd>
|
||||
{% endif %}
|
||||
</h3>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<dl class="dl-horizontal">
|
||||
{% if invoice_address_asked %}
|
||||
<dt>{% trans "Company" %}</dt>
|
||||
<dd>{{ order.invoice_address.company }}</dd>
|
||||
{% if request.event.settings.invoice_address_vatid %}
|
||||
<dt>{% trans "VAT ID" %}</dt>
|
||||
<dd>{{ order.invoice_address.vat_id }}</dd>
|
||||
{% endif %}
|
||||
<dt>{% trans "Name" %}</dt>
|
||||
<dd>{{ order.invoice_address.name }}</dd>
|
||||
{% if invoice_address_asked %}
|
||||
<dt>{% trans "Address" %}</dt>
|
||||
<dd>{{ order.invoice_address.street|linebreaksbr }}</dd>
|
||||
<dt>{% trans "ZIP code and city" %}</dt>
|
||||
<dd>{{ order.invoice_address.zipcode }} {{ order.invoice_address.city }}</dd>
|
||||
<dt>{% trans "Country" %}</dt>
|
||||
<dd>{{ order.invoice_address.country.name|default:order.invoice_address.country_old }}</dd>
|
||||
{% if order.invoice_address.state %}
|
||||
<dt>{% trans "State" context "address" %}</dt>
|
||||
<dd>{{ order.invoice_address.state_name }}</dd>
|
||||
{% endif %}
|
||||
{% if request.event.settings.invoice_address_vatid %}
|
||||
<dt>{% trans "VAT ID" %}</dt>
|
||||
<dd>{{ order.invoice_address.vat_id }}</dd>
|
||||
{% endif %}
|
||||
{% if request.event.settings.invoice_address_custom_field and order.invoice_address.custom_field %}
|
||||
<dt>{{ request.event.settings.invoice_address_custom_field }}</dt>
|
||||
<dd>{{ order.invoice_address.custom_field }}</dd>
|
||||
{% endif %}
|
||||
<dt>{% trans "Internal Reference" %}</dt>
|
||||
<dd>{{ order.invoice_address.internal_reference }}</dd>
|
||||
{% if request.event.settings.invoice_address_custom_field and order.invoice_address.custom_field %}
|
||||
<dt>{{ request.event.settings.invoice_address_custom_field }}</dt>
|
||||
<dd>{{ order.invoice_address.custom_field }}</dd>
|
||||
{% endif %}
|
||||
</dl>
|
||||
</div>
|
||||
<dt>{% trans "Internal Reference" %}</dt>
|
||||
<dd>{{ order.invoice_address.internal_reference }}</dd>
|
||||
{% endif %}
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
{% if user_change_allowed or user_cancel_allowed %}
|
||||
|
||||
@@ -258,7 +258,7 @@ var form_handlers = function (el) {
|
||||
dependency = $($(this).attr("data-checkbox-dependency")),
|
||||
update = function () {
|
||||
var enabled = dependency.prop('checked');
|
||||
dependent.prop('disabled', !enabled).parents('.form-group').toggleClass('disabled', !enabled);
|
||||
dependent.prop('disabled', !enabled).closest('.form-group, .form-field-boundary').toggleClass('disabled', !enabled);
|
||||
if (!enabled && !$(this).is('[data-checkbox-dependency-visual]')) {
|
||||
dependent.prop('checked', false);
|
||||
}
|
||||
@@ -278,7 +278,7 @@ var form_handlers = function (el) {
|
||||
var dependent = $(this),
|
||||
update = function () {
|
||||
var enabled = !dependency.prop('checked');
|
||||
dependent.prop('disabled', !enabled).parents('.form-group').toggleClass('disabled', !enabled);
|
||||
dependent.prop('disabled', !enabled).closest('.form-group, .form-field-boundary').toggleClass('disabled', !enabled);
|
||||
};
|
||||
update();
|
||||
dependency.on("change", update);
|
||||
|
||||
@@ -80,6 +80,19 @@ div[data-formset-body], div[data-formset-form], div[data-nested-formset-form], d
|
||||
|
||||
.tabbed-form > .tab-pane {
|
||||
padding: 10px;
|
||||
|
||||
h4 {
|
||||
// like bs3 legend
|
||||
display: block;
|
||||
width: 100%;
|
||||
padding: 0;
|
||||
margin-bottom: $line-height-computed;
|
||||
font-size: ($font-size-base * 1.5);
|
||||
line-height: inherit;
|
||||
color: $legend-color;
|
||||
border: 0;
|
||||
border-bottom: 1px solid $legend-border-color;
|
||||
}
|
||||
}
|
||||
|
||||
.nav-tabs {
|
||||
|
||||
@@ -211,6 +211,7 @@ TEST_ORDER_RES = {
|
||||
"testmode": False,
|
||||
"secret": "k24fiuwvu8kxz3y1",
|
||||
"email": "dummy@dummy.test",
|
||||
"phone": None,
|
||||
"locale": "en",
|
||||
"datetime": "2017-12-01T10:00:00Z",
|
||||
"expires": "2017-12-10T10:00:00Z",
|
||||
@@ -1528,6 +1529,7 @@ def test_order_invalid_state_deny(token_client, organizer, event, order):
|
||||
|
||||
ORDER_CREATE_PAYLOAD = {
|
||||
"email": "dummy@dummy.test",
|
||||
"phone": "+49622112345",
|
||||
"locale": "en",
|
||||
"sales_channel": "web",
|
||||
"fees": [
|
||||
@@ -1589,6 +1591,7 @@ def test_order_create(token_client, organizer, event, item, quota, question):
|
||||
with scopes_disabled():
|
||||
o = Order.objects.get(code=resp.data['code'])
|
||||
assert o.email == "dummy@dummy.test"
|
||||
assert o.phone == "+49622112345"
|
||||
assert o.locale == "en"
|
||||
assert o.total == Decimal('23.25')
|
||||
assert o.status == Order.STATUS_PENDING
|
||||
@@ -1657,6 +1660,7 @@ def test_order_create_simulate(token_client, organizer, event, item, quota, ques
|
||||
'status': 'n',
|
||||
'testmode': False,
|
||||
'email': 'dummy@dummy.test',
|
||||
'phone': '+49622112345',
|
||||
'locale': 'en',
|
||||
'datetime': None,
|
||||
'payment_date': None,
|
||||
@@ -4107,6 +4111,7 @@ def test_order_update_allowed_fields(token_client, organizer, event, order):
|
||||
'comment': 'Here is a comment',
|
||||
'checkin_attention': True,
|
||||
'email': 'foo@bar.com',
|
||||
'phone': '+4962219999',
|
||||
'locale': 'de',
|
||||
'invoice_address': {
|
||||
"is_business": False,
|
||||
@@ -4128,6 +4133,7 @@ def test_order_update_allowed_fields(token_client, organizer, event, order):
|
||||
assert order.comment == 'Here is a comment'
|
||||
assert order.checkin_attention
|
||||
assert order.email == 'foo@bar.com'
|
||||
assert order.phone == '+4962219999'
|
||||
assert order.locale == 'de'
|
||||
assert order.invoice_address.company == "This is my company name"
|
||||
assert order.invoice_address.name_cached == "John Doe"
|
||||
@@ -4139,6 +4145,7 @@ def test_order_update_allowed_fields(token_client, organizer, event, order):
|
||||
assert order.all_logentries().get(action_type='pretix.event.order.comment')
|
||||
assert order.all_logentries().get(action_type='pretix.event.order.checkin_attention')
|
||||
assert order.all_logentries().get(action_type='pretix.event.order.contact.changed')
|
||||
assert order.all_logentries().get(action_type='pretix.event.order.phone.changed')
|
||||
assert order.all_logentries().get(action_type='pretix.event.order.locale.changed')
|
||||
assert order.all_logentries().get(action_type='pretix.event.order.modified')
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ def extract_form_fields(soup):
|
||||
continue
|
||||
|
||||
if field['type'] in ('checkbox', 'radio'):
|
||||
if field.has_attr('checked'):
|
||||
if field.has_attr('checked') and field.has_attr('name'):
|
||||
data[field['name']] = field.get('value', 'on')
|
||||
continue
|
||||
elif field.has_attr('name'):
|
||||
|
||||
@@ -568,6 +568,44 @@ class CheckoutTestCase(BaseCheckoutTestCase, TestCase):
|
||||
assert not cr1.answers.exists()
|
||||
assert not os.path.exists(os.path.join(settings.MEDIA_ROOT, a.file.name))
|
||||
|
||||
def test_phone_required(self):
|
||||
self.event.settings.set('order_phone_asked', True)
|
||||
self.event.settings.set('order_phone_required', True)
|
||||
with scopes_disabled():
|
||||
CartPosition.objects.create(
|
||||
event=self.event, cart_id=self.session_key, item=self.ticket,
|
||||
price=23, expires=now() + timedelta(minutes=10)
|
||||
)
|
||||
response = self.client.get('/%s/%s/checkout/questions/' % (self.orga.slug, self.event.slug), follow=True)
|
||||
doc = BeautifulSoup(response.rendered_content, "lxml")
|
||||
self.assertEqual(len(doc.select('input[name="phone_1"]')), 1)
|
||||
|
||||
# Not all required fields filled out, expect failure
|
||||
response = self.client.post('/%s/%s/checkout/questions/' % (self.orga.slug, self.event.slug), {
|
||||
'email': 'admin@localhost',
|
||||
}, follow=True)
|
||||
doc = BeautifulSoup(response.rendered_content, "lxml")
|
||||
self.assertGreaterEqual(len(doc.select('.has-error')), 1)
|
||||
|
||||
# Corrected request
|
||||
response = self.client.post('/%s/%s/checkout/questions/' % (self.orga.slug, self.event.slug), {
|
||||
'email': 'admin@localhost',
|
||||
'phone_0': '+49',
|
||||
'phone_1': '0622199999', # yeah the 0 is wrong but users don't know that so it should work fine
|
||||
}, follow=True)
|
||||
self.assertRedirects(response, '/%s/%s/checkout/payment/' % (self.orga.slug, self.event.slug),
|
||||
target_status_code=200)
|
||||
|
||||
self.client.post('/%s/%s/checkout/payment/' % (self.orga.slug, self.event.slug), {
|
||||
'payment': 'banktransfer',
|
||||
}, follow=True)
|
||||
response = self.client.post('/%s/%s/checkout/confirm/' % (self.orga.slug, self.event.slug), follow=True)
|
||||
doc = BeautifulSoup(response.rendered_content, "lxml")
|
||||
self.assertEqual(len(doc.select(".thank-you")), 1)
|
||||
with scopes_disabled():
|
||||
o = Order.objects.last()
|
||||
assert o.phone == '+49622199999'
|
||||
|
||||
def test_attendee_email_required(self):
|
||||
self.event.settings.set('attendee_emails_asked', True)
|
||||
self.event.settings.set('attendee_emails_required', True)
|
||||
|
||||
Reference in New Issue
Block a user