From d38668c5f2e970b27ec3e5059eeb8a109a171dad Mon Sep 17 00:00:00 2001 From: Anna Shamray Date: Thu, 26 Sep 2024 15:45:48 +0200 Subject: [PATCH] :fire: [#453] remove v1 --- .github/workflows/code-quality.yml | 11 +- .../workflows/generate-postman-collection.yml | 8 +- .github/workflows/generate-sdks.yml | 8 +- .github/workflows/lint-oas.yml | 8 +- bin/generate_schema.sh | 15 +- src/objects/api/urls.py | 1 - src/objects/api/v1/__init__.py | 0 src/objects/api/v1/filters.py | 121 --- src/objects/api/v1/openapi.yaml | 919 ------------------ src/objects/api/v1/urls.py | 39 - src/objects/api/v1/views.py | 159 --- src/objects/api/v2/openapi.yaml | 6 +- src/objects/conf/api.py | 10 +- src/objects/utils/hooks.py | 13 - 14 files changed, 19 insertions(+), 1299 deletions(-) delete mode 100644 src/objects/api/v1/__init__.py delete mode 100644 src/objects/api/v1/filters.py delete mode 100644 src/objects/api/v1/openapi.yaml delete mode 100644 src/objects/api/v1/urls.py delete mode 100644 src/objects/api/v1/views.py delete mode 100644 src/objects/utils/hooks.py diff --git a/.github/workflows/code-quality.yml b/.github/workflows/code-quality.yml index 1ed6cc55..a9889071 100644 --- a/.github/workflows/code-quality.yml +++ b/.github/workflows/code-quality.yml @@ -35,11 +35,8 @@ jobs: black --check src docs oas-up-to-date: - name: Check for unexepected OAS changes + name: Check for unexpected OAS changes runs-on: ubuntu-latest - strategy: - matrix: - version: ['v1', 'v2'] steps: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 @@ -60,18 +57,18 @@ jobs: run: pip install -r requirements/ci.txt - name: Generate OAS files - run: ./bin/generate_schema.sh ${{ matrix.version }} openapi-${{ matrix.version }}.yaml + run: ./bin/generate_schema.sh openapi.yaml env: DJANGO_SETTINGS_MODULE: objects.conf.ci - name: Check for OAS changes run: | - diff openapi-${{ matrix.version }}.yaml src/objects/api/${{ matrix.version }}/openapi.yaml + diff openapi.yaml src/objects/api/v2/openapi.yaml - name: Write failure markdown if: ${{ failure() }} run: | echo 'Run the following command locally and commit the changes' >> $GITHUB_STEP_SUMMARY echo '' >> $GITHUB_STEP_SUMMARY echo '```bash' >> $GITHUB_STEP_SUMMARY - echo './bin/generate_schema.sh ${{ matrix.version }}' >> $GITHUB_STEP_SUMMARY + echo './bin/generate_schema.sh' >> $GITHUB_STEP_SUMMARY echo '```' >> $GITHUB_STEP_SUMMARY diff --git a/.github/workflows/generate-postman-collection.yml b/.github/workflows/generate-postman-collection.yml index 6d4e844f..b0bca4ac 100644 --- a/.github/workflows/generate-postman-collection.yml +++ b/.github/workflows/generate-postman-collection.yml @@ -12,11 +12,7 @@ on: jobs: run: runs-on: ubuntu-latest - strategy: - matrix: - version: ['v1', 'v2'] - - name: Run with version ${{ matrix.version }} + name: Generate Postman collection steps: - uses: actions/checkout@v4 @@ -29,4 +25,4 @@ jobs: - name: Create tests folder run: mkdir -p ./tests/postman - name: Generate Postman collection - run: openapi2postmanv2 -s ./src/objects/api/${{ matrix.version }}/openapi.yaml -o ./tests/postman/collection.json --pretty + run: openapi2postmanv2 -s ./src/objects/api/v2/openapi.yaml -o ./tests/postman/collection.json --pretty diff --git a/.github/workflows/generate-sdks.yml b/.github/workflows/generate-sdks.yml index 2fcd7988..959a9c25 100644 --- a/.github/workflows/generate-sdks.yml +++ b/.github/workflows/generate-sdks.yml @@ -12,11 +12,7 @@ on: jobs: run: runs-on: ubuntu-latest - strategy: - matrix: - version: [ 'v1', 'v2' ] - - name: Run with version ${{ matrix.version }} + name: Generate SDKs steps: - uses: actions/checkout@v4 @@ -28,7 +24,7 @@ jobs: run: npm install -g @openapitools/openapi-generator-cli - name: Determing oas path id: vars - run: echo ::set-output name=oas::./src/objects/api/${{ matrix.version }}/openapi.yaml + run: echo ::set-output name=oas::./src/objects/api/v2/openapi.yaml - name: Validate schema run: openapi-generator-cli validate -i ${{ steps.vars.outputs.oas }} - name: Generate Java client diff --git a/.github/workflows/lint-oas.yml b/.github/workflows/lint-oas.yml index 8767511b..850478e3 100644 --- a/.github/workflows/lint-oas.yml +++ b/.github/workflows/lint-oas.yml @@ -12,11 +12,7 @@ on: jobs: run: runs-on: ubuntu-latest - strategy: - matrix: - version: [ 'v1', 'v2' ] - - name: Run with version ${{ matrix.version }} + name: Lint OAS steps: - uses: actions/checkout@v4 @@ -27,4 +23,4 @@ jobs: - name: Install spectral run: npm install -g @stoplight/spectral@5 - name: Run OAS linter - run: spectral lint ./src/objects/api/${{ matrix.version }}/openapi.yaml + run: spectral lint ./src/objects/api/v2/openapi.yaml diff --git a/bin/generate_schema.sh b/bin/generate_schema.sh index d24ece5b..61907011 100755 --- a/bin/generate_schema.sh +++ b/bin/generate_schema.sh @@ -4,18 +4,9 @@ # # Run this script from the root of the repository -if [ "$1" = "" ]; then - echo "You need to pass a version in the first argument" - exit 1 -fi -if [ "$1" != "v1" ] && [ "$1" != "v2" ]; then - echo "You need to pass a correct version in the first argument. Available values: v1, v2" - exit 1 -fi +export SCHEMA_PATH=src/objects/api/v2/openapi.yaml -export SCHEMA_PATH=src/objects/api/$1/openapi.yaml +OUTPUT_FILE=$1 -OUTPUT_FILE=$2 - -src/manage.py spectacular --file ${OUTPUT_FILE:-$SCHEMA_PATH} --validate --api-version $1 +src/manage.py spectacular --file ${OUTPUT_FILE:-$SCHEMA_PATH} --validate diff --git a/src/objects/api/urls.py b/src/objects/api/urls.py index 1c5ba014..6444483e 100644 --- a/src/objects/api/urls.py +++ b/src/objects/api/urls.py @@ -1,6 +1,5 @@ from django.urls import include, path urlpatterns = [ - path("v1", include("objects.api.v1.urls", namespace="v1")), path("v2", include("objects.api.v2.urls", namespace="v2")), ] diff --git a/src/objects/api/v1/__init__.py b/src/objects/api/v1/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/src/objects/api/v1/filters.py b/src/objects/api/v1/filters.py deleted file mode 100644 index 4bdb24ac..00000000 --- a/src/objects/api/v1/filters.py +++ /dev/null @@ -1,121 +0,0 @@ -from datetime import date as date_ - -from django import forms -from django.utils.translation import gettext_lazy as _ - -from django_filters import filters -from rest_framework import serializers -from vng_api_common.filtersets import FilterSet - -from objects.core.models import ObjectRecord, ObjectType -from objects.utils.filters import ObjectTypeFilter - -from ..constants import Operators -from ..utils import display_choice_values_for_help_text, string_to_value -from ..validators import validate_data_attrs - - -class ObjectRecordFilterForm(forms.Form): - def clean(self): - cleaned_data = super().clean() - date = cleaned_data.get("date") - registration_date = cleaned_data.get("registrationDate") - - if date and registration_date: - raise serializers.ValidationError( - _( - "'date' and 'registrationDate' parameters can't be used in the same request" - ), - code="invalid-date-query-params", - ) - - return cleaned_data - - -class ObjectRecordFilterSet(FilterSet): - type = ObjectTypeFilter( - field_name="object__object_type", - help_text=_("Url reference to OBJECTTYPE in Objecttypes API"), - queryset=ObjectType.objects.all(), - min_length=1, - max_length=1000, - ) - date = filters.DateFilter( - method="filter_date", - help_text=_( - "Display record data for the specified material date, i.e. the specified " - "date would be between `startAt` and `endAt` attributes. The default value is today" - ), - ) - registrationDate = filters.DateFilter( - method="filter_registration_date", - help_text=_( - "Display record data for the specified registration date, i.e. the specified " - "date would be between `registrationAt` attributes of different records" - ), - ) - data_attrs = filters.CharFilter( - method="filter_data_attrs", - validators=[validate_data_attrs], - help_text=_( - """Only include objects that have attributes with certain values. -Data filtering expressions are comma-separated and are structured as follows: -A valid parameter value has the form `key__operator__value`. -`key` is the attribute name, `operator` is the comparison operator to be used and `value` is the attribute value. -Note: Values can be string, numeric, or dates (ISO format; YYYY-MM-DD). - -Valid operator values are: -%(operator_choices)s - -`value` may not contain double underscore or comma characters. -`key` may not contain comma characters and includes double underscore only if it indicates nested attributes. - -Example: in order to display only objects with `height` equal to 100, query `data_attrs=height__exact__100` -should be used. If `height` is nested inside `dimensions` attribute, query should look like -`data_attrs=dimensions__height__exact__100` -""" - ) - % {"operator_choices": display_choice_values_for_help_text(Operators)}, - ) - - class Meta: - model = ObjectRecord - fields = ("type", "data_attrs", "date", "registrationDate") - form = ObjectRecordFilterForm - - def filter_data_attrs(self, queryset, name, value: str): - parts = value.split(",") - - for value_part in parts: - variable, operator, str_value = value_part.rsplit("__", 2) - real_value = string_to_value(str_value) - - if operator == "exact": - # for exact operator try to filter on string and numeric values - in_vals = [str_value] - if real_value != value: - in_vals.append(real_value) - queryset = queryset.filter(**{f"data__{variable}__in": in_vals}) - elif operator == "icontains": - # icontains treats everything like strings - queryset = queryset.filter( - **{f"data__{variable}__icontains": str_value} - ) - elif operator == "in": - # in must be a list - values = str_value.split("|") - queryset = queryset.filter(**{f"data__{variable}__in": values}) - - else: - # gt, gte, lt, lte operators - queryset = queryset.filter( - **{f"data__{variable}__{operator}": real_value} - ) - - return queryset - - def filter_date(self, queryset, name, value: date_): - return queryset.filter_for_date(value) - - def filter_registration_date(self, queryset, name, value: date_): - return queryset.filter_for_registration_date(value) diff --git a/src/objects/api/v1/openapi.yaml b/src/objects/api/v1/openapi.yaml deleted file mode 100644 index 22c70e7f..00000000 --- a/src/objects/api/v1/openapi.yaml +++ /dev/null @@ -1,919 +0,0 @@ -openapi: 3.0.3 -info: - title: Objects API - version: 1.3.0 (v1) - description: | - An API to manage Objects. - - # Introduction - - An OBJECT is of a certain OBJECTTYPE (defined in the Objecttypes API). An - OBJECT has a few core attributes that every OBJECT (technically a RECORD, - see below) has, although these attributes can sometimes be empty. They are - attributes like `geometry` and some administrative attributes. The data that - describes the actual object is stored in the `data` attribute and follows - the JSON schema as given by the OBJECTTYPE. - - ## Validation - - When an OBJECT is created or changed the `OBJECT.type` attribute refers to the - matching OBJECTTYPE in the Objecttypes API. The RECORD always indicates which - OBJECTTYPE-VERSION is used, shown in the `RECORD.typeVersion` attribute. - - Using these 2 attributes, the appropriate JSON schema is retrieved from the - Objecttypes API and the OBJECT data is validated against this JSON schema. - - ## History - - Each OBJECT has 1 or more RECORDs. A RECORD contains the data of an OBJECT - at a certain time. An OBJECT can have multiple RECORDS that describe the - history of that OBJECT. Changes to an OBJECT actually create a new RECORD - under the OBJECT and leaves the old RECORD as is. - - ### Material and formal history - - History can be seen from 2 perspectives: formal and material history. The - formal history describes the history as it should be (stored in the - `startAt` and `endAt` attributes). The material history describes the - history as it was administratively processed (stored in the `registeredAt` - attribute). - - The difference is that an object could be created or updated in the real - world at a certain point in time but the administrative change (ie. save or - update the object in the Objects API) can be done at a later time. The - query parameters `?date=2021-01-01` (formal history) and - `?registrationDate=2021-01-01` (material history) allow for querying the - RECORDS as seen from both perspectives, and can yield different results. - - ### Corrections - - RECORDs cannot be deleted or changed once saved. If an error was made to - a RECORD, the RECORD can be "corrected" by saving a new RECORD and indicate - that it corrects a previous RECORD. This is done via the attribute - `correctionFor`. - - ### Deletion - - Although OBJECTs can be deleted, it is sometimes better to set the - `endDate` of an OBJECT. Deleting an OBJECT also deletes all RECORDs in - accordance with privacy laws. - - # Authorizations - - The API uses API-tokens that grant certain permissions. The API-token is - passed via a header, like this: `Authorization: Token ` - - # Notifications - - When OBJECTs are created, updated or deleted via the API, notifications of - these operations are published to the configured Notifications API in the - `objecten` channel. - contact: - url: https://github.com/maykinmedia/objects-api - license: - name: EUPL-1.2 -paths: - /objects: - get: - operationId: object_list - description: Retrieve a list of OBJECTs and their actual RECORD. The actual - record is defined as if the query parameter `date=` was given. - 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: query - name: data_attrs - schema: - type: string - description: | - Only include objects that have attributes with certain values. - Data filtering expressions are comma-separated and are structured as follows: - A valid parameter value has the form `key__operator__value`. - `key` is the attribute name, `operator` is the comparison operator to be used and `value` is the attribute value. - Note: Values can be string, numeric, or dates (ISO format; YYYY-MM-DD). - - Valid operator values are: - * `exact` - equal to - * `gt` - greater than - * `gte` - greater than or equal to - * `lt` - lower than - * `lte` - lower than or equal to - * `icontains` - case-insensitive partial match - * `in` - in a list of values separated by `|` - - `value` may not contain double underscore or comma characters. - `key` may not contain comma characters and includes double underscore only if it indicates nested attributes. - - Example: in order to display only objects with `height` equal to 100, query `data_attrs=height__exact__100` - should be used. If `height` is nested inside `dimensions` attribute, query should look like - `data_attrs=dimensions__height__exact__100` - - in: query - name: date - schema: - type: string - format: date - description: Display record data for the specified material date, i.e. the - specified date would be between `startAt` and `endAt` attributes. The default - value is today - - in: query - name: fields - schema: - type: string - description: 'Comma-separated fields, which should be displayed in the response. - For example: ''url, uuid, record__geometry''.' - - in: query - name: registrationDate - schema: - type: string - format: date - description: Display record data for the specified registration date, i.e. - the specified date would be between `registrationAt` attributes of different - records - - in: query - name: type - schema: - type: string - format: uri - maxLength: 1000 - minLength: 1 - description: Url reference to OBJECTTYPE in Objecttypes API - 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).' - X-Unauthorized-Fields: - schema: - type: string - description: 'List of fields that are not allowed to display if the - field-based authorization is turned on. The value has the following - format: `objectType1:fieldA,fieldB; objectType2:fieldC,fieldD`' - content: - application/json: - schema: - type: array - items: - $ref: '#/components/schemas/Object' - description: OK - post: - operationId: object_create - description: Create an OBJECT and its initial RECORD. - 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: header - name: 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).' - required: true - - in: header - name: Content-Type - schema: - type: string - enum: - - application/json - description: Content type of the request body. - required: true - tags: - - objects - requestBody: - content: - application/json: - schema: - $ref: '#/components/schemas/Object' - required: true - security: - - tokenAuth: [] - responses: - '201': - 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/Object' - description: Created - /objects/{uuid}: - get: - operationId: object_read - description: Retrieve a single OBJECT and its actual RECORD. The actual record - is defined as if the query parameter `date=` was given. - 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: query - name: fields - schema: - type: string - description: 'Comma-separated fields, which should be displayed in the response. - For example: ''url, uuid, record__geometry''.' - - in: path - name: uuid - schema: - type: string - format: uuid - description: Unique identifier (UUID4) - 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).' - X-Unauthorized-Fields: - schema: - type: string - description: 'List of fields that are not allowed to display if the - field-based authorization is turned on. The value has the following - format: `objectType1:fieldA,fieldB; objectType2:fieldC,fieldD`' - content: - application/json: - schema: - $ref: '#/components/schemas/Object' - description: OK - put: - operationId: object_update - description: Update the OBJECT by creating a new RECORD with the updates values. - 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: header - name: 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).' - required: true - - in: header - name: Content-Type - schema: - type: string - enum: - - application/json - description: Content type of the request body. - required: true - - in: path - name: uuid - schema: - type: string - format: uuid - description: Unique identifier (UUID4) - required: true - tags: - - objects - requestBody: - content: - application/json: - schema: - $ref: '#/components/schemas/Object' - required: true - 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/Object' - description: OK - patch: - operationId: object_partial_update - description: Update the OBJECT by creating a new RECORD with the updates values. - The provided `record.data` value will be merged recursively with the existing - record data. - 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: header - name: 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).' - required: true - - in: header - name: Content-Type - schema: - type: string - enum: - - application/json - description: Content type of the request body. - required: true - - in: path - name: uuid - schema: - type: string - format: uuid - description: Unique identifier (UUID4) - required: true - tags: - - objects - requestBody: - content: - application/json: - schema: - $ref: '#/components/schemas/PatchedObject' - 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/Object' - description: OK - delete: - operationId: object_delete - description: Delete an OBJECT and all RECORDs belonging to it. - parameters: - - in: path - name: uuid - schema: - type: string - format: uuid - description: Unique identifier (UUID4) - required: true - tags: - - objects - security: - - tokenAuth: [] - responses: - '204': - description: No response body - /objects/{uuid}/history: - get: - operationId: object_history - description: Retrieve all RECORDs of an OBJECT. - 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: uuid - schema: - type: string - format: uuid - description: Unique identifier (UUID4) - 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: - type: array - items: - $ref: '#/components/schemas/HistoryRecord' - description: OK - /objects/search: - post: - operationId: object_search - description: Perform a (geo) search on OBJECTs. - 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: header - name: 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).' - required: true - - in: header - name: Content-Type - schema: - type: string - enum: - - application/json - description: Content type of the request body. - required: true - tags: - - objects - requestBody: - content: - application/json: - schema: - type: object - allOf: - - $ref: '#/components/schemas/ObjectSearch' - - type: object - properties: - type: - type: string - format: uri - maxLength: 1000 - minLength: 1 - description: Url reference to OBJECTTYPE in Objecttypes API - data_attrs: - type: string - description: | - Only include objects that have attributes with certain values. - Data filtering expressions are comma-separated and are structured as follows: - A valid parameter value has the form `key__operator__value`. - `key` is the attribute name, `operator` is the comparison operator to be used and `value` is the attribute value. - Note: Values can be string, numeric, or dates (ISO format; YYYY-MM-DD). - - Valid operator values are: - * `exact` - equal to - * `gt` - greater than - * `gte` - greater than or equal to - * `lt` - lower than - * `lte` - lower than or equal to - * `icontains` - case-insensitive partial match - * `in` - in a list of values separated by `|` - - `value` may not contain double underscore or comma characters. - `key` may not contain comma characters and includes double underscore only if it indicates nested attributes. - - Example: in order to display only objects with `height` equal to 100, query `data_attrs=height__exact__100` - should be used. If `height` is nested inside `dimensions` attribute, query should look like - `data_attrs=dimensions__height__exact__100` - date: - type: string - format: date - description: Display record data for the specified material date, - i.e. the specified date would be between `startAt` and `endAt` - attributes. The default value is today - registrationDate: - type: string - format: date - description: Display record data for the specified registration - date, i.e. the specified date would be between `registrationAt` - attributes of different records - 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: - type: array - items: - $ref: '#/components/schemas/Object' - description: OK -components: - schemas: - GeoJSONGeometry: - oneOf: - - $ref: '#/components/schemas/Point' - - $ref: '#/components/schemas/MultiPoint' - - $ref: '#/components/schemas/LineString' - - $ref: '#/components/schemas/MultiLineString' - - $ref: '#/components/schemas/Polygon' - - $ref: '#/components/schemas/MultiPolygon' - - $ref: '#/components/schemas/GeometryCollection' - discriminator: - propertyName: type - mapping: - Point: '#/components/schemas/Point' - MultiPoint: '#/components/schemas/MultiPoint' - LineString: '#/components/schemas/LineString' - MultiLineString: '#/components/schemas/MultiLineString' - Polygon: '#/components/schemas/Polygon' - MultiPolygon: '#/components/schemas/MultiPolygon' - GeometryCollection: '#/components/schemas/GeometryCollection' - GeoWithin: - type: object - properties: - within: - $ref: '#/components/schemas/GeoJSONGeometry' - Geometry: - type: object - title: Geometry - description: GeoJSON geometry - required: - - type - externalDocs: - url: https://tools.ietf.org/html/rfc7946#section-3.1 - properties: - type: - type: string - description: The geometry type - GeometryCollection: - type: object - description: GeoJSON geometry collection - externalDocs: - url: https://tools.ietf.org/html/rfc7946#section-3.1.8 - allOf: - - $ref: '#/components/schemas/Geometry' - - type: object - required: - - geometries - properties: - geometries: - type: array - items: - $ref: '#/components/schemas/Geometry' - HistoryRecord: - type: object - properties: - index: - type: integer - readOnly: true - description: Incremental index number of the object record. - typeVersion: - type: integer - maximum: 32767 - minimum: 0 - description: Version of the OBJECTTYPE for data in the object record - data: - description: Object data, based on OBJECTTYPE - geometry: - allOf: - - $ref: '#/components/schemas/GeoJSONGeometry' - nullable: true - description: Point, linestring or polygon object which represents the coordinates - of the object. Geometry can be added only if the related OBJECTTYPE allows - this (`OBJECTTYPE.allowGeometry = true` or `OBJECTTYPE.allowGeometry` - doesn't exist) - startAt: - type: string - format: date - description: Legal start date of the object record - endAt: - type: string - format: date - readOnly: true - nullable: true - description: Legal end date of the object record - registrationAt: - type: string - format: date - readOnly: true - description: The date when the record was registered in the system - correctionFor: - type: integer - maximum: 2147483647 - minimum: 0 - description: Index of the record corrected by the current record - readOnly: true - correctedBy: - type: integer - maximum: 2147483647 - minimum: 0 - description: Index of the record, which corrects the current record - readOnly: true - required: - - startAt - - typeVersion - LineString: - type: object - description: GeoJSON line-string geometry - externalDocs: - url: https://tools.ietf.org/html/rfc7946#section-3.1.4 - allOf: - - $ref: '#/components/schemas/Geometry' - - type: object - required: - - coordinates - properties: - coordinates: - type: array - items: - $ref: '#/components/schemas/Point2D' - minItems: 2 - MultiLineString: - type: object - description: GeoJSON multi-line-string geometry - externalDocs: - url: https://tools.ietf.org/html/rfc7946#section-3.1.5 - allOf: - - $ref: '#/components/schemas/Geometry' - - type: object - required: - - coordinates - properties: - coordinates: - type: array - items: - type: array - items: - $ref: '#/components/schemas/Point2D' - MultiPoint: - type: object - description: GeoJSON multi-point geometry - externalDocs: - url: https://tools.ietf.org/html/rfc7946#section-3.1.3 - allOf: - - $ref: '#/components/schemas/Geometry' - - type: object - required: - - coordinates - properties: - coordinates: - type: array - items: - $ref: '#/components/schemas/Point2D' - MultiPolygon: - type: object - description: GeoJSON multi-polygon geometry - externalDocs: - url: https://tools.ietf.org/html/rfc7946#section-3.1.7 - allOf: - - $ref: '#/components/schemas/Geometry' - - type: object - required: - - coordinates - properties: - coordinates: - type: array - items: - type: array - items: - type: array - items: - $ref: '#/components/schemas/Point2D' - Object: - type: object - description: |- - this mixin allows selecting fields for serializer in the query param - It also supports nested fields. - properties: - url: - type: string - format: uri - minLength: 1 - maxLength: 1000 - description: URL reference to this object. This is the unique identification - and location of this object. - readOnly: true - uuid: - type: string - format: uuid - description: Unique identifier (UUID4) - type: - type: string - format: uri - minLength: 1 - maxLength: 1000 - description: Url reference to OBJECTTYPE in Objecttypes API - record: - allOf: - - $ref: '#/components/schemas/ObjectRecord' - description: State of the OBJECT at a certain time - required: - - record - - type - ObjectRecord: - type: object - properties: - index: - type: integer - readOnly: true - description: Incremental index number of the object record. - typeVersion: - type: integer - maximum: 32767 - minimum: 0 - description: Version of the OBJECTTYPE for data in the object record - data: - description: Object data, based on OBJECTTYPE - geometry: - allOf: - - $ref: '#/components/schemas/GeoJSONGeometry' - nullable: true - description: Point, linestring or polygon object which represents the coordinates - of the object. Geometry can be added only if the related OBJECTTYPE allows - this (`OBJECTTYPE.allowGeometry = true` or `OBJECTTYPE.allowGeometry` - doesn't exist) - startAt: - type: string - format: date - description: Legal start date of the object record - endAt: - type: string - format: date - readOnly: true - nullable: true - description: Legal end date of the object record - registrationAt: - type: string - format: date - readOnly: true - description: The date when the record was registered in the system - correctionFor: - type: integer - maximum: 2147483647 - minimum: 0 - description: Index of the record corrected by the current record - nullable: true - correctedBy: - type: integer - maximum: 2147483647 - minimum: 0 - description: Index of the record, which corrects the current record - readOnly: true - required: - - startAt - - typeVersion - ObjectSearch: - type: object - properties: - geometry: - $ref: '#/components/schemas/GeoWithin' - PatchedObject: - type: object - description: |- - this mixin allows selecting fields for serializer in the query param - It also supports nested fields. - properties: - url: - type: string - format: uri - minLength: 1 - maxLength: 1000 - description: URL reference to this object. This is the unique identification - and location of this object. - readOnly: true - uuid: - type: string - format: uuid - description: Unique identifier (UUID4) - type: - type: string - format: uri - minLength: 1 - maxLength: 1000 - description: Url reference to OBJECTTYPE in Objecttypes API - record: - allOf: - - $ref: '#/components/schemas/ObjectRecord' - description: State of the OBJECT at a certain time - Point: - type: object - description: GeoJSON point geometry - externalDocs: - url: https://tools.ietf.org/html/rfc7946#section-3.1.2 - allOf: - - $ref: '#/components/schemas/Geometry' - - type: object - required: - - coordinates - properties: - coordinates: - $ref: '#/components/schemas/Point2D' - Point2D: - type: array - title: Point2D - description: A 2D point - items: - type: number - maxItems: 2 - minItems: 2 - Polygon: - type: object - description: GeoJSON polygon geometry - externalDocs: - url: https://tools.ietf.org/html/rfc7946#section-3.1.6 - allOf: - - $ref: '#/components/schemas/Geometry' - - type: object - required: - - coordinates - properties: - coordinates: - type: array - items: - type: array - items: - $ref: '#/components/schemas/Point2D' - securitySchemes: - tokenAuth: - type: apiKey - in: header - name: Authorization - description: Token-based authentication with required prefix "Token" -tags: -- name: objects -- name: permissions -externalDocs: - url: https://objects-and-objecttypes-api.readthedocs.io/ -servers: -- url: /api/v1 diff --git a/src/objects/api/v1/urls.py b/src/objects/api/v1/urls.py deleted file mode 100644 index 8282de30..00000000 --- a/src/objects/api/v1/urls.py +++ /dev/null @@ -1,39 +0,0 @@ -from django.urls import include, path - -from drf_spectacular.views import ( - SpectacularJSONAPIView, - SpectacularRedocView, - SpectacularYAMLAPIView, -) -from rest_framework import routers - -from .views import ObjectViewSet - -router = routers.DefaultRouter(trailing_slash=False) -router.register(r"objects", ObjectViewSet, basename="object") - -app_name = "v1" - -urlpatterns = [ - path("", SpectacularJSONAPIView.as_view(), name="schema-json"), - path( - "/", - include( - [ - # schema - path( - "schema/openapi.yaml", - SpectacularYAMLAPIView.as_view(), - name="schema", - ), - path( - "schema/", - SpectacularRedocView.as_view(url_name="schema"), - name="schema-redoc", - ), - # actual endpoints - path("", include(router.urls)), - ] - ), - ), -] diff --git a/src/objects/api/v1/views.py b/src/objects/api/v1/views.py deleted file mode 100644 index 85a0f265..00000000 --- a/src/objects/api/v1/views.py +++ /dev/null @@ -1,159 +0,0 @@ -import datetime - -from django.conf import settings -from django.db import models - -from drf_spectacular.utils import extend_schema, extend_schema_view -from rest_framework import viewsets -from rest_framework.decorators import action -from rest_framework.response import Response -from vng_api_common.search import SearchMixin - -from objects.core.models import ObjectRecord -from objects.token.models import Permission -from objects.token.permissions import ObjectTypeBasedPermission - -from ..kanalen import KANAAL_OBJECTEN -from ..mixins import GeoMixin, ObjectNotificationMixin -from ..serializers import ( - HistoryRecordSerializer, - ObjectSearchSerializer, - ObjectSerializer, -) -from .filters import ObjectRecordFilterSet - - -@extend_schema_view( - list=extend_schema( - description="Retrieve a list of OBJECTs and their actual RECORD. " - "The actual record is defined as if the query parameter `date=` was given." - ), - retrieve=extend_schema( - description="Retrieve a single OBJECT and its actual RECORD. " - "The actual record is defined as if the query parameter `date=` was given.", - operation_id="object_read", - ), - create=extend_schema(description="Create an OBJECT and its initial RECORD."), - update=extend_schema( - description="Update the OBJECT by creating a new RECORD with the updates values." - ), - partial_update=extend_schema( - description="Update the OBJECT by creating a new RECORD with the updates values. " - "The provided `record.data` value will be merged recursively with the existing record data." - ), - destroy=extend_schema( - description="Delete an OBJECT and all RECORDs belonging to it.", - operation_id="object_delete", - ), -) -class ObjectViewSet( - ObjectNotificationMixin, SearchMixin, GeoMixin, viewsets.ModelViewSet -): - queryset = ObjectRecord.objects.select_related( - "object", - "object__object_type", - "object__object_type__service", - "correct", - "corrected", - ).order_by("-pk") - serializer_class = ObjectSerializer - filterset_class = ObjectRecordFilterSet - lookup_field = "object__uuid" - lookup_url_kwarg = "uuid" - search_input_serializer_class = ObjectSearchSerializer - permission_classes = [ObjectTypeBasedPermission] - notifications_kanaal = KANAAL_OBJECTEN - - def get_queryset(self): - base = super().get_queryset() - token_auth = getattr(self.request, "auth", None) - # prefetch permissions for DB optimization. Used in DynamicFieldsMixin - base = base.prefetch_related( - models.Prefetch( - "object__object_type__permissions", - queryset=Permission.objects.filter(token_auth=token_auth), - to_attr="token_permissions", - ), - ) - - if self.action not in ("list", "search"): - return base - - # show only allowed objects - base = base.filter_for_token(token_auth) - - # show only actual objects - date = getattr(self.request, "query_params", {}).get("date", None) - registration_date = getattr(self.request, "query_params", {}).get( - "registrationDate", None - ) - if not date and not registration_date: - base = base.filter_for_date(datetime.date.today()) - - return base - - def filter_queryset(self, queryset): - queryset = super().filter_queryset(queryset) - - # keep only records with max index per object - return queryset.keep_max_record_per_object() - - def perform_destroy(self, instance): - instance.object.delete() - - @extend_schema( - description="Retrieve all RECORDs of an OBJECT.", - responses={"200": HistoryRecordSerializer(many=True)}, - ) - @action(detail=True, methods=["get"], serializer_class=HistoryRecordSerializer) - def history(self, request, uuid=None): - """Retrieve all RECORDs of an OBJECT.""" - records = self.get_object().object.records.order_by("id") - serializer = self.get_serializer(records, many=True) - return Response(serializer.data) - - @extend_schema( - description="Perform a (geo) search on OBJECTs.", - request=ObjectSearchSerializer, - responses={"200": ObjectSerializer(many=True)}, - ) - @action(detail=False, methods=["post"]) - def search(self, request): - """Perform a (geo) search on OBJECTs""" - search_input = self.get_search_input() - queryset = self.filter_queryset(self.get_queryset()) - - if "geometry" in search_input: - within = search_input["geometry"]["within"] - queryset = queryset.filter(geometry__within=within).distinct() - - return self.get_search_output(queryset) - - def get_search_output(self, queryset: models.QuerySet) -> Response: - """wrapper to make sure the result is a Response subclass""" - result = super().get_search_output(queryset) - - if not isinstance(result, Response): - result = Response(result) - - return result - - # for OAS generation - search.is_search_action = True - - def finalize_response(self, request, response, *args, **kwargs): - """add warning header if not all data is allowed to display""" - serializer = getattr(response.data, "serializer", None) - - if serializer and response.status_code == 200: - if self.action == "retrieve" and serializer.not_allowed: - self.headers[settings.UNAUTHORIZED_FIELDS_HEADER] = ( - serializer.not_allowed.pretty() - ) - - elif self.action in ("list", "search") and serializer.child.not_allowed: - self.headers[settings.UNAUTHORIZED_FIELDS_HEADER] = ( - serializer.child.not_allowed.pretty() - ) - - return super().finalize_response(request, response, *args, **kwargs) diff --git a/src/objects/api/v2/openapi.yaml b/src/objects/api/v2/openapi.yaml index 125a9b28..8dc2885b 100644 --- a/src/objects/api/v2/openapi.yaml +++ b/src/objects/api/v2/openapi.yaml @@ -1,7 +1,7 @@ openapi: 3.0.3 info: title: Objects API - version: 2.4.3 (v2) + version: 2.4.3 description: | An API to manage Objects. @@ -1148,10 +1148,10 @@ components: in: header name: Authorization description: Token-based authentication with required prefix "Token" +servers: +- url: /api/v2 tags: - name: objects - name: permissions externalDocs: url: https://objects-and-objecttypes-api.readthedocs.io/ -servers: -- url: /api/v2 diff --git a/src/objects/conf/api.py b/src/objects/conf/api.py index c8b72a93..0a40a12b 100644 --- a/src/objects/conf/api.py +++ b/src/objects/conf/api.py @@ -1,5 +1,4 @@ API_VERSION = "2.4.3" -VERSIONS = {"v1": "1.3.0", "v2": "2.4.3"} # api settings REST_FRAMEWORK = { @@ -12,7 +11,7 @@ "DEFAULT_SCHEMA_CLASS": "objects.utils.autoschema.AutoSchema", "DEFAULT_VERSIONING_CLASS": "rest_framework.versioning.NamespaceVersioning", "DEFAULT_VERSION": "v2", # NOT to be confused with API_VERSION - it's the major version part - "ALLOWED_VERSIONS": ("v1", "v2"), + "ALLOWED_VERSIONS": ("v2",), "VERSION_PARAM": "version", "EXCEPTION_HANDLER": "objects.utils.views.exception_handler", # test @@ -99,16 +98,13 @@ "EXTERNAL_DOCS": { "url": "https://objects-and-objecttypes-api.readthedocs.io/", }, - "VERSION": None, + "VERSION": API_VERSION, "COMPONENT_NO_READ_ONLY_REQUIRED": True, "POSTPROCESSING_HOOKS": [ "drf_spectacular.hooks.postprocess_schema_enums", - "objects.utils.hooks.postprocess_servers", - "objects.utils.hooks.postprocess_versions", ], "TAGS": [{"name": "objects"}, {"name": "permissions"}], + "SERVERS": [{"url": "/api/v2"}], } -OAS_SERVERS = {"v1": [{"url": "/api/v1"}], "v2": [{"url": "/api/v2"}]} - UNAUTHORIZED_FIELDS_HEADER = "X-Unauthorized-Fields" diff --git a/src/objects/utils/hooks.py b/src/objects/utils/hooks.py deleted file mode 100644 index 07376d66..00000000 --- a/src/objects/utils/hooks.py +++ /dev/null @@ -1,13 +0,0 @@ -from django.conf import settings - - -def postprocess_versions(result, generator, **kwargs): - major_version = result["info"]["version"] - result["info"]["version"] = f"{settings.VERSIONS[major_version]} ({major_version})" - return result - - -def postprocess_servers(result, generator, **kwargs): - major_version = result["info"]["version"] - result["servers"] = settings.OAS_SERVERS[major_version] - return result