Skip to content

Commit

Permalink
[#3993] Added deriveAddress for addressNL component
Browse files Browse the repository at this point in the history
  • Loading branch information
vaszig committed Jun 11, 2024
1 parent a27ae39 commit 2706282
Show file tree
Hide file tree
Showing 13 changed files with 271 additions and 11 deletions.
49 changes: 47 additions & 2 deletions docs/manual/forms/examples/autofill_address.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,51 @@
Adres automatisch aanvullen
===========================

.. warning::

The former functionality (via textbox) has been deprecated and will be removed
in a future version. Instead, you should use addressNL component which works in
the same way.

Configuratie (new version)
==========================

You first need to configure:

* :ref:`BAG configuratie <configuration_prefill_bag>`: Voor het opzoeken van adressen.


Formulier maken
===============

1. Create a form with the following information:

**Name** : Demo address

2. Click the **Steps and Fields** tab .

3. on the left Click **Add Step** and select **Create New form definition** .

4. Under the (Reusable) step data section , enter the following:

**Name** : Address details

5. Scroll to the **Fields** section .

6. Drag an **addressNL Field** component onto the white area:

**Label** : AddressNL

7. Check the **Derive address** property and then press Save .

8. Click Save at the bottom to save the form completely.

You can now view the form.

This works in the same way **textbox** component was working. By filling the required
data (postcode and house number), the city and the street name will be derived and
automatically filled in the suitable fields if found.

In dit voorbeeld maken we een deel-formulier bestaande uit 1 stap, waarbij de
straatnaam en stad automatisch worden ingevuld zodra de postcode en huisnummer
zijn ingevuld.
Expand All @@ -21,8 +66,8 @@ In dit voorbeeld gaan we er van uit dat u een
Download: :download:`autofill_address_2.zip <_assets/autofill_address_2.zip>`


Configuratie
============
Configuratie (deprecated)
==========================

Voor dit formulier is bepaalde configuratie nodig. Hieronder staan de onderdelen
die geconfigureerd moeten zijn:
Expand Down
4 changes: 4 additions & 0 deletions src/openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8289,6 +8289,10 @@ components:
city:
type: string
description: Found city
secretStreetCity:
type: string
title: city and street name secret
description: Secret for the combination of city and street name
required:
- city
- streetName
Expand Down
3 changes: 3 additions & 0 deletions src/openforms/contrib/brk/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,6 @@ class AddressValue(TypedDict):
house_number: str
house_letter: NotRequired[str]
house_number_addition: NotRequired[str]
city: str
streetName: str
secretStreetCity: str
11 changes: 11 additions & 0 deletions src/openforms/contrib/kadaster/api/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,23 @@ class GetStreetNameAndCityViewInputSerializer(serializers.Serializer):
label=_("house number"), help_text=_("House number to use in search")
)

secret_street_city = serializers.CharField(
label=_("city and street name secret"),
help_text=_("Secret for the combination of city and street name"),
required=False,
)


class GetStreetNameAndCityViewResultSerializer(serializers.Serializer):
street_name = serializers.CharField(
label=_("street name"), help_text=_("Found street name")
)
city = serializers.CharField(label=_("city"), help_text=_("Found city"))
secret_street_city = serializers.CharField(
label=_("city and street name secret"),
help_text=_("Secret for the combination of city and street name"),
required=False,
)


