Add phone number to customer profile (Z#178346) (#2414)

This commit is contained in:
Richard Schreiber
2022-01-18 11:38:32 +01:00
committed by GitHub
parent cbdafac999
commit 768bb8c106
10 changed files with 150 additions and 73 deletions

View File

@@ -333,23 +333,41 @@ class WrappedPhoneNumberPrefixWidget(PhoneNumberPrefixWidget):
def guess_country(event):
# Try to guess the initial country from either the country of the merchant
# or the locale. This will hopefully save at least some users some scrolling :)
locale = get_language_without_region()
country = event.settings.region or event.settings.invoice_address_from_country
if not country:
valid_countries = countries.countries
if '-' in locale:
parts = locale.split('-')
# TODO: does this actually work?
if parts[1].upper() in valid_countries:
country = Country(parts[1].upper())
elif parts[0].upper() in valid_countries:
country = Country(parts[0].upper())
else:
if locale.upper() in valid_countries:
country = Country(locale.upper())
country = get_country_by_locale(get_language_without_region())
return country
def get_country_by_locale(locale):
country = None
valid_countries = countries.countries
if '-' in locale:
parts = locale.split('-')
# TODO: does this actually work?
if parts[1].upper() in valid_countries:
country = Country(parts[1].upper())
elif parts[0].upper() in valid_countries:
country = Country(parts[0].upper())
else:
if locale.upper() in valid_countries:
country = Country(locale.upper())
return country
def guess_phone_prefix(event):
with language(get_babel_locale()):
country = str(guess_country(event))
return get_phone_prefix(country)
def get_phone_prefix(country):
for prefix, values in _COUNTRY_CODE_TO_REGION_CODE.items():
if country in values:
return prefix
return None
class QuestionCheckboxSelectMultiple(forms.CheckboxSelectMultiple):
option_template_name = 'pretixbase/forms/widgets/checkbox_option_with_links.html'
@@ -780,25 +798,26 @@ class BaseQuestionsForm(forms.Form):
if q.valid_datetime_max:
field.validators.append(MaxDateTimeValidator(q.valid_datetime_max))
elif q.type == Question.TYPE_PHONENUMBER:
with language(get_babel_locale()):
default_country = guess_country(event)
default_prefix = None
for prefix, values in _COUNTRY_CODE_TO_REGION_CODE.items():
if str(default_country) in values:
default_prefix = prefix
if initial:
try:
initial = PhoneNumber().from_string(initial.answer) if initial else "+{}.".format(default_prefix)
initial = PhoneNumber().from_string(initial.answer)
except NumberParseException:
initial = None
field = PhoneNumberField(
label=label, required=required,
help_text=help_text,
# 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 initial:
phone_prefix = guess_phone_prefix(event)
if phone_prefix:
initial = "+{}.".format(phone_prefix)
field = PhoneNumberField(
label=label, required=required,
help_text=help_text,
# 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()
)
field.question = q
if answers:
# Cache the answer object for later use

View File

@@ -0,0 +1,19 @@
# Generated by Django 3.2.9 on 2022-01-12 10:59
import phonenumber_field.modelfields
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('pretixbase', '0205_itemvariation_require_approval'),
]
operations = [
migrations.AddField(
model_name='customer',
name='phone',
field=phonenumber_field.modelfields.PhoneNumberField(max_length=128, null=True, region=None),
),
]

View File

@@ -29,6 +29,7 @@ from django.db.models import F, Q
from django.utils.crypto import get_random_string, salted_hmac
from django.utils.translation import gettext_lazy as _, pgettext_lazy
from django_scopes import ScopedManager, scopes_disabled
from phonenumber_field.modelfields import PhoneNumberField
from pretix.base.banlist import banned
from pretix.base.models.base import LoggedModel
@@ -45,6 +46,7 @@ class Customer(LoggedModel):
organizer = models.ForeignKey(Organizer, related_name='customers', on_delete=models.CASCADE)
identifier = models.CharField(max_length=190, db_index=True, unique=True)
email = models.EmailField(db_index=True, null=True, blank=False, verbose_name=_('E-mail'), max_length=190)
phone = PhoneNumberField(null=True, blank=True, verbose_name=_('Phone number'))
password = models.CharField(verbose_name=_('Password'), max_length=128)
name_cached = models.CharField(max_length=255, verbose_name=_('Full name'), blank=True)
name_parts = models.JSONField(default=dict)
@@ -87,6 +89,7 @@ class Customer(LoggedModel):
self.name_parts = {}
self.name_cached = ''
self.email = None
self.phone = None
self.save()
self.all_logentries().update(data={}, shredded=True)
self.orders.all().update(customer=None)

View File

@@ -44,12 +44,16 @@ 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 phonenumber_field.formfields import PhoneNumberField
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, PlaceholderValidator, SettingsForm
from pretix.base.forms.questions import NamePartsFormField
from pretix.base.forms.questions import (
NamePartsFormField, WrappedPhoneNumberPrefixWidget, get_country_by_locale,
get_phone_prefix,
)
from pretix.base.forms.widgets import SplitDateTimePickerWidget
from pretix.base.models import (
Customer, Device, EventMetaProperty, Gate, GiftCard, Membership,
@@ -535,11 +539,21 @@ class CustomerUpdateForm(forms.ModelForm):
class Meta:
model = Customer
fields = ['is_active', 'name_parts', 'email', 'is_verified', 'locale']
fields = ['is_active', 'name_parts', 'email', 'is_verified', 'phone', 'locale']
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if not self.instance.phone and (self.instance.organizer.settings.region or self.instance.locale):
country_code = self.instance.organizer.settings.region or get_country_by_locale(self.instance.locale)
phone_prefix = get_phone_prefix(country_code)
if phone_prefix:
self.initial['phone'] = "+{}.".format(phone_prefix)
self.fields['phone'] = PhoneNumberField(
label=_('Phone'),
required=False,
widget=WrappedPhoneNumberPrefixWidget()
)
self.fields['name_parts'] = NamePartsFormField(
max_length=255,
required=False,

View File

@@ -48,6 +48,10 @@
</dd>
<dt>{% trans "Name" %}</dt>
<dd>{{ customer.name }}</dd>
{% if customer.phone %}
<dt>{% trans "Phone" %}</dt>
<dd>{{ customer.phone }}</dd>
{% endif %}
<dt>{% trans "Locale" %}</dt>
<dd>{{ display_locale }}</dd>
<dt>{% trans "Registration date" %}</dt>

View File

@@ -697,7 +697,7 @@ class QuestionsStep(QuestionsViewMixin, CartMixin, TemplateFlowStep):
self.cart_session.get('email', '') or
wd.get('email', '')
),
'phone': wd.get('phone', None)
'phone': self.cart_session.get('phone', '') or wd.get('phone', None)
}
initial.update(self.cart_session.get('contact_form_data', {}))
@@ -709,6 +709,8 @@ class QuestionsStep(QuestionsViewMixin, CartMixin, TemplateFlowStep):
if self.cart_customer:
initial['email'] = self.cart_customer.email
initial['email_repeat'] = self.cart_customer.email
if not initial['phone']:
initial['phone'] = str(self.cart_customer.phone)
f = ContactForm(data=self.request.POST if self.request.method == "POST" else None,
event=self.request.event,

View File

@@ -42,15 +42,11 @@ from django.utils.html import escape
from django.utils.safestring import mark_safe
from django.utils.translation import 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, WrappedPhoneNumberPrefixWidget,
guess_country,
guess_phone_prefix,
)
from pretix.base.i18n import get_babel_locale, language
from pretix.base.validators import EmailBanlistValidator
from pretix.presale.signals import contact_form_fields
@@ -75,27 +71,20 @@ class ContactForm(forms.Form):
)
if self.event.settings.order_phone_asked:
with language(get_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,
if not self.initial.get('phone'):
phone_prefix = guess_phone_prefix(self.event)
if phone_prefix:
# 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()
)
self.initial['phone'] = "+{}.".format(phone_prefix)
self.fields['phone'] = PhoneNumberField(
label=_('Phone number'),
required=self.event.settings.order_phone_required,
help_text=self.event.settings.checkout_phone_helptext,
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

View File

@@ -31,8 +31,12 @@ from django.contrib.auth.password_validation import (
from django.contrib.auth.tokens import PasswordResetTokenGenerator
from django.utils.functional import cached_property
from django.utils.translation import gettext_lazy as _
from phonenumber_field.formfields import PhoneNumberField
from pretix.base.forms.questions import NamePartsFormField
from pretix.base.forms.questions import (
NamePartsFormField, WrappedPhoneNumberPrefixWidget, get_country_by_locale,
get_phone_prefix,
)
from pretix.base.i18n import get_language_without_region
from pretix.base.models import Customer
from pretix.base.services.mail import mail
@@ -137,6 +141,19 @@ class RegistrationForm(forms.Form):
self.request = request
super().__init__(*args, **kwargs)
event = getattr(request, "event", None)
if event and event.settings.order_phone_asked:
if event.settings.region or event.organizer.settings.region:
country_code = event.settings.region or event.organizer.settings.region
phone_prefix = get_phone_prefix(country_code)
if phone_prefix:
self.initial['phone'] = "+{}.".format(phone_prefix)
self.fields['phone'] = PhoneNumberField(
label=_('Phone'),
required=event.settings.order_phone_required,
widget=WrappedPhoneNumberPrefixWidget()
)
self.fields['name_parts'] = NamePartsFormField(
max_length=255,
required=True,
@@ -394,7 +411,7 @@ class ChangeInfoForm(forms.ModelForm):
class Meta:
model = Customer
fields = ('name_parts', 'email')
fields = ('name_parts', 'email', 'phone')
def __init__(self, request=None, *args, **kwargs):
self.request = request
@@ -408,6 +425,18 @@ class ChangeInfoForm(forms.ModelForm):
label=_('Name'),
)
if not self.initial.get('phone') and (request.organizer.settings.region or self.instance.locale):
country_code = self.instance.organizer.settings.region or get_country_by_locale(self.instance.locale)
phone_prefix = get_phone_prefix(country_code)
if phone_prefix:
self.initial['phone'] = "+{}.".format(phone_prefix)
self.fields['phone'] = PhoneNumberField(
label=_('Phone'),
required=False,
widget=WrappedPhoneNumberPrefixWidget()
)
def clean_password_current(self):
old_pw = self.cleaned_data.get('password_current')

View File

@@ -23,12 +23,10 @@ from django import forms
from django.core.exceptions import ObjectDoesNotExist, ValidationError
from django.utils.translation import gettext_lazy as _
from phonenumber_field.formfields import PhoneNumberField
from phonenumbers.data import _COUNTRY_CODE_TO_REGION_CODE
from pretix.base.forms.questions import (
NamePartsFormField, WrappedPhoneNumberPrefixWidget, guess_country,
NamePartsFormField, WrappedPhoneNumberPrefixWidget, guess_phone_prefix,
)
from pretix.base.i18n import get_babel_locale, language
from pretix.base.models import Quota, WaitingListEntry
from pretix.presale.views.event import get_grouped_items
@@ -93,21 +91,17 @@ class WaitingListForm(forms.ModelForm):
del self.fields['name_parts']
if event.settings.waiting_list_phones_asked:
with language(get_babel_locale()):
default_country = guess_country(self.event)
for prefix, values in _COUNTRY_CODE_TO_REGION_CODE.items():
if str(default_country) in values and not self.initial.get('phone'):
# 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.
self.initial['phone'] = "+{}.".format(prefix)
if not self.initial.get('phone'):
phone_prefix = guess_phone_prefix(event)
if phone_prefix:
self.initial['phone'] = "+{}.".format(phone_prefix)
self.fields['phone'] = PhoneNumberField(
label=_("Phone number"),
required=event.settings.waiting_list_phones_required,
help_text=event.settings.waiting_list_phones_explanation_text,
widget=WrappedPhoneNumberPrefixWidget()
)
self.fields['phone'] = PhoneNumberField(
label=_("Phone number"),
required=event.settings.waiting_list_phones_required,
help_text=event.settings.waiting_list_phones_explanation_text,
widget=WrappedPhoneNumberPrefixWidget()
)
else:
del self.fields['phone']

View File

@@ -24,6 +24,10 @@
</dd>
<dt>{% trans "Name" %}</dt>
<dd>{{ customer.name }}</dd>
{% if customer.phone %}
<dt>{% trans "Phone" %}</dt>
<dd>{{ customer.phone }}</dd>
{% endif %}
</dl>
<div class="text-right">
<a href="{% eventurl request.organizer "presale:organizer.customer.change" %}"