Skip to content

Commit 25c04e3

Browse files
Merge pull request #4916 from open-formulieren/cleanup/3283-make-fields-not-nullable
💥 Make service FKs in API group config models not nullable
2 parents 9be9f8c + 3f27cb1 commit 25c04e3

19 files changed

+215
-177
lines changed

Dockerfile

+1
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ COPY \
9393
./bin/check_celery_worker_liveness.py \
9494
./bin/report_component_problems.py \
9595
./bin/check_temporary_uploads.py \
96+
./bin/check_api_groups_null.py \
9697
./bin/fix_selectboxes_component_default_values.py \
9798
./bin/
9899

bin/check_api_groups_null.py

+80
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
#!/usr/bin/env python
2+
import sys
3+
from pathlib import Path
4+
5+
import django
6+
7+
from tabulate import tabulate
8+
9+
SRC_DIR = Path(__file__).parent.parent / "src"
10+
sys.path.insert(0, str(SRC_DIR.resolve()))
11+
12+
13+
def check_for_null_services_in_api_groups():
14+
from openforms.contrib.objects_api.models import ObjectsAPIGroupConfig
15+
from openforms.registrations.contrib.zgw_apis.models import ZGWApiGroupConfig
16+
17+
problems: list[tuple[str, int, str, str]] = []
18+
19+
objects_groups = ObjectsAPIGroupConfig.objects.exclude(
20+
objects_service__isnull=False,
21+
objecttypes_service__isnull=False,
22+
).values_list("id", "name", "objects_service_id", "objecttypes_service_id")
23+
24+
for pk, name, objects_service_id, objecttypes_service_id in objects_groups:
25+
problem = ("Objects API", pk, name)
26+
if objects_service_id is None:
27+
problems.append((*problem, "No objects service configured"))
28+
if objecttypes_service_id is None:
29+
problems.append((*problem, "No object types service configured"))
30+
31+
zgw_groups = ZGWApiGroupConfig.objects.exclude(
32+
zrc_service__isnull=False,
33+
drc_service__isnull=False,
34+
ztc_service__isnull=False,
35+
).values_list(
36+
"id",
37+
"name",
38+
"zrc_service_id",
39+
"drc_service_id",
40+
"ztc_service_id",
41+
)
42+
for pk, name, zrc_service_id, drc_service_id, ztc_service_id in zgw_groups:
43+
problem = ("ZGW APIs", pk, name)
44+
if zrc_service_id is None:
45+
problems.append((*problem, "No Zaken API service configured"))
46+
if drc_service_id is None:
47+
problems.append((*problem, "No Documenten API service configured"))
48+
if ztc_service_id is None:
49+
problems.append((*problem, "No Catalogi API service configured"))
50+
51+
if not problems:
52+
return False
53+
54+
print(
55+
"Can't upgrade yet - some API group services are not properly configured yet."
56+
)
57+
print(
58+
"Go into the admin to fix their configuration, and then try to upgrade again."
59+
)
60+
print(
61+
tabulate(
62+
problems,
63+
headers=("API group type", "ID", "Name", "Problem"),
64+
)
65+
)
66+
return True
67+
68+
69+
def main(skip_setup=False) -> bool:
70+
from openforms.setup import setup_env
71+
72+
if not skip_setup:
73+
setup_env()
74+
django.setup()
75+
76+
return check_for_null_services_in_api_groups()
77+
78+
79+
if __name__ == "__main__":
80+
main()

src/openforms/contrib/objects_api/clients/__init__.py

