diff --git a/src/pretix/base/forms/questions.py b/src/pretix/base/forms/questions.py index e7e6b6a4e..5222dc7e6 100644 --- a/src/pretix/base/forms/questions.py +++ b/src/pretix/base/forms/questions.py @@ -54,6 +54,7 @@ from django.core.validators import ( from django.db.models import QuerySet from django.forms import Select, widgets from django.forms.widgets import FILE_INPUT_CONTRADICTION +from django.urls import reverse from django.utils.formats import date_format from django.utils.html import escape from django.utils.safestring import mark_safe @@ -712,6 +713,7 @@ class BaseQuestionsForm(forms.Form): initial=country, widget=forms.Select(attrs={ 'autocomplete': 'country', + 'data-country-information-url': reverse('js_helpers.states'), }), ) c = [('', pgettext_lazy('address', 'Select state'))] @@ -1005,7 +1007,7 @@ class BaseInvoiceAddressForm(forms.ModelForm): 'street': forms.Textarea(attrs={ 'rows': 2, 'placeholder': _('Street and Number'), - 'autocomplete': 'street-address' + 'autocomplete': 'street-address', }), 'beneficiary': forms.Textarea(attrs={'rows': 3}), 'country': forms.Select(attrs={ @@ -1021,7 +1023,7 @@ class BaseInvoiceAddressForm(forms.ModelForm): 'data-display-dependency': '#id_is_business_1', 'autocomplete': 'organization', }), - 'vat_id': forms.TextInput(attrs={'data-display-dependency': '#id_is_business_1', 'data-countries-with-vat-id': ','.join(VAT_ID_COUNTRIES)}), + 'vat_id': forms.TextInput(attrs={'data-display-dependency': '#id_is_business_1'}), 'internal_reference': forms.TextInput, } labels = { @@ -1055,6 +1057,7 @@ class BaseInvoiceAddressForm(forms.ModelForm): ]) self.fields['country'].choices = CachedCountries() + self.fields['country'].widget.attrs['data-country-information-url'] = reverse('js_helpers.states') c = [('', pgettext_lazy('address', 'Select state'))] fprefix = self.prefix + '-' if self.prefix else '' @@ -1083,6 +1086,10 @@ class BaseInvoiceAddressForm(forms.ModelForm): ) self.fields['state'].widget.is_required = True + self.fields['street'].required = False + self.fields['zipcode'].required = False + self.fields['city'].required = False + # Without JavaScript the VAT ID field is not hidden, so we empty the field if a country outside the EU is selected. if cc and not ask_for_vat_id(cc) and fprefix + 'vat_id' in self.data: self.data = self.data.copy() diff --git a/src/pretix/base/models/orders.py b/src/pretix/base/models/orders.py index d0786d8d9..f151dbb34 100644 --- a/src/pretix/base/models/orders.py +++ b/src/pretix/base/models/orders.py @@ -3204,9 +3204,9 @@ class InvoiceAddress(models.Model): company = models.CharField(max_length=255, blank=True, verbose_name=_('Company name')) name_cached = models.CharField(max_length=255, verbose_name=_('Full name'), blank=True) name_parts = models.JSONField(default=dict) - street = models.TextField(verbose_name=_('Address'), blank=False) - zipcode = models.CharField(max_length=30, verbose_name=_('ZIP code'), blank=False) - city = models.CharField(max_length=255, verbose_name=_('City'), blank=False) + street = models.TextField(verbose_name=_('Address'), blank=True) + zipcode = models.CharField(max_length=30, verbose_name=_('ZIP code'), blank=True) + city = models.CharField(max_length=255, verbose_name=_('City'), blank=True) country_old = models.CharField(max_length=255, verbose_name=_('Country'), blank=False) country = FastCountryField(verbose_name=_('Country'), blank=False, blank_label=_('Select country'), countries=CachedCountries) diff --git a/src/pretix/base/views/js_helpers.py b/src/pretix/base/views/js_helpers.py index 458f55d44..0ea97bc92 100644 --- a/src/pretix/base/views/js_helpers.py +++ b/src/pretix/base/views/js_helpers.py @@ -22,16 +22,28 @@ import pycountry from django.http import JsonResponse +from pretix.base.addressvalidation import COUNTRIES_WITH_STREET_ZIPCODE_AND_CITY_REQUIRED +from pretix.base.models.tax import VAT_ID_COUNTRIES from pretix.base.settings import COUNTRIES_WITH_STATE_IN_ADDRESS def states(request): cc = request.GET.get("country", "DE") + info = { + 'street': {'required': cc in COUNTRIES_WITH_STREET_ZIPCODE_AND_CITY_REQUIRED}, + 'zipcode': {'required': cc in COUNTRIES_WITH_STREET_ZIPCODE_AND_CITY_REQUIRED}, + 'city': {'required': cc in COUNTRIES_WITH_STREET_ZIPCODE_AND_CITY_REQUIRED}, + 'state': {'visible': cc in COUNTRIES_WITH_STATE_IN_ADDRESS, 'required': cc in COUNTRIES_WITH_STATE_IN_ADDRESS}, + 'vat_id': {'visible': cc in VAT_ID_COUNTRIES, 'required': False}, + } if cc not in COUNTRIES_WITH_STATE_IN_ADDRESS: - return JsonResponse({'data': []}) + return JsonResponse({'data': [], **info,}) types, form = COUNTRIES_WITH_STATE_IN_ADDRESS[cc] statelist = [s for s in pycountry.subdivisions.get(country_code=cc) if s.type in types] - return JsonResponse({'data': [ - {'name': s.name, 'code': s.code[3:]} - for s in sorted(statelist, key=lambda s: s.name) - ]}) + return JsonResponse({ + 'data': [ + {'name': s.name, 'code': s.code[3:]} + for s in sorted(statelist, key=lambda s: s.name) + ], + **info, + }) diff --git a/src/pretix/static/pretixbase/js/addressform.js b/src/pretix/static/pretixbase/js/addressform.js index 89e999178..0a5c4652a 100644 --- a/src/pretix/static/pretixbase/js/addressform.js +++ b/src/pretix/static/pretixbase/js/addressform.js @@ -1,65 +1,61 @@ $(function () { "use strict"; - $("select[name$=state]:not([data-static])").each(function () { - var dependent = $(this), - counter = 0, - dependency = $(this).closest(".panel-body, form").find('select[name$=country]'), - depRequired = dependency.closest(".form-group").is(".required"), + $("select[data-country-information-url]").each(function () { + let counter = 0; + const dependency = $(this), + url = this.getAttribute('data-country-information-url'), + form = $(this).closest(".panel-body, form"), + isRequired = dependency.closest(".form-group").is(".required"), + dependents = { + 'city': form.find("input[name$=city]"), + 'zipcode': form.find("input[name$=zipcode]"), + 'street': form.find("textarea[name$=street]"), + 'state': form.find("select[name$=state]"), + 'vat_id': form.find("input[name$=vat_id]"), + }, update = function (ev) { counter++; - var curCounter = counter; - dependent.prop("disabled", true); + const curCounter = counter; + for (var k in dependents) dependents[k].prop("disabled", true); dependency.closest(".form-group").find("label").prepend(" "); - $.getJSON('/js_helpers/states/?country=' + dependency.val(), function (data) { + $.getJSON(url + '?country=' + dependency.val(), function (data) { if (counter > curCounter) { return; // Lost race } - var selected_value = dependent.prop("data-selected-value"); - dependent.find("option").filter(function (t) {return !!$(this).attr("value")}).remove(); + var selected_value = dependents.state.prop("data-selected-value"); + dependents.state.find("option").filter(function (t) {return !!$(this).attr("value")}).remove(); if (data.data.length > 0) { $.each(data.data, function (k, s) { var o = $("