Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[#467] Setup objecttypes through django-setup-configuration #492

Merged
merged 8 commits into from
Dec 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions docker/setup_configuration/data.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,15 @@
zgw_consumers_config_enable: true
zgw_consumers:
services:
- identifier: objecttypes-api
label: Objecttypes API
api_root: http://objecttypes.local/api/v1/
api_connection_check_path: objecttypes
api_type: orc
auth_type: api_key
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let's also set the headers to make it a bit more of a real example:

header_key: Authorization
header_value: Token b9f100590925b529664ed9d370f5f8da124b2c20

header_key: Authorization
header_value: Token b9f100590925b529664ed9d370f5f8da124b2c20

- identifier: notifications-api
label: Notificaties API
api_root: http://notificaties.local/api/v1/
Expand All @@ -16,3 +25,10 @@ notifications_config:
notification_delivery_max_retries: 1
notification_delivery_retry_backoff: 2
notification_delivery_retry_backoff_max: 3

objecttypes_config_enable: true
objecttypes:
items:
- uuid: b427ef84-189d-43aa-9efd-7bb2c459e281
name: Object Type 1
service_identifier: objecttypes-api
75 changes: 71 additions & 4 deletions docs/installation/config_cli.rst
Original file line number Diff line number Diff line change
Expand Up @@ -31,14 +31,81 @@ format, use by each step.
Objects API
===========

Objecttypes configuration
-------------------------

To configure objecttypes the following configuration could be used:

.. code-block:: yaml
...
zgw_consumers_config_enable: true
zgw_consumers:
services:
- identifier: objecttypen-foo
label: Objecttypen API Foo
api_root: http://objecttypen.foo/api/v1/
api_type: orc
auth_type: api_key
header_key: Authorization
header_value: Token ba9d233e95e04c4a8a661a27daffe7c9bd019067

- identifier: objecttypen-bar
label: Objecttypen API Bar
api_root: http://objecttypen.bar/api/v1/
api_type: orc
auth_type: api_key
header_key: Authorization
header_value: Token b9f100590925b529664ed9d370f5f8da124b2c20

objecttypes_config_enable: true
objecttypes:
items:
- uuid: b427ef84-189d-43aa-9efd-7bb2c459e281
name: Object Type 1
service_identifier: objecttypen-foo

- uuid: b0e8553f-8b1a-4d55-ab90-6d02f1bcf2c2
name: Object Type 2
service_identifier: objecttypen-bar
...
.. note:: The ``uuid`` field will be used to lookup existing ``ObjectType``'s.

Objecttypes require a corresponding ``Service`` to work correctly. Creating
these ``Service``'s can be done by defining these in the same yaml file. ``Service``
instances will be created before the ``ObjectType``'s are created.

Objecttypes connection configuration
------------------------------------

Tokens configuration
-------------------
In order to be able to retrieve objecttypes, a corresponding ``Service`` should be
created. An example of a configuration could be seen below:

Notifications configuration
-------------------------
.. code-block:: yaml
...

zgw_consumers_config_enable: true
zgw_consumers:
services:
- identifier: objecttypes-api-1
label: Objecttypes API 1
api_root: http://objecttypes-1.local/api/v1/
api_connection_check_path: objecttypes
api_type: orc
auth_type: api_key
header_key: Authorization
header_value: Token ba9d233e95e04c4a8a661a27daffe7c9bd019067
- identifier: objecttypes-api-2
label: Objecttypes API 2
api_root: http://objecttypes-2.local/api/v1/
api_connection_check_path: objecttypes
api_type: orc
auth_type: api_key
header_key: Authorization
header_value: Token b9f100590925b529664ed9d370f5f8da124b2c20
....

Tokens configuration
--------------------

Mozilla-django-oidc-db
----------------------
Expand Down
2 changes: 2 additions & 0 deletions src/objects/conf/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
"rest_framework_gis",
# Project applications.
"objects.accounts",
"objects.setup_configuration",
"objects.api",
"objects.core",
"objects.token",
Expand Down Expand Up @@ -85,4 +86,5 @@
SETUP_CONFIGURATION_STEPS = (
"zgw_consumers.contrib.setup_configuration.steps.ServiceConfigurationStep",
"notifications_api_common.contrib.setup_configuration.steps.NotificationConfigurationStep",
"objects.setup_configuration.steps.objecttypes.ObjectTypesConfigurationStep",
)
11 changes: 9 additions & 2 deletions src/objects/core/models.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import datetime
import uuid
from typing import Iterable

from django.contrib.gis.db.models import GeometryField
from django.core.exceptions import ValidationError
Expand Down Expand Up @@ -41,8 +42,14 @@ def url(self):
# zds_client.get_operation_url() can be used here but it increases HTTP overhead
return f"{self.service.api_root}objecttypes/{self.uuid}"

def clean(self):
def clean_fields(self, exclude: Iterable[str] | None = None) -> None:
super().clean_fields(exclude=exclude)

if exclude and "service" in exclude:
return

client = build_client(self.service)

try:
response = client.get(url=self.url)
except (requests.RequestException, ConnectionError, ValueError) as exc:
Expand All @@ -51,7 +58,7 @@ def clean(self):
try:
object_type_data = response.json()
except requests.exceptions.JSONDecodeError:
ValidationError("Object type version didn't have any data")
raise ValidationError("Object type version didn't have any data")

if not self._name:
self._name = object_type_data["name"]
Expand Down
10 changes: 10 additions & 0 deletions src/objects/core/tests/files/objecttypes_empty_database.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
objecttypes_config_enable: true
objecttypes:
items:
- uuid: b427ef84-189d-43aa-9efd-7bb2c459e281
name: Object Type 1
service_identifier: service-1

- uuid: b0e8553f-8b1a-4d55-ab90-6d02f1bcf2c2
name: Object Type 2
service_identifier: service-2
10 changes: 10 additions & 0 deletions src/objects/core/tests/files/objecttypes_existing_objecttype.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
objecttypes_config_enable: true
objecttypes:
items:
- uuid: b427ef84-189d-43aa-9efd-7bb2c459e281
name: Object Type 1
service_identifier: service-1

- uuid: 7229549b-7b41-47d1-8106-414b2a69751b
name: Object Type 3
service_identifier: service-2
10 changes: 10 additions & 0 deletions src/objects/core/tests/files/objecttypes_idempotent.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
objecttypes_config_enable: true
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need separate files for these tests? Most of them seem the same

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed the idempotent test file, the other files are quite different from each other

objecttypes:
items:
- uuid: b427ef84-189d-43aa-9efd-7bb2c459e281
name: Object Type 1
service_identifier: service-1

- uuid: b0e8553f-8b1a-4d55-ab90-6d02f1bcf2c2
name: Object Type 2
service_identifier: service-2
10 changes: 10 additions & 0 deletions src/objects/core/tests/files/objecttypes_invalid_uuid.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
objecttypes_config_enable: true
objecttypes:
items:
- uuid: b427ef84-189d-43aa-9efd-7bb2c459e281
name: Object Type 1
service_identifier: service-1

- uuid: foobar
name: Object Type 2
service_identifier: service-1
10 changes: 10 additions & 0 deletions src/objects/core/tests/files/objecttypes_unknown_service.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
objecttypes_config_enable: true
objecttypes:
items:
- uuid: b427ef84-189d-43aa-9efd-7bb2c459e281
name: Object Type 1
service_identifier: unknown

- uuid: b0e8553f-8b1a-4d55-ab90-6d02f1bcf2c2
name: Object Type 2
service_identifier: service-1
172 changes: 172 additions & 0 deletions src/objects/core/tests/test_objecttype_config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
from pathlib import Path

from django.db.models import QuerySet
from django.test import TestCase

from django_setup_configuration.exceptions import ConfigurationRunFailed
from django_setup_configuration.test_utils import execute_single_step
from zgw_consumers.models import Service
from zgw_consumers.test.factories import ServiceFactory

from objects.core.models import ObjectType
from objects.core.tests.factories import ObjectTypeFactory
from objects.setup_configuration.steps.objecttypes import ObjectTypesConfigurationStep

TEST_FILES = (Path(__file__).parent / "files").resolve()


class ObjectTypesConfigurationStepTests(TestCase):
def test_empty_database(self):
service_1 = ServiceFactory(slug="service-1")
service_2 = ServiceFactory(slug="service-2")

test_file_path = str(TEST_FILES / "objecttypes_empty_database.yaml")

execute_single_step(ObjectTypesConfigurationStep, yaml_source=test_file_path)

objecttypes: QuerySet[ObjectType] = ObjectType.objects.order_by("_name")

self.assertEqual(objecttypes.count(), 2)

objecttype_1: ObjectType = objecttypes.first()

self.assertEqual(str(objecttype_1.uuid), "b427ef84-189d-43aa-9efd-7bb2c459e281")
self.assertEqual(objecttype_1._name, "Object Type 1")
self.assertEqual(objecttype_1.service, service_1)

objecttype_2: ObjectType = objecttypes.last()

self.assertEqual(str(objecttype_2.uuid), "b0e8553f-8b1a-4d55-ab90-6d02f1bcf2c2")
self.assertEqual(objecttype_2._name, "Object Type 2")
self.assertEqual(objecttype_2.service, service_2)

def test_existing_objecttype(self):
test_file_path = str(TEST_FILES / "objecttypes_existing_objecttype.yaml")

service_1: Service = ServiceFactory(slug="service-1")
service_2: Service = ServiceFactory(slug="service-2")

objecttype_1: ObjectType = ObjectTypeFactory(
service=service_1,
uuid="b427ef84-189d-43aa-9efd-7bb2c459e281",
_name="Object Type 001",
)
objecttype_2: ObjectType = ObjectTypeFactory(
service=service_2,
uuid="b0e8553f-8b1a-4d55-ab90-6d02f1bcf2c2",
_name="Object Type 002",
)

execute_single_step(ObjectTypesConfigurationStep, yaml_source=test_file_path)

self.assertEqual(ObjectType.objects.count(), 3)

objecttype_1.refresh_from_db()

self.assertEqual(str(objecttype_1.uuid), "b427ef84-189d-43aa-9efd-7bb2c459e281")
self.assertEqual(objecttype_1._name, "Object Type 1")
self.assertEqual(objecttype_1.service, service_1)

objecttype_2.refresh_from_db()

self.assertEqual(str(objecttype_2.uuid), "b0e8553f-8b1a-4d55-ab90-6d02f1bcf2c2")
self.assertEqual(objecttype_2._name, "Object Type 002")
self.assertEqual(objecttype_2.service, service_2)

objecttype_3: ObjectType = ObjectType.objects.get(
uuid="7229549b-7b41-47d1-8106-414b2a69751b"
)

self.assertEqual(str(objecttype_3.uuid), "7229549b-7b41-47d1-8106-414b2a69751b")
self.assertEqual(objecttype_3._name, "Object Type 3")
self.assertEqual(objecttype_3.service, service_2)

def test_unknown_service(self):
service = ServiceFactory(slug="service-1")

objecttype: ObjectType = ObjectTypeFactory(
uuid="b427ef84-189d-43aa-9efd-7bb2c459e281",
_name="Object Type 001",
service=service,
)

test_file_path = str(TEST_FILES / "objecttypes_unknown_service.yaml")

with self.assertRaises(ConfigurationRunFailed):
execute_single_step(
ObjectTypesConfigurationStep, yaml_source=test_file_path
)

self.assertEqual(ObjectType.objects.count(), 1)

objecttype.refresh_from_db()

self.assertEqual(str(objecttype.uuid), "b427ef84-189d-43aa-9efd-7bb2c459e281")
self.assertEqual(objecttype._name, "Object Type 001")
self.assertEqual(objecttype.service, service)

def test_invalid_uuid(self):
test_file_path = str(TEST_FILES / "objecttypes_invalid_uuid.yaml")

service: Service = ServiceFactory(slug="service-1")

objecttype: ObjectType = ObjectTypeFactory(
service=service,
uuid="b427ef84-189d-43aa-9efd-7bb2c459e281",
_name="Object Type 001",
)

with self.assertRaises(ConfigurationRunFailed):
execute_single_step(
ObjectTypesConfigurationStep, yaml_source=test_file_path
)

self.assertEqual(ObjectType.objects.count(), 1)

objecttype.refresh_from_db()

self.assertEqual(str(objecttype.uuid), "b427ef84-189d-43aa-9efd-7bb2c459e281")
self.assertEqual(objecttype._name, "Object Type 1")
self.assertEqual(objecttype.service, service)

def test_idempotent_step(self):
service_1 = ServiceFactory(slug="service-1")
service_2 = ServiceFactory(slug="service-2")

test_file_path = str(TEST_FILES / "objecttypes_idempotent.yaml")

execute_single_step(ObjectTypesConfigurationStep, yaml_source=test_file_path)

objecttypes: QuerySet[ObjectType] = ObjectType.objects.order_by("_name")

self.assertEqual(objecttypes.count(), 2)

objecttype_1: ObjectType = objecttypes.first()

self.assertEqual(str(objecttype_1.uuid), "b427ef84-189d-43aa-9efd-7bb2c459e281")
self.assertEqual(objecttype_1._name, "Object Type 1")
self.assertEqual(objecttype_1.service, service_1)

objecttype_2: ObjectType = objecttypes.last()

self.assertEqual(str(objecttype_2.uuid), "b0e8553f-8b1a-4d55-ab90-6d02f1bcf2c2")
self.assertEqual(objecttype_2._name, "Object Type 2")
self.assertEqual(objecttype_2.service, service_2)

# Rerun
execute_single_step(ObjectTypesConfigurationStep, yaml_source=test_file_path)

objecttype_1.refresh_from_db()
objecttype_2.refresh_from_db()

self.assertEqual(ObjectType.objects.count(), 2)

# objecttype 1
self.assertEqual(str(objecttype_1.uuid), "b427ef84-189d-43aa-9efd-7bb2c459e281")
self.assertEqual(objecttype_1._name, "Object Type 1")
self.assertEqual(objecttype_1.service, service_1)

# objecttype 2
self.assertEqual(str(objecttype_2.uuid), "b0e8553f-8b1a-4d55-ab90-6d02f1bcf2c2")
self.assertEqual(objecttype_2._name, "Object Type 2")
self.assertEqual(objecttype_2.service, service_2)
Empty file.
Loading
Loading