+10-8
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
in the form builder
1010
"""
1111

12+
from __future__ import annotations
13+
1214
from typing import TYPE_CHECKING
1315

1416
from zgw_consumers.client import build_client
@@ -26,25 +28,25 @@ class NoServiceConfigured(RuntimeError):
2628
pass
2729

2830

29-
def get_objects_client(config: "ObjectsAPIGroupConfig") -> ObjectsClient:
30-
if not (service := config.objects_service):
31-
raise NoServiceConfigured("No Objects API service configured!")
31+
def get_objects_client(config: ObjectsAPIGroupConfig) -> ObjectsClient:
32+
service = config.objects_service
33+
assert service is not None
3234
return build_client(service, client_factory=ObjectsClient)
3335

3436

35-
def get_objecttypes_client(config: "ObjectsAPIGroupConfig") -> ObjecttypesClient:
36-
if not (service := config.objecttypes_service):
37-
raise NoServiceConfigured("No Objecttypes API service configured!")
37+
def get_objecttypes_client(config: ObjectsAPIGroupConfig) -> ObjecttypesClient:
38+
service = config.objecttypes_service
39+
assert service is not None
3840
return build_client(service, client_factory=ObjecttypesClient)
3941

4042

41-
def get_documents_client(config: "ObjectsAPIGroupConfig") -> DocumentenClient:
43+
def get_documents_client(config: ObjectsAPIGroupConfig) -> DocumentenClient:
4244
if not (service := config.drc_service):
4345
raise NoServiceConfigured("No Documents API service configured!")
4446
return build_client(service, client_factory=DocumentenClient)
4547

4648

47-
def get_catalogi_client(config: "ObjectsAPIGroupConfig") -> CatalogiClient:
49+
def get_catalogi_client(config: ObjectsAPIGroupConfig) -> CatalogiClient:
4850
if not (service := config.catalogi_service):
4951
raise NoServiceConfigured("No Catalogi API service configured!")
5052
return build_client(service, client_factory=CatalogiClient)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
# Generated by Django 4.2.17 on 2024-12-12 22:40
2+
3+
import django.db.models.deletion
4+
from django.db import migrations, models
5+
6+
7+
class Migration(migrations.Migration):
8+
9+
dependencies = [
10+
("zgw_consumers", "0022_set_default_service_slug"),
11+
("objects_api", "0003_objectsapigroupconfig_identifier_unique"),
12+
]
13+
14+
operations = [
15+
migrations.AlterField(
16+
model_name="objectsapigroupconfig",
17+
name="objects_service",
18+
field=models.ForeignKey(
19+
limit_choices_to={"api_type": "orc"},
20+
on_delete=django.db.models.deletion.PROTECT,
21+
related_name="+",
22+
to="zgw_consumers.service",
23+
verbose_name="Objects API",
24+
),
25+
),
26+
migrations.AlterField(
27+
model_name="objectsapigroupconfig",
28+
name="objecttypes_service",
29+
field=models.ForeignKey(
30+
limit_choices_to={"api_type": "orc"},
31+
on_delete=django.db.models.deletion.PROTECT,
32+
related_name="+",
33+
to="zgw_consumers.service",
34+
verbose_name="Objecttypes API",
35+
),
36+
),
37+
]

src/openforms/contrib/objects_api/models.py

+2-3
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212

1313

1414
class ObjectsAPIGroupConfig(models.Model):
15-
# TODO OF 3.0: remove `null=True` from the service fields
1615
name = models.CharField(
1716
_("name"),
1817
max_length=255,
@@ -30,15 +29,15 @@ class ObjectsAPIGroupConfig(models.Model):
3029
verbose_name=_("Objects API"),
3130
on_delete=models.PROTECT,
3231
limit_choices_to={"api_type": APITypes.orc},
33-
null=True,
32+
null=False,
3433
related_name="+",
3534
)
3635
objecttypes_service = models.ForeignKey(
3736
"zgw_consumers.Service",
3837
verbose_name=_("Objecttypes API"),
3938
on_delete=models.PROTECT,
4039
limit_choices_to={"api_type": APITypes.orc},
41-
null=True,
40+
null=False,
4241
related_name="+",
4342
)
4443
drc_service = models.ForeignKey(

src/openforms/contrib/objects_api/tests/test_admin.py

+1-2
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
from openforms.accounts.tests.factories import SuperUserFactory
1010
from openforms.utils.tests.vcr import OFVCRMixin
1111

12-
from ..models import ObjectsAPIGroupConfig
1312
from .factories import ObjectsAPIGroupConfigFactory
1413

1514
TEST_FILES = Path(__file__).parent / "files"
@@ -20,7 +19,7 @@ class ObjectsAPIGroupConfigAdminTest(OFVCRMixin, WebTest):
2019
VCR_TEST_FILES = TEST_FILES
2120

2221
def test_name(self):
23-
ObjectsAPIGroupConfig.objects.create(name="test group")
22+
ObjectsAPIGroupConfigFactory.create(name="test group")
2423
user = SuperUserFactory.create()
2524

2625
response = self.app.get(

src/openforms/prefill/contrib/objects_api/tests/test_config.py

-16
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,6 @@
11
from pathlib import Path
22
from unittest.mock import patch
33

4-
from django.test import override_settings
5-
from django.utils.translation import gettext as _
6-
74
from rest_framework.test import APITestCase
85
from zgw_consumers.constants import APITypes, AuthTypes
96
from zgw_consumers.test.factories import ServiceFactory
@@ -41,19 +38,6 @@ def setUp(self):
4138
for_test_docker_compose=True
4239
)
4340

44-
@override_settings(LANGUAGE_CODE="en")
45-
def test_undefined_service_raises_exception(self):
46-
self.objects_api_group.objects_service = None
47-
self.objects_api_group.save()
48-
49-
with self.assertRaisesMessage(
50-
InvalidPluginConfiguration,
51-
_(
52-
"Objects API endpoint is not configured for Objects API group {objects_api_group}."
53-
).format(objects_api_group=self.objects_api_group),
54-
):
55-
plugin.check_config()
56-
5741
def test_invalid_service_raises_exception(self):
5842
objects_service = ServiceFactory.create(
5943
api_root="http://localhost:8002/api/v2/invalid",

src/openforms/registrations/contrib/objects_api/config.py

+4-6
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import warnings
22

3-
from django.db.models import IntegerChoices, Q
3+
from django.db.models import IntegerChoices
44
from django.utils.translation import gettext_lazy as _
55

66
from drf_spectacular.utils import OpenApiExample, extend_schema_serializer
@@ -67,11 +67,9 @@ class ObjecttypeVariableMappingSerializer(serializers.Serializer):
6767

6868
class ObjectsAPIOptionsSerializer(JsonSchemaSerializerMixin, serializers.Serializer):
6969
objects_api_group = PrimaryKeyRelatedAsChoicesField(
70-
queryset=ObjectsAPIGroupConfig.objects.exclude(
71-
Q(objects_service=None)
72-
| Q(objecttypes_service=None)
73-
| Q(drc_service=None)
74-
| Q(catalogi_service=None)
70+
queryset=ObjectsAPIGroupConfig.objects.filter(
71+
drc_service__isnull=False,
72+
catalogi_service__isnull=False,
7573
),
7674
label=("Objects API group"),
7775
help_text=_("Which Objects API group to use."),

src/openforms/registrations/contrib/objects_api/tests/test_api_endpoints.py

+6-27
Original file line numberDiff line numberDiff line change
@@ -4,34 +4,16 @@
44
from rest_framework.reverse import reverse_lazy
55
from rest_framework.test import APITestCase
66
from zgw_consumers.constants import APITypes, AuthTypes
7-
from zgw_consumers.models import Service
87
from zgw_consumers.test.factories import ServiceFactory
98

109
from openforms.accounts.tests.factories import StaffUserFactory, UserFactory
1110
from openforms.contrib.objects_api.tests.factories import ObjectsAPIGroupConfigFactory
1211
from openforms.utils.tests.feature_flags import enable_feature_flag
1312
from openforms.utils.tests.vcr import OFVCRMixin
1413

15-
from ..models import ObjectsAPIGroupConfig
16-
1714
TEST_FILES = Path(__file__).parent / "files"
1815

1916

20-
def get_test_config() -> ObjectsAPIGroupConfig:
21-
"""Returns a preconfigured ``ObjectsAPIGroupConfig`` instance matching the docker compose configuration."""
22-
23-
return ObjectsAPIGroupConfig(
24-
objecttypes_service=Service(
25-
api_root="http://localhost:8001/api/v2/",
26-
api_type=APITypes.orc,
27-
oas="https://example.com/",
28-
header_key="Authorization",
29-
header_value="Token 171be5abaf41e7856b423ad513df1ef8f867ff48",
30-
auth_type=AuthTypes.api_key,
31-
)
32-
)
33-
34-
3517
class ObjecttypesAPIEndpointTests(OFVCRMixin, APITestCase):
3618

3719
VCR_TEST_FILES = TEST_FILES
@@ -40,9 +22,8 @@ class ObjecttypesAPIEndpointTests(OFVCRMixin, APITestCase):
4022
@classmethod
4123
def setUpTestData(cls) -> None:
4224
super().setUpTestData()
43-
cls.config = get_test_config()
44-
cls.config.objecttypes_service.save()
45-
cls.config.save()
25+
26+
cls.config = ObjectsAPIGroupConfigFactory.create(for_test_docker_compose=True)
4627

4728
def test_auth_required(self):
4829
response = self.client.get(self.endpoint)
@@ -101,9 +82,8 @@ class ObjecttypeVersionsAPIEndpointTests(OFVCRMixin, APITestCase):
10182
@classmethod
10283
def setUpTestData(cls) -> None:
10384
super().setUpTestData()
104-
cls.config = get_test_config()
105-
cls.config.objecttypes_service.save()
106-
cls.config.save()
85+
86+
cls.config = ObjectsAPIGroupConfigFactory.create(for_test_docker_compose=True)
10787

10888
def test_auth_required(self):
10989
response = self.client.get(self.endpoint)
@@ -156,9 +136,8 @@ class TargetPathsAPIEndpointTests(OFVCRMixin, APITestCase):
156136
@classmethod
157137
def setUpTestData(cls) -> None:
158138
super().setUpTestData()
159-
cls.config = get_test_config()
160-
cls.config.objecttypes_service.save()
161-
cls.config.save()
139+
140+
cls.config = ObjectsAPIGroupConfigFactory.create(for_test_docker_compose=True)
162141

163142
def test_auth_required(self):
164143
response = self.client.post(self.endpoint)

src/openforms/registrations/contrib/objects_api/tests/test_config_checks.py

-22
Original file line numberDiff line numberDiff line change
@@ -40,28 +40,6 @@ def _mockForValidServiceConfiguration(self, m: requests_mock.Mocker) -> None:
4040
headers={"API-version": "1.0.0"},
4141
)
4242

43-
def test_no_objects_service_configured(self):
44-
self.config.objects_service = None
45-
self.config.save()
46-
plugin = ObjectsAPIRegistration(PLUGIN_IDENTIFIER)
47-
48-
with self.assertRaises(InvalidPluginConfiguration):
49-
plugin.check_config()
50-
51-
@requests_mock.Mocker()
52-
def test_no_objecttypes_service_configured(self, m: requests_mock.Mocker):
53-
# Objects API needs to be set up as services are checked in a certain order
54-
m.get(
55-
"https://objects.example.com/api/v1/objects?pageSize=1",
56-
json={"results": []},
57-
)
58-
self.config.objecttypes_service = None
59-
self.config.save()
60-
plugin = ObjectsAPIRegistration(PLUGIN_IDENTIFIER)
61-
62-
with self.assertRaises(InvalidPluginConfiguration):
63-
plugin.check_config()
64-
6543
@requests_mock.Mocker()
6644
def test_objects_service_misconfigured_connection_error(self, m):
6745
m.get(

0 commit comments

Comments
 (0)