Skip to content

Commit

Permalink
Merge pull request #4589 from open-formulieren/feature/4577-documentt…
Browse files Browse the repository at this point in the history
…ype-select

Add document type select/dropdowns
  • Loading branch information
sergei-maertens authored Aug 12, 2024
2 parents c2f078b + acecae5 commit 8f7b003
Show file tree
Hide file tree
Showing 18 changed files with 922 additions and 203 deletions.
25 changes: 16 additions & 9 deletions src/openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3905,6 +3905,7 @@ paths:
schema:
type: string
format: uri
default: ''
minLength: 1
description: Filter informatieobjecttypen against this catalogus URL.
- in: query
Expand Down Expand Up @@ -4105,6 +4106,7 @@ paths:
schema:
type: string
format: uri
default: ''
minLength: 1
description: Filter informatieobjecttypen against this catalogus URL.
- in: query
Expand Down Expand Up @@ -6842,10 +6844,16 @@ components:
label:
type: string
description: The display label of the catalogue
url:
type: string
format: uri
description: The API resource URL of the catalogue. You can use this for
subsequent filtering operations.
required:
- domain
- label
- rsin
- url
Category:
type: object
properties:
Expand Down Expand Up @@ -8396,22 +8404,21 @@ components:
InformatieObjectType:
type: object
properties:
catalogusDomein:
type: string
catalogusRsin:
type: string
catalogusLabel:
type: string
description: The display label of the catalogus
url:
type: string
format: uri
omschrijving:
type: string
title: description
description: The description uniquely identifies a document type within
a catalogue. Multiple versions of the same document type may exist, these
have non-overlapping valid from/valid until dates.
catalogusLabel:
type: string
title: catalogue label
description: A representation of the catalogue containing the document type.
required:
- catalogusDomein
- catalogusLabel
- catalogusRsin
- omschrijving
- url
Language:
Expand Down
14 changes: 14 additions & 0 deletions src/openforms/contrib/zgw/api/filters.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from django.utils.translation import gettext_lazy as _

from rest_framework import serializers

from ..clients import CatalogiClient
Expand All @@ -7,3 +9,15 @@ class ProvidesCatalogiClientQueryParamsSerializer(serializers.Serializer):

def get_ztc_client(self) -> CatalogiClient:
raise NotImplementedError()


class DocumentTypesFilter(serializers.Serializer):
catalogus_url = serializers.URLField(
label=_("catalogus URL"),
help_text=_("Filter informatieobjecttypen against this catalogus URL."),
required=False,
default="",
)

def get_ztc_client(self) -> CatalogiClient:
raise NotImplementedError()
24 changes: 19 additions & 5 deletions src/openforms/contrib/zgw/api/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,27 @@ class CatalogueSerializer(serializers.Serializer):
label = serializers.CharField( # type: ignore
label=_("label"), help_text=_("The display label of the catalogue")
)
url = serializers.URLField(
label=_("url"),
help_text=_(
"The API resource URL of the catalogue. You can use this for subsequent "
"filtering operations.",
),
)


# TODO: OF 3.0 -> use English instead of Dutch.
class InformatieObjectTypeSerializer(serializers.Serializer):
catalogus_domein = serializers.CharField(label=_("catalogus domein"))
catalogus_rsin = serializers.CharField(label=_("catalogus rsin"))
url = serializers.URLField(label=_("url"))
omschrijving = serializers.CharField(
label=_("description"),
help_text=_(
"The description uniquely identifies a document type within a catalogue. "
"Multiple versions of the same document type may exist, these have "
"non-overlapping valid from/valid until dates."
),
)
catalogus_label = serializers.CharField(
label=_("catalogus label"), help_text=_("The display label of the catalogus")
label=_("catalogue label"),
help_text=_("A representation of the catalogue containing the document type."),
)
url = serializers.URLField(label=_("url"))
omschrijving = serializers.CharField(label=_("omschrijving"))
95 changes: 54 additions & 41 deletions src/openforms/contrib/zgw/api/views.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
from dataclasses import dataclass
from typing import Any, ClassVar
from dataclasses import dataclass, field
from typing import ClassVar

