From 4f0789d458ec558dfc8a2091045510ccccfbe639 Mon Sep 17 00:00:00 2001 From: SonnyBA Date: Mon, 16 Sep 2024 12:15:07 +0200 Subject: [PATCH] [#328] get specific object version (#429) * [#328] add object-history-detail endpoint * [#328] update oas spec * [#328] update path parameter documentation * [#328] apply formatting --------- Co-authored-by: Sonny Bakker --- src/objects/api/v2/openapi.yaml | 45 ++++++++++++++++++ src/objects/api/v2/views.py | 35 +++++++++++++- src/objects/tests/v2/test_object_api.py | 62 +++++++++++++++++++++++++ 3 files changed, 141 insertions(+), 1 deletion(-) diff --git a/src/objects/api/v2/openapi.yaml b/src/objects/api/v2/openapi.yaml index 907c0ae3..e3cc4b9c 100644 --- a/src/objects/api/v2/openapi.yaml +++ b/src/objects/api/v2/openapi.yaml @@ -458,6 +458,51 @@ paths: responses: '204': description: No response body + /objects/{uuid}/{index}: + get: + operationId: object_history_detail + description: Retrieve the specified OBJECT given an UUID and INDEX. + parameters: + - in: header + name: Accept-Crs + schema: + type: string + enum: + - EPSG:4326 + description: 'The desired ''Coordinate Reference System'' (CRS) of the response + data. According to the GeoJSON spec, WGS84 is the default (EPSG: 4326 is + the same as WGS84).' + - in: path + name: index + schema: + type: number + required: true + - in: path + name: uuid + schema: + type: string + format: uuid + required: true + tags: + - objects + security: + - tokenAuth: [] + responses: + '200': + headers: + Content-Crs: + schema: + type: string + enum: + - EPSG:4326 + description: 'The ''Coordinate Reference System'' (CRS) of the request + data. According to the GeoJSON spec, WGS84 is the default (EPSG: 4326 + is the same as WGS84).' + content: + application/json: + schema: + $ref: '#/components/schemas/HistoryRecord' + description: OK /objects/{uuid}/history: get: operationId: object_history diff --git a/src/objects/api/v2/views.py b/src/objects/api/v2/views.py index d2d24ff1..6843706d 100644 --- a/src/objects/api/v2/views.py +++ b/src/objects/api/v2/views.py @@ -3,9 +3,11 @@ from django.conf import settings from django.db import models -from drf_spectacular.utils import extend_schema, extend_schema_view +from drf_spectacular.types import OpenApiTypes +from drf_spectacular.utils import OpenApiParameter, extend_schema, extend_schema_view from rest_framework import mixins, viewsets from rest_framework.decorators import action +from rest_framework.generics import get_object_or_404 from rest_framework.response import Response from vng_api_common.filters import Backend as FilterBackend from vng_api_common.search import SearchMixin @@ -126,6 +128,37 @@ def history(self, request, uuid=None): serializer = self.get_serializer(records, many=True) return Response(serializer.data) + @extend_schema( + description="Retrieve the specified OBJECT given an UUID and INDEX.", + responses={"200": HistoryRecordSerializer()}, + parameters=[ + OpenApiParameter( + name="index", + location=OpenApiParameter.PATH, + required=True, + type=OpenApiTypes.NUMBER, + ), + OpenApiParameter( + name="uuid", + location=OpenApiParameter.PATH, + required=True, + type=OpenApiTypes.UUID, + ), + ], + ) + @action( + detail=True, + methods=["get"], + url_path=r"(?P\d+)", + serializer_class=HistoryRecordSerializer, + ) + def history_detail(self, request, uuid=None, index=None): + """Retrieve a RECORD of an OBJECT.""" + queryset = self.get_queryset() + record = get_object_or_404(queryset, object__uuid=uuid, index=index) + serializer = self.get_serializer(record) + return Response(serializer.data) + @extend_schema( description="Perform a (geo) search on OBJECTs.", request=ObjectSearchSerializer, diff --git a/src/objects/tests/v2/test_object_api.py b/src/objects/tests/v2/test_object_api.py index 23bf4933..29819f94 100644 --- a/src/objects/tests/v2/test_object_api.py +++ b/src/objects/tests/v2/test_object_api.py @@ -123,6 +123,68 @@ def test_retrieve_object(self, m): }, ) + def test_retrieve_by_index(self, m): + record1 = ObjectRecordFactory.create( + object__object_type=self.object_type, + start_at=date(2020, 1, 1), + geometry="POINT (4.910649523925713 52.37240093589432)", + index=1, + ) + + object = record1.object + + record2 = ObjectRecordFactory.create( + object=object, start_at=date.today(), correct=record1, index=2 + ) + + with self.subTest(record=record1): + url = reverse("object-history-detail", args=[object.uuid, 1]) + + response = self.client.get(url) + + self.assertEqual(response.status_code, status.HTTP_200_OK) + + data = response.json() + + self.assertEqual( + data, + { + "index": 1, + "typeVersion": record1.version, + "data": record1.data, + "geometry": json.loads(record1.geometry.json), + "startAt": record1.start_at.isoformat(), + "endAt": record2.start_at.isoformat(), + "registrationAt": record1.registration_at.isoformat(), + "correctionFor": None, + "correctedBy": 2, + }, + ) + + with self.subTest(record=record2): + url = reverse("object-history-detail", args=[object.uuid, 2]) + + response = self.client.get(url) + + self.assertEqual(response.status_code, status.HTTP_200_OK) + + data = response.json() + + self.assertEqual( + data, + { + "index": 2, + "typeVersion": record2.version, + "data": record2.data, + "geometry": json.loads(record2.geometry.json), + "startAt": record2.start_at.isoformat(), + "endAt": None, + "registrationAt": record2.registration_at.isoformat(), + "correctionFor": 1, + "correctedBy": None, + }, + ) + def test_create_object(self, m): mock_service_oas_get(m, OBJECT_TYPES_API, "objecttypes") m.get(