Skip to content

Commit

Permalink
[#5006] Updated AddressValueSerializer to adapt manual filling of str…
Browse files Browse the repository at this point in the history
…eetname and city

Updated street name and city serializer fields in order to be able to
require these fields in case the component is required.
  • Loading branch information
vaszig committed Jan 23, 2025
1 parent 46f3859 commit eba53a2
Show file tree
Hide file tree
Showing 4 changed files with 154 additions and 7 deletions.
1 change: 1 addition & 0 deletions src/openforms/contrib/brk/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@ class AddressValue(TypedDict):
city: NotRequired[str]
streetName: NotRequired[str]
secretStreetCity: NotRequired[str]
autoPopulated: NotRequired[bool]
43 changes: 39 additions & 4 deletions src/openforms/formio/components/custom.py
Original file line number Diff line number Diff line change
Expand Up @@ -425,12 +425,36 @@ class AddressValueSerializer(serializers.Serializer):
required=False,
allow_blank=True,
)
autoPopulated = serializers.BooleanField(
label=_("city and street name auto populated"),
help_text=_("Whether city and street name have been retrieved from the API"),
default=False,
)

def __init__(self, **kwargs):
self.derive_address = kwargs.pop("derive_address", None)
self.component = kwargs.pop("component", None)
super().__init__(**kwargs)

def get_fields(self):
fields = super().get_fields()

# Some fields have to be treated as required or not dynamically and based on
# specific situations.
if self.component and (validate := self.component.get("validate")):
if validate["required"] is True:
if self.derive_address:
fields["city"].required = True
fields["city"].allow_blank = False
fields["streetName"].required = True
fields["streetName"].allow_blank = False
elif validate["required"] is False:
fields["postcode"].required = False
fields["postcode"].allow_blank = True
fields["houseNumber"].required = False
fields["houseNumber"].allow_blank = True
return fields

