From 8c4fbae6aae7476270c04418865480c9958ad5b9 Mon Sep 17 00:00:00 2001 From: SilviaAmAm Date: Fri, 7 Feb 2025 15:57:04 +0100 Subject: [PATCH 1/9] :sparkles: [#357] Add search zaken endpoint --- .../openarchiefbeheer/zaken/api/filtersets.py | 10 ++++++++++ .../openarchiefbeheer/zaken/api/viewsets.py | 19 ++++++++++++++++--- 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/backend/src/openarchiefbeheer/zaken/api/filtersets.py b/backend/src/openarchiefbeheer/zaken/api/filtersets.py index e8f1f0219..b7f6c3f56 100644 --- a/backend/src/openarchiefbeheer/zaken/api/filtersets.py +++ b/backend/src/openarchiefbeheer/zaken/api/filtersets.py @@ -22,6 +22,7 @@ NumberFilter, UUIDFilter, ) +from django_filters.rest_framework import DjangoFilterBackend from openarchiefbeheer.destruction.constants import ListItemStatus from openarchiefbeheer.destruction.models import ( @@ -282,3 +283,12 @@ def filter_behandelend_afdeling( ) return zaken_with_afdeling.filter(behandelend_afdeling__contains=value) + + +class ZaakFilterBackend(DjangoFilterBackend): + def get_filterset_kwargs(self, request, queryset, view): + return { + "data": request.query_params or request.data, + "queryset": queryset, + "request": request, + } diff --git a/backend/src/openarchiefbeheer/zaken/api/viewsets.py b/backend/src/openarchiefbeheer/zaken/api/viewsets.py index 4c2a63133..d4dfb23fb 100644 --- a/backend/src/openarchiefbeheer/zaken/api/viewsets.py +++ b/backend/src/openarchiefbeheer/zaken/api/viewsets.py @@ -1,8 +1,8 @@ from django.utils.translation import gettext_lazy as _ -from django_filters.rest_framework import DjangoFilterBackend from drf_spectacular.utils import extend_schema, extend_schema_view from rest_framework import mixins, viewsets +from rest_framework.decorators import action from rest_framework.filters import OrderingFilter from rest_framework.permissions import IsAuthenticated @@ -14,15 +14,24 @@ from openarchiefbeheer.utils.paginators import PageNumberPagination from ..models import Zaak -from .filtersets import ZaakFilterSet +from .filtersets import ZaakFilterBackend, ZaakFilterSet from .serializers import ZaakSerializer @extend_schema_view( list=extend_schema( summary=_("List zaken"), + tags=["Zaken"], description=_("List cases retrieved and cached from Open Zaak."), ), + search=extend_schema( + tags=["Zaken"], + summary=_("Search zaken"), + description=_( + "Search cases retrieved and cached from Open Zaak. " + "You can use the same arguments in the JSON body as the query params of the 'List Zaken' endpoint " + ), + ), ) class ZakenViewSet(mixins.ListModelMixin, viewsets.GenericViewSet): queryset = Zaak.objects.all().order_by("pk") @@ -32,6 +41,10 @@ class ZakenViewSet(mixins.ListModelMixin, viewsets.GenericViewSet): & (CanStartDestructionPermission | CanReviewPermission | CanCoReviewPermission) ] pagination_class = PageNumberPagination - filter_backends = (DjangoFilterBackend, OrderingFilter) + filter_backends = (ZaakFilterBackend, OrderingFilter) filterset_class = ZaakFilterSet ordering_fields = "__all__" + + @action(detail=False, methods=["post"], name="search") + def search(self, request, *args, **kwargs) -> None: + return self.list(request, *args, **kwargs) From 1dbeb5cb750d5006b6e77d55e19b2b175a8dba87 Mon Sep 17 00:00:00 2001 From: SilviaAmAm Date: Fri, 7 Feb 2025 15:57:32 +0100 Subject: [PATCH 2/9] :white_check_mark: [#357] Test search zaken endpoint --- .../zaken/tests/test_filtersets.py | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/backend/src/openarchiefbeheer/zaken/tests/test_filtersets.py b/backend/src/openarchiefbeheer/zaken/tests/test_filtersets.py index 5f2eb72c5..ae47378d3 100644 --- a/backend/src/openarchiefbeheer/zaken/tests/test_filtersets.py +++ b/backend/src/openarchiefbeheer/zaken/tests/test_filtersets.py @@ -410,3 +410,24 @@ def test_filter_on_zaaktype(self): self.assertIn(zaken_2[0].url, urls_zaken) self.assertIn(zaken_2[1].url, urls_zaken) + + def test_filter_on_zaaktype_with_post(self): + ZaakFactory.create_batch(3, identificatie="ZAAK-01") + zaken_2 = ZaakFactory.create_batch(2, identificatie="ZAAK-02") + + user = UserFactory(username="record_manager", post__can_start_destruction=True) + + self.client.force_authenticate(user) + response = self.client.post( + reverse("api:zaken-search"), + data={"zaaktype__in": ",".join([zaken_2[0].zaaktype, zaken_2[1].zaaktype])}, + ) + data = response.json() + + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(data["count"], 2) + + urls_zaken = [zaak["url"] for zaak in data["results"]] + + self.assertIn(zaken_2[0].url, urls_zaken) + self.assertIn(zaken_2[1].url, urls_zaken) From 6a552472603d88ae561f6ea30419e03ba47ed169 Mon Sep 17 00:00:00 2001 From: SilviaAmAm Date: Fri, 7 Feb 2025 16:26:05 +0100 Subject: [PATCH 3/9] :recycle: [#357] Use search endpoint to retrieve zaken --- frontend/src/lib/api/zaken.ts | 11 +++++++---- .../create/DestructionListCreate.loader.ts | 13 +++++++------ 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/frontend/src/lib/api/zaken.ts b/frontend/src/lib/api/zaken.ts index 829c4787e..081c92cfd 100644 --- a/frontend/src/lib/api/zaken.ts +++ b/frontend/src/lib/api/zaken.ts @@ -8,10 +8,13 @@ export type PaginatedZaken = PaginatedResults; * Retrieve zaken using the configured ZRC service. For information over the query parameters accepted and the schema of * the response, look at the '/zaken/api/v1/zaken' list endpoint of Open Zaak. */ -export async function listZaken( - params?: URLSearchParams | Record, -) { - const response = await request("GET", "/zaken/", params); +export async function listZaken(params?: Record) { + const response = await request( + "POST", + "/zaken/search/", + new URLSearchParams(), + params, + ); const promise: Promise = response.json(); return promise; } diff --git a/frontend/src/pages/destructionlist/create/DestructionListCreate.loader.ts b/frontend/src/pages/destructionlist/create/DestructionListCreate.loader.ts index 8d241bfda..cde8f6256 100644 --- a/frontend/src/pages/destructionlist/create/DestructionListCreate.loader.ts +++ b/frontend/src/pages/destructionlist/create/DestructionListCreate.loader.ts @@ -22,19 +22,20 @@ export const destructionListCreateLoader = loginRequired( async ({ request, }: LoaderFunctionArgs): Promise => { + const searchParams = new URL(request.url).searchParams; + const searchParamsZakenEndpoint: Record = { not_in_destruction_list: "true", }; - const searchParams = new URL(request.url).searchParams; - // Update search params efficiently - Object.entries(searchParamsZakenEndpoint).forEach(([key, value]) => - searchParams.set(key, value), - ); + for (const [key, value] of searchParams) { + searchParamsZakenEndpoint[key] = value; + } + searchParams.set("not_in_destruction_list", "true"); // Fetch reviewers, zaken, and choices concurrently const [zaken, reviewers] = await Promise.all([ - listZaken(searchParams), + listZaken(searchParamsZakenEndpoint), listReviewers(), ]); From f93a68f6ba64920197675c310e6f9710824cd90f Mon Sep 17 00:00:00 2001 From: SilviaAmAm Date: Fri, 7 Feb 2025 16:27:01 +0100 Subject: [PATCH 4/9] :white_check_mark: [#357] Update mock data for stories --- frontend/.storybook/mockData.ts | 8 ++++++++ .../create/DestructionListCreate.stories.tsx | 1 + 2 files changed, 9 insertions(+) diff --git a/frontend/.storybook/mockData.ts b/frontend/.storybook/mockData.ts index 0cb4e29bf..bb6db370c 100644 --- a/frontend/.storybook/mockData.ts +++ b/frontend/.storybook/mockData.ts @@ -5,6 +5,7 @@ import { coReviewsFactory, destructionListAssigneesFactory, destructionListFactory, + paginatedZakenFactory, recordManagerFactory, userFactory, usersFactory, @@ -201,6 +202,13 @@ export const MOCKS = { status: "201", response: {}, }, + // ZAKEN + ZAKEN_SEARCH: { + url: "http://localhost:8000/api/v1/zaken/search/", + method: "POST", + status: 200, + response: paginatedZakenFactory(), + }, }; export const MOCK_BASE = Object.values(MOCKS); diff --git a/frontend/src/pages/destructionlist/create/DestructionListCreate.stories.tsx b/frontend/src/pages/destructionlist/create/DestructionListCreate.stories.tsx index 352248f9a..0af6cc15b 100644 --- a/frontend/src/pages/destructionlist/create/DestructionListCreate.stories.tsx +++ b/frontend/src/pages/destructionlist/create/DestructionListCreate.stories.tsx @@ -33,6 +33,7 @@ const meta: Meta = { MOCKS.OIDC_INFO, MOCKS.SELECTIE_LIJST_CHOICES, MOCKS.ZAAKTYPE_CHOICES, + MOCKS.ZAKEN_SEARCH, { url: "http://localhost:8000/api/v1/whoami/", method: "GET", From d4b2f0e4b4f16431b3e1269e9b750a254f24a410 Mon Sep 17 00:00:00 2001 From: SilviaAmAm Date: Fri, 7 Feb 2025 16:59:05 +0100 Subject: [PATCH 5/9] :sparkles: [#357] Add search method to the zaaktype choices endpoint --- .../src/openarchiefbeheer/zaken/api/views.py | 46 ++++++++++++++----- 1 file changed, 34 insertions(+), 12 deletions(-) diff --git a/backend/src/openarchiefbeheer/zaken/api/views.py b/backend/src/openarchiefbeheer/zaken/api/views.py index 8b02508e9..27d32a077 100644 --- a/backend/src/openarchiefbeheer/zaken/api/views.py +++ b/backend/src/openarchiefbeheer/zaken/api/views.py @@ -53,6 +53,21 @@ class InternalZaaktypenChoicesView(GenericAPIView): def get_queryset(self): return Zaak.objects.all() + def _retrieve_zaaktypen(self, request): + filterset = ZaakFilterSet(data=request.query_params or request.data) + is_valid = filterset.is_valid() + if not is_valid: + raise ValidationError(filterset.errors) + + zaaktypen = filterset.qs.distinct("_expand__zaaktype__url").values_list( + "_expand__zaaktype", flat=True + ) + zaaktypen_choices = format_zaaktype_choices(zaaktypen) + + serializer = ChoiceSerializer(data=zaaktypen_choices, many=True) + serializer.is_valid(raise_exception=True) + return Response(serializer.data, status=status.HTTP_200_OK) + @extend_schema( summary=_("Retrieve local zaaktypen choices"), description=_( @@ -70,19 +85,26 @@ def get_queryset(self): ) @method_decorator(cache_page(60 * 15)) def get(self, request, *args, **kwargs): - filterset = ZaakFilterSet(data=request.query_params) - is_valid = filterset.is_valid() - if not is_valid: - raise ValidationError(filterset.errors) - - zaaktypen = filterset.qs.distinct("_expand__zaaktype__url").values_list( - "_expand__zaaktype", flat=True - ) - zaaktypen_choices = format_zaaktype_choices(zaaktypen) + return self._retrieve_zaaktypen(request) - serializer = ChoiceSerializer(data=zaaktypen_choices, many=True) - serializer.is_valid(raise_exception=True) - return Response(serializer.data, status=status.HTTP_200_OK) + @extend_schema( + summary=_("Search local zaaktypen choices"), + description=_( + "Retrieve zaaktypen of the zaken stored in the OAB database and return a value and a label per zaaktype. " + "The label is the 'identificatie' field an the value is a string of comma separated URLs. " + "There are multiple URLs per identificatie if there are multiple versions of a zaaktype. " + "If there are no zaken of a particular zaaktype in the database, then that zaaktype is not returned. " + "The response is cached for 15 minutes.\n" + "All the filters for the zaken are available to limit which zaaktypen should be returned." + ), + tags=["private"], + responses={ + 200: ChoiceSerializer(many=True), + }, + ) + @method_decorator(cache_page(60 * 15)) + def post(self, request, *args, **kwargs): + return self._retrieve_zaaktypen(request) class ExternalZaaktypenChoicesView(APIView): From 8fc330666f30387583dbeb8748d5e92d626a0def Mon Sep 17 00:00:00 2001 From: SilviaAmAm Date: Fri, 7 Feb 2025 16:59:32 +0100 Subject: [PATCH 6/9] :white_check_mark: [#357] Test search method to the zaaktype choices endpoint --- .../zaken/tests/test_views.py | 56 ++++++++++++++----- 1 file changed, 41 insertions(+), 15 deletions(-) diff --git a/backend/src/openarchiefbeheer/zaken/tests/test_views.py b/backend/src/openarchiefbeheer/zaken/tests/test_views.py index 31d7f0f69..e06aa81cf 100644 --- a/backend/src/openarchiefbeheer/zaken/tests/test_views.py +++ b/backend/src/openarchiefbeheer/zaken/tests/test_views.py @@ -209,28 +209,54 @@ def test_retrieve_zaaktypen_choices_for_destruction_list(self): DestructionListItemFactory.create_batch(2, with_zaak=True) self.client.force_authenticate(user=user) - endpoint = furl(reverse("api:retrieve-zaaktypen-choices")) - endpoint.args["in_destruction_list"] = destruction_list.uuid + url = reverse("api:retrieve-zaaktypen-choices") - response = self.client.get(endpoint.url) + with self.subTest("with GET"): + endpoint = furl(url) + endpoint.args["in_destruction_list"] = destruction_list.uuid - self.assertEqual(response.status_code, status.HTTP_200_OK) + response = self.client.get(endpoint.url) - choices = sorted(response.json(), key=lambda choice: choice["label"]) + self.assertEqual(response.status_code, status.HTTP_200_OK) - self.assertEqual(len(choices), 2) - self.assertEqual(choices[0]["label"], "ZAAKTYPE 1.1 (ZAAKTYPE-1)") - self.assertEqual(choices[1]["label"], "ZAAKTYPE 2.0 (ZAAKTYPE-2)") + choices = sorted(response.json(), key=lambda choice: choice["label"]) - values = choices[0]["value"].split(",") + self.assertEqual(len(choices), 2) + self.assertEqual(choices[0]["label"], "ZAAKTYPE 1.1 (ZAAKTYPE-1)") + self.assertEqual(choices[1]["label"], "ZAAKTYPE 2.0 (ZAAKTYPE-2)") - self.assertEqual(len(values), 2) - self.assertIn(items_in_list[0].zaak._expand["zaaktype"]["url"], values) - self.assertIn(items_in_list[1].zaak._expand["zaaktype"]["url"], values) + values = choices[0]["value"].split(",") - self.assertEqual( - choices[1]["value"], items_in_list[2].zaak._expand["zaaktype"]["url"] - ) + self.assertEqual(len(values), 2) + self.assertIn(items_in_list[0].zaak._expand["zaaktype"]["url"], values) + self.assertIn(items_in_list[1].zaak._expand["zaaktype"]["url"], values) + + self.assertEqual( + choices[1]["value"], items_in_list[2].zaak._expand["zaaktype"]["url"] + ) + + with self.subTest("with POST"): + response = self.client.post( + url, data={"in_destruction_list": destruction_list.uuid} + ) + + self.assertEqual(response.status_code, status.HTTP_200_OK) + + choices = sorted(response.json(), key=lambda choice: choice["label"]) + + self.assertEqual(len(choices), 2) + self.assertEqual(choices[0]["label"], "ZAAKTYPE 1.1 (ZAAKTYPE-1)") + self.assertEqual(choices[1]["label"], "ZAAKTYPE 2.0 (ZAAKTYPE-2)") + + values = choices[0]["value"].split(",") + + self.assertEqual(len(values), 2) + self.assertIn(items_in_list[0].zaak._expand["zaaktype"]["url"], values) + self.assertIn(items_in_list[1].zaak._expand["zaaktype"]["url"], values) + + self.assertEqual( + choices[1]["value"], items_in_list[2].zaak._expand["zaaktype"]["url"] + ) @tag("gh-303") def test_retrieve_zaaktypen_choices_for_destruction_list_if_zaaktype_id_empty(self): From 356da754e51852fd15f2effa6a66ac909db942e5 Mon Sep 17 00:00:00 2001 From: SilviaAmAm Date: Fri, 7 Feb 2025 17:00:11 +0100 Subject: [PATCH 7/9] :recycle: [#357] Use new post endpoint for zaaktype-choices --- frontend/.storybook/mockData.ts | 6 ++++++ frontend/src/lib/api/private.ts | 15 +++++++++++---- .../create/DestructionListCreate.stories.tsx | 1 + .../ShortProcedureSettingsPage.stories.tsx | 1 + 4 files changed, 19 insertions(+), 4 deletions(-) diff --git a/frontend/.storybook/mockData.ts b/frontend/.storybook/mockData.ts index bb6db370c..c388409f9 100644 --- a/frontend/.storybook/mockData.ts +++ b/frontend/.storybook/mockData.ts @@ -50,6 +50,12 @@ export const MOCKS = { status: 200, response: zaaktypeChoicesFactory(), }, + DESTRUCTION_SEARCH_ZAAKTYPE_CHOICES: { + url: "http://localhost:8000/api/v1/_zaaktypen-choices/", + method: "POST", + status: 200, + response: zaaktypeChoicesFactory(), + }, DESTRUCTION_LISTS: { url: "http://localhost:8000/api/v1/destruction-lists/?ordering=-created", method: "GET", diff --git a/frontend/src/lib/api/private.ts b/frontend/src/lib/api/private.ts index dfa35e5f4..d0fa4d057 100644 --- a/frontend/src/lib/api/private.ts +++ b/frontend/src/lib/api/private.ts @@ -124,11 +124,18 @@ export async function listZaaktypeChoices( params.set("notInDestructionList", "true"); } - const endpoint = external - ? "/_external-zaaktypen-choices/" - : "/_zaaktypen-choices/"; + let response; + if (external) { + response = await request( + "GET", + "/_external-zaaktypen-choices/", + params, + ); + } else { + const filters = Object.fromEntries(params.entries()); + response = await request("POST", "/_zaaktypen-choices/", {}, filters); + } - const response = await request("GET", endpoint, params); const promise: Promise = response.json(); return promise; diff --git a/frontend/src/pages/destructionlist/create/DestructionListCreate.stories.tsx b/frontend/src/pages/destructionlist/create/DestructionListCreate.stories.tsx index 0af6cc15b..73f7cfa00 100644 --- a/frontend/src/pages/destructionlist/create/DestructionListCreate.stories.tsx +++ b/frontend/src/pages/destructionlist/create/DestructionListCreate.stories.tsx @@ -33,6 +33,7 @@ const meta: Meta = { MOCKS.OIDC_INFO, MOCKS.SELECTIE_LIJST_CHOICES, MOCKS.ZAAKTYPE_CHOICES, + MOCKS.DESTRUCTION_SEARCH_ZAAKTYPE_CHOICES, MOCKS.ZAKEN_SEARCH, { url: "http://localhost:8000/api/v1/whoami/", diff --git a/frontend/src/pages/settings/pages/short-procedure/ShortProcedureSettingsPage.stories.tsx b/frontend/src/pages/settings/pages/short-procedure/ShortProcedureSettingsPage.stories.tsx index 1a5ddb50d..cfd8cf46d 100644 --- a/frontend/src/pages/settings/pages/short-procedure/ShortProcedureSettingsPage.stories.tsx +++ b/frontend/src/pages/settings/pages/short-procedure/ShortProcedureSettingsPage.stories.tsx @@ -32,6 +32,7 @@ const meta: Meta = { parameters: { mockData: [ MOCKS.OIDC_INFO, + MOCKS.DESTRUCTION_SEARCH_ZAAKTYPE_CHOICES, { url: "http://localhost:8000/api/v1/whoami/", method: "GET", From 1a3059e9a243a129cfa4edebace59de99517fb8b Mon Sep 17 00:00:00 2001 From: SilviaAmAm Date: Mon, 10 Feb 2025 12:25:48 +0100 Subject: [PATCH 8/9] :sparkles: [#357] Support ordering and pagination params in request body Since the filters now can come through the request body, so can the ordering and the pagination params. So these need to be supported --- .../destruction/api/backends.py | 12 +++++--- .../utils/django_filters/backends.py | 29 +++++++++++++++++++ .../src/openarchiefbeheer/utils/paginators.py | 14 +++++++++ .../openarchiefbeheer/zaken/api/viewsets.py | 10 ++++--- 4 files changed, 57 insertions(+), 8 deletions(-) diff --git a/backend/src/openarchiefbeheer/destruction/api/backends.py b/backend/src/openarchiefbeheer/destruction/api/backends.py index 04ccbff66..01d4fd153 100644 --- a/backend/src/openarchiefbeheer/destruction/api/backends.py +++ b/backend/src/openarchiefbeheer/destruction/api/backends.py @@ -1,6 +1,9 @@ from django_filters.rest_framework import DjangoFilterBackend from django_filters.utils import translate_validation -from rest_framework.filters import OrderingFilter + +from openarchiefbeheer.utils.django_filters.backends import ( + OrderingWithPostFilterBackend, +) class NestedFilterBackend(DjangoFilterBackend): @@ -38,7 +41,7 @@ def get_nested_filterset(self, request, view): return nested_filterset_class(**kwargs) -class NestedOrderingFilterBackend(OrderingFilter): +class NestedOrderingFilterBackend(OrderingWithPostFilterBackend): def get_valid_fields(self, queryset, view, context={}) -> list[tuple[str, str]]: valid_fields = getattr( view, context.get("ordering_fields_view_attr", "ordering_fields"), [] @@ -72,12 +75,13 @@ def is_term_valid(self, term, valid_fields) -> bool: return term in valid_fields def get_ordering(self, request, queryset, view) -> list[str]: + ordering_filters = self.get_ordering_filters(request) base_ordering_param = ( f"{view.nested_ordering_prefix}-{self.ordering_param}" if view.nested_ordering_prefix else self.ordering_param ) - base_params = request.query_params.get(base_ordering_param) + base_params = ordering_filters.get(base_ordering_param) formatted_fields = [] if base_params: fields = [param.strip() for param in base_params.split(",")] @@ -88,7 +92,7 @@ def get_ordering(self, request, queryset, view) -> list[str]: term for term in fields if self.is_term_valid(term, valid_fields) ] - nested_params = request.query_params.get(self.ordering_param) + nested_params = ordering_filters.get(self.ordering_param) if nested_params: fields = [param.strip() for param in nested_params.split(",")] nested_queryset = view.nested_ordering_model.objects.all() diff --git a/backend/src/openarchiefbeheer/utils/django_filters/backends.py b/backend/src/openarchiefbeheer/utils/django_filters/backends.py index d924dcb72..9ca5ba9c2 100644 --- a/backend/src/openarchiefbeheer/utils/django_filters/backends.py +++ b/backend/src/openarchiefbeheer/utils/django_filters/backends.py @@ -1,5 +1,34 @@ from django_filters.rest_framework import DjangoFilterBackend +from rest_framework.filters import OrderingFilter class NoModelFilterBackend(DjangoFilterBackend): pass + + +class OrderingWithPostFilterBackend(OrderingFilter): + """Support ordering params also in the request body.""" + + def get_ordering_filters(self, request): + return request.query_params or request.data + + def get_ordering(self, request, queryset, view): + """ + Ordering is set by a comma delimited ?ordering=... query parameter. + + The `ordering` query parameter can be overridden by setting + the `ordering_param` value on the OrderingFilter or by + specifying an `ORDERING_PARAM` value in the API settings. + """ + # Overriding where the filters are coming from (for us from the POST request body). + # Normally they are query params. + ordering_filters = self.get_ordering_filters(request) + params = ordering_filters.get(self.ordering_param) + if params: + fields = [param.strip() for param in params.split(",")] + ordering = self.remove_invalid_fields(queryset, fields, view, request) + if ordering: + return ordering + + # No ordering was included, or all the ordering fields were invalid + return self.get_default_ordering(view) diff --git a/backend/src/openarchiefbeheer/utils/paginators.py b/backend/src/openarchiefbeheer/utils/paginators.py index 11ff343b9..fcb035c5f 100644 --- a/backend/src/openarchiefbeheer/utils/paginators.py +++ b/backend/src/openarchiefbeheer/utils/paginators.py @@ -4,3 +4,17 @@ class PageNumberPagination(_PageNumberPagination): page_size_query_param = "page_size" page_size = 100 + + +class PageNumberPaginationWithPost(_PageNumberPagination): + """Support pagination param also in request body""" + + page_size_query_param = "page_size" + page_size = 100 + + def get_page_number(self, request, paginator): + params = request.query_params or request.data + page_number = params.get(self.page_query_param) or 1 + if page_number in self.last_page_strings: + page_number = paginator.num_pages + return page_number diff --git a/backend/src/openarchiefbeheer/zaken/api/viewsets.py b/backend/src/openarchiefbeheer/zaken/api/viewsets.py index d4dfb23fb..25a6584ee 100644 --- a/backend/src/openarchiefbeheer/zaken/api/viewsets.py +++ b/backend/src/openarchiefbeheer/zaken/api/viewsets.py @@ -3,7 +3,6 @@ from drf_spectacular.utils import extend_schema, extend_schema_view from rest_framework import mixins, viewsets from rest_framework.decorators import action -from rest_framework.filters import OrderingFilter from rest_framework.permissions import IsAuthenticated from openarchiefbeheer.destruction.api.permissions import ( @@ -11,7 +10,10 @@ CanReviewPermission, CanStartDestructionPermission, ) -from openarchiefbeheer.utils.paginators import PageNumberPagination +from openarchiefbeheer.utils.django_filters.backends import ( + OrderingWithPostFilterBackend, +) +from openarchiefbeheer.utils.paginators import PageNumberPaginationWithPost from ..models import Zaak from .filtersets import ZaakFilterBackend, ZaakFilterSet @@ -40,8 +42,8 @@ class ZakenViewSet(mixins.ListModelMixin, viewsets.GenericViewSet): IsAuthenticated & (CanStartDestructionPermission | CanReviewPermission | CanCoReviewPermission) ] - pagination_class = PageNumberPagination - filter_backends = (ZaakFilterBackend, OrderingFilter) + pagination_class = PageNumberPaginationWithPost + filter_backends = (ZaakFilterBackend, OrderingWithPostFilterBackend) filterset_class = ZaakFilterSet ordering_fields = "__all__" From ec4d6bd372cbd1ffb3435950330d1f9ec9093858 Mon Sep 17 00:00:00 2001 From: SilviaAmAm Date: Tue, 11 Feb 2025 12:53:52 +0100 Subject: [PATCH 9/9] :ok_hand: [#357] PR feedback --- frontend/src/lib/api/zaken.test.ts | 8 ++++---- frontend/src/lib/api/zaken.ts | 6 +++--- .../create/DestructionListCreate.loader.ts | 4 ++-- .../detail/DestructionListDetail.loader.ts | 6 +++--- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/frontend/src/lib/api/zaken.test.ts b/frontend/src/lib/api/zaken.test.ts index aa2ce9317..ccf53fa3d 100644 --- a/frontend/src/lib/api/zaken.test.ts +++ b/frontend/src/lib/api/zaken.test.ts @@ -1,9 +1,9 @@ import fetchMock from "jest-fetch-mock"; import { zakenFactory } from "../../fixtures/zaak"; -import { listZaken } from "./zaken"; +import { searchZaken } from "./zaken"; -describe("listZaken", () => { +describe("searchZaken", () => { beforeAll(() => { fetchMock.enableMocks(); }); @@ -15,11 +15,11 @@ describe("listZaken", () => { it("should return a list of users on success", async () => { const zaken = zakenFactory(); fetchMock.mockResponseOnce(JSON.stringify(zaken)); - await expect(listZaken()).resolves.toEqual(zaken); + await expect(searchZaken()).resolves.toEqual(zaken); }); it("should throw an error on failure", async () => { fetchMock.mockRejectOnce(new Error("Internal Server Error")); - await expect(listZaken()).rejects.toThrow("Internal Server Error"); + await expect(searchZaken()).rejects.toThrow("Internal Server Error"); }); }); diff --git a/frontend/src/lib/api/zaken.ts b/frontend/src/lib/api/zaken.ts index 081c92cfd..1553af944 100644 --- a/frontend/src/lib/api/zaken.ts +++ b/frontend/src/lib/api/zaken.ts @@ -5,10 +5,10 @@ import { request } from "./request"; export type PaginatedZaken = PaginatedResults; /** - * Retrieve zaken using the configured ZRC service. For information over the query parameters accepted and the schema of - * the response, look at the '/zaken/api/v1/zaken' list endpoint of Open Zaak. + * Search zaken using the configured ZRC service. For information over the filters that can be passed in the + * request body, look at query params of the '/zaken/api/v1/zaken' list endpoint of Open Zaak. */ -export async function listZaken(params?: Record) { +export async function searchZaken(params?: Record) { const response = await request( "POST", "/zaken/search/", diff --git a/frontend/src/pages/destructionlist/create/DestructionListCreate.loader.ts b/frontend/src/pages/destructionlist/create/DestructionListCreate.loader.ts index cde8f6256..6502e9a1b 100644 --- a/frontend/src/pages/destructionlist/create/DestructionListCreate.loader.ts +++ b/frontend/src/pages/destructionlist/create/DestructionListCreate.loader.ts @@ -2,7 +2,7 @@ import { LoaderFunctionArgs } from "@remix-run/router/utils"; import { User } from "../../../lib/api/auth"; import { listReviewers } from "../../../lib/api/reviewers"; -import { PaginatedZaken, listZaken } from "../../../lib/api/zaken"; +import { PaginatedZaken, searchZaken } from "../../../lib/api/zaken"; import { canStartDestructionListRequired, loginRequired, @@ -35,7 +35,7 @@ export const destructionListCreateLoader = loginRequired( // Fetch reviewers, zaken, and choices concurrently const [zaken, reviewers] = await Promise.all([ - listZaken(searchParamsZakenEndpoint), + searchZaken(searchParamsZakenEndpoint), listReviewers(), ]); diff --git a/frontend/src/pages/destructionlist/detail/DestructionListDetail.loader.ts b/frontend/src/pages/destructionlist/detail/DestructionListDetail.loader.ts index a178134bc..51e71f557 100644 --- a/frontend/src/pages/destructionlist/detail/DestructionListDetail.loader.ts +++ b/frontend/src/pages/destructionlist/detail/DestructionListDetail.loader.ts @@ -22,7 +22,7 @@ import { ReviewResponse, getLatestReviewResponse, } from "../../../lib/api/reviewResponse"; -import { PaginatedZaken, listZaken } from "../../../lib/api/zaken"; +import { PaginatedZaken, searchZaken } from "../../../lib/api/zaken"; import { canViewDestructionListRequired, loginRequired, @@ -95,7 +95,7 @@ export const destructionListDetailLoader = loginRequired( async (): Promise => { const params = searchParams; if (isEditing) { - params["item-order_match_zaken"] = "true"; // Must be in sync with `listZaken()` ordering. + params["item-order_match_zaken"] = "true"; // Must be in sync with `searchZaken()` ordering. } return reviewItemsWithZaak ? { @@ -158,7 +158,7 @@ export const destructionListDetailLoader = loginRequired( previous: null, results: [], } as PaginatedZaken) - : listZaken({ + : searchZaken({ ...searchParams, not_in_destruction_list_except: uuid, });