forked from CGM_Public/pretix_original
Add order-level telephone field to core (#1872)
Co-authored-by: Martin Gross <gross@rami.io>
This commit is contained in:
@@ -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
|
||||
Reference in New Issue
Block a user