commonground-api-common 1.13.4__py3-none-any.whl → 2.0.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. {commonground_api_common-1.13.4.dist-info → commonground_api_common-2.0.1.dist-info}/METADATA +25 -24
  2. {commonground_api_common-1.13.4.dist-info → commonground_api_common-2.0.1.dist-info}/RECORD +29 -27
  3. {commonground_api_common-1.13.4.dist-info → commonground_api_common-2.0.1.dist-info}/WHEEL +1 -1
  4. vng_api_common/__init__.py +1 -1
  5. vng_api_common/admin.py +1 -20
  6. vng_api_common/authorizations/admin.py +1 -1
  7. vng_api_common/authorizations/middleware.py +15 -6
  8. vng_api_common/authorizations/migrations/0016_remove_authorizationsconfig_api_root_and_more.py +76 -0
  9. vng_api_common/authorizations/models.py +38 -3
  10. vng_api_common/authorizations/utils.py +17 -0
  11. vng_api_common/client.py +56 -27
  12. vng_api_common/conf/api.py +0 -3
  13. vng_api_common/management/commands/generate_swagger.py +1 -1
  14. vng_api_common/migrations/0006_delete_apicredential.py +119 -0
  15. vng_api_common/mocks.py +4 -1
  16. vng_api_common/models.py +0 -111
  17. vng_api_common/notifications/api/views.py +1 -1
  18. vng_api_common/notifications/handlers.py +8 -3
  19. vng_api_common/notifications/migrations/0011_remove_subscription_config_and_more.py +23 -0
  20. vng_api_common/oas.py +0 -3
  21. vng_api_common/routers.py +3 -3
  22. vng_api_common/tests/schema.py +12 -0
  23. vng_api_common/utils.py +0 -22
  24. vng_api_common/validators.py +0 -38
  25. vng_api_common/views.py +26 -22
  26. vng_api_common/notifications/constants.py +0 -3
  27. vng_api_common/notifications/models.py +0 -97
  28. {commonground_api_common-1.13.4.data → commonground_api_common-2.0.1.data}/scripts/generate_schema +0 -0
  29. {commonground_api_common-1.13.4.data → commonground_api_common-2.0.1.data}/scripts/patch_content_types +0 -0
  30. {commonground_api_common-1.13.4.data → commonground_api_common-2.0.1.data}/scripts/use_external_components +0 -0
  31. {commonground_api_common-1.13.4.dist-info → commonground_api_common-2.0.1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,119 @@
1
+ # Generated by Django 5.1.2 on 2024-10-24 13:51
2
+
3
+ import logging
4
+ from typing import Set
5
+
6
+ from django.db import migrations, models
7
+ from django.utils.text import slugify
8
+
9
+ from zgw_consumers.constants import APITypes, AuthTypes
10
+
11
+ logger = logging.getLogger(__name__)
12
+
13
+
14
+ def _get_api_type(api_root: str) -> APITypes:
15
+ mapping = {
16
+ "/autorisaties/api/": APITypes.ac,
17
+ "/zaken/api/": APITypes.zrc,
18
+ "/catalogi/api/": APITypes.ztc,
19
+ "/documenten/api/": APITypes.drc,
20
+ "/besluiten/api/": APITypes.drc,
21
+ }
22
+
23
+ for path, _type in mapping.items():
24
+ if path in api_root.lower():
25
+ return _type
26
+
27
+ return APITypes.orc
28
+
29
+
30
+ def _get_service_slug(credential: models.Model, existing_slugs: Set[str]) -> str:
31
+ default_slug: str = slugify(credential.label)
32
+
33
+ if default_slug not in existing_slugs or not existing_slugs:
34
+ return default_slug
35
+
36
+ count = 2
37
+ slug = f"{default_slug}-{count}"
38
+
39
+ while slug in existing_slugs:
40
+ count += 1
41
+ slug = f"{default_slug}-{count}"
42
+
43
+ return slug
44
+
45
+
46
+ def migrate_credentials_to_service(apps, _) -> None:
47
+ APICredential = apps.get_model("vng_api_common", "APICredential")
48
+ Service = apps.get_model("zgw_consumers", "Service")
49
+
50
+ credentials = APICredential.objects.all()
51
+
52
+ existings_service_slugs = set(Service.objects.values_list("slug", flat=True))
53
+
54
+ for credential in credentials:
55
+ logger.info(f"Creating Service for {credential.client_id}")
56
+
57
+ service_slug = _get_service_slug(credential, existings_service_slugs)
58
+
59
+ _, created = Service.objects.get_or_create(
60
+ api_root=credential.api_root,
61
+ defaults=dict(
62
+ label=credential.label,
63
+ slug=service_slug,
64
+ api_type=_get_api_type(credential.api_root),
65
+ auth_type=AuthTypes.zgw,
66
+ client_id=credential.client_id,
67
+ secret=credential.secret,
68
+ user_id=credential.user_id,
69
+ user_representation=credential.user_representation,
70
+ ),
71
+ )
72
+
73
+ existings_service_slugs.add(service_slug)
74
+
75
+ if created:
76
+ logger.info(f"Created new Service for {credential.api_root}")
77
+ else:
78
+ logger.info(f"Existing service found for {credential.api_root}")
79
+
80
+
81
+ def migrate_service_to_credentials(apps, _) -> None:
82
+ APICredential = apps.get_model("vng_api_common", "APICredential")
83
+ Service = apps.get_model("zgw_consumers", "Service")
84
+
85
+ services = Service.objects.filter(auth_type=AuthTypes.zgw)
86
+
87
+ for service in services:
88
+ logger.info(f"Creating APICredentials for {service.client_id}")
89
+
90
+ _, created = APICredential.objects.get_or_create(
91
+ api_root=service.api_root,
92
+ defaults=dict(
93
+ label=f"Migrated credentials for {service.client_id}",
94
+ client_id=service.client_id,
95
+ secret=service.secret,
96
+ user_id=service.user_id,
97
+ user_representation=service.user_representation,
98
+ ),
99
+ )
100
+ if created:
101
+ logger.info(f"Created new APICredentials for {service.api_root}")
102
+ else:
103
+ logger.info(f"Existing APICredentials found for {service.api_root}")
104
+
105
+
106
+ class Migration(migrations.Migration):
107
+
108
+ dependencies = [
109
+ ("vng_api_common", "0005_auto_20190614_1346"),
110
+ ]
111
+
112
+ operations = [
113
+ migrations.RunPython(
114
+ migrate_credentials_to_service, reverse_code=migrate_service_to_credentials
115
+ ),
116
+ migrations.DeleteModel(
117
+ name="APICredential",
118
+ ),
119
+ ]
vng_api_common/mocks.py CHANGED
@@ -1,7 +1,10 @@
1
1
  import re
2
2
  from urllib.parse import urlparse
3
3
 
4
- from zds_client.client import UUID_PATTERN
4
+ UUID_PATTERN = re.compile(
5
+ r"[0-9a-f]{8}\-[0-9a-f]{4}\-4[0-9a-f]{3}\-[89ab][0-9a-f]{3}\-[0-9a-f]{12}",
6
+ flags=re.I,
7
+ )
5
8
 
6
9
 
7
10
  class Response:
vng_api_common/models.py CHANGED
@@ -1,15 +1,7 @@
1
- from typing import Optional, Union
2
- from urllib.parse import urlsplit, urlunsplit
3
-
4
1
  from django.db import models
5
- from django.db.models.functions import Length
6
2
  from django.utils.translation import gettext_lazy as _
7
3
 
8
4
  from rest_framework.reverse import reverse
9
- from solo.models import SingletonModel
10
- from zds_client import Client, ClientAuth
11
-
12
- from .client import get_client as _get_client
13
5
 
14
6
 
15
7
  class APIMixin:
@@ -69,106 +61,3 @@ class JWTSecret(models.Model):
69
61
 
70
62
  def __str__(self):
71
63
  return self.identifier
72
-
73
-
74
- class APICredential(models.Model):
75
- """
76
- Store credentials for external APIs.
77
-
78
- When we need to authenticate against a remote API, we need to know which
79
- client ID and secret to use to sign the JWT.
80
- """
81
-
82
- api_root = models.URLField(
83
- _("API-root"),
84
- unique=True,
85
- help_text=_(
86
- "URL of the external API, ending in a trailing slash. Example: https://example.com/api/v1/"
87
- ),
88
- )
89
- label = models.CharField(
90
- _("label"),
91
- max_length=100,
92
- default="",
93
- help_text=_("Human readable label of the external API."),
94
- )
95
- client_id = models.CharField(
96
- _("client ID"),
97
- max_length=255,
98
- help_text=_("Client ID to identify this API at the external API."),
99
- )
100
- secret = models.CharField(
101
- _("secret"), max_length=255, help_text=_("Secret belonging to the client ID.")
102
- )
103
- user_id = models.CharField(
104
- _("user ID"),
105
- max_length=255,
106
- help_text=_(
107
- "User ID to use for the audit trail. Although these external API credentials are typically used by"
108
- "this API itself instead of a user, the user ID is required."
109
- ),
110
- )
111
- user_representation = models.CharField(
112
- _("user representation"),
113
- max_length=255,
114
- default="",
115
- help_text=_("Human readable representation of the user."),
116
- )
117
-
118
- class Meta:
119
- verbose_name = _("external API credential")
120
- verbose_name_plural = _("external API credentials")
121
-
122
- def __str__(self):
123
- return self.api_root
124
-
125
- @classmethod
126
- def get_auth(cls, url: str, **kwargs) -> Union[ClientAuth, None]:
127
- split_url = urlsplit(url)
128
- scheme_and_domain = urlunsplit(split_url[:2] + ("", "", ""))
129
-
130
- candidates = (
131
- cls.objects.filter(api_root__startswith=scheme_and_domain)
132
- .annotate(api_root_length=Length("api_root"))
133
- .order_by("-api_root_length")
134
- )
135
-
136
- # select the one matching
137
- for candidate in candidates.iterator():
138
- if url.startswith(candidate.api_root):
139
- credentials = candidate
140
- break
141
- else:
142
- return None
143
-
144
- auth = ClientAuth(
145
- client_id=credentials.client_id,
146
- secret=credentials.secret,
147
- user_id=credentials.user_id,
148
- user_representation=credentials.user_representation,
149
- **kwargs,
150
- )
151
- return auth
152
-
153
-
154
- class ClientConfig(SingletonModel):
155
- api_root = models.URLField(_("api root"), unique=True)
156
-
157
- class Meta:
158
- abstract = True
159
-
160
- def __str__(self):
161
- return self.api_root
162
-
163
- def save(self, *args, **kwargs):
164
- if not self.api_root.endswith("/"):
165
- self.api_root = f"{self.api_root}/"
166
- super().save(*args, **kwargs)
167
-
168
- @classmethod
169
- def get_client(cls) -> Optional[Client]:
170
- """
171
- Construct a client, prepared with the required auth.
172
- """
173
- config = cls.get_solo()
174
- return _get_client(config.api_root, url_is_api_root=True)
@@ -3,6 +3,7 @@ from django.utils.module_loading import import_string
3
3
 
4
4
  from drf_yasg.utils import swagger_auto_schema
5
5
  from notifications_api_common.api.serializers import NotificatieSerializer
6
+ from notifications_api_common.constants import SCOPE_NOTIFICATIES_PUBLICEREN_LABEL
6
7
  from rest_framework import status
7
8
  from rest_framework.response import Response
8
9
  from rest_framework.views import APIView
@@ -10,7 +11,6 @@ from rest_framework.views import APIView
10
11
  from ...permissions import AuthScopesRequired
11
12
  from ...scopes import Scope
12
13
  from ...serializers import FoutSerializer, ValidatieFoutSerializer
13
- from ..constants import SCOPE_NOTIFICATIES_PUBLICEREN_LABEL
14
14
 
15
15
 
16
16
  class NotificationBaseView(APIView):
@@ -4,7 +4,7 @@ from djangorestframework_camel_case.util import underscoreize
4
4
 
5
5
  from ..authorizations.models import Applicatie
6
6
  from ..authorizations.serializers import ApplicatieUuidSerializer
7
- from ..client import get_client
7
+ from ..client import get_client, to_internal_data
8
8
  from ..constants import CommonResourceAction
9
9
  from ..utils import get_uuid_from_path
10
10
 
@@ -20,8 +20,13 @@ class LoggingHandler:
20
20
  class AuthHandler:
21
21
  def _request_auth(self, url: str) -> dict:
22
22
  client = get_client(url)
23
- response = client.retrieve("applicatie", url)
24
- return underscoreize(response)
23
+
24
+ if not client:
25
+ return {}
26
+
27
+ response = client.get(url)
28
+ data = to_internal_data(response)
29
+ return underscoreize(data)
25
30
 
26
31
  def handle(self, message: dict) -> None:
27
32
  uuid = get_uuid_from_path(message["resource_url"])
@@ -0,0 +1,23 @@
1
+ # Generated by Django 5.1.2 on 2024-10-25 14:07
2
+
3
+ from django.db import migrations
4
+
5
+
6
+ class Migration(migrations.Migration):
7
+
8
+ dependencies = [
9
+ ("notifications", "0010_auto_20220704_1419"),
10
+ ]
11
+
12
+ operations = [
13
+ migrations.RemoveField(
14
+ model_name="subscription",
15
+ name="config",
16
+ ),
17
+ migrations.DeleteModel(
18
+ name="NotificationsConfig",
19
+ ),
20
+ migrations.DeleteModel(
21
+ name="Subscription",
22
+ ),
23
+ ]
vng_api_common/oas.py CHANGED
@@ -1,8 +1,5 @@
1
1
  """
2
2
  Utility module for Open API Specification 3.0.x.
3
-
4
- This should get merged into gemma-zds-client, but some heavy refactoring is
5
- needed for that.
6
3
  """
7
4
 
8
5
  from typing import Union
vng_api_common/routers.py CHANGED
@@ -8,7 +8,7 @@ class APIRootView(_APIRootView):
8
8
  permission_classes = ()
9
9
 
10
10
 
11
- class ZDSNestedRegisteringMixin:
11
+ class NestedRegisteringMixin:
12
12
  _nested_router = None
13
13
 
14
14
  def __init__(self, *args, **kwargs):
@@ -47,11 +47,11 @@ class ZDSNestedRegisteringMixin:
47
47
  )