def validate_city(self, value: str) -> str:
if city_regex := glom(
self.component, "openForms.components.city.validate.pattern", default=""
Expand All @@ -457,18 +481,29 @@ def validate_postcode(self, value: str) -> str:
def validate(self, attrs):
attrs = super().validate(attrs)

auto_populated = attrs.get("autoPopulated", False)
postcode = attrs.get("postcode", "")
house_number = attrs.get("houseNumber", "")
city = attrs.get("city", "")
street_name = attrs.get("streetName", "")

if (postcode and not house_number) or (not postcode and house_number):
raise serializers.ValidationError(
_("Both postcode and house number are required or none of them"),
code="required",
)

if self.derive_address:
existing_hmac = attrs.get("secretStreetCity", "")
postcode = attrs.get("postcode", "")
number = attrs.get("houseNumber", "")
# when the user fills in manually the city and the street name we do not
# need to check the secret city - street name combination
if not auto_populated:
return attrs

existing_hmac = attrs.get("secretStreetCity", "")
computed_hmac = salt_location_message(
{
"postcode": postcode,
"number": number,
"number": house_number,
"city": city,
"street_name": street_name,
}
Expand Down
1 change: 1 addition & 0 deletions src/openforms/formio/formatters/custom.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ class AddressValue(TypedDict):
city: NotRequired[str]
streetName: NotRequired[str]
secretStreetCity: NotRequired[str]
autoPopulated: NotRequired[bool]


class AddressNLFormatter(FormatterBase):
Expand Down
116 changes: 113 additions & 3 deletions src/openforms/formio/tests/validation/test_addressnl.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,12 +106,88 @@ def test_addressNL_field_regex_pattern_success(self):

self.assertTrue(is_valid)

def test_missing_keys(self):
def test_missing_keys_when_component_optional(self):
component: AddressNLComponent = {
"key": "addressNl",
"type": "addressNL",
"label": "AddressNL missing keys",
"deriveAddress": False,
"validate": {"required": False},
}

data = {
"addressNl": {
"houseLetter": "A",
}
}

is_valid, _ = validate_formio_data(component, data)

self.assertTrue(is_valid)

def test_postcode_housenumber_combination(self):
component: AddressNLComponent = {
"key": "addressNl",
"type": "addressNL",
"label": "AddressNL missing keys",
"deriveAddress": False,
"validate": {"required": False},
}

with self.subTest("valid postcode and missing housenumber"):
data = {
"addressNl": {
"postcode": "1234AA",
}
}

is_valid, errors = validate_formio_data(component, data)

combination_error = extract_error(errors["addressNl"], "non_field_errors")

self.assertFalse(is_valid)
self.assertEqual(combination_error.code, "required")

with self.subTest("missing postcode and valid housenumber"):
data = {
"addressNl": {
"houseNumber": "2",
}
}

is_valid, errors = validate_formio_data(component, data)

combination_error = extract_error(errors["addressNl"], "non_field_errors")

self.assertFalse(is_valid)
self.assertEqual(combination_error.code, "required")

def test_missing_keys_when_autofill_and_component_optional(self):
component: AddressNLComponent = {
"key": "addressNl",
"type": "addressNL",
"label": "AddressNL missing keys",
"deriveAddress": True,
"validate": {"required": False},
}

data = {
"addressNl": {
"houseLetter": "A",
}
}

is_valid, _ = validate_formio_data(component, data)

self.assertTrue(is_valid)

def test_missing_keys_when_component_required(self):
component: AddressNLComponent = {
"key": "addressNl",
"type": "addressNL",
"label": "AddressNL missing keys",
"deriveAddress": True,
"validate": {"required": True},
}

invalid_values = {
Expand All @@ -124,10 +200,14 @@ def test_missing_keys(self):

postcode_error = extract_error(errors["addressNl"], "postcode")
house_number_error = extract_error(errors["addressNl"], "houseNumber")
street_name_error = extract_error(errors["addressNl"], "streetName")
city_error = extract_error(errors["addressNl"], "city")

self.assertFalse(is_valid)
self.assertEqual(postcode_error.code, "required")
self.assertEqual(house_number_error.code, "required")
self.assertEqual(street_name_error.code, "required")
self.assertEqual(city_error.code, "required")

def test_plugin_validator(self):
with replace_validators_registry() as register:
Expand All @@ -138,7 +218,7 @@ def test_plugin_validator(self):
"type": "addressNL",
"label": "AddressNL plugin validator",
"deriveAddress": False,
"validate": {"plugins": ["postcode_validator"]},
"validate": {"required": False, "plugins": ["postcode_validator"]},
}

with self.subTest("valid value"):
Expand All @@ -150,6 +230,8 @@ def test_plugin_validator(self):
"houseNumber": "3",
"houseLetter": "A",
"houseNumberAddition": "",
"streetName": "Keizersgracht",
"city": "Amsterdam",
}
},
)
Expand All @@ -176,7 +258,7 @@ def test_addressNL_field_secret_success(self):
"key": "addressNl",
"type": "addressNL",
"label": "AddressNL secret success",
"deriveAddress": False,
"deriveAddress": True,
}

message = "1015CJ/117/Amsterdam/Keizersgracht"
Expand All @@ -190,6 +272,7 @@ def test_addressNL_field_secret_success(self):
"city": "Amsterdam",
"streetName": "Keizersgracht",
"secretStreetCity": secret,
"autoPopulated": True,
}
}

Expand All @@ -214,6 +297,7 @@ def test_addressNL_field_secret_failure(self):
"city": "Amsterdam",
"streetName": "Keizersgracht",
"secretStreetCity": "invalid secret",
"autoPopulated": True,
}
}

Expand All @@ -224,6 +308,32 @@ def test_addressNL_field_secret_failure(self):
self.assertFalse(is_valid)
self.assertEqual(secret_error.code, "invalid")

def test_addressNL_field_secret_not_used_when_manual_address(self):
component: AddressNLComponent = {
"key": "addressNl",
"type": "addressNL",
"label": "AddressNL secret failure",
"deriveAddress": True,
"validate": {"required": False},
}

data = {
"addressNl": {
"postcode": "1015CJ",
"houseNumber": "117",
"houseLetter": "",
"houseNumberAddition": "",
"city": "Amsterdam",
"streetName": "Keizersgracht",
"secretStreetCity": "a secret",
"autoPopulated": False,
}
}

is_valid, _ = validate_formio_data(component, data)

self.assertTrue(is_valid)

def test_addressNL_field_missing_city(self):
component: AddressNLComponent = {
"key": "addressNl",
Expand Down

0 comments on commit eba53a2

Please sign in to comment.