diff --git a/src/openforms/fixtures/admin_index_unlisted.json b/src/openforms/fixtures/admin_index_unlisted.json index db613aae81..388824fb21 100644 --- a/src/openforms/fixtures/admin_index_unlisted.json +++ b/src/openforms/fixtures/admin_index_unlisted.json @@ -20,6 +20,7 @@ "qmatic.QmaticConfig", "registrations_email.EmailConfig", "registrations_microsoft_graph.MSGraphRegistrationConfig", + "registrations_objects_api.ObjectsAPIGroupConfig", "registrations_objects_api.ObjectsAPIConfig", "stuf.StufService", "stuf_bg.StufBGConfig", diff --git a/src/openforms/js/compiled-lang/en.json b/src/openforms/js/compiled-lang/en.json index 7f76ed270e..8cfcf1dcaa 100644 --- a/src/openforms/js/compiled-lang/en.json +++ b/src/openforms/js/compiled-lang/en.json @@ -1893,6 +1893,12 @@ "value": "Decision definition ID" } ], + "Ijrbrp": [ + { + "type": 0, + "value": "Which Objects API group to use. If not provided, the default Objects API group will be used." + } + ], "Ika4IH": [ { "type": 0, @@ -4499,6 +4505,12 @@ "value": "House number" } ], + "mgs/Xe": [ + { + "type": 0, + "value": "Objects API group" + } + ], "mpzdoT": [ { "type": 0, diff --git a/src/openforms/js/compiled-lang/nl.json b/src/openforms/js/compiled-lang/nl.json index 64ca781076..5d64b6a534 100644 --- a/src/openforms/js/compiled-lang/nl.json +++ b/src/openforms/js/compiled-lang/nl.json @@ -1897,6 +1897,12 @@ "value": "Beslisdefinitie-ID" } ], + "Ijrbrp": [ + { + "type": 0, + "value": "Which Objects API group to use. If not provided, the default Objects API group will be used." + } + ], "Ika4IH": [ { "type": 0, @@ -4504,6 +4510,12 @@ "value": "Huisnummer" } ], + "mgs/Xe": [ + { + "type": 0, + "value": "Objects API group" + } + ], "mpzdoT": [ { "type": 0, diff --git a/src/openforms/js/components/admin/form_design/registrations/objectsapi/LegacyConfigFields.js b/src/openforms/js/components/admin/form_design/registrations/objectsapi/LegacyConfigFields.js index c3bfad01a9..3bccc2dc87 100644 --- a/src/openforms/js/components/admin/form_design/registrations/objectsapi/LegacyConfigFields.js +++ b/src/openforms/js/components/admin/form_design/registrations/objectsapi/LegacyConfigFields.js @@ -4,15 +4,17 @@ import {useIntl} from 'react-intl'; import {CustomFieldTemplate} from 'components/admin/RJSFWrapper'; import {Checkbox, NumberInput, TextArea, TextInput} from 'components/admin/forms/Inputs'; +import Select from 'components/admin/forms/Select'; import {ValidationErrorContext} from 'components/admin/forms/ValidationErrors'; -import {getErrorMarkup, getFieldErrors} from './utils'; +import {getChoicesFromSchema, getErrorMarkup, getFieldErrors} from './utils'; -const LegacyConfigFields = ({index, name, formData, onChange}) => { +const LegacyConfigFields = ({index, name, schema, formData, onChange}) => { const intl = useIntl(); const validationErrors = useContext(ValidationErrorContext); const { + objectsApiGroup = '', objecttype = '', objecttypeVersion = '', productaanvraagType = '', @@ -32,6 +34,33 @@ const LegacyConfigFields = ({index, name, formData, onChange}) => { return ( <> + + + { > {/* TODO: fallback to legacy UI if there are loading errors */} { V2ConfigFields.propTypes = { index: PropTypes.number, name: PropTypes.string, + schema: PropTypes.any, formData: PropTypes.shape({ + objectsApiGroup: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), version: PropTypes.number, objecttype: PropTypes.string, objecttypeVersion: PropTypes.string, diff --git a/src/openforms/js/components/admin/form_design/registrations/objectsapi/hooks.js b/src/openforms/js/components/admin/form_design/registrations/objectsapi/hooks.js index 3e7039bfd2..7e17df3148 100644 --- a/src/openforms/js/components/admin/form_design/registrations/objectsapi/hooks.js +++ b/src/openforms/js/components/admin/form_design/registrations/objectsapi/hooks.js @@ -3,22 +3,20 @@ import useAsync from 'react-use/esm/useAsync'; import {REGISTRATION_OBJECTTYPES_ENDPOINT} from 'components/admin/form_design/constants'; import {get} from 'utils/fetch'; -export const useGetAvailableObjectTypes = () => { +export const useGetAvailableObjectTypes = objectsApiGroup => { const { loading, value: availableObjecttypes = [], error, - } = useAsync( - async () => { - const response = await get(REGISTRATION_OBJECTTYPES_ENDPOINT); - if (!response.ok) { - throw new Error('Loading available object types failed'); - } - return response.data; - }, - // available object types only need to be loaded once when the component mounts - [] - ); + } = useAsync(async () => { + const params = {}; + if (objectsApiGroup) params.objects_api_group = objectsApiGroup; + const response = await get(REGISTRATION_OBJECTTYPES_ENDPOINT, params); + if (!response.ok) { + throw new Error('Loading available object types failed'); + } + return response.data; + }, [objectsApiGroup]); return { loading, diff --git a/src/openforms/js/components/admin/form_design/registrations/objectsapi/utils.js b/src/openforms/js/components/admin/form_design/registrations/objectsapi/utils.js index a0d2852110..7d0b92c4a6 100644 --- a/src/openforms/js/components/admin/form_design/registrations/objectsapi/utils.js +++ b/src/openforms/js/components/admin/form_design/registrations/objectsapi/utils.js @@ -3,6 +3,15 @@ import React from 'react'; // TODO This is duplicated from the ZGW registration, but will be cleaned up // with the backend UI refactor. +const getChoicesFromSchema = (enums, enumNames) => { + let finalChoices = {}; + Object.keys(enums).forEach(key => { + finalChoices[enums[key]] = enumNames[key]; + }); + + return finalChoices; +}; + const getFieldErrors = (name, index, errors, field) => { const errorMessages = []; @@ -30,6 +39,8 @@ const getErrorMarkup = errorMessages => { ); }; +// End TODO + const VARIABLE_TYPE_MAP = { boolean: 'boolean', int: 'integer', @@ -89,4 +100,4 @@ const asJsonSchema = (variable, components) => { }; }; -export {getErrorMarkup, getFieldErrors, asJsonSchema}; +export {getChoicesFromSchema, getErrorMarkup, getFieldErrors, asJsonSchema}; diff --git a/src/openforms/js/components/form/file.js b/src/openforms/js/components/form/file.js index 467db20a05..cdcaa1f475 100644 --- a/src/openforms/js/components/form/file.js +++ b/src/openforms/js/components/form/file.js @@ -25,6 +25,7 @@ const getInformatieObjectTypen = async (backend, options) => { } case 'objects_api': return await get('/api/v2/registration/informatieobjecttypen', { + objects_api_group: options.objectsApiGroup, registration_backend: backend, }); default: diff --git a/src/openforms/js/lang/en.json b/src/openforms/js/lang/en.json index df8fc2ac0a..1d06524e12 100644 --- a/src/openforms/js/lang/en.json +++ b/src/openforms/js/lang/en.json @@ -839,6 +839,11 @@ "description": "Decision definition ID label", "originalDefault": "Decision definition ID" }, + "Ijrbrp": { + "defaultMessage": "Which Objects API group to use. If not provided, the default Objects API group will be used.", + "description": "Objects API group selection", + "originalDefault": "Which Objects API group to use. If not provided, the default Objects API group will be used." + }, "Ika4IH": { "defaultMessage": "Submissions will be deleted", "description": "delete_permanently removal method option label", @@ -2079,6 +2084,11 @@ "description": "JSON variable type \"Form field\" representation", "originalDefault": "Form field" }, + "mgs/Xe": { + "defaultMessage": "Objects API group", + "description": "Objects API group", + "originalDefault": "Objects API group" + }, "myvtu0": { "defaultMessage": "Manage process variables", "description": "Open manage camunda process vars modal button", diff --git a/src/openforms/js/lang/nl.json b/src/openforms/js/lang/nl.json index 171bac3364..2f197bf945 100644 --- a/src/openforms/js/lang/nl.json +++ b/src/openforms/js/lang/nl.json @@ -845,6 +845,11 @@ "description": "Decision definition ID label", "originalDefault": "Decision definition ID" }, + "Ijrbrp": { + "defaultMessage": "Which Objects API group to use. If not provided, the default Objects API group will be used.", + "description": "Objects API group selection", + "originalDefault": "Which Objects API group to use. If not provided, the default Objects API group will be used." + }, "Ika4IH": { "defaultMessage": "Inzendingen worden permanent verwijderd", "description": "delete_permanently removal method option label", @@ -2093,6 +2098,11 @@ "description": "JSON variable type \"Form field\" representation", "originalDefault": "Form field" }, + "mgs/Xe": { + "defaultMessage": "Objects API group", + "description": "Objects API group", + "originalDefault": "Objects API group" + }, "myvtu0": { "defaultMessage": "Beheer procesvariabelen", "description": "Open manage camunda process vars modal button", diff --git a/src/openforms/registrations/api/filters.py b/src/openforms/registrations/api/filters.py index 0541d6f6ae..7b646e1a8c 100644 --- a/src/openforms/registrations/api/filters.py +++ b/src/openforms/registrations/api/filters.py @@ -5,7 +5,10 @@ from openforms.api.fields import PrimaryKeyRelatedAsChoicesField from openforms.contrib.zgw.clients.catalogi import CatalogiClient -from openforms.registrations.contrib.objects_api.models import ObjectsAPIConfig +from openforms.registrations.contrib.objects_api.models import ( + ObjectsAPIConfig, + ObjectsAPIGroupConfig, +) from openforms.registrations.contrib.zgw_apis.client import get_catalogi_client from openforms.registrations.contrib.zgw_apis.models import ZGWApiGroupConfig, ZgwConfig @@ -23,33 +26,54 @@ class ListInformatieObjectTypenQueryParamsSerializer(serializers.Serializer): label=_("ZGW API set"), required=False, ) + objects_api_group = PrimaryKeyRelatedAsChoicesField( + queryset=ObjectsAPIGroupConfig.objects.all(), + help_text=_( + "The primary key of the Objects API group to use. If provided, the informatieobjecttypen from the Catalogi API " + "in this group will be returned. Otherwise, the Catalogi API in the default Objects API group will be used to find " + "informatieobjecttypen." + ), + label=_("Objects API group"), + required=False, + ) registration_backend = serializers.ChoiceField( help_text=_("The ID of the registration backend to use."), label=_("Registration backend ID"), - required=False, choices=[], ) def get_fields(self): fields = super().get_fields() # lazy set choices - if "registration_backend" in fields: - fields["registration_backend"].choices = register.get_choices() + fields["registration_backend"].choices = register.get_choices() return fields def get_ztc_client(self) -> CatalogiClient | None: registration_backend = self.validated_data.get("registration_backend") - zgw_api_group: ZGWApiGroupConfig = self.validated_data.get("zgw_api_group") + zgw_api_group: ZGWApiGroupConfig | None = self.validated_data.get( + "zgw_api_group" + ) + objects_api_group: ObjectsAPIGroupConfig | None = self.validated_data.get( + "objects_api_group" + ) - if registration_backend == "zgw-create-zaak" and zgw_api_group is not None: - return get_catalogi_client(zgw_api_group) + if registration_backend == "zgw-create-zaak": + if zgw_api_group is not None: + return get_catalogi_client(zgw_api_group) + else: + config = ZgwConfig.get_solo() + assert isinstance(config, ZgwConfig) + if group := config.default_zgw_api_group: + return get_catalogi_client(group) elif registration_backend == "objects_api": - config = ObjectsAPIConfig.get_solo() - assert isinstance(config, ObjectsAPIConfig) - if service := config.catalogi_service: - return build_client(service, client_factory=CatalogiClient) + if objects_api_group is not None: + service = objects_api_group.catalogi_service + else: + config = ObjectsAPIConfig.get_solo() + assert isinstance(config, ObjectsAPIConfig) + if config.default_objects_api_group is None: + return + service = config.default_objects_api_group.catalogi_service - config = ZgwConfig.get_solo() - assert isinstance(config, ZgwConfig) - if group := config.default_zgw_api_group: - return get_catalogi_client(group) + if service is not None: + return build_client(service, client_factory=CatalogiClient) diff --git a/src/openforms/registrations/contrib/objects_api/admin.py b/src/openforms/registrations/contrib/objects_api/admin.py index 2c3feb4b36..548855363b 100644 --- a/src/openforms/registrations/contrib/objects_api/admin.py +++ b/src/openforms/registrations/contrib/objects_api/admin.py @@ -2,9 +2,34 @@ from solo.admin import SingletonModelAdmin -from .models import ObjectsAPIConfig +from .models import ObjectsAPIConfig, ObjectsAPIGroupConfig @admin.register(ObjectsAPIConfig) class ObjectsAPIConfigAdmin(SingletonModelAdmin): pass + + +@admin.register(ObjectsAPIGroupConfig) +class ObjectsAPIGroupConfigAdmin(admin.ModelAdmin): + list_display = ( + "name", + "objects_service", + "objecttypes_service", + "drc_service", + "catalogi_service", + ) + list_select_related = ( + "objects_service", + "objecttypes_service", + "drc_service", + "catalogi_service", + ) + search_fields = ("name",) + raw_id_fields = ( + "objects_service", + "objecttypes_service", + "drc_service", + "catalogi_service", + ) + ordering = ("name",) diff --git a/src/openforms/registrations/contrib/objects_api/api/serializers.py b/src/openforms/registrations/contrib/objects_api/api/serializers.py index 78e0c2fb1e..306d5b3c9f 100644 --- a/src/openforms/registrations/contrib/objects_api/api/serializers.py +++ b/src/openforms/registrations/contrib/objects_api/api/serializers.py @@ -2,6 +2,21 @@ from rest_framework import serializers +from openforms.api.fields import PrimaryKeyRelatedAsChoicesField + +from ..models import ObjectsAPIGroupConfig + + +class ObjectsAPIGroupInputSerializer(serializers.Serializer): + objects_api_group = PrimaryKeyRelatedAsChoicesField( + queryset=ObjectsAPIGroupConfig.objects.all(), + label=("Objects API group"), + help_text=_( + "Which Objects API group to use. If not provided, the default Objects API group will be used." + ), + required=False, + ) + class ObjecttypeSerializer(serializers.Serializer): # Keys are defined in camel case as this is what we get from the Objecttype API @@ -43,7 +58,7 @@ class TargetPathsSerializer(serializers.Serializer): ) -class TargetPathsInputSerializer(serializers.Serializer): +class TargetPathsInputSerializer(ObjectsAPIGroupInputSerializer): objecttype_url = serializers.URLField( label=_("objecttype url"), help_text=("The URL of the objecttype.") ) diff --git a/src/openforms/registrations/contrib/objects_api/api/views.py b/src/openforms/registrations/contrib/objects_api/api/views.py index 08992a5573..2277380132 100644 --- a/src/openforms/registrations/contrib/objects_api/api/views.py +++ b/src/openforms/registrations/contrib/objects_api/api/views.py @@ -3,7 +3,8 @@ from django.utils.translation import gettext_lazy as _ -from drf_spectacular.utils import extend_schema, extend_schema_view +from drf_spectacular.types import OpenApiTypes +from drf_spectacular.utils import OpenApiParameter, extend_schema from rest_framework import authentication, exceptions, permissions, views from rest_framework.request import Request from rest_framework.response import Response @@ -12,18 +13,30 @@ from ..client import get_objecttypes_client from ..json_schema import InvalidReference, iter_json_schema_paths, json_schema_matches +from ..models import ObjectsAPIConfig from .serializers import ( + ObjectsAPIGroupInputSerializer, ObjecttypeSerializer, ObjecttypeVersionSerializer, TargetPathsInputSerializer, TargetPathsSerializer, ) - -@extend_schema_view( - get=extend_schema( - tags=["registration"], +# TODO: https://github.com/open-formulieren/open-forms/issues/611 +OBJECTS_API_GROUP_QUERY_PARAMETER = OpenApiParameter( + name="objects_api_group", + type=OpenApiTypes.STR, + location=OpenApiParameter.QUERY, + description=_( + "Which Objects API group to use. If not provided, the default Objects API group will be used." ), + required=False, +) + + +@extend_schema( + tags=["registration"], + parameters=[OBJECTS_API_GROUP_QUERY_PARAMETER], ) class ObjecttypesListView(ListMixin, views.APIView): """ @@ -37,14 +50,24 @@ class ObjecttypesListView(ListMixin, views.APIView): serializer_class = ObjecttypeSerializer def get_objects(self) -> list[dict[str, Any]]: - with get_objecttypes_client() as client: + input_serializer = ObjectsAPIGroupInputSerializer( + data=self.request.query_params + ) + if not input_serializer.is_valid(raise_exception=False): + return [] + + config_group = ( + input_serializer.validated_data.get("objects_api_group") + or ObjectsAPIConfig.get_solo().default_objects_api_group + ) + + with get_objecttypes_client(config_group) as client: return client.list_objecttypes() -@extend_schema_view( - get=extend_schema( - tags=["registration"], - ), +@extend_schema( + tags=["registration"], + parameters=[OBJECTS_API_GROUP_QUERY_PARAMETER], ) class ObjecttypeVersionsListView(ListMixin, views.APIView): """ @@ -58,7 +81,18 @@ class ObjecttypeVersionsListView(ListMixin, views.APIView): serializer_class = ObjecttypeVersionSerializer def get_objects(self) -> list[dict[str, Any]]: - with get_objecttypes_client() as client: + input_serializer = ObjectsAPIGroupInputSerializer( + data=self.request.query_params + ) + if not input_serializer.is_valid(raise_exception=False): + return [] + + config_group = ( + input_serializer.validated_data.get("objects_api_group") + or ObjectsAPIConfig.get_solo().default_objects_api_group + ) + + with get_objecttypes_client(config_group) as client: return client.list_objecttype_versions(self.kwargs["submission_uuid"]) @@ -85,8 +119,12 @@ def post(self, request: Request, *args: Any, **kwargs: Any): objecttype_uuid = match.group() - with get_objecttypes_client() as client: + config_group = ( + input_serializer.validated_data.get("objects_api_group") + or ObjectsAPIConfig.get_solo().default_objects_api_group + ) + with get_objecttypes_client(config_group) as client: json_schema = client.get_objecttype_version( objecttype_uuid, input_serializer.validated_data["objecttype_version"] )["jsonSchema"] diff --git a/src/openforms/registrations/contrib/objects_api/checks.py b/src/openforms/registrations/contrib/objects_api/checks.py index 9c0d712b3f..67c0fd2237 100644 --- a/src/openforms/registrations/contrib/objects_api/checks.py +++ b/src/openforms/registrations/contrib/objects_api/checks.py @@ -10,93 +10,105 @@ get_objects_client, get_objecttypes_client, ) -from .models import ObjectsAPIConfig +from .models import ObjectsAPIGroupConfig -def check_objects_service(): - with get_objects_client() as client: +def check_objects_service(config: ObjectsAPIGroupConfig): + with get_objects_client(config) as client: resp = client.get("objects", params={"pageSize": 1}) resp.raise_for_status() if not 200 <= resp.status_code < 300: - raise InvalidPluginConfiguration(_("Missing API credentials")) + raise InvalidPluginConfiguration( + _( + "Missing Objects API credentials for Objects API group {objects_api_group}" + ).format(objects_api_group=config.name) + ) -def check_objecttypes_service(): - with get_objecttypes_client() as client: +def check_objecttypes_service(config: ObjectsAPIGroupConfig): + with get_objecttypes_client(config) as client: resp = client.get("objecttypes", params={"pageSize": 1}) resp.raise_for_status() if not 200 <= resp.status_code < 300: - raise InvalidPluginConfiguration(_("Missing API credentials")) + raise InvalidPluginConfiguration( + _( + "Missing Objecttypes API credentials for Objects API group {objects_api_group}" + ).format(objects_api_group=config.name) + ) -def check_documents_service(): - with get_documents_client() as client: +def check_documents_service(config: ObjectsAPIGroupConfig): + with get_documents_client(config) as client: resp = client.get("enkelvoudiginformatieobjecten") resp.raise_for_status() def check_config(): - # First, check that the necessary services are configured correctly. + configs = ObjectsAPIGroupConfig.objects.all() services = ( (check_objects_service, "objects_service"), (check_objecttypes_service, "objecttypes_service"), (check_documents_service, "drc_service"), ) + for config in configs: + # First, check that the necessary services are configured correctly. + for check_function, field_name in services: + api_name = ObjectsAPIGroupConfig._meta.get_field(field_name).verbose_name # type: ignore + try: + check_function(config) + except NoServiceConfigured as exc: + raise InvalidPluginConfiguration( + _( + "{api_name} endpoint is not configured for Objects API group {objects_api_group}." + ).format(api_name=api_name, objects_api_group=config.name) + ) from exc + except requests.RequestException as exc: + raise InvalidPluginConfiguration( + _("Client error: {exception}").format(exception=exc) + ) - for check_function, field_name in services: - api_name = ObjectsAPIConfig._meta.get_field(field_name).verbose_name # type: ignore - try: - check_function() - except NoServiceConfigured as exc: - raise InvalidPluginConfiguration( - _("{api_name} endpoint is not configured.").format(api_name=api_name) - ) from exc - except requests.RequestException as exc: - raise InvalidPluginConfiguration( - _("Client error: {exception}").format(exception=exc) + urls = [ + "objecttype", + "informatieobjecttype_submission_report", + "informatieobjecttype_submission_csv", + "informatieobjecttype_attachment", + ] + # we don't know for sure that these URLs share the same base, so instead we use a + # raw requests session to get connection pooling without tying this to the configured + # services. + with requests.Session() as session: + error_template = _( + "Could not access default '{service_field}' ({url}) for Objects API group {objects_api_group}: {exception}" ) + for service_field in urls: + # these fields are optional defaults, but if configured, they need to point to + # something that looks right. + if not (url := getattr(config, service_field)): + continue + + # this is a deliberate non-authenticated call, a 403 means we're hitting a + # valid endpoint + try: + response = session.get(url) + except requests.RequestException as exc: + raise InvalidPluginConfiguration( + error_template.format( + service_field=service_field, + url=url, + objects_api_group=config.name, + exception=exc, + ) + ) from exc + + # okay, looks like a valid endpoint + if response.status_code in (200, 403) or response.ok: + continue - config = ObjectsAPIConfig.get_solo() - assert isinstance(config, ObjectsAPIConfig) - - urls = [ - "objecttype", - "informatieobjecttype_submission_report", - "informatieobjecttype_submission_csv", - "informatieobjecttype_attachment", - ] - # we don't know for sure that these URLs share the same base, so instead we use a - # raw requests session to get connection pooling without tying this to the configured - # services. - with requests.Session() as session: - error_template = _( - "Could not access default '{service_field}' ({url}): {exception}" - ) - for service_field in urls: - # these fields are optional defaults, but if configured, they need to point to - # something that looks right. - if not (url := getattr(config, service_field)): - continue - - # this is a deliberate non-authenticated call, a 403 means we're hitting a - # valid endpoint - try: - response = session.get(url) - except requests.RequestException as exc: raise InvalidPluginConfiguration( error_template.format( - service_field=service_field, url=url, exception=exc + service_field=service_field, + url=url, + objects_api_group=config.name, + exception=f"HTTP status {response.status_code}", ) - ) from exc - - # okay, looks like a valid endpoint - if response.status_code in (200, 403) or response.ok: - continue - - raise InvalidPluginConfiguration( - error_template.format( - service_field=service_field, - url=url, - exception=f"HTTP status {response.status_code}", ) - ) diff --git a/src/openforms/registrations/contrib/objects_api/client.py b/src/openforms/registrations/contrib/objects_api/client.py index 3b73219dcc..4c30333617 100644 --- a/src/openforms/registrations/contrib/objects_api/client.py +++ b/src/openforms/registrations/contrib/objects_api/client.py @@ -14,32 +14,26 @@ from openforms.contrib.objects_api.clients import ObjectsClient, ObjecttypesClient from openforms.contrib.zgw.clients import DocumentenClient -from .models import ObjectsAPIConfig +from .models import ObjectsAPIGroupConfig class NoServiceConfigured(RuntimeError): pass -def get_objects_client() -> ObjectsClient: - config = ObjectsAPIConfig.get_solo() - assert isinstance(config, ObjectsAPIConfig) +def get_objects_client(config: ObjectsAPIGroupConfig) -> ObjectsClient: if not (service := config.objects_service): raise NoServiceConfigured("No Objects API service configured!") return build_client(service, client_factory=ObjectsClient) -def get_objecttypes_client() -> ObjecttypesClient: - config = ObjectsAPIConfig.get_solo() - assert isinstance(config, ObjectsAPIConfig) +def get_objecttypes_client(config: ObjectsAPIGroupConfig) -> ObjecttypesClient: if not (service := config.objecttypes_service): raise NoServiceConfigured("No Objecttypes API service configured!") return build_client(service, client_factory=ObjecttypesClient) -def get_documents_client() -> DocumentenClient: - config = ObjectsAPIConfig.get_solo() - assert isinstance(config, ObjectsAPIConfig) +def get_documents_client(config: ObjectsAPIGroupConfig) -> DocumentenClient: if not (service := config.drc_service): raise NoServiceConfigured("No Documents API service configured!") return build_client(service, client_factory=DocumentenClient) diff --git a/src/openforms/registrations/contrib/objects_api/config.py b/src/openforms/registrations/contrib/objects_api/config.py index 7a92270dba..a10765f4b4 100644 --- a/src/openforms/registrations/contrib/objects_api/config.py +++ b/src/openforms/registrations/contrib/objects_api/config.py @@ -7,12 +7,15 @@ from rest_framework import serializers from rest_framework.exceptions import ValidationError +from openforms.api.fields import PrimaryKeyRelatedAsChoicesField from openforms.api.utils import get_from_serializer_data_or_instance from openforms.formio.api.fields import FormioVariableKeyField from openforms.template.validators import DjangoTemplateValidator from openforms.utils.mixins import JsonSchemaSerializerMixin from openforms.utils.validators import validate_rsin +from .models import ObjectsAPIGroupConfig + class VersionChoices(models.IntegerChoices): V1 = 1, _("v1, template based") @@ -49,7 +52,14 @@ class ObjecttypeVariableMappingSerializer(serializers.Serializer): class ObjectsAPIOptionsSerializer(JsonSchemaSerializerMixin, serializers.Serializer): - + objects_api_group = PrimaryKeyRelatedAsChoicesField( + queryset=ObjectsAPIGroupConfig.objects.all(), + label=("Objects API group"), + help_text=_( + "Which Objects API group to use. If not provided, the default Objects API group will be used." + ), + required=False, + ) version = serializers.ChoiceField( label=_("options version"), help_text=_( diff --git a/src/openforms/registrations/contrib/objects_api/migrations/0016_objectsapigroupconfig.py b/src/openforms/registrations/contrib/objects_api/migrations/0016_objectsapigroupconfig.py new file mode 100644 index 0000000000..25ae169fcd --- /dev/null +++ b/src/openforms/registrations/contrib/objects_api/migrations/0016_objectsapigroupconfig.py @@ -0,0 +1,183 @@ +# Generated by Django 4.2.11 on 2024-05-27 13:04 + +from django.db import migrations, models +import django.db.models.deletion +import openforms.registrations.contrib.objects_api.models +import openforms.template.validators +import openforms.utils.validators + + +class Migration(migrations.Migration): + + dependencies = [ + ("zgw_consumers", "0020_service_timeout"), + ( + "registrations_objects_api", + "0015_alter_objectsapiconfig_objecttype_and_more", + ), + ] + + operations = [ + migrations.CreateModel( + name="ObjectsAPIGroupConfig", + fields=[ + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "name", + models.CharField( + help_text="A recognisable name for this set of Objects APIs.", + max_length=255, + verbose_name="name", + ), + ), + ( + "objecttype", + models.URLField( + blank=True, + editable=False, + help_text="Default URL of the ProductAanvraag OBJECTTYPE in the Objecttypes API. The objecttype should have the following three attributes: 1) submission_id; 2) type (the type of productaanvraag); 3) data (the submitted form data)", + max_length=1000, + verbose_name="objecttype", + ), + ), + ( + "objecttype_version", + models.IntegerField( + blank=True, + editable=False, + help_text="Default version of the OBJECTTYPE in the Objecttypes API", + null=True, + verbose_name="objecttype version", + ), + ), + ( + "productaanvraag_type", + models.CharField( + blank=True, + help_text="Description of the 'ProductAanvraag' type. This value is saved in the 'type' attribute of the 'ProductAanvraag'.", + max_length=255, + verbose_name="Productaanvraag type", + ), + ), + ( + "informatieobjecttype_submission_report", + models.URLField( + blank=True, + help_text="Default URL that points to the INFORMATIEOBJECTTYPE in the Catalogi API to be used for the submission report PDF", + max_length=1000, + verbose_name="submission report informatieobjecttype", + ), + ), + ( + "informatieobjecttype_submission_csv", + models.URLField( + blank=True, + help_text="Default URL that points to the INFORMATIEOBJECTTYPE in the Catalogi API to be used for the submission report CSV", + max_length=1000, + verbose_name="submission report CSV informatieobjecttype", + ), + ), + ( + "informatieobjecttype_attachment", + models.URLField( + blank=True, + help_text="Default URL that points to the INFORMATIEOBJECTTYPE in the Catalogi API to be used for the submission attachments", + max_length=1000, + verbose_name="attachment informatieobjecttype", + ), + ), + ( + "organisatie_rsin", + models.CharField( + blank=True, + help_text="Default RSIN of organization, which creates the INFORMATIEOBJECT", + max_length=9, + validators=[openforms.utils.validators.RSINValidator()], + verbose_name="organisation RSIN", + ), + ), + ( + "content_json", + models.TextField( + default=openforms.registrations.contrib.objects_api.models.get_content_text, + help_text="This template is evaluated with the submission data and the resulting JSON is sent to the objects API.", + validators=[ + openforms.template.validators.DjangoTemplateValidator( + backend="openforms.template.openforms_backend" + ) + ], + verbose_name="JSON content template", + ), + ), + ( + "payment_status_update_json", + models.TextField( + default=openforms.registrations.contrib.objects_api.models.get_payment_status_update_text, + help_text="This template is evaluated with the submission data and the resulting JSON is sent to the objects API with a PATCH to update the payment field.", + validators=[ + openforms.template.validators.DjangoTemplateValidator( + backend="openforms.template.openforms_backend" + ) + ], + verbose_name="payment status update JSON template", + ), + ), + ( + "catalogi_service", + models.ForeignKey( + limit_choices_to={"api_type": "ztc"}, + null=True, + on_delete=django.db.models.deletion.PROTECT, + related_name="+", + to="zgw_consumers.service", + verbose_name="Catalogi API", + ), + ), + ( + "drc_service", + models.ForeignKey( + limit_choices_to={"api_type": "drc"}, + null=True, + on_delete=django.db.models.deletion.PROTECT, + related_name="+", + to="zgw_consumers.service", + verbose_name="Documenten API", + ), + ), + ( + "objects_service", + models.ForeignKey( + limit_choices_to={"api_type": "orc"}, + null=True, + on_delete=django.db.models.deletion.PROTECT, + related_name="+", + to="zgw_consumers.service", + verbose_name="Objects API", + ), + ), + ( + "objecttypes_service", + models.ForeignKey( + limit_choices_to={"api_type": "orc"}, + null=True, + on_delete=django.db.models.deletion.PROTECT, + related_name="+", + to="zgw_consumers.service", + verbose_name="Objecttypes API", + ), + ), + ], + options={ + "verbose_name": "Objects API group", + "verbose_name_plural": "Objects API groups", + }, + ), + ] diff --git a/src/openforms/registrations/contrib/objects_api/migrations/0017_move_singleton_data.py b/src/openforms/registrations/contrib/objects_api/migrations/0017_move_singleton_data.py new file mode 100644 index 0000000000..7502173c12 --- /dev/null +++ b/src/openforms/registrations/contrib/objects_api/migrations/0017_move_singleton_data.py @@ -0,0 +1,89 @@ +# Generated by Django 4.2.11 on 2024-05-27 13:06 + +from django.db import migrations + +from django.db.migrations.state import StateApps +from django.db.backends.base.schema import BaseDatabaseSchemaEditor + + +def move_singleton_data_to_objects_api_set_model( + apps: StateApps, schema_editor: BaseDatabaseSchemaEditor +) -> None: + ObjectsAPIConfig = apps.get_model("registrations_objects_api", "ObjectsAPIConfig") + ObjectsAPIGroupConfig = apps.get_model( + "registrations_objects_api", "ObjectsAPIGroupConfig" + ) + + solo_model = ObjectsAPIConfig.objects.first() + if not solo_model: + return + + new_config_set = ObjectsAPIGroupConfig( + name="Objects APIs", + objects_service=solo_model.objects_service, + objecttypes_service=solo_model.objecttypes_service, + drc_service=solo_model.drc_service, + catalogi_service=solo_model.catalogi_service, + objecttype=solo_model.objecttype, + objecttype_version=solo_model.objecttype_version, + productaanvraag_type=solo_model.productaanvraag_type, + informatieobjecttype_submission_report=solo_model.informatieobjecttype_submission_report, + informatieobjecttype_submission_csv=solo_model.informatieobjecttype_submission_csv, + informatieobjecttype_attachment=solo_model.informatieobjecttype_attachment, + organisatie_rsin=solo_model.organisatie_rsin, + content_json=solo_model.content_json, + payment_status_update_json=solo_model.payment_status_update_json, + ) + + new_config_set.save() + + +def reconfigure_objects_api_solo_model( + apps: StateApps, schema_editor: BaseDatabaseSchemaEditor +) -> None: + ObjectsAPIConfig = apps.get_model("registrations_objects_api", "ObjectsAPIConfig") + ObjectsAPIGroupConfig = apps.get_model( + "registrations_objects_api", "ObjectsAPIGroupConfig" + ) + + solo_model = ObjectsAPIConfig.objects.first() + objects_api_group = ObjectsAPIGroupConfig.objects.all().order_by("pk").first() + + if not solo_model or not objects_api_group: + return + + solo_model.objects_service = objects_api_group.objects_service + solo_model.objecttypes_service = objects_api_group.objecttypes_service + solo_model.drc_service = objects_api_group.drc_service + solo_model.catalogi_service = objects_api_group.catalogi_service + solo_model.objecttype = objects_api_group.objecttype + solo_model.objecttype_version = objects_api_group.objecttype_version + solo_model.productaanvraag_type = objects_api_group.productaanvraag_type + solo_model.informatieobjecttype_submission_report = ( + objects_api_group.informatieobjecttype_submission_report + ) + solo_model.informatieobjecttype_submission_csv = ( + objects_api_group.informatieobjecttype_submission_csv + ) + solo_model.informatieobjecttype_attachment = ( + objects_api_group.informatieobjecttype_attachment + ) + solo_model.organisatie_rsin = objects_api_group.organisatie_rsin + solo_model.organisatie_rsin = objects_api_group.organisatie_rsin + solo_model.content_json = objects_api_group.content_json + solo_model.payment_status_update_json = objects_api_group.payment_status_update_json + solo_model.save() + + +class Migration(migrations.Migration): + + dependencies = [ + ("registrations_objects_api", "0016_objectsapigroupconfig"), + ] + + operations = [ + migrations.RunPython( + move_singleton_data_to_objects_api_set_model, + reconfigure_objects_api_solo_model, + ), + ] diff --git a/src/openforms/registrations/contrib/objects_api/migrations/0018_alter_objectsapiconfig_options_and_more.py b/src/openforms/registrations/contrib/objects_api/migrations/0018_alter_objectsapiconfig_options_and_more.py new file mode 100644 index 0000000000..3f9a48f904 --- /dev/null +++ b/src/openforms/registrations/contrib/objects_api/migrations/0018_alter_objectsapiconfig_options_and_more.py @@ -0,0 +1,81 @@ +# Generated by Django 4.2.11 on 2024-05-27 15:02 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ("registrations_objects_api", "0017_move_singleton_data"), + ] + + operations = [ + migrations.AlterModelOptions( + name="objectsapiconfig", + options={"verbose_name": "Objects APIs configuration"}, + ), + migrations.RemoveField( + model_name="objectsapiconfig", + name="catalogi_service", + ), + migrations.RemoveField( + model_name="objectsapiconfig", + name="content_json", + ), + migrations.RemoveField( + model_name="objectsapiconfig", + name="drc_service", + ), + migrations.RemoveField( + model_name="objectsapiconfig", + name="informatieobjecttype_attachment", + ), + migrations.RemoveField( + model_name="objectsapiconfig", + name="informatieobjecttype_submission_csv", + ), + migrations.RemoveField( + model_name="objectsapiconfig", + name="informatieobjecttype_submission_report", + ), + migrations.RemoveField( + model_name="objectsapiconfig", + name="objects_service", + ), + migrations.RemoveField( + model_name="objectsapiconfig", + name="objecttype", + ), + migrations.RemoveField( + model_name="objectsapiconfig", + name="objecttype_version", + ), + migrations.RemoveField( + model_name="objectsapiconfig", + name="objecttypes_service", + ), + migrations.RemoveField( + model_name="objectsapiconfig", + name="organisatie_rsin", + ), + migrations.RemoveField( + model_name="objectsapiconfig", + name="payment_status_update_json", + ), + migrations.RemoveField( + model_name="objectsapiconfig", + name="productaanvraag_type", + ), + migrations.AddField( + model_name="objectsapiconfig", + name="default_objects_api_group", + field=models.ForeignKey( + help_text="Which Objects API group should be used by default.", + null=True, + on_delete=django.db.models.deletion.PROTECT, + to="registrations_objects_api.objectsapigroupconfig", + verbose_name="default Objects API group.", + ), + ), + ] diff --git a/src/openforms/registrations/contrib/objects_api/migrations/0019_add_default.py b/src/openforms/registrations/contrib/objects_api/migrations/0019_add_default.py new file mode 100644 index 0000000000..3b36298d02 --- /dev/null +++ b/src/openforms/registrations/contrib/objects_api/migrations/0019_add_default.py @@ -0,0 +1,39 @@ +# Generated by Django 4.2.11 on 2024-05-28 10:14 + +from django.db import migrations + + +from django.db.migrations.state import StateApps +from django.db.backends.base.schema import BaseDatabaseSchemaEditor + + +def attach_default_objects_api_group( + apps: StateApps, schema_editor: BaseDatabaseSchemaEditor +) -> None: + ObjectsAPIConfig = apps.get_model("registrations_objects_api", "ObjectsAPIConfig") + ObjectsAPIGroupConfig = apps.get_model( + "registrations_objects_api", "ObjectsAPIGroupConfig" + ) + + # There should be at most one (since it was created by migration 0017_move_singleton_data). + # But in case that there are multiple, we just pick one. + objects_api_group = ObjectsAPIGroupConfig.objects.first() + if not objects_api_group: + return + + solo_config = ObjectsAPIConfig.objects.get() + solo_config.default_objects_api_group = objects_api_group + solo_config.save() + + +class Migration(migrations.Migration): + + dependencies = [ + ("registrations_objects_api", "0018_alter_objectsapiconfig_options_and_more"), + ] + + operations = [ + migrations.RunPython( + attach_default_objects_api_group, migrations.RunPython.noop + ), + ] diff --git a/src/openforms/registrations/contrib/objects_api/models.py b/src/openforms/registrations/contrib/objects_api/models.py index 461054ae93..6769d81321 100644 --- a/src/openforms/registrations/contrib/objects_api/models.py +++ b/src/openforms/registrations/contrib/objects_api/models.py @@ -25,10 +25,28 @@ def get_payment_status_update_text() -> str: class ObjectsAPIConfig(SingletonModel): """ - global configuration and defaults + Entry point to the default Objects APIs set. """ - objects_service = models.OneToOneField( + default_objects_api_group = models.ForeignKey( + to="ObjectsAPIGroupConfig", + on_delete=models.PROTECT, + verbose_name=_("default Objects API group."), + help_text=_("Which Objects API group should be used by default."), + null=True, + ) + + class Meta: + verbose_name = _("Objects APIs configuration") + + +class ObjectsAPIGroupConfig(models.Model): + name = models.CharField( + _("name"), + max_length=255, + help_text=_("A recognisable name for this set of Objects APIs."), + ) + objects_service = models.ForeignKey( "zgw_consumers.Service", verbose_name=_("Objects API"), on_delete=models.PROTECT, @@ -36,7 +54,7 @@ class ObjectsAPIConfig(SingletonModel): null=True, related_name="+", ) - objecttypes_service = models.OneToOneField( + objecttypes_service = models.ForeignKey( "zgw_consumers.Service", verbose_name=_("Objecttypes API"), on_delete=models.PROTECT, @@ -44,7 +62,7 @@ class ObjectsAPIConfig(SingletonModel): null=True, related_name="+", ) - drc_service = models.OneToOneField( + drc_service = models.ForeignKey( "zgw_consumers.Service", verbose_name=_("Documenten API"), on_delete=models.PROTECT, @@ -52,7 +70,7 @@ class ObjectsAPIConfig(SingletonModel): null=True, related_name="+", ) - catalogi_service = models.OneToOneField( + catalogi_service = models.ForeignKey( "zgw_consumers.Service", verbose_name=_("Catalogi API"), on_delete=models.PROTECT, @@ -153,7 +171,11 @@ class ObjectsAPIConfig(SingletonModel): ) class Meta: - verbose_name = _("Objects API configuration") + verbose_name = _("Objects API group") + verbose_name_plural = _("Objects API groups") + + def __str__(self) -> str: + return self.name def clean(self) -> None: super().clean() @@ -174,6 +196,7 @@ def clean(self) -> None: ) def apply_defaults_to(self, options): + options.setdefault("objects_api_group", self) options.setdefault("version", 1) options.setdefault("objecttype", self.objecttype) options.setdefault("objecttype_version", self.objecttype_version) diff --git a/src/openforms/registrations/contrib/objects_api/plugin.py b/src/openforms/registrations/contrib/objects_api/plugin.py index ae1bc5d259..20647e061b 100644 --- a/src/openforms/registrations/contrib/objects_api/plugin.py +++ b/src/openforms/registrations/contrib/objects_api/plugin.py @@ -17,7 +17,7 @@ from .checks import check_config from .client import get_objects_client from .config import ObjectsAPIOptionsSerializer -from .models import ObjectsAPIConfig +from .models import ObjectsAPIConfig, ObjectsAPIGroupConfig from .registration_variables import register as variables_registry from .submission_registration import HANDLER_MAPPING from .typing import RegistrationOptions @@ -49,6 +49,15 @@ class ObjectsAPIRegistration(BasePlugin): verbose_name = _("Objects API registration") configuration_options = ObjectsAPIOptionsSerializer + @staticmethod + def get_objects_api_config(options: RegistrationOptions) -> ObjectsAPIGroupConfig: + objects_api_group = options.get("objects_api_group") + if objects_api_group is None: + config = ObjectsAPIConfig.get_solo() + assert isinstance(config, ObjectsAPIConfig) + objects_api_group = config.default_objects_api_group + return objects_api_group # type: ignore | can it really be None? + @override def register_submission( self, submission: Submission, options: RegistrationOptions @@ -59,8 +68,7 @@ def register_submission( will be created differently. The actual logic lives in the ``submission_registration`` submodule. """ - config = ObjectsAPIConfig.get_solo() - assert isinstance(config, ObjectsAPIConfig) + config = self.get_objects_api_config(options) config.apply_defaults_to(options) handler = HANDLER_MAPPING[options["version"]] @@ -72,7 +80,7 @@ def register_submission( options=options, ) - with get_objects_client() as objects_client: + with get_objects_client(config) as objects_client: response = execute_unless_result_exists( partial(objects_client.create_object, object_data=object_data), submission, @@ -108,8 +116,7 @@ def get_custom_templatetags_libraries(self) -> list[str]: def update_payment_status( self, submission: Submission, options: RegistrationOptions ) -> dict[str, Any] | None: - config = ObjectsAPIConfig.get_solo() - assert isinstance(config, ObjectsAPIConfig) + config = self.get_objects_api_config(options) config.apply_defaults_to(options) handler = HANDLER_MAPPING[options["version"]] @@ -121,7 +128,7 @@ def update_payment_status( return object_url = submission.registration_result["url"] - with get_objects_client() as objects_client: + with get_objects_client(config) as objects_client: response = objects_client.patch( url=object_url, json=updated_object_data, diff --git a/src/openforms/registrations/contrib/objects_api/submission_registration.py b/src/openforms/registrations/contrib/objects_api/submission_registration.py index 617ec1de68..71a5200087 100644 --- a/src/openforms/registrations/contrib/objects_api/submission_registration.py +++ b/src/openforms/registrations/contrib/objects_api/submission_registration.py @@ -219,7 +219,7 @@ def save_registration_data( submission_attachments: list[ObjectsAPISubmissionAttachment] = [] with ( - get_documents_client() as documents_client, + get_documents_client(options["objects_api_group"]) as documents_client, save_and_raise(registration_data, submission_attachments), ): if not registration_data.pdf_url: diff --git a/src/openforms/registrations/contrib/objects_api/tests/files/vcr_cassettes/ObjecttypeVersionsAPIEndpointTests/ObjecttypeVersionsAPIEndpointTests.test_list_objecttype_verions_unknown_objecttype.yaml b/src/openforms/registrations/contrib/objects_api/tests/files/vcr_cassettes/ObjecttypeVersionsAPIEndpointTests/ObjecttypeVersionsAPIEndpointTests.test_list_objecttype_versions_unknown_objecttype.yaml similarity index 100% rename from src/openforms/registrations/contrib/objects_api/tests/files/vcr_cassettes/ObjecttypeVersionsAPIEndpointTests/ObjecttypeVersionsAPIEndpointTests.test_list_objecttype_verions_unknown_objecttype.yaml rename to src/openforms/registrations/contrib/objects_api/tests/files/vcr_cassettes/ObjecttypeVersionsAPIEndpointTests/ObjecttypeVersionsAPIEndpointTests.test_list_objecttype_versions_unknown_objecttype.yaml diff --git a/src/openforms/registrations/contrib/objects_api/tests/files/vcr_cassettes/ObjecttypeVersionsAPIEndpointsTests/ObjecttypeVersionsAPIEndpointsTests.test_list_objecttype_verions_unknown_objecttype.yaml b/src/openforms/registrations/contrib/objects_api/tests/files/vcr_cassettes/ObjecttypeVersionsAPIEndpointsTests/ObjecttypeVersionsAPIEndpointsTests.test_list_objecttype_verions_unknown_objecttype.yaml deleted file mode 100644 index bbc4f89c37..0000000000 --- a/src/openforms/registrations/contrib/objects_api/tests/files/vcr_cassettes/ObjecttypeVersionsAPIEndpointsTests/ObjecttypeVersionsAPIEndpointsTests.test_list_objecttype_verions_unknown_objecttype.yaml +++ /dev/null @@ -1,36 +0,0 @@ -interactions: -- request: - body: null - headers: - Accept: - - '*/*' - Accept-Encoding: - - gzip, deflate, br - Authorization: - - Token 171be5abaf41e7856b423ad513df1ef8f867ff48 - Connection: - - keep-alive - User-Agent: - - python-requests/2.31.0 - method: GET - uri: http://localhost:8001/api/v2/objecttypes/39da819c-ac6c-4037-ae2b-6bfc39f6564b/versions - response: - body: - string: '{"count":0,"next":null,"previous":null,"results":[]}' - headers: - Allow: - - GET, POST, HEAD, OPTIONS - Content-Length: - - '52' - Content-Type: - - application/json - Referrer-Policy: - - same-origin - X-Content-Type-Options: - - nosniff - X-Frame-Options: - - DENY - status: - code: 200 - message: OK -version: 1 diff --git a/src/openforms/registrations/contrib/objects_api/tests/files/vcr_cassettes/ObjecttypeVersionsAPIEndpointsTests/ObjecttypeVersionsAPIEndpointsTests.test_list_objecttype_versions.yaml b/src/openforms/registrations/contrib/objects_api/tests/files/vcr_cassettes/ObjecttypeVersionsAPIEndpointsTests/ObjecttypeVersionsAPIEndpointsTests.test_list_objecttype_versions.yaml deleted file mode 100644 index f89fab844b..0000000000 --- a/src/openforms/registrations/contrib/objects_api/tests/files/vcr_cassettes/ObjecttypeVersionsAPIEndpointsTests/ObjecttypeVersionsAPIEndpointsTests.test_list_objecttype_versions.yaml +++ /dev/null @@ -1,37 +0,0 @@ -interactions: -- request: - body: null - headers: - Accept: - - '*/*' - Accept-Encoding: - - gzip, deflate, br - Authorization: - - Token 171be5abaf41e7856b423ad513df1ef8f867ff48 - Connection: - - keep-alive - User-Agent: - - python-requests/2.31.0 - method: GET - uri: http://localhost:8001/api/v2/objecttypes/3edfdaf7-f469-470b-a391-bb7ea015bd6f/versions - response: - body: - string: '{"count":1,"next":null,"previous":null,"results":[{"url":"http://localhost:8001/api/v2/objecttypes/3edfdaf7-f469-470b-a391-bb7ea015bd6f/versions/1","version":1,"objectType":"http://localhost:8001/api/v2/objecttypes/3edfdaf7-f469-470b-a391-bb7ea015bd6f","status":"published","jsonSchema":{"$id":"https://example.com/tree.schema.json","type":"object","title":"Tree","$schema":"https://json-schema.org/draft/2020-12/schema","properties":{"height":{"type":"integer","description":"The - height of the tree."}}},"createdAt":"2024-02-08","modifiedAt":"2024-02-08","publishedAt":"2024-02-08"}]}' - headers: - Allow: - - GET, POST, HEAD, OPTIONS - Content-Length: - - '585' - Content-Type: - - application/json - Referrer-Policy: - - same-origin - X-Content-Type-Options: - - nosniff - X-Frame-Options: - - DENY - status: - code: 200 - message: OK -version: 1 diff --git a/src/openforms/registrations/contrib/objects_api/tests/files/vcr_cassettes/ObjecttypeVersionsAPIEndpointsTests/ObjecttypeVersionsAPIEndpointsTests.test_staff_user_required.yaml b/src/openforms/registrations/contrib/objects_api/tests/files/vcr_cassettes/ObjecttypeVersionsAPIEndpointsTests/ObjecttypeVersionsAPIEndpointsTests.test_staff_user_required.yaml deleted file mode 100644 index f89fab844b..0000000000 --- a/src/openforms/registrations/contrib/objects_api/tests/files/vcr_cassettes/ObjecttypeVersionsAPIEndpointsTests/ObjecttypeVersionsAPIEndpointsTests.test_staff_user_required.yaml +++ /dev/null @@ -1,37 +0,0 @@ -interactions: -- request: - body: null - headers: - Accept: - - '*/*' - Accept-Encoding: - - gzip, deflate, br - Authorization: - - Token 171be5abaf41e7856b423ad513df1ef8f867ff48 - Connection: - - keep-alive - User-Agent: - - python-requests/2.31.0 - method: GET - uri: http://localhost:8001/api/v2/objecttypes/3edfdaf7-f469-470b-a391-bb7ea015bd6f/versions - response: - body: - string: '{"count":1,"next":null,"previous":null,"results":[{"url":"http://localhost:8001/api/v2/objecttypes/3edfdaf7-f469-470b-a391-bb7ea015bd6f/versions/1","version":1,"objectType":"http://localhost:8001/api/v2/objecttypes/3edfdaf7-f469-470b-a391-bb7ea015bd6f","status":"published","jsonSchema":{"$id":"https://example.com/tree.schema.json","type":"object","title":"Tree","$schema":"https://json-schema.org/draft/2020-12/schema","properties":{"height":{"type":"integer","description":"The - height of the tree."}}},"createdAt":"2024-02-08","modifiedAt":"2024-02-08","publishedAt":"2024-02-08"}]}' - headers: - Allow: - - GET, POST, HEAD, OPTIONS - Content-Length: - - '585' - Content-Type: - - application/json - Referrer-Policy: - - same-origin - X-Content-Type-Options: - - nosniff - X-Frame-Options: - - DENY - status: - code: 200 - message: OK -version: 1 diff --git a/src/openforms/registrations/contrib/objects_api/tests/files/vcr_cassettes/ObjecttypesAPIEndpointTests/ObjecttypesAPIEndpointTests.test_list_objecttypes.yaml b/src/openforms/registrations/contrib/objects_api/tests/files/vcr_cassettes/ObjecttypesAPIEndpointTests/ObjecttypesAPIEndpointTests.test_list_objecttypes.yaml deleted file mode 100644 index cdabace3c7..0000000000 --- a/src/openforms/registrations/contrib/objects_api/tests/files/vcr_cassettes/ObjecttypesAPIEndpointTests/ObjecttypesAPIEndpointTests.test_list_objecttypes.yaml +++ /dev/null @@ -1,36 +0,0 @@ -interactions: -- request: - body: null - headers: - Accept: - - '*/*' - Accept-Encoding: - - gzip, deflate, br - Authorization: - - Token 171be5abaf41e7856b423ad513df1ef8f867ff48 - Connection: - - keep-alive - User-Agent: - - python-requests/2.31.0 - method: GET - uri: http://localhost:8001/api/v2/objecttypes - response: - body: - string: '{"count":2,"next":null,"previous":null,"results":[{"url":"http://localhost:8001/api/v2/objecttypes/3edfdaf7-f469-470b-a391-bb7ea015bd6f","uuid":"3edfdaf7-f469-470b-a391-bb7ea015bd6f","name":"Tree","namePlural":"Trees","description":"","dataClassification":"confidential","maintainerOrganization":"","maintainerDepartment":"","contactPerson":"","contactEmail":"","source":"","updateFrequency":"unknown","providerOrganization":"","documentationUrl":"","labels":{},"createdAt":"2024-02-08","modifiedAt":"2024-02-08","allowGeometry":true,"versions":["http://localhost:8001/api/v2/objecttypes/3edfdaf7-f469-470b-a391-bb7ea015bd6f/versions/1"]},{"url":"http://localhost:8001/api/v2/objecttypes/8e46e0a5-b1b4-449b-b9e9-fa3cea655f48","uuid":"8e46e0a5-b1b4-449b-b9e9-fa3cea655f48","name":"Person","namePlural":"Persons","description":"","dataClassification":"open","maintainerOrganization":"","maintainerDepartment":"","contactPerson":"","contactEmail":"","source":"","updateFrequency":"unknown","providerOrganization":"","documentationUrl":"","labels":{},"createdAt":"2023-10-24","modifiedAt":"2024-02-08","allowGeometry":true,"versions":["http://localhost:8001/api/v2/objecttypes/8e46e0a5-b1b4-449b-b9e9-fa3cea655f48/versions/1","http://localhost:8001/api/v2/objecttypes/8e46e0a5-b1b4-449b-b9e9-fa3cea655f48/versions/2","http://localhost:8001/api/v2/objecttypes/8e46e0a5-b1b4-449b-b9e9-fa3cea655f48/versions/3"]}]}' - headers: - Allow: - - GET, POST, HEAD, OPTIONS - Content-Length: - - '1407' - Content-Type: - - application/json - Referrer-Policy: - - same-origin - X-Content-Type-Options: - - nosniff - X-Frame-Options: - - DENY - status: - code: 200 - message: OK -version: 1 diff --git a/src/openforms/registrations/contrib/objects_api/tests/files/vcr_cassettes/ObjecttypesAPIEndpointTests/ObjecttypesAPIEndpointTests.test_list_objecttypes_default_objects_api_group.yaml b/src/openforms/registrations/contrib/objects_api/tests/files/vcr_cassettes/ObjecttypesAPIEndpointTests/ObjecttypesAPIEndpointTests.test_list_objecttypes_default_objects_api_group.yaml new file mode 100644 index 0000000000..a02d0b7bb3 --- /dev/null +++ b/src/openforms/registrations/contrib/objects_api/tests/files/vcr_cassettes/ObjecttypesAPIEndpointTests/ObjecttypesAPIEndpointTests.test_list_objecttypes_default_objects_api_group.yaml @@ -0,0 +1,39 @@ +interactions: +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate, br + Authorization: + - Token 171be5abaf41e7856b423ad513df1ef8f867ff48 + Connection: + - keep-alive + User-Agent: + - python-requests/2.31.0 + method: GET + uri: http://localhost:8001/api/v2/objecttypes + response: + body: + string: '{"count":5,"next":null,"previous":null,"results":[{"url":"http://localhost:8001/api/v2/objecttypes/644ab597-e88c-43c0-8321-f12113510b0e","uuid":"644ab597-e88c-43c0-8321-f12113510b0e","name":"Fieldset + component","namePlural":"Fieldset component","description":"","dataClassification":"confidential","maintainerOrganization":"","maintainerDepartment":"","contactPerson":"","contactEmail":"","source":"","updateFrequency":"unknown","providerOrganization":"","documentationUrl":"","labels":{},"createdAt":"2024-02-08","modifiedAt":"2024-02-08","allowGeometry":true,"versions":["http://localhost:8001/api/v2/objecttypes/644ab597-e88c-43c0-8321-f12113510b0e/versions/1"]},{"url":"http://localhost:8001/api/v2/objecttypes/f1dde4fe-b7f9-46dc-84ae-429ae49e3705","uuid":"f1dde4fe-b7f9-46dc-84ae-429ae49e3705","name":"Geo + in data","namePlural":"Geo in data","description":"","dataClassification":"confidential","maintainerOrganization":"","maintainerDepartment":"","contactPerson":"","contactEmail":"","source":"","updateFrequency":"unknown","providerOrganization":"","documentationUrl":"","labels":{},"createdAt":"2024-02-08","modifiedAt":"2024-02-08","allowGeometry":true,"versions":["http://localhost:8001/api/v2/objecttypes/f1dde4fe-b7f9-46dc-84ae-429ae49e3705/versions/1"]},{"url":"http://localhost:8001/api/v2/objecttypes/527b8408-7421-4808-a744-43ccb7bdaaa2","uuid":"527b8408-7421-4808-a744-43ccb7bdaaa2","name":"File + Uploads","namePlural":"File Uploads","description":"","dataClassification":"confidential","maintainerOrganization":"","maintainerDepartment":"","contactPerson":"","contactEmail":"","source":"","updateFrequency":"unknown","providerOrganization":"","documentationUrl":"","labels":{},"createdAt":"2024-02-08","modifiedAt":"2024-02-08","allowGeometry":true,"versions":["http://localhost:8001/api/v2/objecttypes/527b8408-7421-4808-a744-43ccb7bdaaa2/versions/1"]},{"url":"http://localhost:8001/api/v2/objecttypes/3edfdaf7-f469-470b-a391-bb7ea015bd6f","uuid":"3edfdaf7-f469-470b-a391-bb7ea015bd6f","name":"Tree","namePlural":"Trees","description":"","dataClassification":"confidential","maintainerOrganization":"","maintainerDepartment":"","contactPerson":"","contactEmail":"","source":"","updateFrequency":"unknown","providerOrganization":"","documentationUrl":"","labels":{},"createdAt":"2024-02-08","modifiedAt":"2024-02-08","allowGeometry":true,"versions":["http://localhost:8001/api/v2/objecttypes/3edfdaf7-f469-470b-a391-bb7ea015bd6f/versions/1"]},{"url":"http://localhost:8001/api/v2/objecttypes/8e46e0a5-b1b4-449b-b9e9-fa3cea655f48","uuid":"8e46e0a5-b1b4-449b-b9e9-fa3cea655f48","name":"Person","namePlural":"Persons","description":"","dataClassification":"open","maintainerOrganization":"","maintainerDepartment":"","contactPerson":"","contactEmail":"","source":"","updateFrequency":"unknown","providerOrganization":"","documentationUrl":"","labels":{},"createdAt":"2023-10-24","modifiedAt":"2024-02-08","allowGeometry":true,"versions":["http://localhost:8001/api/v2/objecttypes/8e46e0a5-b1b4-449b-b9e9-fa3cea655f48/versions/3","http://localhost:8001/api/v2/objecttypes/8e46e0a5-b1b4-449b-b9e9-fa3cea655f48/versions/1","http://localhost:8001/api/v2/objecttypes/8e46e0a5-b1b4-449b-b9e9-fa3cea655f48/versions/2"]}]}' + headers: + Allow: + - GET, POST, HEAD, OPTIONS + Content-Length: + - '3229' + Content-Type: + - application/json + Referrer-Policy: + - same-origin + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + status: + code: 200 + message: OK +version: 1 diff --git a/src/openforms/registrations/contrib/objects_api/tests/files/vcr_cassettes/ObjecttypesAPIEndpointTests/ObjecttypesAPIEndpointTests.test_list_objecttypes_explicit_objects_api_group.yaml b/src/openforms/registrations/contrib/objects_api/tests/files/vcr_cassettes/ObjecttypesAPIEndpointTests/ObjecttypesAPIEndpointTests.test_list_objecttypes_explicit_objects_api_group.yaml new file mode 100644 index 0000000000..a02d0b7bb3 --- /dev/null +++ b/src/openforms/registrations/contrib/objects_api/tests/files/vcr_cassettes/ObjecttypesAPIEndpointTests/ObjecttypesAPIEndpointTests.test_list_objecttypes_explicit_objects_api_group.yaml @@ -0,0 +1,39 @@ +interactions: +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate, br + Authorization: + - Token 171be5abaf41e7856b423ad513df1ef8f867ff48 + Connection: + - keep-alive + User-Agent: + - python-requests/2.31.0 + method: GET + uri: http://localhost:8001/api/v2/objecttypes + response: + body: + string: '{"count":5,"next":null,"previous":null,"results":[{"url":"http://localhost:8001/api/v2/objecttypes/644ab597-e88c-43c0-8321-f12113510b0e","uuid":"644ab597-e88c-43c0-8321-f12113510b0e","name":"Fieldset + component","namePlural":"Fieldset component","description":"","dataClassification":"confidential","maintainerOrganization":"","maintainerDepartment":"","contactPerson":"","contactEmail":"","source":"","updateFrequency":"unknown","providerOrganization":"","documentationUrl":"","labels":{},"createdAt":"2024-02-08","modifiedAt":"2024-02-08","allowGeometry":true,"versions":["http://localhost:8001/api/v2/objecttypes/644ab597-e88c-43c0-8321-f12113510b0e/versions/1"]},{"url":"http://localhost:8001/api/v2/objecttypes/f1dde4fe-b7f9-46dc-84ae-429ae49e3705","uuid":"f1dde4fe-b7f9-46dc-84ae-429ae49e3705","name":"Geo + in data","namePlural":"Geo in data","description":"","dataClassification":"confidential","maintainerOrganization":"","maintainerDepartment":"","contactPerson":"","contactEmail":"","source":"","updateFrequency":"unknown","providerOrganization":"","documentationUrl":"","labels":{},"createdAt":"2024-02-08","modifiedAt":"2024-02-08","allowGeometry":true,"versions":["http://localhost:8001/api/v2/objecttypes/f1dde4fe-b7f9-46dc-84ae-429ae49e3705/versions/1"]},{"url":"http://localhost:8001/api/v2/objecttypes/527b8408-7421-4808-a744-43ccb7bdaaa2","uuid":"527b8408-7421-4808-a744-43ccb7bdaaa2","name":"File + Uploads","namePlural":"File Uploads","description":"","dataClassification":"confidential","maintainerOrganization":"","maintainerDepartment":"","contactPerson":"","contactEmail":"","source":"","updateFrequency":"unknown","providerOrganization":"","documentationUrl":"","labels":{},"createdAt":"2024-02-08","modifiedAt":"2024-02-08","allowGeometry":true,"versions":["http://localhost:8001/api/v2/objecttypes/527b8408-7421-4808-a744-43ccb7bdaaa2/versions/1"]},{"url":"http://localhost:8001/api/v2/objecttypes/3edfdaf7-f469-470b-a391-bb7ea015bd6f","uuid":"3edfdaf7-f469-470b-a391-bb7ea015bd6f","name":"Tree","namePlural":"Trees","description":"","dataClassification":"confidential","maintainerOrganization":"","maintainerDepartment":"","contactPerson":"","contactEmail":"","source":"","updateFrequency":"unknown","providerOrganization":"","documentationUrl":"","labels":{},"createdAt":"2024-02-08","modifiedAt":"2024-02-08","allowGeometry":true,"versions":["http://localhost:8001/api/v2/objecttypes/3edfdaf7-f469-470b-a391-bb7ea015bd6f/versions/1"]},{"url":"http://localhost:8001/api/v2/objecttypes/8e46e0a5-b1b4-449b-b9e9-fa3cea655f48","uuid":"8e46e0a5-b1b4-449b-b9e9-fa3cea655f48","name":"Person","namePlural":"Persons","description":"","dataClassification":"open","maintainerOrganization":"","maintainerDepartment":"","contactPerson":"","contactEmail":"","source":"","updateFrequency":"unknown","providerOrganization":"","documentationUrl":"","labels":{},"createdAt":"2023-10-24","modifiedAt":"2024-02-08","allowGeometry":true,"versions":["http://localhost:8001/api/v2/objecttypes/8e46e0a5-b1b4-449b-b9e9-fa3cea655f48/versions/3","http://localhost:8001/api/v2/objecttypes/8e46e0a5-b1b4-449b-b9e9-fa3cea655f48/versions/1","http://localhost:8001/api/v2/objecttypes/8e46e0a5-b1b4-449b-b9e9-fa3cea655f48/versions/2"]}]}' + headers: + Allow: + - GET, POST, HEAD, OPTIONS + Content-Length: + - '3229' + Content-Type: + - application/json + Referrer-Policy: + - same-origin + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + status: + code: 200 + message: OK +version: 1 diff --git a/src/openforms/registrations/contrib/objects_api/tests/files/vcr_cassettes/ObjecttypesAPIEndpointTests/ObjecttypesAPIEndpointTests.test_staff_user_required.yaml b/src/openforms/registrations/contrib/objects_api/tests/files/vcr_cassettes/ObjecttypesAPIEndpointTests/ObjecttypesAPIEndpointTests.test_staff_user_required.yaml index cdabace3c7..a02d0b7bb3 100644 --- a/src/openforms/registrations/contrib/objects_api/tests/files/vcr_cassettes/ObjecttypesAPIEndpointTests/ObjecttypesAPIEndpointTests.test_staff_user_required.yaml +++ b/src/openforms/registrations/contrib/objects_api/tests/files/vcr_cassettes/ObjecttypesAPIEndpointTests/ObjecttypesAPIEndpointTests.test_staff_user_required.yaml @@ -16,12 +16,15 @@ interactions: uri: http://localhost:8001/api/v2/objecttypes response: body: - string: '{"count":2,"next":null,"previous":null,"results":[{"url":"http://localhost:8001/api/v2/objecttypes/3edfdaf7-f469-470b-a391-bb7ea015bd6f","uuid":"3edfdaf7-f469-470b-a391-bb7ea015bd6f","name":"Tree","namePlural":"Trees","description":"","dataClassification":"confidential","maintainerOrganization":"","maintainerDepartment":"","contactPerson":"","contactEmail":"","source":"","updateFrequency":"unknown","providerOrganization":"","documentationUrl":"","labels":{},"createdAt":"2024-02-08","modifiedAt":"2024-02-08","allowGeometry":true,"versions":["http://localhost:8001/api/v2/objecttypes/3edfdaf7-f469-470b-a391-bb7ea015bd6f/versions/1"]},{"url":"http://localhost:8001/api/v2/objecttypes/8e46e0a5-b1b4-449b-b9e9-fa3cea655f48","uuid":"8e46e0a5-b1b4-449b-b9e9-fa3cea655f48","name":"Person","namePlural":"Persons","description":"","dataClassification":"open","maintainerOrganization":"","maintainerDepartment":"","contactPerson":"","contactEmail":"","source":"","updateFrequency":"unknown","providerOrganization":"","documentationUrl":"","labels":{},"createdAt":"2023-10-24","modifiedAt":"2024-02-08","allowGeometry":true,"versions":["http://localhost:8001/api/v2/objecttypes/8e46e0a5-b1b4-449b-b9e9-fa3cea655f48/versions/1","http://localhost:8001/api/v2/objecttypes/8e46e0a5-b1b4-449b-b9e9-fa3cea655f48/versions/2","http://localhost:8001/api/v2/objecttypes/8e46e0a5-b1b4-449b-b9e9-fa3cea655f48/versions/3"]}]}' + string: '{"count":5,"next":null,"previous":null,"results":[{"url":"http://localhost:8001/api/v2/objecttypes/644ab597-e88c-43c0-8321-f12113510b0e","uuid":"644ab597-e88c-43c0-8321-f12113510b0e","name":"Fieldset + component","namePlural":"Fieldset component","description":"","dataClassification":"confidential","maintainerOrganization":"","maintainerDepartment":"","contactPerson":"","contactEmail":"","source":"","updateFrequency":"unknown","providerOrganization":"","documentationUrl":"","labels":{},"createdAt":"2024-02-08","modifiedAt":"2024-02-08","allowGeometry":true,"versions":["http://localhost:8001/api/v2/objecttypes/644ab597-e88c-43c0-8321-f12113510b0e/versions/1"]},{"url":"http://localhost:8001/api/v2/objecttypes/f1dde4fe-b7f9-46dc-84ae-429ae49e3705","uuid":"f1dde4fe-b7f9-46dc-84ae-429ae49e3705","name":"Geo + in data","namePlural":"Geo in data","description":"","dataClassification":"confidential","maintainerOrganization":"","maintainerDepartment":"","contactPerson":"","contactEmail":"","source":"","updateFrequency":"unknown","providerOrganization":"","documentationUrl":"","labels":{},"createdAt":"2024-02-08","modifiedAt":"2024-02-08","allowGeometry":true,"versions":["http://localhost:8001/api/v2/objecttypes/f1dde4fe-b7f9-46dc-84ae-429ae49e3705/versions/1"]},{"url":"http://localhost:8001/api/v2/objecttypes/527b8408-7421-4808-a744-43ccb7bdaaa2","uuid":"527b8408-7421-4808-a744-43ccb7bdaaa2","name":"File + Uploads","namePlural":"File Uploads","description":"","dataClassification":"confidential","maintainerOrganization":"","maintainerDepartment":"","contactPerson":"","contactEmail":"","source":"","updateFrequency":"unknown","providerOrganization":"","documentationUrl":"","labels":{},"createdAt":"2024-02-08","modifiedAt":"2024-02-08","allowGeometry":true,"versions":["http://localhost:8001/api/v2/objecttypes/527b8408-7421-4808-a744-43ccb7bdaaa2/versions/1"]},{"url":"http://localhost:8001/api/v2/objecttypes/3edfdaf7-f469-470b-a391-bb7ea015bd6f","uuid":"3edfdaf7-f469-470b-a391-bb7ea015bd6f","name":"Tree","namePlural":"Trees","description":"","dataClassification":"confidential","maintainerOrganization":"","maintainerDepartment":"","contactPerson":"","contactEmail":"","source":"","updateFrequency":"unknown","providerOrganization":"","documentationUrl":"","labels":{},"createdAt":"2024-02-08","modifiedAt":"2024-02-08","allowGeometry":true,"versions":["http://localhost:8001/api/v2/objecttypes/3edfdaf7-f469-470b-a391-bb7ea015bd6f/versions/1"]},{"url":"http://localhost:8001/api/v2/objecttypes/8e46e0a5-b1b4-449b-b9e9-fa3cea655f48","uuid":"8e46e0a5-b1b4-449b-b9e9-fa3cea655f48","name":"Person","namePlural":"Persons","description":"","dataClassification":"open","maintainerOrganization":"","maintainerDepartment":"","contactPerson":"","contactEmail":"","source":"","updateFrequency":"unknown","providerOrganization":"","documentationUrl":"","labels":{},"createdAt":"2023-10-24","modifiedAt":"2024-02-08","allowGeometry":true,"versions":["http://localhost:8001/api/v2/objecttypes/8e46e0a5-b1b4-449b-b9e9-fa3cea655f48/versions/3","http://localhost:8001/api/v2/objecttypes/8e46e0a5-b1b4-449b-b9e9-fa3cea655f48/versions/1","http://localhost:8001/api/v2/objecttypes/8e46e0a5-b1b4-449b-b9e9-fa3cea655f48/versions/2"]}]}' headers: Allow: - GET, POST, HEAD, OPTIONS Content-Length: - - '1407' + - '3229' Content-Type: - application/json Referrer-Policy: diff --git a/src/openforms/registrations/contrib/objects_api/tests/files/vcr_cassettes/ObjecttypesAPIEndpointsTests/ObjecttypesAPIEndpointsTests.test_list_objecttypes.yaml b/src/openforms/registrations/contrib/objects_api/tests/files/vcr_cassettes/ObjecttypesAPIEndpointsTests/ObjecttypesAPIEndpointsTests.test_list_objecttypes.yaml deleted file mode 100644 index cdabace3c7..0000000000 --- a/src/openforms/registrations/contrib/objects_api/tests/files/vcr_cassettes/ObjecttypesAPIEndpointsTests/ObjecttypesAPIEndpointsTests.test_list_objecttypes.yaml +++ /dev/null @@ -1,36 +0,0 @@ -interactions: -- request: - body: null - headers: - Accept: - - '*/*' - Accept-Encoding: - - gzip, deflate, br - Authorization: - - Token 171be5abaf41e7856b423ad513df1ef8f867ff48 - Connection: - - keep-alive - User-Agent: - - python-requests/2.31.0 - method: GET - uri: http://localhost:8001/api/v2/objecttypes - response: - body: - string: '{"count":2,"next":null,"previous":null,"results":[{"url":"http://localhost:8001/api/v2/objecttypes/3edfdaf7-f469-470b-a391-bb7ea015bd6f","uuid":"3edfdaf7-f469-470b-a391-bb7ea015bd6f","name":"Tree","namePlural":"Trees","description":"","dataClassification":"confidential","maintainerOrganization":"","maintainerDepartment":"","contactPerson":"","contactEmail":"","source":"","updateFrequency":"unknown","providerOrganization":"","documentationUrl":"","labels":{},"createdAt":"2024-02-08","modifiedAt":"2024-02-08","allowGeometry":true,"versions":["http://localhost:8001/api/v2/objecttypes/3edfdaf7-f469-470b-a391-bb7ea015bd6f/versions/1"]},{"url":"http://localhost:8001/api/v2/objecttypes/8e46e0a5-b1b4-449b-b9e9-fa3cea655f48","uuid":"8e46e0a5-b1b4-449b-b9e9-fa3cea655f48","name":"Person","namePlural":"Persons","description":"","dataClassification":"open","maintainerOrganization":"","maintainerDepartment":"","contactPerson":"","contactEmail":"","source":"","updateFrequency":"unknown","providerOrganization":"","documentationUrl":"","labels":{},"createdAt":"2023-10-24","modifiedAt":"2024-02-08","allowGeometry":true,"versions":["http://localhost:8001/api/v2/objecttypes/8e46e0a5-b1b4-449b-b9e9-fa3cea655f48/versions/1","http://localhost:8001/api/v2/objecttypes/8e46e0a5-b1b4-449b-b9e9-fa3cea655f48/versions/2","http://localhost:8001/api/v2/objecttypes/8e46e0a5-b1b4-449b-b9e9-fa3cea655f48/versions/3"]}]}' - headers: - Allow: - - GET, POST, HEAD, OPTIONS - Content-Length: - - '1407' - Content-Type: - - application/json - Referrer-Policy: - - same-origin - X-Content-Type-Options: - - nosniff - X-Frame-Options: - - DENY - status: - code: 200 - message: OK -version: 1 diff --git a/src/openforms/registrations/contrib/objects_api/tests/files/vcr_cassettes/ObjecttypesAPIEndpointsTests/ObjecttypesAPIEndpointsTests.test_staff_user_required.yaml b/src/openforms/registrations/contrib/objects_api/tests/files/vcr_cassettes/ObjecttypesAPIEndpointsTests/ObjecttypesAPIEndpointsTests.test_staff_user_required.yaml deleted file mode 100644 index cdabace3c7..0000000000 --- a/src/openforms/registrations/contrib/objects_api/tests/files/vcr_cassettes/ObjecttypesAPIEndpointsTests/ObjecttypesAPIEndpointsTests.test_staff_user_required.yaml +++ /dev/null @@ -1,36 +0,0 @@ -interactions: -- request: - body: null - headers: - Accept: - - '*/*' - Accept-Encoding: - - gzip, deflate, br - Authorization: - - Token 171be5abaf41e7856b423ad513df1ef8f867ff48 - Connection: - - keep-alive - User-Agent: - - python-requests/2.31.0 - method: GET - uri: http://localhost:8001/api/v2/objecttypes - response: - body: - string: '{"count":2,"next":null,"previous":null,"results":[{"url":"http://localhost:8001/api/v2/objecttypes/3edfdaf7-f469-470b-a391-bb7ea015bd6f","uuid":"3edfdaf7-f469-470b-a391-bb7ea015bd6f","name":"Tree","namePlural":"Trees","description":"","dataClassification":"confidential","maintainerOrganization":"","maintainerDepartment":"","contactPerson":"","contactEmail":"","source":"","updateFrequency":"unknown","providerOrganization":"","documentationUrl":"","labels":{},"createdAt":"2024-02-08","modifiedAt":"2024-02-08","allowGeometry":true,"versions":["http://localhost:8001/api/v2/objecttypes/3edfdaf7-f469-470b-a391-bb7ea015bd6f/versions/1"]},{"url":"http://localhost:8001/api/v2/objecttypes/8e46e0a5-b1b4-449b-b9e9-fa3cea655f48","uuid":"8e46e0a5-b1b4-449b-b9e9-fa3cea655f48","name":"Person","namePlural":"Persons","description":"","dataClassification":"open","maintainerOrganization":"","maintainerDepartment":"","contactPerson":"","contactEmail":"","source":"","updateFrequency":"unknown","providerOrganization":"","documentationUrl":"","labels":{},"createdAt":"2023-10-24","modifiedAt":"2024-02-08","allowGeometry":true,"versions":["http://localhost:8001/api/v2/objecttypes/8e46e0a5-b1b4-449b-b9e9-fa3cea655f48/versions/1","http://localhost:8001/api/v2/objecttypes/8e46e0a5-b1b4-449b-b9e9-fa3cea655f48/versions/2","http://localhost:8001/api/v2/objecttypes/8e46e0a5-b1b4-449b-b9e9-fa3cea655f48/versions/3"]}]}' - headers: - Allow: - - GET, POST, HEAD, OPTIONS - Content-Length: - - '1407' - Content-Type: - - application/json - Referrer-Policy: - - same-origin - X-Content-Type-Options: - - nosniff - X-Frame-Options: - - DENY - status: - code: 200 - message: OK -version: 1 diff --git a/src/openforms/registrations/contrib/objects_api/tests/test_api_endpoints.py b/src/openforms/registrations/contrib/objects_api/tests/test_api_endpoints.py index 96778e60a6..1ab1b177a8 100644 --- a/src/openforms/registrations/contrib/objects_api/tests/test_api_endpoints.py +++ b/src/openforms/registrations/contrib/objects_api/tests/test_api_endpoints.py @@ -8,6 +8,7 @@ from openforms.accounts.tests.factories import StaffUserFactory, UserFactory from openforms.utils.tests.vcr import OFVCRMixin +from ..models import ObjectsAPIConfig from .test_objecttypes_client import get_test_config @@ -21,8 +22,8 @@ def setUp(self) -> None: self.endpoint = reverse_lazy("api:objects_api:object-types") patcher = patch( - "openforms.registrations.contrib.objects_api.client.ObjectsAPIConfig.get_solo", - return_value=get_test_config(), + "openforms.registrations.contrib.objects_api.api.views.ObjectsAPIConfig.get_solo", + return_value=ObjectsAPIConfig(default_objects_api_group=get_test_config()), ) self.config_mock = patcher.start() @@ -51,31 +52,50 @@ def test_staff_user_required(self): self.assertEqual(response.status_code, status.HTTP_200_OK) - def test_list_objecttypes(self): + def test_list_objecttypes_default_objects_api_group(self): + # As no `ObjectsAPIGroupConfig` is persisted, the endpoint implementation + # will default to the `ObjectsAPIConfig.default_objects_api_group`. + # This test is not duplicated for the other endpoints. staff_user = StaffUserFactory.create() self.client.force_authenticate(user=staff_user) response = self.client.get(self.endpoint) + tree_objecttype = next(obj for obj in response.json() if obj["name"] == "Tree") self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual( - response.json(), - [ - { - "dataClassification": "confidential", - "name": "Tree", - "namePlural": "Trees", - "url": "http://localhost:8001/api/v2/objecttypes/3edfdaf7-f469-470b-a391-bb7ea015bd6f", - "uuid": "3edfdaf7-f469-470b-a391-bb7ea015bd6f", - }, - { - "dataClassification": "open", - "name": "Person", - "namePlural": "Persons", - "url": "http://localhost:8001/api/v2/objecttypes/8e46e0a5-b1b4-449b-b9e9-fa3cea655f48", - "uuid": "8e46e0a5-b1b4-449b-b9e9-fa3cea655f48", - }, - ], + tree_objecttype, + { + "dataClassification": "confidential", + "name": "Tree", + "namePlural": "Trees", + "url": "http://localhost:8001/api/v2/objecttypes/3edfdaf7-f469-470b-a391-bb7ea015bd6f", + "uuid": "3edfdaf7-f469-470b-a391-bb7ea015bd6f", + }, + ) + + def test_list_objecttypes_explicit_objects_api_group(self): + staff_user = StaffUserFactory.create() + self.client.force_authenticate(user=staff_user) + + config = get_test_config() + config.objecttypes_service.save() + config.save() + + response = self.client.get(self.endpoint, data={"objects_api_group": config.pk}) + + tree_objecttype = next(obj for obj in response.json() if obj["name"] == "Tree") + + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual( + tree_objecttype, + { + "dataClassification": "confidential", + "name": "Tree", + "namePlural": "Trees", + "url": "http://localhost:8001/api/v2/objecttypes/3edfdaf7-f469-470b-a391-bb7ea015bd6f", + "uuid": "3edfdaf7-f469-470b-a391-bb7ea015bd6f", + }, ) @@ -91,8 +111,8 @@ def setUp(self) -> None: ) patcher = patch( - "openforms.registrations.contrib.objects_api.client.ObjectsAPIConfig.get_solo", - return_value=get_test_config(), + "openforms.registrations.contrib.objects_api.api.views.ObjectsAPIConfig.get_solo", + return_value=ObjectsAPIConfig(default_objects_api_group=get_test_config()), ) self.config_mock = patcher.start() @@ -130,7 +150,7 @@ def test_list_objecttype_versions(self): self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.json(), [{"status": "published", "version": 1}]) - def test_list_objecttype_verions_unknown_objecttype(self): + def test_list_objecttype_versions_unknown_objecttype(self): staff_user = StaffUserFactory.create() self.client.force_authenticate(user=staff_user) @@ -158,8 +178,8 @@ def setUp(self) -> None: self.endpoint = reverse_lazy("api:objects_api:target-paths") patcher = patch( - "openforms.registrations.contrib.objects_api.client.ObjectsAPIConfig.get_solo", - return_value=get_test_config(), + "openforms.registrations.contrib.objects_api.api.views.ObjectsAPIConfig.get_solo", + return_value=ObjectsAPIConfig(default_objects_api_group=get_test_config()), ) self.config_mock = patcher.start() diff --git a/src/openforms/registrations/contrib/objects_api/tests/test_backend.py b/src/openforms/registrations/contrib/objects_api/tests/test_backend.py index 48608741d0..23b67da832 100644 --- a/src/openforms/registrations/contrib/objects_api/tests/test_backend.py +++ b/src/openforms/registrations/contrib/objects_api/tests/test_backend.py @@ -17,7 +17,7 @@ SubmissionFileAttachmentFactory, ) -from ..models import ObjectsAPIConfig +from ..models import ObjectsAPIConfig, ObjectsAPIGroupConfig from ..plugin import PLUGIN_IDENTIFIER, ObjectsAPIRegistration @@ -34,18 +34,20 @@ def setUp(self): super().setUp() config = ObjectsAPIConfig( - objects_service=ServiceFactory.build( - api_root="https://objecten.nl/api/v1/", - api_type=APITypes.orc, - ), - drc_service=ServiceFactory.build( - api_root="https://documenten.nl/api/v1/", - api_type=APITypes.drc, - ), + default_objects_api_group=ObjectsAPIGroupConfig( + objects_service=ServiceFactory.build( + api_root="https://objecten.nl/api/v1/", + api_type=APITypes.orc, + ), + drc_service=ServiceFactory.build( + api_root="https://documenten.nl/api/v1/", + api_type=APITypes.drc, + ), + ) ) config_patcher = patch( - "openforms.registrations.contrib.objects_api.models.ObjectsAPIConfig.get_solo", + "openforms.registrations.contrib.objects_api.plugin.ObjectsAPIConfig.get_solo", return_value=config, ) self.mock_get_config = config_patcher.start() diff --git a/src/openforms/registrations/contrib/objects_api/tests/test_backend_v1.py b/src/openforms/registrations/contrib/objects_api/tests/test_backend_v1.py index 66dc679314..110db69452 100644 --- a/src/openforms/registrations/contrib/objects_api/tests/test_backend_v1.py +++ b/src/openforms/registrations/contrib/objects_api/tests/test_backend_v1.py @@ -19,7 +19,7 @@ ) from ....constants import RegistrationAttribute -from ..models import ObjectsAPIConfig, ObjectsAPIRegistrationData +from ..models import ObjectsAPIConfig, ObjectsAPIGroupConfig, ObjectsAPIRegistrationData from ..plugin import PLUGIN_IDENTIFIER, ObjectsAPIRegistration from ..submission_registration import ObjectsAPIV1Handler from ..typing import RegistrationOptionsV1 @@ -50,51 +50,53 @@ def setUp(self): super().setUp() config = ObjectsAPIConfig( - objects_service=ServiceFactory.build( - api_root="https://objecten.nl/api/v1/", - api_type=APITypes.orc, - ), - drc_service=ServiceFactory.build( - api_root="https://documenten.nl/api/v1/", - api_type=APITypes.drc, - ), - objecttype="https://objecttypen.nl/api/v1/objecttypes/1", - objecttype_version=1, - productaanvraag_type="terugbelnotitie", - informatieobjecttype_submission_report="https://catalogi.nl/api/v1/informatieobjecttypen/1", - informatieobjecttype_submission_csv="https://catalogi.nl/api/v1/informatieobjecttypen/4", - informatieobjecttype_attachment="https://catalogi.nl/api/v1/informatieobjecttypen/3", - organisatie_rsin="000000000", - content_json=textwrap.dedent( - """ - { - "bron": { - "naam": "Open Formulieren", - "kenmerk": "{{ submission.kenmerk }}" - }, - "type": "{{ productaanvraag_type }}", - "aanvraaggegevens": {% json_summary %}, - "taal": "{{ submission.language_code }}", - "betrokkenen": [ - { - "inpBsn" : "{{ variables.auth_bsn }}", - "rolOmschrijvingGeneriek" : "initiator" + default_objects_api_group=ObjectsAPIGroupConfig( + objects_service=ServiceFactory.build( + api_root="https://objecten.nl/api/v1/", + api_type=APITypes.orc, + ), + drc_service=ServiceFactory.build( + api_root="https://documenten.nl/api/v1/", + api_type=APITypes.drc, + ), + objecttype="https://objecttypen.nl/api/v1/objecttypes/1", + objecttype_version=1, + productaanvraag_type="terugbelnotitie", + informatieobjecttype_submission_report="https://catalogi.nl/api/v1/informatieobjecttypen/1", + informatieobjecttype_submission_csv="https://catalogi.nl/api/v1/informatieobjecttypen/4", + informatieobjecttype_attachment="https://catalogi.nl/api/v1/informatieobjecttypen/3", + organisatie_rsin="000000000", + content_json=textwrap.dedent( + """ + { + "bron": { + "naam": "Open Formulieren", + "kenmerk": "{{ submission.kenmerk }}" + }, + "type": "{{ productaanvraag_type }}", + "aanvraaggegevens": {% json_summary %}, + "taal": "{{ submission.language_code }}", + "betrokkenen": [ + { + "inpBsn" : "{{ variables.auth_bsn }}", + "rolOmschrijvingGeneriek" : "initiator" + } + ], + "pdf": "{{ submission.pdf_url }}", + "csv": "{{ submission.csv_url }}", + "bijlagen": {% uploaded_attachment_urls %}, + "payment": { + "completed": {% if payment.completed %}true{% else %}false{% endif %}, + "amount": {{ payment.amount }}, + "public_order_ids": {{ payment.public_order_ids }} } - ], - "pdf": "{{ submission.pdf_url }}", - "csv": "{{ submission.csv_url }}", - "bijlagen": {% uploaded_attachment_urls %}, - "payment": { - "completed": {% if payment.completed %}true{% else %}false{% endif %}, - "amount": {{ payment.amount }}, - "public_order_ids": {{ payment.public_order_ids }} - } - }""" - ), + }""" + ), + ) ) config_patcher = patch( - "openforms.registrations.contrib.objects_api.models.ObjectsAPIConfig.get_solo", + "openforms.registrations.contrib.objects_api.plugin.ObjectsAPIConfig.get_solo", return_value=config, ) self.mock_get_config = config_patcher.start() diff --git a/src/openforms/registrations/contrib/objects_api/tests/test_backend_v2.py b/src/openforms/registrations/contrib/objects_api/tests/test_backend_v2.py index d11930e1ce..520630cd89 100644 --- a/src/openforms/registrations/contrib/objects_api/tests/test_backend_v2.py +++ b/src/openforms/registrations/contrib/objects_api/tests/test_backend_v2.py @@ -15,7 +15,7 @@ ) from openforms.utils.tests.vcr import OFVCRMixin -from ..models import ObjectsAPIConfig, ObjectsAPIRegistrationData +from ..models import ObjectsAPIConfig, ObjectsAPIGroupConfig, ObjectsAPIRegistrationData from ..plugin import PLUGIN_IDENTIFIER, ObjectsAPIRegistration from ..submission_registration import ObjectsAPIV2Handler from ..typing import RegistrationOptionsV2 @@ -35,27 +35,29 @@ def setUp(self): super().setUp() config = ObjectsAPIConfig( - objects_service=ServiceFactory.build( - api_root="http://localhost:8002/api/v2/", - api_type=APITypes.orc, - oas="https://example.com/", - header_key="Authorization", - # See the docker compose fixtures: - header_value="Token 7657474c3d75f56ae0abd0d1bf7994b09964dca9", - auth_type=AuthTypes.api_key, - ), - drc_service=ServiceFactory.build( - api_root="http://localhost:8003/documenten/api/v1/", - api_type=APITypes.drc, - # See the docker compose fixtures: - client_id="test_client_id", - secret="test_secret_key", - auth_type=AuthTypes.zgw, - ), + default_objects_api_group=ObjectsAPIGroupConfig( + objects_service=ServiceFactory.build( + api_root="http://localhost:8002/api/v2/", + api_type=APITypes.orc, + oas="https://example.com/", + header_key="Authorization", + # See the docker compose fixtures: + header_value="Token 7657474c3d75f56ae0abd0d1bf7994b09964dca9", + auth_type=AuthTypes.api_key, + ), + drc_service=ServiceFactory.build( + api_root="http://localhost:8003/documenten/api/v1/", + api_type=APITypes.drc, + # See the docker compose fixtures: + client_id="test_client_id", + secret="test_secret_key", + auth_type=AuthTypes.zgw, + ), + ) ) config_patcher = patch( - "openforms.registrations.contrib.objects_api.models.ObjectsAPIConfig.get_solo", + "openforms.registrations.contrib.objects_api.plugin.ObjectsAPIConfig.get_solo", return_value=config, ) self.mock_get_config = config_patcher.start() diff --git a/src/openforms/registrations/contrib/objects_api/tests/test_config_checks.py b/src/openforms/registrations/contrib/objects_api/tests/test_config_checks.py index 89d453c0d5..98bd647c0c 100644 --- a/src/openforms/registrations/contrib/objects_api/tests/test_config_checks.py +++ b/src/openforms/registrations/contrib/objects_api/tests/test_config_checks.py @@ -1,7 +1,4 @@ -from copy import copy -from unittest.mock import patch - -from django.test import SimpleTestCase +from django.test import TestCase import requests import requests_mock @@ -9,24 +6,23 @@ from zgw_consumers.test.factories import ServiceFactory from openforms.plugins.exceptions import InvalidPluginConfiguration -from openforms.utils.tests.nlx import DisableNLXRewritingMixin -from ..models import ObjectsAPIConfig +from ..models import ObjectsAPIGroupConfig from ..plugin import PLUGIN_IDENTIFIER, ObjectsAPIRegistration -class ConfigCheckTests(DisableNLXRewritingMixin, SimpleTestCase): +class ConfigCheckTests(TestCase): def setUp(self): super().setUp() - self.config = ObjectsAPIConfig( - objects_service=ServiceFactory.build( + self.config = ObjectsAPIGroupConfig.objects.create( + objects_service=ServiceFactory.create( api_root="https://objects.example.com/api/v1/", ), - objecttypes_service=ServiceFactory.build( + objecttypes_service=ServiceFactory.create( api_root="https://objecttypes.example.com/api/v1/", ), - drc_service=ServiceFactory.build( + drc_service=ServiceFactory.create( api_root="https://documents.example.com/api/v1/", api_type=APITypes.drc, ), @@ -35,12 +31,6 @@ def setUp(self): objecttype_version=42, organisatie_rsin="123456782", ) - patcher = patch( - "openforms.registrations.contrib.objects_api.client.ObjectsAPIConfig.get_solo", - return_value=self.config, - ) - self.mock_get_solo = patcher.start() - self.addCleanup(patcher.stop) def _mockForValidServiceConfiguration(self, m: requests_mock.Mocker) -> None: m.get( @@ -58,6 +48,7 @@ def _mockForValidServiceConfiguration(self, m: requests_mock.Mocker) -> None: def test_no_objects_service_configured(self): self.config.objects_service = None + self.config.save() plugin = ObjectsAPIRegistration(PLUGIN_IDENTIFIER) with self.assertRaises(InvalidPluginConfiguration): @@ -71,6 +62,7 @@ def test_no_objecttypes_service_configured(self, m: requests_mock.Mocker): json={"results": []}, ) self.config.objecttypes_service = None + self.config.save() plugin = ObjectsAPIRegistration(PLUGIN_IDENTIFIER) with self.assertRaises(InvalidPluginConfiguration): @@ -127,6 +119,7 @@ def test_objecttypes_service_misconfigured_redirect(self, m): @requests_mock.Mocker() def test_no_documents_service_configured(self, m): self.config.drc_service = None + self.config.save() m.get( "https://objects.example.com/api/v1/objects?pageSize=1", json={"results": []}, @@ -153,10 +146,9 @@ def test_invalid_url_references_with_error(self, m): ) for field in fields: with self.subTest(field=field): - config = copy(self.config) - assert not getattr(config, field, "") - setattr(config, field, "https://example.com") - self.mock_get_solo.return_value = config + assert not getattr(self.config, field, "") + setattr(self.config, field, "https://example.com") + self.config.save() with self.assertRaises(InvalidPluginConfiguration): plugin.check_config() @@ -174,10 +166,9 @@ def test_invalid_url_references_with_unexpected_status(self, m): ) for field in fields: with self.subTest(field=field): - config = copy(self.config) - assert not getattr(config, field, "") - setattr(config, field, "https://example.com") - self.mock_get_solo.return_value = config + assert not getattr(self.config, field, "") + setattr(self.config, field, "https://example.com") + self.config.save() with self.assertRaises(InvalidPluginConfiguration): plugin.check_config() @@ -201,10 +192,9 @@ def test_valid_url_references(self, m): ) for field in fields: with self.subTest(field=field): - config = copy(self.config) - assert not getattr(config, field, "") - setattr(config, field, "https://example.com") - self.mock_get_solo.return_value = config + assert not getattr(self.config, field, "") + setattr(self.config, field, "https://example.com") + self.config.save() try: plugin.check_config() diff --git a/src/openforms/registrations/contrib/objects_api/tests/test_migrations.py b/src/openforms/registrations/contrib/objects_api/tests/test_migrations.py index 1e412bb147..3cc0c6efd7 100644 --- a/src/openforms/registrations/contrib/objects_api/tests/test_migrations.py +++ b/src/openforms/registrations/contrib/objects_api/tests/test_migrations.py @@ -1,6 +1,6 @@ from django.db.migrations.state import StateApps -from zgw_consumers.constants import AuthTypes +from zgw_consumers.constants import APITypes, AuthTypes from openforms.registrations.contrib.objects_api.plugin import ( PLUGIN_IDENTIFIER as OBJECTS_API_PLUGIN_IDENTIFIER, @@ -177,3 +177,245 @@ def test_migration_does_nothing_if_no_objects_api_config(self): "registrations_objects_api", "ObjectsAPIConfig" ) self.assertRaises(ObjectsAPIConfig.DoesNotExist, ObjectsAPIConfig.objects.get) + + +class MoveExistingObjectsAPIConfigMigrationTests(TestMigrations): + app = "registrations_objects_api" + migrate_from = "0016_objectsapigroupconfig" + migrate_to = "0017_move_singleton_data" + + def setUpBeforeMigration(self, apps: StateApps): + ObjectsAPIConfig = apps.get_model( + "registrations_objects_api", "ObjectsAPIConfig" + ) + Service = apps.get_model("zgw_consumers", "Service") + + objects_api = Service.objects.create( + label="Objects API", + api_root="http://objectsapi.nl/api/v1/", + api_type=APITypes.orc, + ) + objecttypes_api = Service.objects.create( + label="Objecttypes API", + api_root="http://objecttypesapi.nl/api/v1/", + api_type=APITypes.orc, + ) + documents_api = Service.objects.create( + label="Documents API", + api_root="http://documentsapi.nl/api/v1/", + api_type=APITypes.drc, + ) + catalogi_api = Service.objects.create( + label="Catalogi API", + api_root="http://catalogiapi.nl/api/v1/", + api_type=APITypes.ztc, + ) + + ObjectsAPIConfig.objects.create( + objects_service=objects_api, + objecttypes_service=objecttypes_api, + drc_service=documents_api, + catalogi_service=catalogi_api, + objecttype="http://objecttypesapi.nl/api/v1/objecttypes/1", + objecttype_version=1, + informatieobjecttype_attachment="https://catalogiapi.nl/api/v1/informatieobjecttypen/1", + informatieobjecttype_submission_csv="https://catalogiapi.nl/api/v1/informatieobjecttypen/2", + informatieobjecttype_submission_report="https://catalogiapi.nl/api/v1/informatieobjecttypen/3", + organisatie_rsin="100000009", + productaanvraag_type="testproduct", + content_json="{{ content_json }}", + payment_status_update_json="{{ payment_status_update_json }}", + ) + + def test_services_migrated_correctly(self): + ObjectsAPIGroupConfig = self.apps.get_model( + "registrations_objects_api", "ObjectsAPIGroupConfig" + ) + + migrated_services = ObjectsAPIGroupConfig.objects.all() + + self.assertEqual(1, migrated_services.count()) + + group = migrated_services.get() + + self.assertEqual(group.objects_service.label, "Objects API") + self.assertEqual(group.objecttypes_service.label, "Objecttypes API") + self.assertEqual(group.drc_service.label, "Documents API") + self.assertEqual(group.catalogi_service.label, "Catalogi API") + self.assertEqual( + group.objecttype, "http://objecttypesapi.nl/api/v1/objecttypes/1" + ) + self.assertEqual(group.objecttype_version, 1) + self.assertEqual( + group.informatieobjecttype_attachment, + "https://catalogiapi.nl/api/v1/informatieobjecttypen/1", + ) + self.assertEqual( + group.informatieobjecttype_submission_csv, + "https://catalogiapi.nl/api/v1/informatieobjecttypen/2", + ) + self.assertEqual( + group.informatieobjecttype_submission_report, + "https://catalogiapi.nl/api/v1/informatieobjecttypen/3", + ) + self.assertEqual(group.organisatie_rsin, "100000009") + self.assertEqual(group.productaanvraag_type, "testproduct") + self.assertEqual(group.content_json, "{{ content_json }}") + self.assertEqual( + group.payment_status_update_json, "{{ payment_status_update_json }}" + ) + + +class ReconfigureSoloModelBackwardsMigrationTests(TestMigrations): + app = "registrations_objects_api" + migrate_from = "0017_move_singleton_data" + migrate_to = "0016_objectsapigroupconfig" + + def setUpBeforeMigration(self, apps: StateApps): + ObjectsAPIGroupConfig = apps.get_model( + "registrations_objects_api", "ObjectsAPIGroupConfig" + ) + ObjectsAPIConfig = apps.get_model( + "registrations_objects_api", "ObjectsAPIConfig" + ) + + ObjectsAPIConfig.objects.create() + self.objects_api_group_1 = ObjectsAPIGroupConfig.objects.create( + name="ZGW API 1", organisatie_rsin="100000001" + ) + self.objects_api_group_2 = ObjectsAPIGroupConfig.objects.create( + name="ZGW API 2", organisatie_rsin="100000002" + ) + self.assertTrue(self.objects_api_group_1.pk < self.objects_api_group_2.pk) + + def test_solo_reconfigured_correctly(self): + ObjectsAPIConfig = self.apps.get_model( + "registrations_objects_api", "ObjectsAPIConfig" + ) + + solo = ObjectsAPIConfig.objects.get() + + self.assertEqual(solo.organisatie_rsin, "100000001") + + +class BackwardsMigrationNoObjectsAPIGroupTests(TestMigrations): + app = "registrations_objects_api" + migrate_from = "0017_move_singleton_data" + migrate_to = "0016_objectsapigroupconfig" + + def setUpBeforeMigration(self, apps: StateApps): + ObjectsAPIConfig = apps.get_model( + "registrations_objects_api", "ObjectsAPIConfig" + ) + + ObjectsAPIConfig.objects.create() + + def test_solo_reconfigured_correctly(self): + ObjectsAPIConfig = self.apps.get_model( + "registrations_objects_api", "ObjectsAPIConfig" + ) + + solo = ObjectsAPIConfig.objects.get() + + self.assertIsNone(solo.objects_service) + + +class NoObjectsAPIConfigDoesntCreateObjectsAPIGroupMigrationTest(TestMigrations): + app = "registrations_objects_api" + migrate_from = "0016_objectsapigroupconfig" + migrate_to = "0017_move_singleton_data" + + def setUpBeforeMigration(self, apps: StateApps): + ObjectsAPIConfig = apps.get_model( + "registrations_objects_api", "ObjectsAPIConfig" + ) + + self.assertFalse(ObjectsAPIConfig.objects.exists()) + + def test_no_zgw_api_group_created(self): + ObjectsAPIGroupConfig = self.apps.get_model( + "registrations_objects_api", "ObjectsAPIGroupConfig" + ) + + self.assertFalse(ObjectsAPIGroupConfig.objects.exists()) + + +class AddDefaultToSoloModelTests(TestMigrations): + app = "registrations_objects_api" + migrate_from = "0018_alter_objectsapiconfig_options_and_more" + migrate_to = "0019_add_default" + + def setUpBeforeMigration(self, apps: StateApps): + ObjectsAPIGroupConfig = apps.get_model( + "registrations_objects_api", "ObjectsAPIGroupConfig" + ) + ObjectsAPIConfig = apps.get_model( + "registrations_objects_api", "ObjectsAPIConfig" + ) + Service = apps.get_model("zgw_consumers", "Service") + + objects_api = Service.objects.create( + label="Objects API", + api_root="http://objectsapi.nl/api/v1/", + api_type=APITypes.orc, + ) + objecttypes_api = Service.objects.create( + label="Objecttypes API", + api_root="http://objecttypesapi.nl/api/v1/", + api_type=APITypes.orc, + ) + documents_api = Service.objects.create( + label="Documents API", + api_root="http://documentsapi.nl/api/v1/", + api_type=APITypes.drc, + ) + catalogi_api = Service.objects.create( + label="Catalogi API", + api_root="http://catalogiapi.nl/api/v1/", + api_type=APITypes.ztc, + ) + + ObjectsAPIConfig.objects.create() + self.objects_api_group = ObjectsAPIGroupConfig.objects.create( + name="Objects API", + objects_service=objects_api, + objecttypes_service=objecttypes_api, + drc_service=documents_api, + catalogi_service=catalogi_api, + ) + + def test_services_migrated_correctly(self): + ObjectsAPIConfig = self.apps.get_model( + "registrations_objects_api", "ObjectsAPIConfig" + ) + + solo = ObjectsAPIConfig.objects.get() + + self.assertEqual(solo.default_objects_api_group.pk, self.objects_api_group.pk) + + +class NoObjectsAPIGroupLeavesConfigEmptyMigrationTest(TestMigrations): + app = "registrations_objects_api" + migrate_from = "0018_alter_objectsapiconfig_options_and_more" + migrate_to = "0019_add_default" + + def setUpBeforeMigration(self, apps: StateApps): + ObjectsAPIGroupConfig = apps.get_model( + "registrations_objects_api", "ObjectsAPIGroupConfig" + ) + ObjectsAPIConfig = apps.get_model( + "registrations_objects_api", "ObjectsAPIConfig" + ) + + self.assertFalse(ObjectsAPIGroupConfig.objects.exists()) + + ObjectsAPIConfig.objects.create() + + def test_no_zgw_api_group_created(self): + ObjectsAPIConfig = self.apps.get_model( + "registrations_objects_api", "ObjectsAPIConfig" + ) + + solo = ObjectsAPIConfig.objects.get() + + self.assertIsNone(solo.default_objects_api_group) diff --git a/src/openforms/registrations/contrib/objects_api/tests/test_models.py b/src/openforms/registrations/contrib/objects_api/tests/test_models.py index 92fffd72fc..f29b764750 100644 --- a/src/openforms/registrations/contrib/objects_api/tests/test_models.py +++ b/src/openforms/registrations/contrib/objects_api/tests/test_models.py @@ -4,13 +4,13 @@ from zgw_consumers.constants import APITypes from zgw_consumers.test.factories import ServiceFactory -from ..models import ObjectsAPIConfig +from ..models import ObjectsAPIGroupConfig class ObjectsAPIConfigTests(TestCase): def test_invalid_objecttypes_url(self): - config = ObjectsAPIConfig( + config = ObjectsAPIGroupConfig( objecttypes_service=ServiceFactory.build( api_root="https://objecttypen.nl/api/v1/", api_type=APITypes.orc, @@ -21,7 +21,7 @@ def test_invalid_objecttypes_url(self): self.assertRaises(ValidationError, config.clean) def test_valid_objecttypes_url(self): - config = ObjectsAPIConfig( + config = ObjectsAPIGroupConfig( objecttypes_service=ServiceFactory.build( api_root="https://objecttypen.nl/api/v1/", api_type=APITypes.orc, diff --git a/src/openforms/registrations/contrib/objects_api/tests/test_objecttypes_client.py b/src/openforms/registrations/contrib/objects_api/tests/test_objecttypes_client.py index 255ede8d72..1f4c5dbacd 100644 --- a/src/openforms/registrations/contrib/objects_api/tests/test_objecttypes_client.py +++ b/src/openforms/registrations/contrib/objects_api/tests/test_objecttypes_client.py @@ -1,5 +1,4 @@ from pathlib import Path -from unittest.mock import patch from django.test import TestCase @@ -9,12 +8,13 @@ from openforms.utils.tests.vcr import OFVCRMixin from ..client import get_objecttypes_client -from ..models import ObjectsAPIConfig +from ..models import ObjectsAPIGroupConfig -def get_test_config() -> ObjectsAPIConfig: - """Returns a preconfigured ``ObjectsAPIConfig`` instance matching the docker compose configuration.""" - return ObjectsAPIConfig( +def get_test_config() -> ObjectsAPIGroupConfig: + """Returns a preconfigured ``ObjectsAPIGroupConfig`` instance matching the docker compose configuration.""" + + return ObjectsAPIGroupConfig( objecttypes_service=Service( api_root="http://localhost:8001/api/v2/", api_type=APITypes.orc, @@ -30,31 +30,25 @@ class ObjecttypesClientTest(OFVCRMixin, TestCase): VCR_TEST_FILES = Path(__file__).parent / "files" - def setUp(self) -> None: - super().setUp() - - patcher = patch( - "openforms.registrations.contrib.objects_api.client.ObjectsAPIConfig.get_solo", - return_value=get_test_config(), - ) - - self.config_mock = patcher.start() - self.addCleanup(patcher.stop) + @classmethod + def setUpTestData(cls) -> None: + super().setUpTestData() + cls.test_config = get_test_config() def test_list_objecttypes(self): - with get_objecttypes_client() as client: + with get_objecttypes_client(self.test_config) as client: data = client.list_objecttypes() self.assertEqual(len(data), 2) def test_list_objectypes_pagination(self): - with get_objecttypes_client() as client: + with get_objecttypes_client(self.test_config) as client: data = client.list_objecttypes(page=1, page_size=1) self.assertEqual(len(data), 1) def test_list_objecttype_versions(self): - with get_objecttypes_client() as client: + with get_objecttypes_client(self.test_config) as client: data = client.list_objecttype_versions( "8e46e0a5-b1b4-449b-b9e9-fa3cea655f48" ) @@ -62,7 +56,7 @@ def test_list_objecttype_versions(self): self.assertEqual(len(data), 3) def test_get_objecttype_version(self): - with get_objecttypes_client() as client: + with get_objecttypes_client(self.test_config) as client: data = client.get_objecttype_version( "8e46e0a5-b1b4-449b-b9e9-fa3cea655f48", version=1, diff --git a/src/openforms/registrations/contrib/objects_api/tests/test_template.py b/src/openforms/registrations/contrib/objects_api/tests/test_template.py index d565d7c157..333c8aca9a 100644 --- a/src/openforms/registrations/contrib/objects_api/tests/test_template.py +++ b/src/openforms/registrations/contrib/objects_api/tests/test_template.py @@ -16,7 +16,7 @@ ) from openforms.submissions.tests.mixins import SubmissionsMixin -from ..models import ObjectsAPIConfig +from ..models import ObjectsAPIConfig, ObjectsAPIGroupConfig from ..plugin import PLUGIN_IDENTIFIER, ObjectsAPIRegistration @@ -38,15 +38,17 @@ def test_default_template(self, m): ) SubmissionFileAttachmentFactory.create(submission_step=submission.steps[0]) config = ObjectsAPIConfig( - objects_service=ServiceFactory.build( - api_root="https://objecten.nl/api/v1/", - api_type=APITypes.orc, - ), - drc_service=ServiceFactory.build( - api_root="https://documenten.nl/api/v1/", - api_type=APITypes.drc, - ), - productaanvraag_type="terugbelnotitie", + default_objects_api_group=ObjectsAPIGroupConfig( + objects_service=ServiceFactory.build( + api_root="https://objecten.nl/api/v1/", + api_type=APITypes.orc, + ), + drc_service=ServiceFactory.build( + api_root="https://documenten.nl/api/v1/", + api_type=APITypes.drc, + ), + productaanvraag_type="terugbelnotitie", + ) ) m.post("https://objecten.nl/api/v1/objects", status_code=201, json={}) plugin = ObjectsAPIRegistration(PLUGIN_IDENTIFIER) @@ -109,35 +111,37 @@ def test_default_template(self, m): @freeze_time("2022-09-12") def test_custom_template(self, m): config = ObjectsAPIConfig( - objects_service=ServiceFactory.build( - api_root="https://objecten.nl/api/v1/", - api_type=APITypes.orc, - ), - drc_service=ServiceFactory.build( - api_root="https://documenten.nl/api/v1/", - api_type=APITypes.drc, - ), - content_json=textwrap.dedent( - """ - { - "bron": { - "naam": "Open Formulieren", - "kenmerk": "{{ submission.kenmerk }}" - }, - "type": "{{ productaanvraag_type }}", - "aanvraaggegevens": {% json_summary %}, - "taal": "{{ submission.language_code }}", - "betrokkenen": [ - { - "inpBsn" : "{{ variables.auth_bsn }}", - "rolOmschrijvingGeneriek" : "initiator" - } - ], - "pdf": "{{ submission.pdf_url }}", - "csv": "{{ submission.csv_url }}", - "bijlagen": {% uploaded_attachment_urls %} - }""" - ), + default_objects_api_group=ObjectsAPIGroupConfig( + objects_service=ServiceFactory.build( + api_root="https://objecten.nl/api/v1/", + api_type=APITypes.orc, + ), + drc_service=ServiceFactory.build( + api_root="https://documenten.nl/api/v1/", + api_type=APITypes.drc, + ), + content_json=textwrap.dedent( + """ + { + "bron": { + "naam": "Open Formulieren", + "kenmerk": "{{ submission.kenmerk }}" + }, + "type": "{{ productaanvraag_type }}", + "aanvraaggegevens": {% json_summary %}, + "taal": "{{ submission.language_code }}", + "betrokkenen": [ + { + "inpBsn" : "{{ variables.auth_bsn }}", + "rolOmschrijvingGeneriek" : "initiator" + } + ], + "pdf": "{{ submission.pdf_url }}", + "csv": "{{ submission.csv_url }}", + "bijlagen": {% uploaded_attachment_urls %} + }""" + ), + ) ) submission = SubmissionFactory.from_components( components_list=[ @@ -227,39 +231,41 @@ def test_custom_template(self, m): def test_submission_with_objects_api_content_json_exceed_max_file_limit(self): submission = SubmissionFactory.create(with_report=True) config = ObjectsAPIConfig( - objects_service=ServiceFactory.build( - api_root="https://objecten.nl/api/v1/", - api_type=APITypes.orc, - ), - drc_service=ServiceFactory.build( - api_root="https://documenten.nl/api/v1/", - api_type=APITypes.drc, - ), - content_json=textwrap.dedent( - """ - { - "bron": { - "naam": "Open Formulieren", - "kenmerk": "{{ submission.kenmerk }}" - }, - "type": "{{ productaanvraag_type }}", - "aanvraaggegevens": {% json_summary %}, - "taal": "{{ submission.language_code }}", - "betrokkenen": [ - { - "inpBsn" : "{{ variables.auth_bsn }}", - "rolOmschrijvingGeneriek" : "initiator" - } - ], - "pdf": "{{ submission.pdf_url }}", - "csv": "{{ submission.csv_url }}", - "bijlagen": [ - {% for attachment in submission.attachments %} - "{{ attachment }}"{% if not forloop.last %},{% endif %} - {% endfor %} - ] - }""" - ), + default_objects_api_group=ObjectsAPIGroupConfig( + objects_service=ServiceFactory.build( + api_root="https://objecten.nl/api/v1/", + api_type=APITypes.orc, + ), + drc_service=ServiceFactory.build( + api_root="https://documenten.nl/api/v1/", + api_type=APITypes.drc, + ), + content_json=textwrap.dedent( + """ + { + "bron": { + "naam": "Open Formulieren", + "kenmerk": "{{ submission.kenmerk }}" + }, + "type": "{{ productaanvraag_type }}", + "aanvraaggegevens": {% json_summary %}, + "taal": "{{ submission.language_code }}", + "betrokkenen": [ + { + "inpBsn" : "{{ variables.auth_bsn }}", + "rolOmschrijvingGeneriek" : "initiator" + } + ], + "pdf": "{{ submission.pdf_url }}", + "csv": "{{ submission.csv_url }}", + "bijlagen": [ + {% for attachment in submission.attachments %} + "{{ attachment }}"{% if not forloop.last %},{% endif %} + {% endfor %} + ] + }""" + ), + ) ) plugin = ObjectsAPIRegistration(PLUGIN_IDENTIFIER) @@ -282,15 +288,17 @@ def test_submission_with_objects_api_content_json_exceed_max_file_limit(self): def test_submission_with_objects_api_content_json_not_valid_json(self): submission = SubmissionFactory.create(with_report=True) config = ObjectsAPIConfig( - objects_service=ServiceFactory.build( - api_root="https://objecten.nl/api/v1/", - api_type=APITypes.orc, - ), - drc_service=ServiceFactory.build( - api_root="https://documenten.nl/api/v1/", - api_type=APITypes.drc, - ), - content_json='{"key": "value",}', # Invalid JSON, + default_objects_api_group=ObjectsAPIGroupConfig( + objects_service=ServiceFactory.build( + api_root="https://objecten.nl/api/v1/", + api_type=APITypes.orc, + ), + drc_service=ServiceFactory.build( + api_root="https://documenten.nl/api/v1/", + api_type=APITypes.drc, + ), + content_json='{"key": "value",}', # Invalid JSON, + ) ) plugin = ObjectsAPIRegistration(PLUGIN_IDENTIFIER) @@ -352,9 +360,11 @@ def test_object_nulls_regression(self): form_definition_kwargs={"slug": "stepwithnulls"}, ) config = ObjectsAPIConfig( - objects_service=ServiceFactory.build(), - drc_service=ServiceFactory.build(), - content_json="{% json_summary %}", + default_objects_api_group=ObjectsAPIGroupConfig( + objects_service=ServiceFactory.build(), + drc_service=ServiceFactory.build(), + content_json="{% json_summary %}", + ) ) plugin = ObjectsAPIRegistration(PLUGIN_IDENTIFIER) prefix = "openforms.registrations.contrib.objects_api" diff --git a/src/openforms/registrations/contrib/objects_api/tests/test_update_payment_status_v1.py b/src/openforms/registrations/contrib/objects_api/tests/test_update_payment_status_v1.py index 3dfcae1b4d..526b1b54d3 100644 --- a/src/openforms/registrations/contrib/objects_api/tests/test_update_payment_status_v1.py +++ b/src/openforms/registrations/contrib/objects_api/tests/test_update_payment_status_v1.py @@ -12,7 +12,7 @@ from openforms.payments.tests.factories import SubmissionPaymentFactory from openforms.submissions.tests.factories import SubmissionFactory -from ..models import ObjectsAPIConfig +from ..models import ObjectsAPIConfig, ObjectsAPIGroupConfig from ..plugin import PLUGIN_IDENTIFIER, ObjectsAPIRegistration @@ -41,20 +41,22 @@ def test_update_payment_status(self, m): ) config = ObjectsAPIConfig( - objects_service=ServiceFactory.build( - api_root="https://objecten.nl/api/v1/", - api_type=APITypes.orc, - ), - payment_status_update_json=textwrap.dedent( - """ - { - "payment": { - "completed": {% if payment.completed %}true{% else %}false{% endif %}, - "amount": {{ payment.amount }}, - "public_order_ids": [{% for order_id in payment.public_order_ids%}"{{ order_id|escapejs }}"{% if not forloop.last %},{% endif %}{% endfor %}] - } - }""" - ), + default_objects_api_group=ObjectsAPIGroupConfig( + objects_service=ServiceFactory.build( + api_root="https://objecten.nl/api/v1/", + api_type=APITypes.orc, + ), + payment_status_update_json=textwrap.dedent( + """ + { + "payment": { + "completed": {% if payment.completed %}true{% else %}false{% endif %}, + "amount": {{ payment.amount }}, + "public_order_ids": [{% for order_id in payment.public_order_ids%}"{{ order_id|escapejs }}"{% if not forloop.last %},{% endif %}{% endfor %}] + } + }""" + ), + ) ) m.patch( @@ -110,10 +112,12 @@ def test_template_overwritten_through_options(self, m): ) config = ObjectsAPIConfig( - objects_service=ServiceFactory.build( - api_root="https://objecten.nl/api/v1/", - api_type=APITypes.orc, - ), + default_objects_api_group=ObjectsAPIGroupConfig( + objects_service=ServiceFactory.build( + api_root="https://objecten.nl/api/v1/", + api_type=APITypes.orc, + ), + ) ) m.patch( @@ -182,11 +186,13 @@ def test_no_template_specified(self, m): ) config = ObjectsAPIConfig( - objects_service=ServiceFactory.build( - api_root="https://objecten.nl/api/v1/", - api_type=APITypes.orc, - ), - payment_status_update_json="", + default_objects_api_group=ObjectsAPIGroupConfig( + objects_service=ServiceFactory.build( + api_root="https://objecten.nl/api/v1/", + api_type=APITypes.orc, + ), + payment_status_update_json="", + ) ) m.patch( diff --git a/src/openforms/registrations/contrib/objects_api/tests/test_update_payment_status_v2.py b/src/openforms/registrations/contrib/objects_api/tests/test_update_payment_status_v2.py index c18303064f..ecbf7058a9 100644 --- a/src/openforms/registrations/contrib/objects_api/tests/test_update_payment_status_v2.py +++ b/src/openforms/registrations/contrib/objects_api/tests/test_update_payment_status_v2.py @@ -15,7 +15,7 @@ from openforms.utils.tests.vcr import OFVCRMixin from ..client import get_objects_client -from ..models import ObjectsAPIConfig, ObjectsAPIRegistrationData +from ..models import ObjectsAPIConfig, ObjectsAPIGroupConfig, ObjectsAPIRegistrationData from ..plugin import PLUGIN_IDENTIFIER, ObjectsAPIRegistration from ..typing import RegistrationOptionsV2 @@ -28,7 +28,7 @@ class ObjectsAPIPaymentStatusUpdateV2Tests(OFVCRMixin, TestCase): def setUp(self): super().setUp() - config = ObjectsAPIConfig( + self.config_group = ObjectsAPIGroupConfig( objects_service=ServiceFactory.build( api_root="http://localhost:8002/api/v2/", api_type=APITypes.orc, @@ -40,6 +40,10 @@ def setUp(self): ), ) + config = ObjectsAPIConfig( + default_objects_api_group=self.config_group, + ) + config_patcher = patch( "openforms.registrations.contrib.objects_api.models.ObjectsAPIConfig.get_solo", return_value=config, @@ -50,7 +54,7 @@ def setUp(self): def test_update_payment_status(self): # We manually create the objects instance, to be in the same state after # `plugin.register_submission` was called: - with get_objects_client() as client: + with get_objects_client(self.config_group) as client: data = client.create_object( object_data=prepare_data_for_registration( record_data={ diff --git a/src/openforms/registrations/contrib/objects_api/typing.py b/src/openforms/registrations/contrib/objects_api/typing.py index fe3ae7c861..d87ba9409f 100644 --- a/src/openforms/registrations/contrib/objects_api/typing.py +++ b/src/openforms/registrations/contrib/objects_api/typing.py @@ -1,11 +1,17 @@ -from typing import Literal, TypeAlias, TypedDict +from __future__ import annotations + +from typing import TYPE_CHECKING, Literal, TypeAlias, TypedDict from typing_extensions import Required +if TYPE_CHECKING: + from .models import ObjectsAPIGroupConfig + ConfigVersion: TypeAlias = Literal[1, 2] class _BaseRegistrationOptions(TypedDict, total=False): + objects_api_group: ObjectsAPIGroupConfig objecttype: Required[str] objecttype_version: Required[int] informatieobjecttype_submission_report: str diff --git a/src/openforms/registrations/contrib/zgw_apis/plugin.py b/src/openforms/registrations/contrib/zgw_apis/plugin.py index eb751148ba..46bd6df283 100644 --- a/src/openforms/registrations/contrib/zgw_apis/plugin.py +++ b/src/openforms/registrations/contrib/zgw_apis/plugin.py @@ -18,6 +18,7 @@ create_report_document, ) from openforms.registrations.contrib.objects_api.client import get_objects_client +from openforms.registrations.contrib.objects_api.models import ObjectsAPIConfig from openforms.submissions.mapping import SKIP, FieldConf, apply_data_mapping from openforms.submissions.models import Submission, SubmissionReport from openforms.variables.utils import get_variables_for_context @@ -498,7 +499,12 @@ def register_submission_to_objects_api( submission, object_mapping, REGISTRATION_ATTRIBUTE, object_data ) - with get_objects_client() as objects_client: + objects_api_config = ObjectsAPIConfig.get_solo() + assert isinstance(objects_api_config, ObjectsAPIConfig) + + with get_objects_client( + objects_api_config.default_objects_api_group + ) as objects_client: response = execute_unless_result_exists( partial(objects_client.create_object, object_data=object_data), submission, diff --git a/src/openforms/registrations/contrib/zgw_apis/tests/test_backend.py b/src/openforms/registrations/contrib/zgw_apis/tests/test_backend.py index 423ab856f0..4a0cd52468 100644 --- a/src/openforms/registrations/contrib/zgw_apis/tests/test_backend.py +++ b/src/openforms/registrations/contrib/zgw_apis/tests/test_backend.py @@ -15,7 +15,10 @@ from zgw_consumers.test.factories import ServiceFactory from openforms.authentication.tests.factories import RegistratorInfoFactory -from openforms.registrations.contrib.objects_api.models import ObjectsAPIConfig +from openforms.registrations.contrib.objects_api.models import ( + ObjectsAPIConfig, + ObjectsAPIGroupConfig, +) from openforms.submissions.constants import PostSubmissionEvents from openforms.submissions.models import SubmissionStep from openforms.submissions.tasks import pre_registration @@ -1495,31 +1498,33 @@ def test_document_size_argument_present(self, m): ) def test_submission_with_zgw_and_objects_api_backends(self, m, obj_api_config): config = ObjectsAPIConfig( - objects_service=ServiceFactory.build( - api_root="https://objecten.nl/api/v1/", - api_type=APITypes.orc, - ), - objecttype="https://objecttypen.nl/api/v1/objecttypes/1", - objecttype_version=1, - organisatie_rsin="000000000", - content_json=textwrap.dedent( - """ - { - "bron": { - "naam": "Open Formulieren", - "kenmerk": "{{ submission.kenmerk }}" - }, - "type": "{{ productaanvraag_type }}", - "aanvraaggegevens": {% json_summary %}, - "taal": "{{ submission.language_code }}", - "betrokkenen": [ - { - "inpBsn" : "{{ variables.auth_bsn }}", - "rolOmschrijvingGeneriek" : "initiator" - } - ], - }""" - ), + default_objects_api_group=ObjectsAPIGroupConfig( + objects_service=ServiceFactory.build( + api_root="https://objecten.nl/api/v1/", + api_type=APITypes.orc, + ), + objecttype="https://objecttypen.nl/api/v1/objecttypes/1", + objecttype_version=1, + organisatie_rsin="000000000", + content_json=textwrap.dedent( + """ + { + "bron": { + "naam": "Open Formulieren", + "kenmerk": "{{ submission.kenmerk }}" + }, + "type": "{{ productaanvraag_type }}", + "aanvraaggegevens": {% json_summary %}, + "taal": "{{ submission.language_code }}", + "betrokkenen": [ + { + "inpBsn" : "{{ variables.auth_bsn }}", + "rolOmschrijvingGeneriek" : "initiator" + } + ], + }""" + ), + ) ) def get_create_json_for_object(req, ctx): diff --git a/src/openforms/registrations/contrib/zgw_apis/tests/test_backend_partial_failure.py b/src/openforms/registrations/contrib/zgw_apis/tests/test_backend_partial_failure.py index 52039de6a2..4c6eb62dda 100644 --- a/src/openforms/registrations/contrib/zgw_apis/tests/test_backend_partial_failure.py +++ b/src/openforms/registrations/contrib/zgw_apis/tests/test_backend_partial_failure.py @@ -11,7 +11,10 @@ from zgw_consumers.test import generate_oas_component from zgw_consumers.test.factories import ServiceFactory -from openforms.registrations.contrib.objects_api.models import ObjectsAPIConfig +from openforms.registrations.contrib.objects_api.models import ( + ObjectsAPIConfig, + ObjectsAPIGroupConfig, +) from openforms.submissions.constants import PostSubmissionEvents, RegistrationStatuses from openforms.submissions.tasks import pre_registration from openforms.submissions.tests.factories import ( @@ -396,31 +399,33 @@ class ObjectsAPIPartialRegistrationFailureTests(TestCase): ) def test_failure_after_object_creation(self, m, obj_api_config): config = ObjectsAPIConfig( - objects_service=ServiceFactory.build( - api_root="https://objecten.nl/api/v1/", - api_type=APITypes.orc, - ), - objecttype="https://objecttypen.nl/api/v1/objecttypes/1", - objecttype_version=1, - organisatie_rsin="000000000", - content_json=textwrap.dedent( - """ - { - "bron": { - "naam": "Open Formulieren", - "kenmerk": "{{ submission.kenmerk }}" - }, - "type": "{{ productaanvraag_type }}", - "aanvraaggegevens": {% json_summary %}, - "taal": "{{ submission.language_code }}", - "betrokkenen": [ - { - "inpBsn" : "{{ variables.auth_bsn }}", - "rolOmschrijvingGeneriek" : "initiator" - } - ], - }""" - ), + default_objects_api_group=ObjectsAPIGroupConfig( + objects_service=ServiceFactory.build( + api_root="https://objecten.nl/api/v1/", + api_type=APITypes.orc, + ), + objecttype="https://objecttypen.nl/api/v1/objecttypes/1", + objecttype_version=1, + organisatie_rsin="000000000", + content_json=textwrap.dedent( + """ + { + "bron": { + "naam": "Open Formulieren", + "kenmerk": "{{ submission.kenmerk }}" + }, + "type": "{{ productaanvraag_type }}", + "aanvraaggegevens": {% json_summary %}, + "taal": "{{ submission.language_code }}", + "betrokkenen": [ + { + "inpBsn" : "{{ variables.auth_bsn }}", + "rolOmschrijvingGeneriek" : "initiator" + } + ], + }""" + ), + ) ) zgw_api_group = ZGWApiGroupConfigFactory.create( zrc_service__api_root="https://zaken.nl/api/v1/", diff --git a/src/openforms/registrations/tests/test_views.py b/src/openforms/registrations/tests/test_views.py index 91b6258ddc..d26e220f63 100644 --- a/src/openforms/registrations/tests/test_views.py +++ b/src/openforms/registrations/tests/test_views.py @@ -8,7 +8,10 @@ from zgw_consumers.test import generate_oas_component from openforms.accounts.tests.factories import StaffUserFactory, UserFactory -from openforms.registrations.contrib.objects_api.models import ObjectsAPIConfig +from openforms.registrations.contrib.objects_api.models import ( + ObjectsAPIConfig, + ObjectsAPIGroupConfig, +) from openforms.registrations.contrib.zgw_apis.models import ZgwConfig from openforms.registrations.contrib.zgw_apis.tests.factories import ( ZGWApiGroupConfigFactory, @@ -32,6 +35,13 @@ def setUpTestData(cls): ztc_service__api_root="https://catalogus-2.nl/api/v1/", ) + cls.objects_api_group1 = ObjectsAPIGroupConfig.objects.create( + catalogi_service=cls.zgw_group1.ztc_service + ) + cls.objects_api_group2 = ObjectsAPIGroupConfig.objects.create( + catalogi_service=cls.zgw_group2.ztc_service + ) + def install_mocks(self, m): informatieobjecttypen1 = [ generate_oas_component( @@ -122,9 +132,10 @@ def test_must_be_logged_in_as_admin(self, m): self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) - def test_retrieve_without_filter_param(self, m): + def test_retrieve_without_explicit_zgw_api_group(self, m): user = StaffUserFactory.create() - url = reverse("api:iotypen-list") + url = furl(reverse("api:iotypen-list")) + url.args["registration_backend"] = "zgw-create-zaak" self.client.force_login(user) self.install_mocks(m) @@ -133,7 +144,7 @@ def test_retrieve_without_filter_param(self, m): "openforms.registrations.api.filters.ZgwConfig.get_solo", return_value=ZgwConfig(default_zgw_api_group=self.zgw_group1), ): - response = self.client.get(url) + response = self.client.get(url.url) self.assertEqual(response.status_code, status.HTTP_200_OK) @@ -141,17 +152,18 @@ def test_retrieve_without_filter_param(self, m): self.assertEqual(len(data), 2) - def test_retrieve_without_filter_param_no_default(self, m): + def test_retrieve_without_explicit_zgw_api_group_no_default(self, m): user = StaffUserFactory.create() - url = reverse("api:iotypen-list") + url = furl(reverse("api:iotypen-list")) + url.args["registration_backend"] = "zgw-create-zaak" self.client.force_login(user) - response = self.client.get(url) + response = self.client.get(url.url) self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.json(), []) - def test_retrieve_with_filter_params(self, m): + def test_retrieve_with_explicit_zgw_api_group(self, m): user = StaffUserFactory.create() url = furl(reverse("api:iotypen-list")) url.args["zgw_api_group"] = self.zgw_group2.pk @@ -168,7 +180,7 @@ def test_retrieve_with_filter_params(self, m): self.assertEqual(len(data), 1) - def test_filter_with_invalid_param(self, m): + def test_filter_with_invalid_zgw_group(self, m): user = StaffUserFactory.create() url = furl(reverse("api:iotypen-list")) url.args["zgw_api_group"] = "INVALID" @@ -179,7 +191,7 @@ def test_filter_with_invalid_param(self, m): self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.json(), []) - def test_with_object_api(self, m): + def test_retrieve_without_explicit_objects_api_group(self, m): user = StaffUserFactory.create() url = furl(reverse("api:iotypen-list")) url.args["registration_backend"] = "objects_api" @@ -189,7 +201,9 @@ def test_with_object_api(self, m): with patch( "openforms.registrations.api.filters.ObjectsAPIConfig.get_solo", - return_value=ObjectsAPIConfig(catalogi_service=self.zgw_group1.ztc_service), + return_value=ObjectsAPIConfig( + default_objects_api_group=self.objects_api_group1 + ), ): response = self.client.get(url.url) @@ -198,3 +212,42 @@ def test_with_object_api(self, m): data = response.json() self.assertEqual(len(data), 2) + + def test_retrieve_without_explicit_objects_api_api_group_no_default(self, m): + user = StaffUserFactory.create() + url = furl(reverse("api:iotypen-list")) + url.args["registration_backend"] = "objects_api" + self.client.force_login(user) + + response = self.client.get(url.url) + + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(response.json(), []) + + def test_retrieve_with_explicit_objects_api_group(self, m): + user = StaffUserFactory.create() + url = furl(reverse("api:iotypen-list")) + url.args["objects_api_group"] = self.objects_api_group2.pk + url.args["registration_backend"] = "objects_api" + self.client.force_login(user) + + self.install_mocks(m) + + response = self.client.get(url.url) + + self.assertEqual(response.status_code, status.HTTP_200_OK) + + data = response.json() + + self.assertEqual(len(data), 1) + + def test_filter_with_invalid_objects_api_group(self, m): + user = StaffUserFactory.create() + url = furl(reverse("api:iotypen-list")) + url.args["objects_api_group"] = "INVALID" + self.client.force_login(user) + + response = self.client.get(url.url) + + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(response.json(), [])