class LatitudeLongitudeSerializer(serializers.Serializer):
Expand Down
2 changes: 2 additions & 0 deletions src/openforms/contrib/kadaster/api/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ class AddressAutocompleteView(APIView):
"Get the street name and city for a given postal code and house number.\n\n"
"**NOTE** the `/api/v2/location/get-street-name-and-city/` endpoint will "
"be removed in v3. Use `/api/v2/geo/address-autocomplete/` instead."
"Deriving the city and street name from the texboxes is deprecated and will be removed"
"in a future version. Instead, the addressNL component should be used."
), # type: ignore
responses=GetStreetNameAndCityViewResultSerializer,
parameters=[
Expand Down
16 changes: 14 additions & 2 deletions src/openforms/contrib/kadaster/clients/bag.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import logging
from dataclasses import dataclass

from django.utils.crypto import salted_hmac

import elasticapm
import requests

Expand All @@ -13,6 +15,7 @@
class AddressResult:
street_name: str
city: str
secret_street_city: str = ""


class BAGClient(HALClient):
Expand Down Expand Up @@ -53,7 +56,16 @@ def get_address(
return None

first_result = response_data["_embedded"]["adressen"][0]
street_name = first_result.pop("korteNaam")
city = first_result.pop("woonplaatsNaam")

# put an extra layer of protection and make sure that the value is not tampered with
message = (
f"{postcode.upper().replace(' ','')}/{house_number}/{city}/{street_name}"
)

return AddressResult(
street_name=first_result.pop("korteNaam"),
city=first_result.pop("woonplaatsNaam"),
street_name=street_name,
city=city,
secret_street_city=salted_hmac("location_check", value=message).hexdigest(),
)
7 changes: 6 additions & 1 deletion src/openforms/contrib/kadaster/service.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,13 @@


def check_bag_config_for_address_fields() -> str:
# this combines the old functionality of deriving the city and streetname
# via textbox, which is deprecated and will be removed, and the new one via
# the addressNL component
has_bag_usage = any(
component.get("deriveCity") or component.get("deriveStreetName")
component.get("deriveCity")
or component.get("deriveStreetName")
or component.get("deriveAddress")
for form in Form.objects.live()
for component in form.iter_components()
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ def setUp(self):
@patch("openforms.contrib.kadaster.api.views.lookup_address")
def test_getting_street_name_and_city(self, m_lookup_address):
m_lookup_address.return_value = AddressResult(
street_name="Keizersgracht", city="Amsterdam"
street_name="Keizersgracht", city="Amsterdam", secret_street_city=""
)

response = self.client.get(
Expand All @@ -44,9 +44,10 @@ def test_getting_street_name_and_city(self, m_lookup_address):
)

self.assertEqual(response.status_code, 200)
self.assertEqual(len(response.json()), 2)
self.assertEqual(len(response.json()), 3)
self.assertEqual(response.json()["streetName"], "Keizersgracht")
self.assertEqual(response.json()["city"], "Amsterdam")
self.assertEqual(response.json()["secretStreetCity"], "")

@patch(
"openforms.api.exception_handling.uuid.uuid4",
Expand Down Expand Up @@ -117,7 +118,7 @@ def test_getting_street_name_and_city_with_extra_query_params_ignores_extra_para
self, m_lookup_address
):
m_lookup_address.return_value = AddressResult(
street_name="Keizersgracht", city="Amsterdam"
street_name="Keizersgracht", city="Amsterdam", secret_street_city=""
)

response = self.client.get(
Expand All @@ -126,9 +127,10 @@ def test_getting_street_name_and_city_with_extra_query_params_ignores_extra_para
)

self.assertEqual(response.status_code, 200)
self.assertEqual(len(response.json()), 2)
self.assertEqual(len(response.json()), 3)
self.assertEqual(response.json()["streetName"], "Keizersgracht")
self.assertEqual(response.json()["city"], "Amsterdam")
self.assertEqual(response.json()["secretStreetCity"], "")

@patch("openforms.contrib.kadaster.api.views.lookup_address")
def test_address_not_found_returns_empty_200_response(self, m_lookup_address):
Expand All @@ -146,7 +148,7 @@ def test_address_not_found_returns_empty_200_response(self, m_lookup_address):
@patch("openforms.contrib.kadaster.api.views.lookup_address")
def test_endpoint_uses_caching(self, m_lookup_address):
m_lookup_address.return_value = AddressResult(
street_name="Keizersgracht", city="Amsterdam"
street_name="Keizersgracht", city="Amsterdam", secret_street_city=""
)
endpoint = reverse("api:get-street-name-and-city-list")

Expand Down
54 changes: 54 additions & 0 deletions src/openforms/emails/tests/test_digest_functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -442,6 +442,60 @@ def test_invalid_bag_configuration_without_required_property_is_not_collected(

self.assertEqual(broken_configuration, [])

@patch("openforms.contrib.kadaster.models.KadasterApiConfig.get_solo")
def test_invalid_bag_configuration_for_address_nl_component_is_collected(
self, kd_config
):
kd_config.return_value = KadasterApiConfig(bag_service=None)

FormFactory.create(
generate_minimal_setup=True,
formstep__form_definition__configuration={
"components": [
{
"key": "addressNl",
"type": "addressNL",
"label": "Address NL",
"deriveAddress": True,
}
]
},
)

broken_configuration = collect_broken_configurations()

self.assertEqual(len(broken_configuration), 2)
self.assertIn(
"BAG Client", [config.config_name for config in broken_configuration]
)

@patch("openforms.contrib.kadaster.models.KadasterApiConfig.get_solo")
def test_invalid_bag_configuration_for_address_nl_component_not_collected_when_disabled(
self, kd_config
):
kd_config.return_value = KadasterApiConfig(bag_service=None)

FormFactory.create(
generate_minimal_setup=True,
formstep__form_definition__configuration={
"components": [
{
"key": "addressNl",
"type": "addressNL",
"label": "Address NL",
"deriveAddress": False,
}
]
},
)

broken_configuration = collect_broken_configurations()

self.assertEqual(len(broken_configuration), 1)
self.assertNotIn(
"BAG Client", [config.config_name for config in broken_configuration]
)


@override_settings(LANGUAGE_CODE="en")
class InvalidCertificatesTests(TestCase):
Expand Down
43 changes: 43 additions & 0 deletions src/openforms/formio/components/custom.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

from django.core.validators import MaxValueValidator, MinValueValidator, RegexValidator
from django.utils import timezone
from django.utils.crypto import salted_hmac
from django.utils.html import format_html
from django.utils.translation import gettext as _

Expand Down Expand Up @@ -390,11 +391,53 @@ class AddressValueSerializer(serializers.Serializer):
required=False,
allow_blank=True,
)
streetName = serializers.CharField(
label=_("street name"),
help_text=_("Found street name"),
required=False,
allow_blank=True,
)
city = serializers.CharField(
label=_("city"),
help_text=_("Found city"),
required=False,
allow_blank=True,
)
secretStreetCity = serializers.CharField(
label=_("city and street name secret"),
help_text=_("Secret for the combination of city and street name"),
required=False,
allow_blank=True,
)

def validate_postcode(self, value: str) -> str:
"""Normalize the postcode so that it matches the regex from the BRK API."""
return value.upper().replace(" ", "")

def validate(self, attrs):
attrs = super().validate(attrs)

city = attrs.get("city", "")
street_name = attrs.get("streetName", "")

if city and street_name:
existing_hmac = attrs.get("secretStreetCity", "")
postcode = attrs.get("postcode", "")
number = attrs.get("houseNumber", "")

computed_message = f"{postcode}/{number}/{city}/{street_name}"
computed_hmac = salted_hmac(
"location_check", value=computed_message
).hexdigest()

if existing_hmac != computed_hmac:
raise serializers.ValidationError(
_("Invalid secret city - street name combination"),
code="invalid",
)

return attrs


@register("addressNL")
class AddressNL(BasePlugin):
Expand Down
Loading

0 comments on commit 2706282

Please sign in to comment.