48
48
 
49
49
 
50
- class NestedSimpleRouter(ZDSNestedRegisteringMixin, routers.NestedSimpleRouter):
50
+ class NestedSimpleRouter(NestedRegisteringMixin, routers.NestedSimpleRouter):
51
51
  pass
52
52
 
53
53
 
54
- class DefaultRouter(ZDSNestedRegisteringMixin, routers.DefaultRouter):
54
+ class DefaultRouter(NestedRegisteringMixin, routers.DefaultRouter):
55
55
  APIRootView = APIRootView
56
56
 
57
57
 
@@ -5,6 +5,8 @@ from urllib.parse import urlparse
5
5
  from django.conf import settings
6
6
 
7
7
  import yaml
8
+ from requests_mock import Mocker
9
+ from zgw_consumers_oas.schema_loading import read_schema
8
10
 
9
11
  DEFAULT_PATH_PARAMETERS = {"version": "1"}
10
12
 
@@ -69,3 +71,13 @@ def get_validation_errors(response, field, index=0):
69
71
  return error
70
72
 
71
73
  i += 1
74
+
75
+
76
+ def mock_service_oas_get(
77
+ mock: Mocker, url: str, service: str, oas_url: str = ""
78
+ ) -> None:
79
+ if not oas_url:
80
+ oas_url = f"{url}schema/openapi.yaml?v=3"
81
+
82
+ content = read_schema(service)
83
+ mock.get(oas_url, content=content)
vng_api_common/utils.py CHANGED
@@ -13,9 +13,6 @@ from django.utils.encoding import smart_str
13
13
  from django.utils.module_loading import import_string