from rest_framework import authentication, permissions
from rest_framework.views import APIView

from openforms.api.views import ListMixin

from .filters import ProvidesCatalogiClientQueryParamsSerializer
from .filters import DocumentTypesFilter, ProvidesCatalogiClientQueryParamsSerializer
from .serializers import CatalogueSerializer, InformatieObjectTypeSerializer


@dataclass
class Catalogue:
url: str
domain: str
rsin: str
name: str = ""
Expand Down Expand Up @@ -41,13 +42,26 @@ def get_objects(self):

return [
Catalogue(
domain=item["domein"], rsin=item["rsin"], name=item.get("naam", "")
url=item["url"],
domain=item["domein"],
rsin=item["rsin"],
name=item.get("naam", ""),
)
for item in catalogus_data
]


class BaseInformatieObjectTypenListView(ListMixin, APIView):
@dataclass
class DocumentType:
catalogue: Catalogue
omschrijving: str
url: str = field(compare=False) # different versions have different URLs

def catalogus_label(self) -> str:
return self.catalogue.label


class BaseInformatieObjectTypenListView(ListMixin[DocumentType], APIView):
"""
List the available InformatieObjectTypen.
Expand All @@ -59,46 +73,45 @@ class BaseInformatieObjectTypenListView(ListMixin, APIView):
authentication_classes = (authentication.SessionAuthentication,)
permission_classes = (permissions.IsAdminUser,)
serializer_class = InformatieObjectTypeSerializer
filter_serializer_class: ClassVar[type[ProvidesCatalogiClientQueryParamsSerializer]]
filter_serializer_class: ClassVar[type[DocumentTypesFilter]]

def get_objects(self):
def get_objects(self) -> list[DocumentType]:
filter_serializer = self.filter_serializer_class(data=self.request.query_params)
filter_serializer.is_valid(raise_exception=True)

iotypen_data: dict[tuple[str, str], dict[str, Any]] = {}
catalogus_url = filter_serializer.validated_data["catalogus_url"]

document_types: list[DocumentType] = []
with filter_serializer.get_ztc_client() as client:
catalogus_data = client.get_all_catalogi()
catalogus_mapping = {
catalogus["url"]: catalogus for catalogus in catalogus_data
# look up the relevant catalogue information, since we need to embed
# information in the document types for the formio-builder file component.
_catalogues: list[dict]
if catalogus_url:
response = client.get(catalogus_url)
response.raise_for_status()
_catalogues = [response.json()]
else:
_catalogues = list(client.get_all_catalogi())

catalogues: dict[str, Catalogue] = {
item["url"]: Catalogue(
url=item["url"],
domain=item["domein"],
rsin=item["rsin"],
name=item.get("naam", ""),
)
for item in _catalogues
}
catalogus = filter_serializer.validated_data.get("catalogus_url", "")

for iotype in client.get_all_informatieobjecttypen(catalogus=catalogus):
# Filter out duplicate entries, as you can set start/end dates on IOTs:
key = (iotype["catalogus"], iotype["omschrijving"])
if key not in iotypen_data:
iotypen_data[key] = iotype

iotypen = []

for iotype in iotypen_data.values():
_catalogus = catalogus_mapping[iotype["catalogus"]]
catalogue = Catalogue(
domain=_catalogus["domein"],
rsin=_catalogus["rsin"],
name=_catalogus.get("naam", ""),
)

iotypen.append(
{
"catalogus_domein": catalogue.domain,
"catalogus_rsin": catalogue.rsin,
"catalogus_label": catalogue.label,
"url": iotype["url"],
"omschrijving": iotype["omschrijving"],
# Not taken into account by the serializer, but used to filter duplicates:
"catalogus_url": iotype["catalogus"],
}
)

return iotypen
# now, look up the document types, possibly filtered
for iotype in client.get_all_informatieobjecttypen(catalogus=catalogus_url):
document_type = DocumentType(
url=iotype["url"],
omschrijving=iotype["omschrijving"],
catalogue=catalogues[iotype["catalogus"]],
)
if document_type in document_types:
continue
document_types.append(document_type)

return document_types
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from unittest.mock import MagicMock, patch
from pathlib import Path

from django.urls import Resolver404, resolve, reverse

Expand All @@ -18,12 +18,17 @@
rs_select_option,
)
from openforms.tests.utils import log_flaky
from openforms.utils.tests.vcr import OFVCRMixin

from ..factories import FormFactory
from .helpers import close_modal, open_component_options_modal, phase

VCR_CASSETTES = (Path(__file__).parent).resolve()


class FormDesignerRegistrationBackendConfigTests(OFVCRMixin, E2ETestCase):
VCR_TEST_FILES = VCR_CASSETTES

class FormDesignerRegistrationBackendConfigTests(E2ETestCase):
async def test_configuring_zgw_api_group(self):
"""
Test that the admin editor dynamically changes the request to InformatieObjectTypenListView based on the registration backend configuration.
Expand Down Expand Up @@ -149,57 +154,32 @@ def collect_requests(request):
str(zgw_api_2.pk),
)

