diff --git a/.bumpversion.cfg b/.bumpversion.cfg index ed9e4848..fa275660 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,7 +1,7 @@ [bumpversion] commit = False tag = False -current_version = 1.3.0 +current_version = 1.4.0 parse = (?P\d+)\.(?P\d+)\.(?P\d+)([-](?P(rc|alpha))+(?P\d+))? serialize = {major}.{minor}.{patch}-{release}{build} diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 744452ae..a9a13cc1 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -2,6 +2,44 @@ Wijzigingen =========== +1.4.0 (2023-08-22) +=========== + +Implementation changes +---------------------- + +`Issue 2247`_: + +- **Added:** Implementation of 'trefwoorden' to the 'EnkelvoudigInformatieobject' model for improved metadata organization. +- **Added:** Integration of 'trefwoorden' filter to facilitate precise content filtering. +- **Changed:** Adjusted test suite to accommodate the new 'trefwoorden' attribute. +- **Added:** Introduction of 'expand' feature to the 'documenten' API, enabling more comprehensive data retrieval. +- **Changed:** Replaced hard-coded search logic for models and serializers. +- **Changed:** Renamed '_inclusions' to '_expand' for better clarity and consistency. +- **Changed:** Updated the 'expand' model to resolve previous errors and improve performance. +- **Fixed:** Corrected the URL for signals to ensure proper functionality. +- **Updated:** Expanded OpenAPI Specification (OAS) to include the 'expand' feature. +- **Added:** Custom renderer to enable camel case formatting for improved API response consistency. +- **Fixed:** Adjusted renderers to handle nested dictionaries and replace 'Expand' with '_expand'. +- **Updated:** 'zrc expansions.py' for alignment with the latest enhancements. +- **Fixed:** Handled scenarios involving empty values more gracefully. +- **Fixed:** Corrected an issue where a URL within a nested dictionary caused a server error. +- **Fixed:** signals url and removed notifications mixin #210 +- **Updated:** 'exclusions.py' to handle non-type objects more effectively during iteration. +- **Updated:** Modified 'expfield' to 'sub_field' for clarity and precision in naming. + +`Issue 2241`_: + +- **Updated:** eio validation such that status==definitief does not block update/patch + +`Issue 2304`_: + +- **Updated:** help text for deprecated resources (verzenddatum and ontvangstdatum in openapi.yaml + +.. _Issue 2247: https://github.com/VNG-Realisatie/gemma-zaken/issues/2247 +.. _Issue 2241: https://github.com/VNG-Realisatie/gemma-zaken/issues/2241 +.. _Issue 2304: https://github.com/VNG-Realisatie/gemma-zaken/issues/2304 + 1.3.0 (2023-03-29) =========== diff --git a/README.rst b/README.rst index 415b9e4a..d6881d78 100644 --- a/README.rst +++ b/README.rst @@ -2,7 +2,7 @@ Documenten API ============== -:Version: 1.3.0 +:Version: 1.4.0 :Source: https://github.com/VNG-Realisatie/documenten-api :Keywords: zaken, zaakgericht werken, GEMMA, RGBZ, DRC @@ -24,6 +24,8 @@ Versie Release datum API specificatie master n.v.t. `ReDoc `_, `Scopes `_ `Berichtkenmerken `_ `Swagger `_ (`verschillen `_) +1.4.0 2023-08-22 `ReDoc `_, `Scopes `_ `Berichtkenmerken `_ + `Swagger `_ 1.3.x 2023-03-29 `ReDoc `_, `Scopes `_ `Berichtkenmerken `_ `Swagger `_ (`verschillen `_) diff --git a/package.json b/package.json index 7390ac0d..1d42d59f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "drc", - "version": "1.3.0", + "version": "1.4.0", "description": "drc referentie implementatie API", "main": "src/index.js", "directories": { diff --git a/src/drc/__init__.py b/src/drc/__init__.py index f3ceaf37..836682f3 100644 --- a/src/drc/__init__.py +++ b/src/drc/__init__.py @@ -7,7 +7,7 @@ import re from collections import namedtuple -__version__ = "1.3.0" +__version__ = "1.4.0" __author__ = "VNG Realisatie" __homepage__ = "https://github.com/VNG-Realisatie/documenten-api" __docformat__ = "restructuredtext" diff --git a/src/drc/api/exclusions.py b/src/drc/api/exclusions.py new file mode 100644 index 00000000..11234cf1 --- /dev/null +++ b/src/drc/api/exclusions.py @@ -0,0 +1,652 @@ +import json +import logging +import re +import uuid +from dataclasses import dataclass +from urllib.request import Request, urlopen + +from django.contrib.contenttypes.models import ContentType +from django.urls import resolve +from django.urls.exceptions import Resolver404 +from django.utils.translation import ugettext_lazy as _ + +from drf_spectacular.types import OpenApiTypes +from drf_spectacular.utils import OpenApiExample, OpenApiParameter, extend_schema +from rest_framework import serializers + +logger = logging.getLogger(__name__) + + +def is_uri(s): + try: + from urllib.parse import urlparse + + result = urlparse(s) + return all([result.scheme, result.netloc]) + except: + return False + + +@dataclass +class ExpansionField: + def __init__( + self, + id, + parent, + sub_field_parent, + sub_field, + level, + struc_type, + value, + is_empty, + code=None, + parent_code=None, + ): + self.id: str = id + self.parent: str = parent + self.sub_field_parent: str = sub_field_parent + self.sub_field: str = sub_field + self.level: int = level + self.type: str = struc_type + self.value: dict = value + self.is_empty: bool = is_empty + self.code = code + self.parent_code = parent_code + + +EXPAND_QUERY_PARAM = OpenApiParameter( + name="expand", + location=OpenApiParameter.QUERY, + description="Haal details van gelinkte resources direct op. Als je meerdere resources tegelijk wilt ophalen kun je deze scheiden met een komma. Voor het ophalen van resources die een laag dieper genest zijn wordt de punt-notatie gebruikt.", + type=OpenApiTypes.STR, + examples=[ + OpenApiExample(name="expand zaaktype", value="zaaktype"), + OpenApiExample(name="expand status", value="status"), + OpenApiExample(name="expand status.statustype", value="status.statustype"), + OpenApiExample( + name="expand hoofdzaak.status.statustype", + value="hoofdzaak.status.statustype", + ), + OpenApiExample( + name="expand hoofdzaak.deelzaken.status.statustype", + value="hoofdzaak.deelzaken.status.statustype", + ), + ], +) + + +class ExpansionMixin: + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.expanded_fields = [] + self.called_external_uris = {} + self.expanded_fields_all = [] + + @extend_schema(parameters=[EXPAND_QUERY_PARAM]) + def retrieve(self, request, *args, **kwargs): + response = super().retrieve(request, *args, **kwargs) + response = self.inclusions(response) + return response + + def list(self, request, *args, **kwargs): + response = super().list(request, *args, **kwargs) + response = self.inclusions(response) + return response + + @staticmethod + def _convert_to_internal_url(url: str) -> str: + """Convert external uri (https://testserver/api/v1...) to internal uri (/api/v1...)""" + keyword = "api" + save_sentence = False + internal_url = "/" + if isinstance(url, dict): + if not url.get("url", None): + for key, value in url.items(): + if is_uri(value): + url = value + break + else: + url = url.get("url", None) + if not url: + return "" + for word in url.split("/"): + if word == keyword: + save_sentence = True + if save_sentence: + internal_url += word + "/" + + return internal_url[:-1] + + def _get_external_data(self, url): + if isinstance(url, dict): + if not url.get("url", None): + for key, value in url.items(): + if is_uri(value): + url = value + break + else: + url = url.get("url", None) + if not self.called_external_uris.get(url, None): + try: + access_token = self.request.jwt_auth.encoded + # access_token = "eyJhbGciOiJIUzI1NiIsImNsaWVudF9pZGVudGlmaWVyIjoiYWxsdGhlc2NvcGVzYXJlYmVsb25ndG91czIyMjIyMzEzMjUzMi1SdTgyYkpMUlNRaWciLCJ0eXAiOiJKV1QifQ.eyJpc3MiOiJhbGx0aGVzY29wZXNhcmViZWxvbmd0b3VzMjIyMjIzMTMyNTMyLVJ1ODJiSkxSU1FpZyIsImlhdCI6MTY5MjA4MTY1NiwiY2xpZW50X2lkIjoiYWxsdGhlc2NvcGVzYXJlYmVsb25ndG91czIyMjIyMzEzMjUzMi1SdTgyYkpMUlNRaWciLCJ1c2VyX2lkIjoiIiwidXNlcl9yZXByZXNlbnRhdGlvbiI6IiJ9.YBE7OTjwhB7xbBXVM1ZMuPixzY2BbhYz8XAcYxrn_GI" + headers = {"Authorization": f"Bearer {access_token}"} + + with urlopen(Request(url, headers=headers)) as response: + data = json.loads(response.read().decode("utf8")) + self.called_external_uris[url] = data + return data + + except: + self.called_external_uris[url] = {} + return {} + else: + return self.called_external_uris[url] + + def _get_internal_data(self, url): + resolver_match = resolve(self._convert_to_internal_url(url)) + + uuid = resolver_match.kwargs["uuid"] + + kwargs = {"uuid": uuid} + + content_type = ContentType.objects.get( + model=resolver_match.func.initkwargs["basename"] + ) + + obj = content_type.get_object_for_this_type(**kwargs) + + serializer = resolver_match.func.cls.serializer_class + + serializer_exp_field = serializer(obj, context={"request": self.request}) + + return serializer_exp_field.data + + def get_data( + self, + url: str, + ) -> dict: + """Get data from external url or from local database""" + + try: + return self._get_internal_data(url) + except Resolver404: + return self._get_external_data(url) + except Exception as e: + logger.error( + f"The following error occured while trying to get data from {url}: {e}" + ) + return {} + + def build_expand_schema( + self, + result: dict, + fields_to_expand: list, + ): + """Build the expand schema for the response. First, the fields to expand are split on the "." character. Then, the first part of the split is used to get the urls from the result. The urls are then used to get the corresponding data from the external api or from the local database. The data is then gathered/collected inside a list consisted of namedtuples. When all data is collected, it calls the _build_json method which builds the json response.""" + expansion = {"_expand": {}} + + for exp_field in fields_to_expand: + loop_id = str(uuid.uuid4()) + self.expanded_fields = [] + for depth, sub_field in enumerate(exp_field.split(".")): + if depth == 0: + try: + urls = result[self.convert_camel_to_snake(sub_field)] + except KeyError: + try: + urls = result[sub_field] + except KeyError: + raise self.validation_invalid_expand_field(sub_field) + + if isinstance(urls, list): + for x in urls: + self._add_to_expanded_fields( + loop_id, + exp_field, + sub_field, + depth, + data=self.get_data(x), + struc_type="list", + is_empty=False, + original_data=result, + ) + if not urls: + expansion["_expand"][sub_field] = [] + else: + if urls: + self._add_to_expanded_fields( + loop_id, + exp_field, + sub_field, + depth, + data=self.get_data(urls), + struc_type="dict", + is_empty=False, + original_data=result, + ) + else: + expansion["_expand"][sub_field] = {} + + else: + + for field in self.expanded_fields: + if ( + field.sub_field == exp_field.split(".")[depth - 1] + and field.level == depth - 1 + ): + if self.next_iter_if_value_is_empty(field.value): + continue + try: + urls = field.value[ + self.convert_camel_to_snake(sub_field) + ] + except KeyError: + try: + urls = field.value[sub_field] + except KeyError: + raise self.validation_invalid_expand_field( + sub_field + ) + if isinstance(urls, list): + if urls: + for x in urls: + self._add_to_expanded_fields( + loop_id, + exp_field, + sub_field, + depth, + data=self.get_data(x), + struc_type="list", + is_empty=False, + original_data=result, + field=field, + ) + else: + self._add_to_expanded_fields( + loop_id, + exp_field, + sub_field, + depth, + data={}, + struc_type="list", + is_empty=True, + original_data=result, + field=field, + ) + else: + if urls: + self._add_to_expanded_fields( + loop_id, + exp_field, + sub_field, + depth, + data=self.get_data(urls), + struc_type="dict", + is_empty=False, + original_data=result, + field=field, + ) + + else: + self._add_to_expanded_fields( + loop_id, + exp_field, + sub_field, + depth, + data={}, + struc_type="dict", + is_empty=True, + original_data=result, + field=field, + ) + else: + if self.next_iter_if_value_is_empty(field.value): + field.is_empty = True + + if not self.expanded_fields: + continue + expansion = self._build_json(expansion) + + for key in ["loop_id", "depth", "code", "parent_code"]: + self.remove_key(expansion, key) + + result["_expand"].update(expansion["_expand"]) + + def _build_json(self, expansion: dict) -> dict: + max_value = max(self.expanded_fields, key=lambda x: x.level).level + for i in range(max_value + 1): + specific_levels = [x for x in self.expanded_fields if x.level == i] + + for index, fields_of_level in enumerate(specific_levels): + if index == 0 and i == 0: + if fields_of_level.type == "list" and not expansion["_expand"].get( + fields_of_level.sub_field, None + ): + expansion["_expand"][fields_of_level.sub_field] = [] + elif fields_of_level.type == "dict" and not expansion[ + "_expand" + ].get(fields_of_level.sub_field, None): + expansion["_expand"][fields_of_level.sub_field] = {} + + if i == 0: + if fields_of_level.type == "list": + skip = False + for field_dict in expansion["_expand"][ + fields_of_level.sub_field + ]: + if fields_of_level.value["url"] == field_dict["url"]: + skip = True + if not skip: + expansion["_expand"][fields_of_level.sub_field].append( + fields_of_level.value + ) + + elif fields_of_level.type == "dict" and not expansion[ + "_expand" + ].get(fields_of_level.sub_field, None): + + expansion["_expand"][ + fields_of_level.sub_field + ] = fields_of_level.value + + else: + match = self.get_parent_dict( + expansion["_expand"], + target_key1="url", + target_key3="code", + target_value1=fields_of_level.parent, + target_value3=fields_of_level.parent_code, + level=i, + field_level=fields_of_level.level, + ) + + if not match: + continue + + for parent_dict in match: + if isinstance(parent_dict, str): + if parent_dict != fields_of_level.sub_field_parent: + continue + parent_dict = match[parent_dict] + if parent_dict.get("url", None) != fields_of_level.parent: + continue + + if not parent_dict.get("_expand", {}) and isinstance( + parent_dict[fields_of_level.sub_field], list + ): + + parent_dict["_expand"] = {fields_of_level.sub_field: []} + elif not parent_dict.get("_expand", {}).get( + fields_of_level.sub_field, None + ) and isinstance(parent_dict[fields_of_level.sub_field], list): + + parent_dict["_expand"].update( + {fields_of_level.sub_field: []} + ) + + elif not parent_dict.get("_expand", {}) and isinstance( + parent_dict[fields_of_level.sub_field], str + ): + parent_dict["_expand"] = {fields_of_level.sub_field: {}} + + elif not parent_dict.get("_expand", {}).get( + fields_of_level.sub_field, None + ) and isinstance(parent_dict[fields_of_level.sub_field], str): + parent_dict["_expand"].update( + {fields_of_level.sub_field: {}} + ) + + if isinstance(parent_dict[fields_of_level.sub_field], list): + add = True + for expand in parent_dict["_expand"][ + fields_of_level.sub_field + ]: + if expand["url"] == fields_of_level.value["url"]: + add = False + + if add: + if fields_of_level.is_empty: + parent_dict["_expand"][ + fields_of_level.sub_field + ] = [] + else: + parent_dict["_expand"][ + fields_of_level.sub_field + ].append(fields_of_level.value) + + elif isinstance(parent_dict[fields_of_level.sub_field], str): + if fields_of_level.is_empty: + parent_dict["_expand"].update( + {fields_of_level.sub_field: {}} + ) + else: + parent_dict["_expand"].update( + {fields_of_level.sub_field: fields_of_level.value} + ) + elif parent_dict[fields_of_level.sub_field] is None: + try: + parent_dict["_expand"].update( + {fields_of_level.sub_field: fields_of_level.value} + ) + except KeyError: + parent_dict["_expand"] = {fields_of_level.sub_field: {}} + + return expansion + + def get_parent_dict( + self, + data, + target_key1, + target_key3, + target_value1, + target_value3, + level, + field_level, + parent=None, + ): + """Get the parent dictionary of the target value.""" + + if isinstance(data, dict): + if target_value3: + to_compare = bool( + data.get(target_key1) == target_value1 + and data.get(target_key3) == target_value3 + and data.get("depth") == level - 1 + ) + elif not target_value3: + to_compare = bool( + data.get(target_key1) == target_value1 + and data.get("depth") == level - 1 + ) + + if to_compare: + return parent + + for key, value in data.items(): + if isinstance(value, (dict, list)): + parent_dict = self.get_parent_dict( + value, + target_key1, + target_key3, + target_value1, + target_value3, + level, + field_level, + parent=data, + ) + if parent_dict is not None: + return parent_dict + + elif isinstance(data, list): + for item in data: + if isinstance(item, (dict, list)): + parent_dict = self.get_parent_dict( + item, + target_key1, + target_key3, + target_value1, + target_value3, + level, + field_level, + parent=data, + ) + if parent_dict is not None: + return parent_dict + return None + + def remove_key(self, data, target_key): + if isinstance(data, dict): + for key in list(data.keys()): + if key == target_key: + del data[key] + elif isinstance(data[key], (dict, list)): + self.remove_key(data[key], target_key) + elif isinstance(data, list): + for item in data: + if isinstance(item, (dict, list)): + self.remove_key(item, target_key) + + def inclusions(self, response): + expand_filter = self.request.query_params.get("expand", "") + if expand_filter: + fields_to_expand = expand_filter.split(",") + if self.action == "list": + for response_data in ( + response.data + if isinstance(response.data, list) + else response.data["results"] + ): + response_data["_expand"] = {} + self.build_expand_schema( + response_data, + fields_to_expand, + ) + elif self.action == "retrieve": + response.data["_expand"] = {} + self.build_expand_schema(response.data, fields_to_expand) + + return response + + @staticmethod + def convert_camel_to_snake(string): + # Insert underscore before each capital letter + import re + + snake_case = re.sub(r"(? 0 else unique_code, + ) + + field_to_add.value["loop_id"] = loop_id + field_to_add.value["depth"] = field_to_add.level + field_to_add.value["parent_code"] = field.code if depth > 0 else unique_code + field_to_add.value["code"] = unique_code if depth > 0 else unique_code + self.expanded_fields.append(field_to_add) + self.expanded_fields_all.append(field_to_add) + + +class ExpandFieldValidator: + MAX_STEPS_DEPTH = 10 + MAX_EXPANDED_FIELDS = 20 + REGEX = r"^[\w']+([.,][\w']+)*$" # regex checks for field names separated by . or , (e.g "rollen,rollen.statussen") + + def _validate_maximum_depth_reached(self, expanded_fields): + """Validate maximum iterations to prevent infinite recursion""" + for expand_combination in expanded_fields.split(","): + if len(expand_combination.split(".")) > self.MAX_STEPS_DEPTH: + raise serializers.ValidationError( + { + "expand": _( + f"The submitted fields have surpassed its maximum recursion limit of {self.MAX_STEPS_DEPTH}" + ) + }, + code="recursion-limit", + ) + elif len(expanded_fields.split(",")) > self.MAX_EXPANDED_FIELDS: + raise serializers.ValidationError( + { + "expand": _( + f"The submitted expansion string has surpassed its maximum limit of {self.MAX_EXPANDED_FIELDS}" + ) + }, + code="max-str-length", + ) + + def _validate_regex(self, expanded_fields): + if not re.match(self.REGEX, expanded_fields): + raise serializers.ValidationError( + { + "expand": _( + f"The submitted expand fields do not match the required regex of {self.REGEX}" + ) + }, + code="expand-format-error", + ) + + def list(self, request, *args, **kwargs): + expand_filter = request.query_params.get("expand", "") + + if not request.query_params or not expand_filter: + return super().list(request, *args, **kwargs) + + self._validate_regex(expand_filter) + self._validate_maximum_depth_reached(expand_filter) + return super().list(request, *args, **kwargs) diff --git a/src/drc/api/filters.py b/src/drc/api/filters.py index 4bf5a240..5825445d 100644 --- a/src/drc/api/filters.py +++ b/src/drc/api/filters.py @@ -1,4 +1,8 @@ +from django.utils.translation import ugettext_lazy as _ + from django_filters import rest_framework as filters +from drf_spectacular.types import OpenApiTypes +from drf_spectacular.utils import extend_schema_field from vng_api_common.filters import URLModelChoiceFilter from vng_api_common.filtersets import FilterSet from vng_api_common.utils import get_help_text @@ -12,10 +16,28 @@ ) +def expand_filter(queryset, name, value): + """expansion filter logic is placed at view level""" + return queryset + + class EnkelvoudigInformatieObjectListFilter(FilterSet): + trefwoorden = filters.CharFilter(lookup_expr="icontains") + + expand = extend_schema_field(OpenApiTypes.STR)( + filters.CharFilter( + method=expand_filter, + help_text=_( + "Examples: \n" + "`expand=zaaktype, status, status.statustype, hoofdzaak.status.statustype, hoofdzaak.deelzaken.status.statustype`\n" + "Haal details van gelinkte resources direct op. Als je meerdere resources tegelijk wilt ophalen kun je deze scheiden met een komma. Voor het ophalen van resources die een laag dieper genest zijn wordt de punt-notatie gebruikt.", + ), + ) + ) + class Meta: model = EnkelvoudigInformatieObject - fields = ("identificatie", "bronorganisatie") + fields = ("identificatie", "bronorganisatie", "trefwoorden") class EnkelvoudigInformatieObjectDetailFilter(FilterSet): @@ -31,6 +53,16 @@ class ObjectInformatieObjectFilter(FilterSet): instance_path="canonical", help_text=get_help_text("datamodel.ObjectInformatieObject", "informatieobject"), ) + expand = extend_schema_field(OpenApiTypes.STR)( + filters.CharFilter( + method=expand_filter, + help_text=_( + "Examples: \n" + "`expand=zaaktype, status, status.statustype, hoofdzaak.status.statustype, hoofdzaak.deelzaken.status.statustype`\n" + "Haal details van gelinkte resources direct op. Als je meerdere resources tegelijk wilt ophalen kun je deze scheiden met een komma. Voor het ophalen van resources die een laag dieper genest zijn wordt de punt-notatie gebruikt.", + ), + ) + ) class Meta: model = ObjectInformatieObject @@ -43,6 +75,16 @@ class GebruiksrechtenFilter(FilterSet): instance_path="canonical", help_text=get_help_text("datamodel.Gebruiksrechten", "informatieobject"), ) + expand = extend_schema_field(OpenApiTypes.STR)( + filters.CharFilter( + method=expand_filter, + help_text=_( + "Examples: \n" + "`expand=zaaktype, status, status.statustype, hoofdzaak.status.statustype, hoofdzaak.deelzaken.status.statustype`\n" + "Haal details van gelinkte resources direct op. Als je meerdere resources tegelijk wilt ophalen kun je deze scheiden met een komma. Voor het ophalen van resources die een laag dieper genest zijn wordt de punt-notatie gebruikt.", + ), + ) + ) class Meta: model = Gebruiksrechten @@ -59,6 +101,16 @@ class VerzendingFilter(FilterSet): instance_path="canonical", help_text=get_help_text("datamodel.Verzending", "informatieobject"), ) + expand = extend_schema_field(OpenApiTypes.STR)( + filters.CharFilter( + method=expand_filter, + help_text=_( + "Examples: \n" + "`expand=zaaktype, status, status.statustype, hoofdzaak.status.statustype, hoofdzaak.deelzaken.status.statustype`\n" + "Haal details van gelinkte resources direct op. Als je meerdere resources tegelijk wilt ophalen kun je deze scheiden met een komma. Voor het ophalen van resources die een laag dieper genest zijn wordt de punt-notatie gebruikt.", + ), + ) + ) class Meta: model = Verzending diff --git a/src/drc/api/renderers.py b/src/drc/api/renderers.py index 7b38cf76..2a47664d 100644 --- a/src/drc/api/renderers.py +++ b/src/drc/api/renderers.py @@ -1,3 +1,7 @@ +import json +from json.decoder import JSONDecodeError + +from djangorestframework_camel_case.render import CamelCaseJSONRenderer from rest_framework.renderers import BaseRenderer @@ -15,3 +19,45 @@ def render( if isinstance(data, str): return data.encode("utf-8") return data + + +class CustomCamelCaseJSONRenderer(CamelCaseJSONRenderer): + """search and replace Expand with _expand in the response""" + + def render(self, data, *args, **kwargs): + rendered = super().render(data, *args, **kwargs) + try: + list_of_responses = json.loads(rendered) + except JSONDecodeError as e: + return rendered + + list_of_responses = self.replace_key_nested_dicts( + list_of_responses, "Expand", "_expand" + ) + data_bytes = json.dumps(list_of_responses).encode("utf-8") + return data_bytes + + def replace_key_nested_dicts(self, nested_dicts, old_key, new_key): + if isinstance(nested_dicts, list): + new_list = [] + for dictionary in nested_dicts: + new_list.append(self.replace_key(dictionary, old_key, new_key)) + return new_list + else: + return self.replace_key(nested_dicts, old_key, new_key) + + def replace_key(self, dictionary, old_key, new_key): + if isinstance(dictionary, dict): + new_dict = {} + for key, value in dictionary.items(): + if key == old_key: + key = new_key + new_dict[key] = self.replace_key(value, old_key, new_key) + return new_dict + elif isinstance(dictionary, list): + new_list = [] + for item in dictionary: + new_list.append(self.replace_key(item, old_key, new_key)) + return new_list + else: + return dictionary diff --git a/src/drc/api/serializers/enkelvoudig_informatieobject.py b/src/drc/api/serializers/enkelvoudig_informatieobject.py index b6891f1f..4c569792 100644 --- a/src/drc/api/serializers/enkelvoudig_informatieobject.py +++ b/src/drc/api/serializers/enkelvoudig_informatieobject.py @@ -8,6 +8,7 @@ from django.utils.module_loading import import_string from django.utils.translation import gettext_lazy as _ +from drf_spectacular.utils import extend_schema from rest_framework import serializers from vng_api_common.constants import VertrouwelijkheidsAanduiding from vng_api_common.models import APICredential @@ -127,6 +128,7 @@ class Meta: "informatieobjecttype", # van-relatie, "locked", "bestandsdelen", + "trefwoorden", ) extra_kwargs = { "informatieobjecttype": { @@ -139,6 +141,12 @@ class Meta: ] }, "taal": {"min_length": 3}, + "verzenddatum": { + "help_text": "**DEPRECATED** Dit attribuut is verplaatst naar resource Verzending. \n\n De datum waarop het INFORMATIEOBJECT verzonden is, zoals deze op het INFORMATIEOBJECT vermeld is. Dit geldt voor zowel inkomende als uitgaande INFORMATIEOBJECTen. Eenzelfde informatieobject kan niet tegelijk inkomend en uitgaand zijn. Ontvangst en verzending is voorbehouden aan documenten die van of naar andere personen ontvangen of verzonden zijn waarbij die personen niet deel uit maken van de behandeling van de zaak waarin het document een rol speelt." + }, + "ontvangstdatum": { + "help_text": "**DEPRECATED** Dit attribuut is verplaatst naar resource Verzending. \n\n De datum waarop het INFORMATIEOBJECT ontvangen is. Verplicht te registreren voor INFORMATIEOBJECTen die van buiten de zaakbehandelende organisatie(s) ontvangen zijn. Ontvangst en verzending is voorbehouden aan documenten die van of naar andere personen ontvangen of verzonden zijn waarbij die personen niet deel uit maken van de behandeling van de zaak waarin het document een rol speelt." + }, } read_only_fields = ["versie", "begin_registratie"] validators = [StatusValidator()] @@ -336,14 +344,6 @@ def validate(self, attrs): raise serializers.ValidationError( _("Lock id is not correct"), code="incorrect-lock-id" ) - if self.instance.canonical.latest_version.status == Statussen.definitief: - if not self.context["force_bijwerken"]: - raise serializers.ValidationError( - _( - "Het bijwerken van Informatieobjecten met status `definitief` is niet toegestaan" - ), - code="modify-status-definitief", - ) return valid_attrs diff --git a/src/drc/api/tests/test_dso_api_strategy.py b/src/drc/api/tests/test_dso_api_strategy.py index ad9ddc06..eaea5f0d 100644 --- a/src/drc/api/tests/test_dso_api_strategy.py +++ b/src/drc/api/tests/test_dso_api_strategy.py @@ -23,7 +23,7 @@ def test_api_19_documentation_version_yaml(self): @override_settings(ROOT_URLCONF="drc.api.tests.test_urls") def test_api_24_version_header(self): response = self.client.get("/test-view") - self.assertEqual(response["API-version"], "1.3.0") + self.assertEqual(response["API-version"], "1.4.0") class DSOApi50Tests(APITestCase): diff --git a/src/drc/api/tests/test_enkelvoudiginformatieobject.py b/src/drc/api/tests/test_enkelvoudiginformatieobject.py index fe957510..c8debc32 100644 --- a/src/drc/api/tests/test_enkelvoudiginformatieobject.py +++ b/src/drc/api/tests/test_enkelvoudiginformatieobject.py @@ -116,6 +116,7 @@ def test_create(self, *mocks): "locked": False, "bestandsdelen": [], "lock": "", + "trefwoorden": None, } ) response_data = response.json() @@ -173,6 +174,7 @@ def test_read(self): "informatieobjecttype": INFORMATIEOBJECTTYPE, "locked": False, "bestandsdelen": [], + "trefwoorden": None, } response_data = response.json() self.assertEqual(sorted(response_data.keys()), sorted(expected.keys())) diff --git a/src/drc/api/tests/test_gebruiksrechten.py b/src/drc/api/tests/test_gebruiksrechten.py index 362b4430..c3e3b10e 100644 --- a/src/drc/api/tests/test_gebruiksrechten.py +++ b/src/drc/api/tests/test_gebruiksrechten.py @@ -131,3 +131,17 @@ def test_filter_invalid_resource_url(self): self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(len(response.data), 0) + + def test_expand_mechanism(self): + gebruiksrechten = GebruiksrechtenFactory.create( + informatieobject__latest_version__informatieobjecttype=INFORMATIEOBJECTTYPE + ) + url = reverse("gebruiksrechten-list") + + response = self.client.get(url, {"expand": "informatieobject"}) + + from pprint import pprint + + pprint(response.json()) + + self.assertEqual(response.status_code, status.HTTP_200_OK) diff --git a/src/drc/api/tests/test_objectinformatieobject.py b/src/drc/api/tests/test_objectinformatieobject.py index e925d5bc..6c50acc6 100644 --- a/src/drc/api/tests/test_objectinformatieobject.py +++ b/src/drc/api/tests/test_objectinformatieobject.py @@ -49,6 +49,14 @@ def test_create_with_objecttype_zaak(self, *mocks): zio = eio.canonical.objectinformatieobject_set.get() self.assertEqual(zio.object, ZAAK) + def test_expand_mechanism(self): + oio = ObjectInformatieObjectFactory.create(is_zaak=True) + oio2 = ObjectInformatieObjectFactory.create(is_zaak=True) + + response = self.client.get(self.list_url, {"expand": "informatieobject"}) + + self.assertEqual(response.status_code, status.HTTP_200_OK) + @patch("vng_api_common.validators.obj_has_shape", return_value=True) def test_create_with_objecttype_besluit(self, *mocks): eio = EnkelvoudigInformatieObjectFactory.create() diff --git a/src/drc/api/tests/test_validation.py b/src/drc/api/tests/test_validation.py index e32efada..49b22434 100644 --- a/src/drc/api/tests/test_validation.py +++ b/src/drc/api/tests/test_validation.py @@ -308,121 +308,6 @@ def test_status_set_ontvangstdatum_is_set_later(self): error = get_validation_errors(response, "status") self.assertEqual(error["code"], "invalid_for_received") - @patch("vng_api_common.validators.fetcher") - @patch("vng_api_common.validators.obj_has_shape", return_value=True) - def test_update_eio_status_definitief_forbidden(self, *mocks): - eio = EnkelvoudigInformatieObjectFactory.create( - beschrijving="beschrijving1", - informatieobjecttype=INFORMATIEOBJECTTYPE, - status=Statussen.definitief, - ) - - eio_url = reverse( - "enkelvoudiginformatieobject-detail", kwargs={"uuid": eio.uuid} - ) - - eio_response = self.client.get(eio_url) - eio_data = eio_response.data - - lock = self.client.post(f"{eio_url}/lock").data["lock"] - eio_data.update( - { - "beschrijving": "beschrijving2", - "inhoud": b64encode(b"aaaaa"), - "bestandsomvang": 5, - "lock": lock, - } - ) - - for i in ["integriteit", "ondertekening"]: - eio_data.pop(i) - - response = self.client.put(eio_url, eio_data) - - self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) - - error = get_validation_errors(response, "nonFieldErrors") - self.assertEqual(error["code"], "modify-status-definitief") - - @patch("vng_api_common.validators.fetcher") - @patch("vng_api_common.validators.obj_has_shape", return_value=True) - def test_update_eio_status_definitief_allowed_with_forced_bijwerken(self, *mocks): - - self.autorisatie.scopes += [SCOPE_DOCUMENTEN_GEFORCEERD_BIJWERKEN] - self.autorisatie.save() - - eio = EnkelvoudigInformatieObjectFactory.create( - beschrijving="beschrijving1", - informatieobjecttype=INFORMATIEOBJECTTYPE, - status=Statussen.definitief, - ) - - eio_url = reverse( - "enkelvoudiginformatieobject-detail", kwargs={"uuid": eio.uuid} - ) - eio_response = self.client.get(eio_url) - eio_data = eio_response.data - lock = self.client.post(f"{eio_url}/lock").data["lock"] - eio_data.update( - { - "beschrijving": "beschrijving2", - "inhoud": b64encode(b"aaaaa"), - "bestandsomvang": 5, - "lock": lock, - } - ) - - for i in ["integriteit", "ondertekening"]: - eio_data.pop(i) - - response = self.client.put(eio_url, eio_data) - - self.assertEqual(response.status_code, status.HTTP_200_OK) - - @patch("vng_api_common.validators.fetcher") - @patch("vng_api_common.validators.obj_has_shape", return_value=True) - def test_update_eio_old_version_forbidden_if_latest_version_is_definitief( - self, *mocks - ): - eio = EnkelvoudigInformatieObjectFactory.create( - beschrijving="beschrijving1", informatieobjecttype=INFORMATIEOBJECTTYPE - ) - - eio2 = EnkelvoudigInformatieObjectFactory.create( - canonical=eio.canonical, - versie=2, - beschrijving="beschrijving1", - informatieobjecttype=INFORMATIEOBJECTTYPE, - status=Statussen.definitief, - ) - - eio_url = reverse( - "enkelvoudiginformatieobject-detail", kwargs={"uuid": eio.uuid} - ) - - eio_response = self.client.get(eio_url) - eio_data = eio_response.data - - lock = self.client.post(f"{eio_url}/lock").data["lock"] - eio_data.update( - { - "beschrijving": "beschrijving2", - "inhoud": b64encode(b"aaaaa"), - "bestandsomvang": 5, - "lock": lock, - } - ) - - for i in ["integriteit", "ondertekening"]: - eio_data.pop(i) - - response = self.client.put(eio_url, eio_data) - - self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) - - error = get_validation_errors(response, "nonFieldErrors") - self.assertEqual(error["code"], "modify-status-definitief") - class FilterValidationTests(JWTAuthMixin, APITestCase): """ diff --git a/src/drc/api/utils.py b/src/drc/api/utils.py index 953c94e8..82c34962 100644 --- a/src/drc/api/utils.py +++ b/src/drc/api/utils.py @@ -12,9 +12,8 @@ def get_absolute_url(url_name: str, uuid: str) -> str: url_name, kwargs={"version": settings.REST_FRAMEWORK["DEFAULT_VERSION"], "uuid": uuid}, ) - domain = Site.objects.get_current().domain - protocol = "https" if settings.IS_HTTPS else "http" - return f"{protocol}://{domain}{path}" + domain = settings.DRC_BASE_URL + return f"{domain}{path}" def merge_files(part_files, file_dir, file_name) -> str: diff --git a/src/drc/api/views/enkelvoudig_informatieobject.py b/src/drc/api/views/enkelvoudig_informatieobject.py index 78b86eaf..7bc5cf62 100644 --- a/src/drc/api/views/enkelvoudig_informatieobject.py +++ b/src/drc/api/views/enkelvoudig_informatieobject.py @@ -26,6 +26,7 @@ from drc.api.audits import AUDIT_DRC from drc.api.data_filtering import ListFilterByAuthorizationsMixin +from drc.api.exclusions import EXPAND_QUERY_PARAM, ExpandFieldValidator, ExpansionMixin from drc.api.filters import ( EnkelvoudigInformatieObjectDetailFilter, EnkelvoudigInformatieObjectListFilter, @@ -146,6 +147,8 @@ class EnkelvoudigInformatieObjectViewSet( SearchMixin, ListFilterByAuthorizationsMixin, AuditTrailViewsetMixin, + ExpandFieldValidator, + ExpansionMixin, viewsets.ModelViewSet, ): global_description = _( @@ -157,7 +160,7 @@ class EnkelvoudigInformatieObjectViewSet( lookup_field = "uuid" pagination_class = PageNumberPagination search_input_serializer_class = EIOZoekSerializer - + serializer_class = EnkelvoudigInformatieObjectSerializer permission_classes = (InformationObjectAuthScopesRequired,) required_scopes = { "list": SCOPE_DOCUMENTEN_ALLES_LEZEN, @@ -242,7 +245,7 @@ def filterset_class(self): return EnkelvoudigInformatieObjectDetailFilter return EnkelvoudigInformatieObjectListFilter - def get_serializer_class(self): + def get_serializer_class(self, *args, **kwargs): """ To validate that a lock id is sent only with PUT and PATCH operations """ @@ -252,7 +255,9 @@ def get_serializer_class(self): return EnkelvoudigInformatieObjectCreateLockSerializer return EnkelvoudigInformatieObjectSerializer - @extend_schema(parameters=[VERSIE_QUERY_PARAM, REGISTRATIE_QUERY_PARAM]) + @extend_schema( + parameters=[VERSIE_QUERY_PARAM, REGISTRATIE_QUERY_PARAM, EXPAND_QUERY_PARAM] + ) def retrieve(self, request, *args, **kwargs): return super().retrieve(request, *args, **kwargs) diff --git a/src/drc/api/views/gebruiksrechten.py b/src/drc/api/views/gebruiksrechten.py index 501e4243..83cfacf9 100644 --- a/src/drc/api/views/gebruiksrechten.py +++ b/src/drc/api/views/gebruiksrechten.py @@ -9,6 +9,7 @@ from drc.api.audits import AUDIT_DRC from drc.api.data_filtering import ListFilterByAuthorizationsMixin +from drc.api.exclusions import ExpandFieldValidator, ExpansionMixin from drc.api.filters import GebruiksrechtenFilter from drc.api.kanalen import KANAAL_DOCUMENTEN from drc.api.permissions import InformationObjectRelatedAuthScopesRequired @@ -64,6 +65,8 @@ class GebruiksrechtenViewSet( CheckQueryParamsMixin, ListFilterByAuthorizationsMixin, AuditTrailViewsetMixin, + ExpandFieldValidator, + ExpansionMixin, viewsets.ModelViewSet, ): global_description = _( diff --git a/src/drc/api/views/object_informatieobject.py b/src/drc/api/views/object_informatieobject.py index 82879ec7..90752747 100644 --- a/src/drc/api/views/object_informatieobject.py +++ b/src/drc/api/views/object_informatieobject.py @@ -7,6 +7,7 @@ from vng_api_common.viewsets import CheckQueryParamsMixin from drc.api.data_filtering import ListFilterByAuthorizationsMixin +from drc.api.exclusions import ExpandFieldValidator, ExpansionMixin from drc.api.filters import ObjectInformatieObjectFilter from drc.api.permissions import InformationObjectRelatedAuthScopesRequired from drc.api.scopes import ( @@ -54,6 +55,8 @@ class ObjectInformatieObjectViewSet( CheckQueryParamsMixin, ListFilterByAuthorizationsMixin, + ExpandFieldValidator, + ExpansionMixin, mixins.CreateModelMixin, mixins.DestroyModelMixin, viewsets.ReadOnlyModelViewSet, diff --git a/src/drc/api/views/verzending.py b/src/drc/api/views/verzending.py index 07c9ed61..dfa8baf2 100644 --- a/src/drc/api/views/verzending.py +++ b/src/drc/api/views/verzending.py @@ -6,6 +6,7 @@ from vng_api_common.caching.decorators import conditional_retrieve from vng_api_common.viewsets import CheckQueryParamsMixin +from drc.api.exclusions import ExpandFieldValidator, ExpansionMixin from drc.api.filters import VerzendingFilter from drc.api.scopes import ( SCOPE_DOCUMENTEN_AANMAKEN, @@ -48,6 +49,8 @@ ) class VerzendingViewSet( CheckQueryParamsMixin, + ExpandFieldValidator, + ExpansionMixin, viewsets.ModelViewSet, ): diff --git a/src/drc/conf/api.py b/src/drc/conf/api.py index a49eda9e..684eb915 100644 --- a/src/drc/conf/api.py +++ b/src/drc/conf/api.py @@ -2,14 +2,18 @@ from vng_api_common.conf.api import * # noqa - imports white-listed -API_VERSION = "1.3.0" +API_VERSION = "1.4.0" REST_FRAMEWORK = BASE_REST_FRAMEWORK.copy() REST_FRAMEWORK["PAGE_SIZE"] = 100 +REST_FRAMEWORK["DEFAULT_RENDERER_CLASSES"] = ( + "drc.api.renderers.CustomCamelCaseJSONRenderer", +) SECURITY_DEFINITION_NAME = "JWT-Claims" DOCUMENTATION_INFO_MODULE = "drc.api.schema" +DRC_BASE_URL = os.getenv("DRC_BASE_URL", "https://documenten-api.test.vng.cloud") SPECTACULAR_SETTINGS = BASE_SPECTACULAR_SETTINGS.copy() SPECTACULAR_SETTINGS.update( diff --git a/src/drc/conf/dev.py b/src/drc/conf/dev.py index 264f8eb4..7b1adcd8 100644 --- a/src/drc/conf/dev.py +++ b/src/drc/conf/dev.py @@ -67,10 +67,10 @@ # # Django debug toolbar -INSTALLED_APPS += ["debug_toolbar"] -MIDDLEWARE += ["debug_toolbar.middleware.DebugToolbarMiddleware"] +# INSTALLED_APPS += ["debug_toolbar"] +# MIDDLEWARE += ["debug_toolbar.middleware.DebugToolbarMiddleware"] INTERNAL_IPS = ("127.0.0.1",) -DEBUG_TOOLBAR_CONFIG = {"INTERCEPT_REDIRECTS": False} +# DEBUG_TOOLBAR_CONFIG = {"INTERCEPT_REDIRECTS": False} AXES_BEHIND_REVERSE_PROXY = ( False # Default: False (we are typically using Nginx as reverse proxy) diff --git a/src/drc/conf/production.py b/src/drc/conf/production.py index 064838a1..44e6962e 100644 --- a/src/drc/conf/production.py +++ b/src/drc/conf/production.py @@ -18,7 +18,7 @@ CACHES = { "default": { "BACKEND": "django_redis.cache.RedisCache", - "LOCATION": "redis://127.0.0.1:6379/2", # NOTE: watch out for multiple projects using the same cache! + "LOCATION": f"redis://{os.getenv('REDIS_CACHE')}/2", # NOTE: watch out for multiple projects using the same cache! "OPTIONS": { "CLIENT_CLASS": "django_redis.client.DefaultClient", "IGNORE_EXCEPTIONS": True, diff --git a/src/drc/conf/staging.py b/src/drc/conf/staging.py index 3babb4ff..a3a5d97e 100644 --- a/src/drc/conf/staging.py +++ b/src/drc/conf/staging.py @@ -18,7 +18,7 @@ CACHES = { "default": { "BACKEND": "django_redis.cache.RedisCache", - "LOCATION": "redis://127.0.0.1:6379/1", # NOTE: watch out for multiple projects using the same cache! + "LOCATION": f"redis://{os.getenv('REDIS_CACHE')}/1", # NOTE: watch out for multiple projects using the same cache! "OPTIONS": { "CLIENT_CLASS": "django_redis.client.DefaultClient", "IGNORE_EXCEPTIONS": True, diff --git a/src/drc/conf/test.py b/src/drc/conf/test.py index 26522bd1..ed4b170f 100644 --- a/src/drc/conf/test.py +++ b/src/drc/conf/test.py @@ -34,7 +34,7 @@ CACHES = { "default": { "BACKEND": "django_redis.cache.RedisCache", - "LOCATION": "redis://127.0.0.1:6379/1", # NOTE: watch out for multiple projects using the same cache! + "LOCATION": f"redis://{os.getenv('REDIS_CACHE')}/1", # NOTE: watch out for multiple projects using the same cache! "OPTIONS": { "CLIENT_CLASS": "django_redis.client.DefaultClient", "IGNORE_EXCEPTIONS": True, diff --git a/src/drc/datamodel/migrations/0063_enkelvoudiginformatieobject_trefwoorden.py b/src/drc/datamodel/migrations/0063_enkelvoudiginformatieobject_trefwoorden.py new file mode 100644 index 00000000..945b2a67 --- /dev/null +++ b/src/drc/datamodel/migrations/0063_enkelvoudiginformatieobject_trefwoorden.py @@ -0,0 +1,25 @@ +# Generated by Django 3.2.13 on 2023-04-19 12:43 + +import django.contrib.postgres.fields +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("datamodel", "0062_auto_20230222_1424"), + ] + + operations = [ + migrations.AddField( + model_name="enkelvoudiginformatieobject", + name="trefwoorden", + field=django.contrib.postgres.fields.ArrayField( + base_field=models.CharField(max_length=100), + blank=True, + help_text="Een lijst van trefwoorden gescheiden door comma's.", + null=True, + size=None, + ), + ), + ] diff --git a/src/drc/datamodel/models/enkelvoudig_informatieobject.py b/src/drc/datamodel/models/enkelvoudig_informatieobject.py index 0ab71db3..679bbdec 100644 --- a/src/drc/datamodel/models/enkelvoudig_informatieobject.py +++ b/src/drc/datamodel/models/enkelvoudig_informatieobject.py @@ -1,5 +1,6 @@ import uuid as _uuid +from django.contrib.postgres.fields import ArrayField from django.core.validators import MinValueValidator from django.db import models from django.utils.translation import ugettext_lazy as _ @@ -148,5 +149,12 @@ class EnkelvoudigInformatieObject(ETagMixin, APIMixin, InformatieObject): ), ) + trefwoorden = ArrayField( + models.CharField(max_length=100), + help_text="Een lijst van trefwoorden gescheiden door comma's.", + blank=True, + null=True, + ) + class Meta: unique_together = ("uuid", "versie") diff --git a/src/drc/tests/test_upload.py b/src/drc/tests/test_upload.py index c0ef7b55..779ce4d3 100644 --- a/src/drc/tests/test_upload.py +++ b/src/drc/tests/test_upload.py @@ -790,7 +790,6 @@ def test_update_metadata_without_upload(self): * bestandsdelen objects are created """ self._create_metadata() - # update file metadata eio_url = get_operation_url( "enkelvoudiginformatieobject_retrieve", uuid=self.eio.uuid @@ -809,6 +808,13 @@ def test_update_metadata_without_upload(self): self.assertEqual(len(data["bestandsdelen"]), 2) self.assertEqual(self.eio.beschrijving, "beschrijving2") + def test_expand_mechanism(self): + self._create_metadata() + list_call = get_operation_url("enkelvoudiginformatieobject_list") + response = self.client.get(list_call, {"expand": "bestandsdelen"}) + + self.assertEqual(response.status_code, status.HTTP_200_OK) + def test_update_metadata_after_unfinished_upload(self): """ Test the update process of the document metadata with some of part files uploaded diff --git a/src/openapi.yaml b/src/openapi.yaml index f6b769a6..eaedfaa8 100644 --- a/src/openapi.yaml +++ b/src/openapi.yaml @@ -1,7 +1,7 @@ openapi: 3.0.3 info: title: Documenten API - version: 1.3.0 + version: 1.4.0 description: "Een API om een documentregistratiecomponent (DRC) te benaderen.\n\n\ In een documentregistratiecomponent worden INFORMATIEOBJECTen opgeslagen. Een\n\ @@ -258,6 +258,23 @@ paths: \ eerste in een samenwerkingsketen heeft vastgelegd." schema: type: string + - name: trefwoorden + required: false + in: query + description: Een lijst van trefwoorden gescheiden door comma's. + schema: + type: string + - name: expand + required: false + in: query + description: + "Examples: \n`expand=zaaktype, status, status.statustype, hoofdzaak.status.statustype,\ + \ hoofdzaak.deelzaken.status.statustype`\nHaal details van gelinkte resources\ + \ direct op. Als je meerdere resources tegelijk wilt ophalen kun je deze\ + \ scheiden met een komma. Voor het ophalen van resources die een laag dieper\ + \ genest zijn wordt de punt-notatie gebruikt." + schema: + type: string - name: page required: false in: query @@ -915,6 +932,31 @@ paths: description: Een datumtijd in ISO8601 formaat. De versie van het INFORMATIEOBJECT die qua `begin_registratie` het kortst hiervoor zit wordt opgehaald. + - in: query + name: expand + schema: + type: string + description: + Haal details van gelinkte resources direct op. Als je meerdere + resources tegelijk wilt ophalen kun je deze scheiden met een komma. Voor + het ophalen van resources die een laag dieper genest zijn wordt de punt-notatie + gebruikt. + examples: + ExpandZaaktype: + value: zaaktype + summary: expand zaaktype + ExpandStatus: + value: status + summary: expand status + ExpandStatus.statustype: + value: status.statustype + summary: expand status.statustype + ExpandHoofdzaak.status.statustype: + value: hoofdzaak.status.statustype + summary: expand hoofdzaak.status.statustype + ExpandHoofdzaak.deelzaken.status.statustype: + value: hoofdzaak.deelzaken.status.statustype + summary: expand hoofdzaak.deelzaken.status.statustype tags: - enkelvoudiginformatieobjecten security: @@ -2182,6 +2224,17 @@ paths: toepassing zijn. schema: type: string + - name: expand + required: false + in: query + description: + "Examples: \n`expand=zaaktype, status, status.statustype, hoofdzaak.status.statustype,\ + \ hoofdzaak.deelzaken.status.statustype`\nHaal details van gelinkte resources\ + \ direct op. Als je meerdere resources tegelijk wilt ophalen kun je deze\ + \ scheiden met een komma. Voor het ophalen van resources die een laag dieper\ + \ genest zijn wordt de punt-notatie gebruikt." + schema: + type: string tags: - gebruiksrechten security: @@ -2525,6 +2578,31 @@ paths: MultipleValues: value: '"79054025255fb1a26e4bc422aef54eb4", "e4d909c290d0fb1ca068ffaddf22cbd0"' summary: Meerdere ETag-waardes + - in: query + name: expand + schema: + type: string + description: + Haal details van gelinkte resources direct op. Als je meerdere + resources tegelijk wilt ophalen kun je deze scheiden met een komma. Voor + het ophalen van resources die een laag dieper genest zijn wordt de punt-notatie + gebruikt. + examples: + ExpandZaaktype: + value: zaaktype + summary: expand zaaktype + ExpandStatus: + value: status + summary: expand status + ExpandStatus.statustype: + value: status.statustype + summary: expand status.statustype + ExpandHoofdzaak.status.statustype: + value: hoofdzaak.status.statustype + summary: expand hoofdzaak.status.statustype + ExpandHoofdzaak.deelzaken.status.statustype: + value: hoofdzaak.deelzaken.status.statustype + summary: expand hoofdzaak.deelzaken.status.statustype tags: - gebruiksrechten security: @@ -3266,6 +3344,17 @@ paths: schema: type: string format: uri + - name: expand + required: false + in: query + description: + "Examples: \n`expand=zaaktype, status, status.statustype, hoofdzaak.status.statustype,\ + \ hoofdzaak.deelzaken.status.statustype`\nHaal details van gelinkte resources\ + \ direct op. Als je meerdere resources tegelijk wilt ophalen kun je deze\ + \ scheiden met een komma. Voor het ophalen van resources die een laag dieper\ + \ genest zijn wordt de punt-notatie gebruikt." + schema: + type: string tags: - objectinformatieobjecten security: @@ -3601,6 +3690,31 @@ paths: MultipleValues: value: '"79054025255fb1a26e4bc422aef54eb4", "e4d909c290d0fb1ca068ffaddf22cbd0"' summary: Meerdere ETag-waardes + - in: query + name: expand + schema: + type: string + description: + Haal details van gelinkte resources direct op. Als je meerdere + resources tegelijk wilt ophalen kun je deze scheiden met een komma. Voor + het ophalen van resources die een laag dieper genest zijn wordt de punt-notatie + gebruikt. + examples: + ExpandZaaktype: + value: zaaktype + summary: expand zaaktype + ExpandStatus: + value: status + summary: expand status + ExpandStatus.statustype: + value: status.statustype + summary: expand status.statustype + ExpandHoofdzaak.status.statustype: + value: hoofdzaak.status.statustype + summary: expand hoofdzaak.status.statustype + ExpandHoofdzaak.deelzaken.status.statustype: + value: hoofdzaak.deelzaken.status.statustype + summary: expand hoofdzaak.deelzaken.status.statustype tags: - objectinformatieobjecten security: @@ -3974,6 +4088,17 @@ paths: schema: type: string format: uri + - name: expand + required: false + in: query + description: + "Examples: \n`expand=zaaktype, status, status.statustype, hoofdzaak.status.statustype,\ + \ hoofdzaak.deelzaken.status.statustype`\nHaal details van gelinkte resources\ + \ direct op. Als je meerdere resources tegelijk wilt ophalen kun je deze\ + \ scheiden met een komma. Voor het ophalen van resources die een laag dieper\ + \ genest zijn wordt de punt-notatie gebruikt." + schema: + type: string - name: page required: false in: query @@ -4302,6 +4427,31 @@ paths: MultipleValues: value: '"79054025255fb1a26e4bc422aef54eb4", "e4d909c290d0fb1ca068ffaddf22cbd0"' summary: Meerdere ETag-waardes + - in: query + name: expand + schema: + type: string + description: + Haal details van gelinkte resources direct op. Als je meerdere + resources tegelijk wilt ophalen kun je deze scheiden met een komma. Voor + het ophalen van resources die een laag dieper genest zijn wordt de punt-notatie + gebruikt. + examples: + ExpandZaaktype: + value: zaaktype + summary: expand zaaktype + ExpandStatus: + value: status + summary: expand status + ExpandStatus.statustype: + value: status.statustype + summary: expand status.statustype + ExpandHoofdzaak.status.statustype: + value: hoofdzaak.status.statustype + summary: expand hoofdzaak.status.statustype + ExpandHoofdzaak.deelzaken.status.statustype: + value: hoofdzaak.deelzaken.status.statustype + summary: expand hoofdzaak.deelzaken.status.statustype tags: - verzendingen responses: @@ -5847,25 +5997,27 @@ components: format: date nullable: true description: - De datum waarop het INFORMATIEOBJECT ontvangen is. Verplicht - te registreren voor INFORMATIEOBJECTen die van buiten de zaakbehandelende - organisatie(s) ontvangen zijn. Ontvangst en verzending is voorbehouden - aan documenten die van of naar andere personen ontvangen of verzonden - zijn waarbij die personen niet deel uit maken van de behandeling van de - zaak waarin het document een rol speelt. + "**DEPRECATED** Dit attribuut is verplaatst naar resource Verzending.\ + \ \n\n De datum waarop het INFORMATIEOBJECT ontvangen is. Verplicht te\ + \ registreren voor INFORMATIEOBJECTen die van buiten de zaakbehandelende\ + \ organisatie(s) ontvangen zijn. Ontvangst en verzending is voorbehouden\ + \ aan documenten die van of naar andere personen ontvangen of verzonden\ + \ zijn waarbij die personen niet deel uit maken van de behandeling van\ + \ de zaak waarin het document een rol speelt." title: ontvangstdatum verzenddatum: type: string format: date nullable: true description: - De datum waarop het INFORMATIEOBJECT verzonden is, zoals deze - op het INFORMATIEOBJECT vermeld is. Dit geldt voor zowel inkomende als - uitgaande INFORMATIEOBJECTen. Eenzelfde informatieobject kan niet tegelijk - inkomend en uitgaand zijn. Ontvangst en verzending is voorbehouden aan - documenten die van of naar andere personen ontvangen of verzonden zijn - waarbij die personen niet deel uit maken van de behandeling van de zaak - waarin het document een rol speelt. + "**DEPRECATED** Dit attribuut is verplaatst naar resource Verzending.\ + \ \n\n De datum waarop het INFORMATIEOBJECT verzonden is, zoals deze op\ + \ het INFORMATIEOBJECT vermeld is. Dit geldt voor zowel inkomende als\ + \ uitgaande INFORMATIEOBJECTen. Eenzelfde informatieobject kan niet tegelijk\ + \ inkomend en uitgaand zijn. Ontvangst en verzending is voorbehouden aan\ + \ documenten die van of naar andere personen ontvangen of verzonden zijn\ + \ waarbij die personen niet deel uit maken van de behandeling van de zaak\ + \ waarin het document een rol speelt." title: verzenddatum indicatieGebruiksrecht: type: boolean @@ -5919,6 +6071,14 @@ components: $ref: '#/components/schemas/BestandsDeel' readOnly: true title: bestandsdelen + trefwoorden: + type: array + items: + type: string + maxLength: 100 + nullable: true + description: Een lijst van trefwoorden gescheiden door comma's. + title: trefwoorden required: - auteur - beginRegistratie @@ -6105,25 +6265,27 @@ components: format: date nullable: true description: - De datum waarop het INFORMATIEOBJECT ontvangen is. Verplicht - te registreren voor INFORMATIEOBJECTen die van buiten de zaakbehandelende - organisatie(s) ontvangen zijn. Ontvangst en verzending is voorbehouden - aan documenten die van of naar andere personen ontvangen of verzonden - zijn waarbij die personen niet deel uit maken van de behandeling van de - zaak waarin het document een rol speelt. + "**DEPRECATED** Dit attribuut is verplaatst naar resource Verzending.\ + \ \n\n De datum waarop het INFORMATIEOBJECT ontvangen is. Verplicht te\ + \ registreren voor INFORMATIEOBJECTen die van buiten de zaakbehandelende\ + \ organisatie(s) ontvangen zijn. Ontvangst en verzending is voorbehouden\ + \ aan documenten die van of naar andere personen ontvangen of verzonden\ + \ zijn waarbij die personen niet deel uit maken van de behandeling van\ + \ de zaak waarin het document een rol speelt." title: ontvangstdatum verzenddatum: type: string format: date nullable: true description: - De datum waarop het INFORMATIEOBJECT verzonden is, zoals deze - op het INFORMATIEOBJECT vermeld is. Dit geldt voor zowel inkomende als - uitgaande INFORMATIEOBJECTen. Eenzelfde informatieobject kan niet tegelijk - inkomend en uitgaand zijn. Ontvangst en verzending is voorbehouden aan - documenten die van of naar andere personen ontvangen of verzonden zijn - waarbij die personen niet deel uit maken van de behandeling van de zaak - waarin het document een rol speelt. + "**DEPRECATED** Dit attribuut is verplaatst naar resource Verzending.\ + \ \n\n De datum waarop het INFORMATIEOBJECT verzonden is, zoals deze op\ + \ het INFORMATIEOBJECT vermeld is. Dit geldt voor zowel inkomende als\ + \ uitgaande INFORMATIEOBJECTen. Eenzelfde informatieobject kan niet tegelijk\ + \ inkomend en uitgaand zijn. Ontvangst en verzending is voorbehouden aan\ + \ documenten die van of naar andere personen ontvangen of verzonden zijn\ + \ waarbij die personen niet deel uit maken van de behandeling van de zaak\ + \ waarin het document een rol speelt." title: verzenddatum indicatieGebruiksrecht: type: boolean @@ -6177,6 +6339,14 @@ components: $ref: '#/components/schemas/BestandsDeel' readOnly: true title: bestandsdelen + trefwoorden: + type: array + items: + type: string + maxLength: 100 + nullable: true + description: Een lijst van trefwoorden gescheiden door comma's. + title: trefwoorden lock: type: string readOnly: true @@ -6350,25 +6520,27 @@ components: format: date nullable: true description: - De datum waarop het INFORMATIEOBJECT ontvangen is. Verplicht - te registreren voor INFORMATIEOBJECTen die van buiten de zaakbehandelende - organisatie(s) ontvangen zijn. Ontvangst en verzending is voorbehouden - aan documenten die van of naar andere personen ontvangen of verzonden - zijn waarbij die personen niet deel uit maken van de behandeling van de - zaak waarin het document een rol speelt. + "**DEPRECATED** Dit attribuut is verplaatst naar resource Verzending.\ + \ \n\n De datum waarop het INFORMATIEOBJECT ontvangen is. Verplicht te\ + \ registreren voor INFORMATIEOBJECTen die van buiten de zaakbehandelende\ + \ organisatie(s) ontvangen zijn. Ontvangst en verzending is voorbehouden\ + \ aan documenten die van of naar andere personen ontvangen of verzonden\ + \ zijn waarbij die personen niet deel uit maken van de behandeling van\ + \ de zaak waarin het document een rol speelt." title: ontvangstdatum verzenddatum: type: string format: date nullable: true description: - De datum waarop het INFORMATIEOBJECT verzonden is, zoals deze - op het INFORMATIEOBJECT vermeld is. Dit geldt voor zowel inkomende als - uitgaande INFORMATIEOBJECTen. Eenzelfde informatieobject kan niet tegelijk - inkomend en uitgaand zijn. Ontvangst en verzending is voorbehouden aan - documenten die van of naar andere personen ontvangen of verzonden zijn - waarbij die personen niet deel uit maken van de behandeling van de zaak - waarin het document een rol speelt. + "**DEPRECATED** Dit attribuut is verplaatst naar resource Verzending.\ + \ \n\n De datum waarop het INFORMATIEOBJECT verzonden is, zoals deze op\ + \ het INFORMATIEOBJECT vermeld is. Dit geldt voor zowel inkomende als\ + \ uitgaande INFORMATIEOBJECTen. Eenzelfde informatieobject kan niet tegelijk\ + \ inkomend en uitgaand zijn. Ontvangst en verzending is voorbehouden aan\ + \ documenten die van of naar andere personen ontvangen of verzonden zijn\ + \ waarbij die personen niet deel uit maken van de behandeling van de zaak\ + \ waarin het document een rol speelt." title: verzenddatum indicatieGebruiksrecht: type: boolean @@ -6410,6 +6582,15 @@ components: API). title: informatieobjecttype maxLength: 200 + trefwoorden: + type: array + items: + type: string + minLength: 1 + maxLength: 100 + nullable: true + description: Een lijst van trefwoorden gescheiden door comma's. + title: trefwoorden required: - auteur - bronorganisatie @@ -6591,25 +6772,27 @@ components: format: date nullable: true description: - De datum waarop het INFORMATIEOBJECT ontvangen is. Verplicht - te registreren voor INFORMATIEOBJECTen die van buiten de zaakbehandelende - organisatie(s) ontvangen zijn. Ontvangst en verzending is voorbehouden - aan documenten die van of naar andere personen ontvangen of verzonden - zijn waarbij die personen niet deel uit maken van de behandeling van de - zaak waarin het document een rol speelt. + "**DEPRECATED** Dit attribuut is verplaatst naar resource Verzending.\ + \ \n\n De datum waarop het INFORMATIEOBJECT ontvangen is. Verplicht te\ + \ registreren voor INFORMATIEOBJECTen die van buiten de zaakbehandelende\ + \ organisatie(s) ontvangen zijn. Ontvangst en verzending is voorbehouden\ + \ aan documenten die van of naar andere personen ontvangen of verzonden\ + \ zijn waarbij die personen niet deel uit maken van de behandeling van\ + \ de zaak waarin het document een rol speelt." title: ontvangstdatum verzenddatum: type: string format: date nullable: true description: - De datum waarop het INFORMATIEOBJECT verzonden is, zoals deze - op het INFORMATIEOBJECT vermeld is. Dit geldt voor zowel inkomende als - uitgaande INFORMATIEOBJECTen. Eenzelfde informatieobject kan niet tegelijk - inkomend en uitgaand zijn. Ontvangst en verzending is voorbehouden aan - documenten die van of naar andere personen ontvangen of verzonden zijn - waarbij die personen niet deel uit maken van de behandeling van de zaak - waarin het document een rol speelt. + "**DEPRECATED** Dit attribuut is verplaatst naar resource Verzending.\ + \ \n\n De datum waarop het INFORMATIEOBJECT verzonden is, zoals deze op\ + \ het INFORMATIEOBJECT vermeld is. Dit geldt voor zowel inkomende als\ + \ uitgaande INFORMATIEOBJECTen. Eenzelfde informatieobject kan niet tegelijk\ + \ inkomend en uitgaand zijn. Ontvangst en verzending is voorbehouden aan\ + \ documenten die van of naar andere personen ontvangen of verzonden zijn\ + \ waarbij die personen niet deel uit maken van de behandeling van de zaak\ + \ waarin het document een rol speelt." title: verzenddatum indicatieGebruiksrecht: type: boolean @@ -6663,6 +6846,14 @@ components: $ref: '#/components/schemas/BestandsDeel' readOnly: true title: bestandsdelen + trefwoorden: + type: array + items: + type: string + maxLength: 100 + nullable: true + description: Een lijst van trefwoorden gescheiden door comma's. + title: trefwoorden required: - auteur - beginRegistratie @@ -6827,25 +7018,27 @@ components: format: date nullable: true description: - De datum waarop het INFORMATIEOBJECT ontvangen is. Verplicht - te registreren voor INFORMATIEOBJECTen die van buiten de zaakbehandelende - organisatie(s) ontvangen zijn. Ontvangst en verzending is voorbehouden - aan documenten die van of naar andere personen ontvangen of verzonden - zijn waarbij die personen niet deel uit maken van de behandeling van de - zaak waarin het document een rol speelt. + "**DEPRECATED** Dit attribuut is verplaatst naar resource Verzending.\ + \ \n\n De datum waarop het INFORMATIEOBJECT ontvangen is. Verplicht te\ + \ registreren voor INFORMATIEOBJECTen die van buiten de zaakbehandelende\ + \ organisatie(s) ontvangen zijn. Ontvangst en verzending is voorbehouden\ + \ aan documenten die van of naar andere personen ontvangen of verzonden\ + \ zijn waarbij die personen niet deel uit maken van de behandeling van\ + \ de zaak waarin het document een rol speelt." title: ontvangstdatum verzenddatum: type: string format: date nullable: true description: - De datum waarop het INFORMATIEOBJECT verzonden is, zoals deze - op het INFORMATIEOBJECT vermeld is. Dit geldt voor zowel inkomende als - uitgaande INFORMATIEOBJECTen. Eenzelfde informatieobject kan niet tegelijk - inkomend en uitgaand zijn. Ontvangst en verzending is voorbehouden aan - documenten die van of naar andere personen ontvangen of verzonden zijn - waarbij die personen niet deel uit maken van de behandeling van de zaak - waarin het document een rol speelt. + "**DEPRECATED** Dit attribuut is verplaatst naar resource Verzending.\ + \ \n\n De datum waarop het INFORMATIEOBJECT verzonden is, zoals deze op\ + \ het INFORMATIEOBJECT vermeld is. Dit geldt voor zowel inkomende als\ + \ uitgaande INFORMATIEOBJECTen. Eenzelfde informatieobject kan niet tegelijk\ + \ inkomend en uitgaand zijn. Ontvangst en verzending is voorbehouden aan\ + \ documenten die van of naar andere personen ontvangen of verzonden zijn\ + \ waarbij die personen niet deel uit maken van de behandeling van de zaak\ + \ waarin het document een rol speelt." title: verzenddatum indicatieGebruiksrecht: type: boolean @@ -6887,6 +7080,15 @@ components: API). title: informatieobjecttype maxLength: 200 + trefwoorden: + type: array + items: + type: string + minLength: 1 + maxLength: 100 + nullable: true + description: Een lijst van trefwoorden gescheiden door comma's. + title: trefwoorden lock: type: string writeOnly: true @@ -7513,25 +7715,27 @@ components: format: date nullable: true description: - De datum waarop het INFORMATIEOBJECT ontvangen is. Verplicht - te registreren voor INFORMATIEOBJECTen die van buiten de zaakbehandelende - organisatie(s) ontvangen zijn. Ontvangst en verzending is voorbehouden - aan documenten die van of naar andere personen ontvangen of verzonden - zijn waarbij die personen niet deel uit maken van de behandeling van de - zaak waarin het document een rol speelt. + "**DEPRECATED** Dit attribuut is verplaatst naar resource Verzending.\ + \ \n\n De datum waarop het INFORMATIEOBJECT ontvangen is. Verplicht te\ + \ registreren voor INFORMATIEOBJECTen die van buiten de zaakbehandelende\ + \ organisatie(s) ontvangen zijn. Ontvangst en verzending is voorbehouden\ + \ aan documenten die van of naar andere personen ontvangen of verzonden\ + \ zijn waarbij die personen niet deel uit maken van de behandeling van\ + \ de zaak waarin het document een rol speelt." title: ontvangstdatum verzenddatum: type: string format: date nullable: true description: - De datum waarop het INFORMATIEOBJECT verzonden is, zoals deze - op het INFORMATIEOBJECT vermeld is. Dit geldt voor zowel inkomende als - uitgaande INFORMATIEOBJECTen. Eenzelfde informatieobject kan niet tegelijk - inkomend en uitgaand zijn. Ontvangst en verzending is voorbehouden aan - documenten die van of naar andere personen ontvangen of verzonden zijn - waarbij die personen niet deel uit maken van de behandeling van de zaak - waarin het document een rol speelt. + "**DEPRECATED** Dit attribuut is verplaatst naar resource Verzending.\ + \ \n\n De datum waarop het INFORMATIEOBJECT verzonden is, zoals deze op\ + \ het INFORMATIEOBJECT vermeld is. Dit geldt voor zowel inkomende als\ + \ uitgaande INFORMATIEOBJECTen. Eenzelfde informatieobject kan niet tegelijk\ + \ inkomend en uitgaand zijn. Ontvangst en verzending is voorbehouden aan\ + \ documenten die van of naar andere personen ontvangen of verzonden zijn\ + \ waarbij die personen niet deel uit maken van de behandeling van de zaak\ + \ waarin het document een rol speelt." title: verzenddatum indicatieGebruiksrecht: type: boolean @@ -7573,6 +7777,15 @@ components: API). title: informatieobjecttype maxLength: 200 + trefwoorden: + type: array + items: + type: string + minLength: 1 + maxLength: 100 + nullable: true + description: Een lijst van trefwoorden gescheiden door comma's. + title: trefwoorden lock: type: string writeOnly: true diff --git a/src/resources.md b/src/resources.md index b8f3a245..ef74e9b5 100755 --- a/src/resources.md +++ b/src/resources.md @@ -102,8 +102,12 @@ Uitleg bij mogelijke waarden: | bestandsomvang | Aantal bytes dat de inhoud van INFORMATIEOBJECT in beslag neemt. | integer | nee | C​R​U​D | | link | De URL waarmee de inhoud van het INFORMATIEOBJECT op te vragen is. | string | nee | C​R​U​D | | beschrijving | Een generieke beschrijving van de inhoud van het INFORMATIEOBJECT. | string | nee | C​R​U​D | -| ontvangstdatum | De datum waarop het INFORMATIEOBJECT ontvangen is. Verplicht te registreren voor INFORMATIEOBJECTen die van buiten de zaakbehandelende organisatie(s) ontvangen zijn. Ontvangst en verzending is voorbehouden aan documenten die van of naar andere personen ontvangen of verzonden zijn waarbij die personen niet deel uit maken van de behandeling van de zaak waarin het document een rol speelt. | string | nee | C​R​U​D | -| verzenddatum | De datum waarop het INFORMATIEOBJECT verzonden is, zoals deze op het INFORMATIEOBJECT vermeld is. Dit geldt voor zowel inkomende als uitgaande INFORMATIEOBJECTen. Eenzelfde informatieobject kan niet tegelijk inkomend en uitgaand zijn. Ontvangst en verzending is voorbehouden aan documenten die van of naar andere personen ontvangen of verzonden zijn waarbij die personen niet deel uit maken van de behandeling van de zaak waarin het document een rol speelt. | string | nee | C​R​U​D | +| ontvangstdatum | **DEPRECATED** Dit attribuut is verplaatst naar resource Verzending. + + De datum waarop het INFORMATIEOBJECT ontvangen is. Verplicht te registreren voor INFORMATIEOBJECTen die van buiten de zaakbehandelende organisatie(s) ontvangen zijn. Ontvangst en verzending is voorbehouden aan documenten die van of naar andere personen ontvangen of verzonden zijn waarbij die personen niet deel uit maken van de behandeling van de zaak waarin het document een rol speelt. | string | nee | C​R​U​D | +| verzenddatum | **DEPRECATED** Dit attribuut is verplaatst naar resource Verzending. + + De datum waarop het INFORMATIEOBJECT verzonden is, zoals deze op het INFORMATIEOBJECT vermeld is. Dit geldt voor zowel inkomende als uitgaande INFORMATIEOBJECTen. Eenzelfde informatieobject kan niet tegelijk inkomend en uitgaand zijn. Ontvangst en verzending is voorbehouden aan documenten die van of naar andere personen ontvangen of verzonden zijn waarbij die personen niet deel uit maken van de behandeling van de zaak waarin het document een rol speelt. | string | nee | C​R​U​D | | indicatieGebruiksrecht | Indicatie of er beperkingen gelden aangaande het gebruik van het informatieobject anders dan raadpleging. Dit veld mag `null` zijn om aan te geven dat de indicatie nog niet bekend is. Als de indicatie gezet is, dan kan je de gebruiksrechten die van toepassing zijn raadplegen via de GEBRUIKSRECHTen resource. | boolean | nee | C​R​U​D | | verschijningsvorm | De essentiële opmaakaspecten van een INFORMATIEOBJECT. | string | nee | C​R​U​D | | ondertekening | Aanduiding van de rechtskracht van een informatieobject. Mag niet van een waarde zijn voorzien als de `status` de waarde 'in bewerking' of 'ter vaststelling' heeft. | | nee | C​R​U​D | @@ -111,6 +115,7 @@ Uitleg bij mogelijke waarden: | informatieobjecttype | URL-referentie naar het INFORMATIEOBJECTTYPE (in de Catalogi API). | string | ja | C​R​U​D | | locked | Geeft aan of het document gelocked is. Alleen als een document gelocked is, mogen er aanpassingen gemaakt worden. | boolean | ja | ~~C~~​R​~~U~~​~~D~~ | | bestandsdelen | | array | ja | ~~C~~​R​~~U~~​~~D~~ | +| trefwoorden | Een lijst van trefwoorden gescheiden door comma's. | array | nee | C​R​U​D | ## Gebruiksrechten