14
14
 
15
15
  from rest_framework.utils import formatting
16
- from zds_client.client import ClientError
17
-
18
- from .client import get_client
19
16
 
20
17
  try:
21
18
  from djangorestframework_camel_case.util import (
@@ -186,25 +183,6 @@ def get_uuid_from_path(path: str) -> str:
186
183
  return uuid_str
187
184
 
188
185
 
189
- def request_object_attribute(
190
- url: str, attribute: str, resource: Union[str, None] = None
191
- ) -> str:
192
- client = get_client(url)
193
-
194
- try:
195
- result = client.retrieve(resource, url=url)[attribute]
196
- except (ClientError, KeyError) as exc:
197
- logger.warning(
198
- "%s was retrieved from %s with the %s: %s",
199
- attribute,
200
- url,
201
- exc.__class__.__name__,
202
- exc,
203
- )
204
- result = ""
205
- return result
206
-
207
-
208
186
  def generate_unique_identification(instance: models.Model, date_field_name: str):
209
187
  model = type(instance)
210
188
  model_name = getattr(model, "IDENTIFICATIE_PREFIX", model._meta.model_name.upper())
@@ -11,10 +11,8 @@ from django.utils.deconstruct import deconstructible
11
11
  from django.utils.module_loading import import_string
12
12
  from django.utils.translation import gettext_lazy as _
13
13
 
14
- import requests
15
14
  from rest_framework import serializers, validators
16
15
 
17
- from .client import get_client
18
16
  from .constants import RSIN_LENGTH
19
17
  from .oas import fetcher, obj_has_shape
20
18
 
@@ -221,42 +219,6 @@ class InformatieObjectUniqueValidator(validators.UniqueTogetherValidator):
221
219
  super().__call__(attrs)
222
220
 
223
221
 
224
- class ObjectInformatieObjectValidator:
225
- """
226
- Validate that the INFORMATIEOBJECT is linked already in the DRC.
227
- """
228
-
229
- message = _(
230
- "Het informatieobject is in het DRC nog niet gerelateerd aan dit object."
231
- )
232
- code = "inconsistent-relation"
233
- requires_context = True
234
-
235
- def __call__(self, informatieobject: str, serializer):
236
- object_url = serializer.context["parent_object"].get_absolute_api_url(
237
- self.request
238
- )
239
-
240
- # dynamic so that it can be mocked in tests easily
241
- client = get_client(informatieobject)
242
-
243
- try:
244
- oios = client.list(
245
- "objectinformatieobject",
246
- query_params={
247
- "informatieobject": informatieobject,
248
- "object": object_url,
249
- },
250
- )
251
- except requests.HTTPError as exc:
252
- raise serializers.ValidationError(
253
- exc.args[0], code="relation-validation-error"
254
- ) from exc
255
-
256
- if len(oios) == 0:
257
- raise serializers.ValidationError(self.message, code=self.code)
258
-
259
-
260
222
  @deconstructible
261
223
  class UntilNowValidator:
262
224
  """
vng_api_common/views.py CHANGED
@@ -1,6 +1,7 @@
1
1
  import logging
2
2
  import os
3
3
  from collections import OrderedDict
4
+ from typing import Optional
4
5
 
5
6
  from django.apps import apps
6
7
  from django.conf import settings
@@ -13,7 +14,8 @@ import requests
13
14
  from rest_framework import exceptions as drf_exceptions, status
14
15
  from rest_framework.response import Response
15
16
  from rest_framework.views import exception_handler as drf_exception_handler
16
- from zds_client import ClientError
17
+
18
+ from vng_api_common.client import Client, ClientError
17
19
 
18
20
  from . import exceptions
19
21
  from .compat import sentry_client
@@ -128,12 +130,20 @@ def _test_ac_config() -> list:
128
130
  auth_config = AuthorizationsConfig.get_solo()
129
131
 
130
132
  # check if AC auth is configured
131
- ac_client = AuthorizationsConfig.get_client()
132
- has_ac_auth = ac_client.auth is not None
133
+ ac_client: Optional[Client] = AuthorizationsConfig.get_client()
134
+ has_ac_auth = ac_client.auth is not None if ac_client else False
133
135
 
134
136
  checks = [
135
137
  (_("Type of component"), auth_config.get_component_display(), None),
136
- (_("AC"), auth_config.api_root, auth_config.api_root.endswith("/")),
138
+ (
139
+ _("AC"),
140
+ (
141
+ auth_config.authorizations_api_service.api_root
142
+ if ac_client
143
+ else _("Missing")
144
+ ),
145
+ bool(ac_client),
146
+ ),
137
147
  (
138
148
  _("Credentials for AC"),
139
149
  _("Configured") if has_ac_auth else _("Missing"),
@@ -145,18 +155,17 @@ def _test_ac_config() -> list:
145
155
  if has_ac_auth:
146
156
  error = False
147
157
 
158
+ client_id = ac_client.auth.service.client_id
159
+
148
160
  try:
149
- ac_client.list(
150
- "applicatie", query_params={"clientIds": ac_client.auth.client_id}
161
+ response: requests.Response = ac_client.get(
162
+ "applicaties", params={"clientIds": client_id}
151
163
  )
152
- except requests.ConnectionError:
164
+
165
+ response.raise_for_status()
166
+ except requests.RequestException:
153
167
  error = True
154
168
  message = _("Could not connect with AC")
155
- except ClientError as exc:
156
- error = True
157
- message = _(
158
- "Cannot retrieve authorizations: HTTP {status_code} - {error_code}"
159
- ).format(status_code=exc.args[0]["status"], error_code=exc.args[0]["code"])
160
169
  else:
161
170
  message = _("Can retrieve authorizations")
162
171
 
@@ -166,14 +175,13 @@ def _test_ac_config() -> list:
166
175
 
167
176
 
168
177
  def _test_nrc_config() -> list:
169
- if not apps.is_installed("vng_api_common.notifications"):
178
+ if not apps.is_installed("notifications_api_common"):
170
179
  return []
171
180
 
172
181
  from notifications_api_common.models import NotificationsConfig, Subscription
173
182
 
174
183
  nrc_config = NotificationsConfig.get_solo()
175
-
176
- nrc_client = NotificationsConfig.get_client()
184
+ nrc_client: Optional[Client] = NotificationsConfig.get_client()
177
185
 
178
186
  has_nrc_auth = nrc_client.auth is not None if nrc_client else False
179
187
 
@@ -199,15 +207,11 @@ def _test_nrc_config() -> list:
199
207
  error = False
200
208
 
201
209
  try:
202
- nrc_client.list("kanaal")
203
- except requests.ConnectionError:
210
+ response: requests.Response = nrc_client.get("kanaal")
211
+ response.raise_for_status()
212
+ except requests.RequestException:
204
213
  error = True
205
214
  message = _("Could not connect with NRC")
206
- except ClientError as exc:
207
- error = True
208
- message = _(
209
- "Cannot retrieve kanalen: HTTP {status_code} - {error_code}"
210
- ).format(status_code=exc.args[0]["status"], error_code=exc.args[0]["code"])
211
215
  else:
212
216
  message = _("Can retrieve kanalen")
213
217
 
@@ -1,3 +0,0 @@
1
- # Exernally defined scopes.
2
- SCOPE_NOTIFICATIES_CONSUMEREN_LABEL = "notificaties.consumeren"
3
- SCOPE_NOTIFICATIES_PUBLICEREN_LABEL = "notificaties.publiceren"
@@ -1,97 +0,0 @@
1
- import uuid
2
- from urllib.parse import urljoin
3
-
4
- from django.contrib.postgres.fields import ArrayField
5
- from django.db import models
6
- from django.utils.translation import gettext_lazy as _
7
-
8
- from zds_client import ClientAuth
9
-
10
- from ..client import get_client
11
- from ..decorators import field_default
12
- from ..models import APICredential, ClientConfig
13
-
14
-
15
- @field_default("api_root", "https://notificaties-api.vng.cloud/api/v1/")
16
- class NotificationsConfig(ClientConfig):
17
- class Meta:
18
- verbose_name = _("Notificatiescomponentconfiguratie")
19
-
20
- def get_auth(self) -> ClientAuth:
21
- auth = APICredential.get_auth(self.api_root)
22
- return auth
23
-
24
-
25
- class Subscription(models.Model):
26
- """
27
- A single subscription.
28
-
29
- TODO: on change/update, update the subscription
30
- """
31
-
32
- config = models.ForeignKey("NotificationsConfig", on_delete=models.CASCADE)
33
-
34
- callback_url = models.URLField(
35
- _("callback url"), help_text=_("Where to send the notifications (webhook url)")
36
- )
37
- client_id = models.CharField(
38
- _("client ID"),
39
- max_length=50,
40
- help_text=_("Client ID to construct the auth token"),
41
- )
42
- secret = models.CharField(
43
- _("client secret"),
44
- max_length=50,
45
- help_text=_("Secret to construct the auth token"),
46
- )
47
- channels = ArrayField(
48
- models.CharField(max_length=100),
49
- verbose_name=_("channels"),
50
- help_text=_("Comma-separated list of channels to subscribe to"),
51
- )
52
-
53
- _subscription = models.URLField(
54
- _("NC subscription"),
55
- blank=True,
56
- editable=False,
57
- help_text=_("Subscription as it is known in the NC"),
58
- )
59
-
60
- class Meta:
61
- verbose_name = _("Webhook subscription")
62
- verbose_name_plural = _("Webhook subscriptions")
63
-
64
- def __str__(self):
65
- return f"{', '.join(self.channels)} - {self.callback_url}"
66
-
67
- def register(self) -> None:
68
- """
69
- Registers the webhook with the notification component.
70
- """
71
- dummy_detail_url = urljoin(self.config.api_root, f"foo/{uuid.uuid4()}")
72
- client = get_client(dummy_detail_url)
73
-
74
- # This authentication is for the NC to call us. Thus, it's *not* for
75
- # calling the NC to create a subscription.
76
- self_auth = ClientAuth(
77
- client_id=self.client_id,
78
- secret=self.secret,
79
- )
80
- data = {
81
- "callbackUrl": self.callback_url,
82
- "auth": self_auth.credentials()["Authorization"],
83
- "kanalen": [
84
- {
85
- "naam": channel,
86
- # FIXME: You need to be able to configure these.
87
- "filters": {},
88
- }
89
- for channel in self.channels
90
- ],
91
- }
92
-
93
- # register the subscriber
94
- subscriber = client.create("abonnement", data=data)
95
-
96
- self._subscription = subscriber["url"]
97
- self.save(update_fields=["_subscription"])