diff --git a/src/pretix/base/forms/questions.py b/src/pretix/base/forms/questions.py index 2c6afdca2c..04501aa428 100644 --- a/src/pretix/base/forms/questions.py +++ b/src/pretix/base/forms/questions.py @@ -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 diff --git a/src/pretix/base/migrations/0206_customer_phone.py b/src/pretix/base/migrations/0206_customer_phone.py new file mode 100644 index 0000000000..d430863f13 --- /dev/null +++ b/src/pretix/base/migrations/0206_customer_phone.py @@ -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), + ), + ] diff --git a/src/pretix/base/models/customers.py b/src/pretix/base/models/customers.py index 9f5007690c..520a2f3e15 100644 --- a/src/pretix/base/models/customers.py +++ b/src/pretix/base/models/customers.py @@ -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) diff --git a/src/pretix/control/forms/organizer.py b/src/pretix/control/forms/organizer.py index 1a44359f99..18cc8c4761 100644 --- a/src/pretix/control/forms/organizer.py +++ b/src/pretix/control/forms/organizer.py @@ -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, diff --git a/src/pretix/control/templates/pretixcontrol/organizers/customer.html b/src/pretix/control/templates/pretixcontrol/organizers/customer.html index a332fa03ac..fff361b1f3 100644 --- a/src/pretix/control/templates/pretixcontrol/organizers/customer.html +++ b/src/pretix/control/templates/pretixcontrol/organizers/customer.html @@ -48,6 +48,10 @@
{% trans "Name" %}
{{ customer.name }}
+ {% if customer.phone %} +
{% trans "Phone" %}
+
{{ customer.phone }}
+ {% endif %}
{% trans "Locale" %}
{{ display_locale }}
{% trans "Registration date" %}
diff --git a/src/pretix/presale/checkoutflow.py b/src/pretix/presale/checkoutflow.py index b2e66a02c4..d43644f00d 100644 --- a/src/pretix/presale/checkoutflow.py +++ b/src/pretix/presale/checkoutflow.py @@ -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, diff --git a/src/pretix/presale/forms/checkout.py b/src/pretix/presale/forms/checkout.py index 1729f2231d..5d58bc24f0 100644 --- a/src/pretix/presale/forms/checkout.py +++ b/src/pretix/presale/forms/checkout.py @@ -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 diff --git a/src/pretix/presale/forms/customer.py b/src/pretix/presale/forms/customer.py index 9c5ef2ec49..8018ae0a4d 100644 --- a/src/pretix/presale/forms/customer.py +++ b/src/pretix/presale/forms/customer.py @@ -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') diff --git a/src/pretix/presale/forms/waitinglist.py b/src/pretix/presale/forms/waitinglist.py index 386c39b90f..cbca5c3616 100644 --- a/src/pretix/presale/forms/waitinglist.py +++ b/src/pretix/presale/forms/waitinglist.py @@ -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'] diff --git a/src/pretix/presale/templates/pretixpresale/organizers/customer_profile.html b/src/pretix/presale/templates/pretixpresale/organizers/customer_profile.html index 3b3a4102ba..c6506471cb 100644 --- a/src/pretix/presale/templates/pretixpresale/organizers/customer_profile.html +++ b/src/pretix/presale/templates/pretixpresale/organizers/customer_profile.html @@ -24,6 +24,10 @@
{% trans "Name" %}
{{ customer.name }}
+ {% if customer.phone %} +
{% trans "Phone" %}
+
{{ customer.phone }}
+ {% endif %}