diff --git a/src/pretix/base/addressvalidation.py b/src/pretix/base/addressvalidation.py index 4dd1fc8cf6..62b89055e5 100644 --- a/src/pretix/base/addressvalidation.py +++ b/src/pretix/base/addressvalidation.py @@ -85,7 +85,7 @@ COUNTRIES_WITH_STREET_ZIPCODE_AND_CITY_REQUIRED = { } -def validate_address(address: dict): +def validate_address(address: dict, all_optional=False): """ :param address: A dictionary with at least the entries ``street``, ``zipcode``, ``city``, ``country``, ``state`` @@ -100,10 +100,10 @@ def validate_address(address: dict): if not address.get('country'): raise ValidationError({'country': [_('This field is required.')]}) - if str(address['country']) in COUNTRIES_WITH_STATE_IN_ADDRESS and not address.get('state'): + if str(address['country']) in COUNTRIES_WITH_STATE_IN_ADDRESS and not address.get('state') and not all_optional: raise ValidationError({'state': [_('This field is required.')]}) - if str(address['country']) in COUNTRIES_WITH_STREET_ZIPCODE_AND_CITY_REQUIRED: + if str(address['country']) in COUNTRIES_WITH_STREET_ZIPCODE_AND_CITY_REQUIRED and not all_optional: for f in ('street', 'zipcode', 'city'): if not address.get(f): raise ValidationError({f: [_('This field is required.')]}) @@ -111,7 +111,8 @@ def validate_address(address: dict): for klass in _validator_classes[str(address['country'])]: validator = klass() try: - address['zipcode'] = validator.validate_zipcode(address['zipcode']) + if address.get('zipcode'): + address['zipcode'] = validator.validate_zipcode(address['zipcode']) except ValidationError as e: raise ValidationError({'zipcode': list(e)}) diff --git a/src/pretix/base/forms/questions.py b/src/pretix/base/forms/questions.py index eb3e72f29a..23ac056a35 100644 --- a/src/pretix/base/forms/questions.py +++ b/src/pretix/base/forms/questions.py @@ -1074,7 +1074,7 @@ class BaseInvoiceAddressForm(forms.ModelForm): self.instance.vat_id_validated = False if self.address_validation: - self.cleaned_data = data = validate_address(data) + self.cleaned_data = data = validate_address(data, self.all_optional) self.instance.name_parts = data.get('name_parts') diff --git a/src/tests/base/test_addressvalidation.py b/src/tests/base/test_addressvalidation.py index b330a751c4..b8e26edc82 100644 --- a/src/tests/base/test_addressvalidation.py +++ b/src/tests/base/test_addressvalidation.py @@ -26,29 +26,49 @@ from pretix.base.addressvalidation import validate_address @pytest.mark.parametrize( - "input,output", + "input,output,all_optional", [ # No address is allowed - ({"name": "Peter"}, {"name": "Peter"}), + ({"name": "Peter"}, {"name": "Peter"}, False), # Country must be given if any part of the address is filled - ({"street": "Main Street"}, {"country": ["This field is required."]}), + ({"street": "Main Street"}, {"country": ["This field is required."]}, False), # Country without any semantic validation ( {"street": "Main Street", "country": "CR"}, {"street": "Main Street", "country": "CR"}, + False, ), # Country that requires all fields except state to be filled ( {"street": "Main Street", "country": "DE"}, {"zipcode": ["This field is required."]}, + False, ), ( {"street": "Main Street", "country": "DE", "zipcode": "12345"}, {"city": ["This field is required."]}, + False, ), ( {"city": "Heidelberg", "country": "DE", "zipcode": "12345"}, {"street": ["This field is required."]}, + False, + ), + # All-optional flag works + ( + {"street": "Main Street", "country": "DE"}, + {"street": "Main Street", "country": "DE"}, + True, + ), + ( + {"street": "Main Street", "country": "DE", "zipcode": "12345"}, + {"street": "Main Street", "country": "DE", "zipcode": "12345"}, + True, + ), + ( + {"city": "Heidelberg", "country": "DE", "zipcode": "12345"}, + {"city": "Heidelberg", "country": "DE", "zipcode": "12345"}, + True, ), ( { @@ -58,6 +78,7 @@ from pretix.base.addressvalidation import validate_address "zipcode": "12345", }, True, + False, ), # Country that requires state to be filled ( @@ -68,6 +89,7 @@ from pretix.base.addressvalidation import validate_address "zipcode": "12345", }, {"state": ["This field is required."]}, + False, ), # Country with zip code validation inherited from django-localflavor ( @@ -78,6 +100,7 @@ from pretix.base.addressvalidation import validate_address "zipcode": "ABCDE", }, {"zipcode": ["Enter a zip code in the format XXXXX."]}, + False, ), # Country with zip code validation implemented directly ( @@ -88,6 +111,7 @@ from pretix.base.addressvalidation import validate_address "zipcode": "ABCDE", }, {"zipcode": ["Enter a postal code in the format XXX."]}, + False, ), # Country with zip code normalization inherited from django-localflavor ( @@ -103,12 +127,13 @@ from pretix.base.addressvalidation import validate_address "country": "GB", "zipcode": "SE1 9DE", }, + False, ), ], ) -def test_validate_address(input, output): +def test_validate_address(input, output, all_optional): try: - actual_output = validate_address(input) + actual_output = validate_address(input, all_optional) except ValidationError as e: assert { k: ["".join(s for s in e) for e in v] for k, v in e.error_dict.items()