@patch(
"openforms.registrations.contrib.objects_api.client.ObjecttypesClient.list_objecttype_versions"
)
@patch(
"openforms.registrations.contrib.objects_api.client.ObjecttypesClient.list_objecttypes"
)
async def test_configuration_objects_api_group(
self,
m_list_objecttypes: MagicMock,
m_list_objecttype_versions: MagicMock,
):
async def test_configuration_objects_api_group(self):
"""
Test that the admin editor dynamically changes the request to
InformatieObjectTypenListView based on the registration backend configuration.
Assert that the document types are retrieved based on the selected API group.
The flow in this test is:
- Configure the Registration backend to use set 1 of the configured Object apis
- Go to the file upload component and check that the query parameter in the
request to the informatieobjecttype endpoint is the PK of the right group
- Go back to the registration tab and change which Objects API group should be used
- Go to the file component and check that the query parameter in the request changes
This test uses VCR, when re-recording cassettes, ensure that the docker-compose
services are up and running. From the root of the repo:
.. code-block:: bash
cd docker
docker compose \
-f docker-compose.open-zaak.yml \
-f docker-compose.objects-apis.yml \
up
"""
ot_root = "https://objecttypes-1.nl/api/v2/"
# subset of fields, API spec is at
# https://github.com/maykinmedia/objecttypes-api/
m_list_objecttypes.return_value = [
{
"url": f"{ot_root}objecttypes/0879828e-823b-493e-9879-e310b1bfda77",
"uuid": "0879828e-823b-493e-9879-e310b1bfda77",
"name": "Some object type",
"namePlural": "Some object types",
"dataClassification": "open",
}
]
m_list_objecttype_versions.return_value = [{"version": 1, "status": "draft"}]

@sync_to_async
def setUpTestData():
# both groups talk to the same services for simplicity
objects_api_1 = ObjectsAPIGroupConfigFactory.create(
name="Group 1",
objects_service__api_root="https://objects-1.nl/api/v1/",
objecttypes_service__api_root=ot_root,
drc_service__api_root="https://documenten-1.nl/api/v1/",
catalogi_service__api_root="https://catalogus-1.nl/api/v1/",
for_test_docker_compose=True,
)
objects_api_2 = ObjectsAPIGroupConfigFactory.create(
name="Group 2",
objects_service__api_root="https://objects-2.nl/api/v1/",
objecttypes_service__api_root="https://objecttypes-2.nl/api/v1/",
drc_service__api_root="https://documenten-2.nl/api/v1/",
catalogi_service__api_root="https://catalogus-2.nl/api/v1/",
for_test_docker_compose=True,
)
form = FormFactory.create(
name="Configure registration test",
Expand Down
Loading

0 comments on commit 8f7b003

Please sign in to comment.