commonground-api-common 1.13.4__py3-none-any.whl → 2.0.0__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.0.dist-info}/METADATA +22 -23
  2. {commonground_api_common-1.13.4.dist-info → commonground_api_common-2.0.0.dist-info}/RECORD +29 -27
  3. {commonground_api_common-1.13.4.dist-info → commonground_api_common-2.0.0.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.0.data}/scripts/generate_schema +0 -0
  29. {commonground_api_common-1.13.4.data → commonground_api_common-2.0.0.data}/scripts/patch_content_types +0 -0
  30. {commonground_api_common-1.13.4.data → commonground_api_common-2.0.0.data}/scripts/use_external_components +0 -0
  31. {commonground_api_common-1.13.4.dist-info → commonground_api_common-2.0.0.dist-info}/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: commonground-api-common
3
- Version: 1.13.4
3
+ Version: 2.0.0
4
4
  Summary: Commonground API tooling
5
5
  Home-page: https://github.com/maykinmedia/commonground-api-common
6
6
  Author: Maykin Media, VNG-Realisatie
@@ -9,7 +9,6 @@ License: EUPL 1.2
9
9
  Keywords: openapi,swagger,django
10
10
  Classifier: Development Status :: 5 - Production/Stable
11
11
  Classifier: Framework :: Django
12
- Classifier: Framework :: Django :: 3.2
13
12
  Classifier: Framework :: Django :: 4.2
14
13
  Classifier: Intended Audience :: Developers
15
14
  Classifier: Operating System :: Unix
@@ -20,35 +19,27 @@ Classifier: Programming Language :: Python :: 3 :: Only
20
19
  Classifier: Programming Language :: Python :: 3.10
21
20
  Classifier: Programming Language :: Python :: 3.11
22
21
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
23
- Requires-Dist: django>=3.2.0
24
- Requires-Dist: django-filter>=2.0
22
+ Requires-Dist: django>=4.2.0
23
+ Requires-Dist: django-filter<=25.1,>=23.1
25
24
  Requires-Dist: django-solo
26
- Requires-Dist: djangorestframework>=3.11.0
27
- Requires-Dist: djangorestframework-camel-case>=1.2.0
25
+ Requires-Dist: djangorestframework>=3.15.0
26
+ Requires-Dist: djangorestframework_camel_case>=1.2.0
28
27
  Requires-Dist: django-rest-framework-condition
29
28
  Requires-Dist: drf-yasg>=1.20.0
30
- Requires-Dist: drf-nested-routers>=0.93.3
31
- Requires-Dist: gemma-zds-client>=0.14.0
29
+ Requires-Dist: drf-nested-routers>=0.94.1
32
30
  Requires-Dist: iso-639
33
31
  Requires-Dist: isodate
34
- Requires-Dist: notifications-api-common>=0.2.2
32
+ Requires-Dist: notifications-api-common>=0.3.0
33
+ Requires-Dist: zgw-consumers>=0.35.1
35
34
  Requires-Dist: oyaml
36
35
  Requires-Dist: PyJWT>=2.0.0
37
36
  Requires-Dist: requests
37
+ Requires-Dist: requests-mock
38
38
  Requires-Dist: coreapi
39
- Provides-Extra: coverage
40
- Requires-Dist: pytest-cov; extra == "coverage"
41
- Provides-Extra: docs
42
- Requires-Dist: psycopg2; extra == "docs"
43
- Requires-Dist: sphinx; extra == "docs"
44
- Requires-Dist: sphinx-rtd-theme; extra == "docs"
45
- Provides-Extra: markdown_docs
39
+ Requires-Dist: ape-pie
40
+ Provides-Extra: markdown-docs
46
41
  Requires-Dist: django-markup<=1.3; extra == "markdown-docs"
47
42
  Requires-Dist: markdown; extra == "markdown-docs"
48
- Provides-Extra: pep8
49
- Requires-Dist: flake8; extra == "pep8"
50
- Provides-Extra: release
51
- Requires-Dist: bump2version; extra == "release"
52
43
  Provides-Extra: tests
53
44
  Requires-Dist: psycopg2; extra == "tests"
54
45
  Requires-Dist: pytest; extra == "tests"
@@ -59,6 +50,17 @@ Requires-Dist: isort; extra == "tests"
59
50
  Requires-Dist: black; extra == "tests"
60
51
  Requires-Dist: requests-mock; extra == "tests"
61
52
  Requires-Dist: freezegun; extra == "tests"
53
+ Requires-Dist: zgw-consumers-oas; extra == "tests"
54
+ Provides-Extra: pep8
55
+ Requires-Dist: flake8; extra == "pep8"
56
+ Provides-Extra: coverage
57
+ Requires-Dist: pytest-cov; extra == "coverage"
58
+ Provides-Extra: docs
59
+ Requires-Dist: psycopg2; extra == "docs"
60
+ Requires-Dist: sphinx; extra == "docs"
61
+ Requires-Dist: sphinx-rtd-theme; extra == "docs"
62
+ Provides-Extra: release
63
+ Requires-Dist: bump2version; extra == "release"
62
64
 
63
65
  ===================================================
64
66
  Commonground-API-common - Tooling voor RESTful APIs
@@ -97,9 +99,6 @@ Features
97
99
  * ``UniekeIdentificatieValidator`` (in combinatie met organisatie)
98
100
  * ``InformatieObjectUniqueValidator`` om te valideren dat M2M entries
99
101
  slechts eenmalig voorkomen
100
- * ``ObjectInformatieObjectValidator`` om te valideren dat de synchronisatie
101
- van een object-informatieobject relatie pas kan nadat deze relatie in het
102
- DRC gemaakt is
103
102
  * ``IsImmutableValidator`` - valideer dat bepaalde velden niet gewijzigd
104
103
  worden bij een (partial) update, maar wel mogen gezet worden bij een create
105
104
  * ``ResourceValidator`` - valideer dat een URL een bepaalde resource ontsluit
@@ -1,12 +1,12 @@
1
- commonground_api_common-1.13.4.data/scripts/generate_schema,sha256=UKhznmbHX1zUjPx8G3XtxUPQiqnyd3TOIXs27Tnjl7U,952
2
- commonground_api_common-1.13.4.data/scripts/patch_content_types,sha256=dpGpYrZOZe8O5CHWd0F0QnP6Wk_7lK6DyuVZpBPr4mY,319
3
- commonground_api_common-1.13.4.data/scripts/use_external_components,sha256=xvvbngO2aDUagVXv4xRUqPaVtH_eOaVMWLQ8lyAPhEA,369
4
- vng_api_common/__init__.py,sha256=Gra4o8i--fyNCOTGnifCcG2dIC3_llV16Cw1aadiFd8,23
5
- vng_api_common/admin.py,sha256=oWKpUl5yjeYOIsh60ruCmBDyPURGq4ALle4zGhlUQsE,902
1
+ commonground_api_common-2.0.0.data/scripts/generate_schema,sha256=UKhznmbHX1zUjPx8G3XtxUPQiqnyd3TOIXs27Tnjl7U,952
2
+ commonground_api_common-2.0.0.data/scripts/patch_content_types,sha256=dpGpYrZOZe8O5CHWd0F0QnP6Wk_7lK6DyuVZpBPr4mY,319
3
+ commonground_api_common-2.0.0.data/scripts/use_external_components,sha256=xvvbngO2aDUagVXv4xRUqPaVtH_eOaVMWLQ8lyAPhEA,369
4
+ vng_api_common/__init__.py,sha256=_7OlQdbVkK4jad0CLdpI0grT-zEAb-qgFmH5mFzDXiA,22
5
+ vng_api_common/admin.py,sha256=iFtUPGf-ha0I-bXgq8QIFrP23Kzk_H3FlgAjt0U-ip0,259
6
6
  vng_api_common/apps.py,sha256=wOQuxUImMpH39R0JrDdCZp5ADaUM8jU8vFPFW9froSs,3458
7
7
  vng_api_common/checks.py,sha256=tOyfV7MMLGh4anrd_W30LvJCxiyQ4sFs1mGd9mtrEc0,1175
8
8
  vng_api_common/choices.py,sha256=dboFRoM34GpRUpxB9WexexccopcQSogu1QIyY4B9ACY,541
9
- vng_api_common/client.py,sha256=IChAkSlxmu0_ZHLmfCo_lGH8MMnbahCkcYXm3_ujUAo,1203
9
+ vng_api_common/client.py,sha256=EZWygrJum6pLeKS77mzynfV9kq0gPTjXXSHEpeyEZHg,1888
10
10
  vng_api_common/compat.py,sha256=n4jDFSPzXnh-D_VXjftKMCJYj_q_t6eJrUTWLayl8jQ,768
11
11
  vng_api_common/constants.py,sha256=yYtnYId9alRuhGBoqaO8lkXYi_ATi2FW-wEDGRpiRuQ,10948
12
12
  vng_api_common/decorators.py,sha256=_p-mAyi5Na_-ll00ErcQ3mRoZNCsJzYDAliXn50Bmes,278
@@ -20,22 +20,22 @@ vng_api_common/filtersets.py,sha256=Ioarp5t_cz6Hf3iIcP2Rc0D7tGYhcMQPVdIJJ2dvtv8,
20
20
  vng_api_common/generators.py,sha256=tD7ZyyFgCQ3KiktpBFtn5YiVdl3jioGxzyvkEZFYdQQ,4911
21
21
  vng_api_common/geo.py,sha256=AZbrw0rwGYOmaSUk8JJSkx-4_tVrfT_cgggh9omRwhU,1862
22
22
  vng_api_common/middleware.py,sha256=2DRw0hPpvUMqwoH1Ze8S7tDB16lmj4Bnd6vN5ijkGM8,875
23
- vng_api_common/mocks.py,sha256=JjpJyVmHReRUXbwDi2cnmuq4NYI1hLRr_W962TIhN6Y,6118
24
- vng_api_common/models.py,sha256=JmUfkM1PuiqW3wuTA-chMtcDGvohy4j0JsB95s7VK4Q,5041
25
- vng_api_common/oas.py,sha256=FiqBZ3n36q-fPRZEgpmLDxjnqmoAKmaTKZ-FlFcuRp0,2712
23
+ vng_api_common/mocks.py,sha256=0sELLs-uy2ndu29m5P6FN_p1ehNhYv7IoRayYPs379Y,6200
24
+ vng_api_common/models.py,sha256=3WgpCWQpkCOrMCtqp7EbnP681II43Sg_cch8ZOm4EcU,1707
25
+ vng_api_common/oas.py,sha256=v_M_nsPRk12VF4lFa3Kw2c-wUThfY8PJ6JlEgeUhz9E,2618
26
26
  vng_api_common/pagination.py,sha256=sI6VJ5AHDBhtNRQ8OFrD2heQK-NxaL_6n73ijiqWDLc,255
27
27
  vng_api_common/permissions.py,sha256=ayDxk9Wt8j0yO57FhZ8XaRkPET4lAqd8SSw3m4o3EGs,7562
28
28
  vng_api_common/polymorphism.py,sha256=N-x39pG2unD4N0ZbxFvuhE8ibPaJ0eKeqfvAJW3NnEU,6623
29
- vng_api_common/routers.py,sha256=Tq_V-ewibFfcMl-0TS91_jvel6SxQGF-649GSKqlexE,1906
29
+ vng_api_common/routers.py,sha256=hEnhBulkgMM-7W_lYaykKTgTBj3-avl7DGsR9P7BbTU,1897
30
30
  vng_api_common/schema.py,sha256=1Bt9dH67AOLpfPNF7Q4IH1cXJeaIevSamZQzJ8DJ7ks,6144
31
31
  vng_api_common/scopes.py,sha256=PGs6CkXorAAdWXGFY1bSy-jmsPn122Njen9aFFOpFIQ,2351
32
32
  vng_api_common/search.py,sha256=yehS6boCOk1JXLCqAMU-B62hWtbTBSf_WKIVGPgp0Mg,1045
33
33
  vng_api_common/serializers.py,sha256=7_7IotlRfVb0woe6Wy79l_el3sPzrSY8-bfiYLbEUqs,10162
34
34
  vng_api_common/urls.py,sha256=9IWHYLlEIIHNaZ_Zq02qNQ2HJpETb7o-89r7yBM_tQs,270
35
- vng_api_common/utils.py,sha256=CFmG4xVabUA4GcWiOEbGSaL5mdFi8njsspTXRl8yjCY,9057
36
- vng_api_common/validators.py,sha256=iYtOZs9Lqm4FDZ0PMfT44wxqlHN58RRd42Bv46iNn0I,12164
35
+ vng_api_common/utils.py,sha256=zrtpssOA-NcJHhAlxioBiXeY3G2R_uf0l8oWkDD_EiE,8511
36
+ vng_api_common/validators.py,sha256=Y1OQrmnH3U01hnCizWc_xjpSSGUWKlwiFxeHHGCThpI,11012
37
37
  vng_api_common/version.py,sha256=yJV9_yTM7Qnzg0zGNkJQkN9Uai3I_ZUkcyseJRPRk5I,129
38
- vng_api_common/views.py,sha256=2fraDBtq9x2hMauRuOKkn6cDUw0IYdzfPKTxFFRRFXk,7309
38
+ vng_api_common/views.py,sha256=seRGa-_2BPtrtHP6C6mmQCnT10q9ECfRHa8lpwyEcmM,7230
39
39
  vng_api_common/viewsets.py,sha256=z5pzvSymFiiuCjP_-uuW-3OJKZY_psPAt8fWeWySU0c,2278
40
40
  vng_api_common/api/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
41
41
  vng_api_common/api/permissions.py,sha256=okbwiscxlAtbQWTCRDLL2reOxgj0rRDZeDcrtXAYq00,739
@@ -75,10 +75,11 @@ vng_api_common/audittrails/migrations/0018_auto_20221212_0745.py,sha256=pf4NBYI0
75
75
  vng_api_common/audittrails/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
76
76
  vng_api_common/audittrails/migrations/_operations.py,sha256=UOMv0zAK8CIQ73cSu6wwQG_hkW46Isdy7JCnljn8GII,3280
77
77
  vng_api_common/authorizations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
78
- vng_api_common/authorizations/admin.py,sha256=BmJQgNH7KlDB3flAXrSMm_w0NowpJrDYWDY61RZcKCI,795
79
- vng_api_common/authorizations/middleware.py,sha256=XcHXBvzGdVXSxDgiVaz6IAZQPS-pFsrJnCzepug1Ddw,8100
80
- vng_api_common/authorizations/models.py,sha256=ahwEMG5R6ILTlrnKo5TggYp52Z7dAJMk2QSZyZW8Qa0,4236
78
+ vng_api_common/authorizations/admin.py,sha256=Tk0yYKbb005E0XZaYYWbucMf_K5M8Hhz62wSBDi8rhM,813
79
+ vng_api_common/authorizations/middleware.py,sha256=KJ3znCXPRMOVqSur62SmBjvC6RcKxtcWq1rzaHdYR98,8416
80
+ vng_api_common/authorizations/models.py,sha256=0bcZwRc0ntIlPzUM7I8R5yhQRrnXvAZ_l9FpTnpbnxk,5207
81
81
  vng_api_common/authorizations/serializers.py,sha256=3HeKWEqhI3UWwI8SttC4rEID-Epbk7SWsC-bEjolbaw,5151
82
+ vng_api_common/authorizations/utils.py,sha256=VCXdU4q3CQ1cvuYkg1dPWSbZym1Ufoz5gSmbHYkQcgw,521
82
83
  vng_api_common/authorizations/validators.py,sha256=u7fKm0QgGy8fiAeYmIEB9Gy-yIE9C-tC2ZpnNQBXPso,2816
83
84
  vng_api_common/authorizations/migrations/0001_initial.py,sha256=ooAZtQeDtWgDxXzAP-KnSyyFYLRPM-PMrK5RgOnTPjQ,4360
84
85
  vng_api_common/authorizations/migrations/0002_authorizationsconfig.py,sha256=m4taH6ClHI-YHYGGOKaq_qYXGx9lq1InXOGLQKg9MSw,1364
@@ -95,6 +96,7 @@ vng_api_common/authorizations/migrations/0012_auto_20200619_0545.py,sha256=ECvJJ
95
96
  vng_api_common/authorizations/migrations/0013_auto_20201207_0846.py,sha256=fyJ0HpQMM0d1UyK9AK5_c7S_as_NejzrOdbMYiIogOA,1788
96
97
  vng_api_common/authorizations/migrations/0014_auto_20201221_0905.py,sha256=pkg7Y9yvUIY-QB9elvCxfR7OZJ3xMBIha_4WSZeDlwY,1659
97
98
  vng_api_common/authorizations/migrations/0015_auto_20220318_1608.py,sha256=jR1MDuqapBZU4bYVx0V3MRauRpDk_DtCVcBSe7gmtUA,1751
99
+ vng_api_common/authorizations/migrations/0016_remove_authorizationsconfig_api_root_and_more.py,sha256=-UbTWExHaPL0O8585mm4_OFCm8xJXAmfwmU7V0ysSgg,2407
98
100
  vng_api_common/authorizations/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
99
101
  vng_api_common/caching/__init__.py,sha256=v_FC6HaHESwr_J94TWzaQBKtIjDLnsQADvqHylEW7g8,687
100
102
  vng_api_common/caching/decorators.py,sha256=lU5dnBjD9aHjoYaV32FlYxY7VFZPdMZe26fVDa_h3Q4,1804
@@ -104,7 +106,7 @@ vng_api_common/caching/models.py,sha256=RroS9HFiKNXDV59Odh0x8BO8Az8E81v4gwuprF1A
104
106
  vng_api_common/caching/registry.py,sha256=mY1r99x7m0DQ1BN9lZF2zMjFj_oDNq7urxDkOH7-8Yg,6205
105
107
  vng_api_common/caching/signals.py,sha256=78ej5cVan-JpNHKzZNAfs0m2ON_TXKphe8ZKtP-6FVY,4615
106
108
  vng_api_common/conf/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
107
- vng_api_common/conf/api.py,sha256=m7ITWOQTZL0_4gh7f0K_MYcFp8KcoT-IxyhzZGrBS_8,4436
109
+ vng_api_common/conf/api.py,sha256=bRc488POvBdKeaKVit7s1_LFP-BRLpETmTzcQ6JD614,4372
108
110
  vng_api_common/db/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
109
111
  vng_api_common/db/operations.py,sha256=3vj9zD4EntMSmYsNGN1pRDjAcVM68MESDdoAk9wl0U0,3333
110
112
  vng_api_common/inspectors/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -122,7 +124,7 @@ vng_api_common/management/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJW
122
124
  vng_api_common/management/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
123
125
  vng_api_common/management/commands/generate_autorisaties.py,sha256=kMaZQo6e6vCXA9y0e6P2PlxObyjHMyNynRcM4WtzVZI,1130
124
126
  vng_api_common/management/commands/generate_notificaties.py,sha256=0p7CkLkQo-BeV2IljGv0rcRNw2aWY_HwlvybjggZ_gs,1090
125
- vng_api_common/management/commands/generate_swagger.py,sha256=t9_w2cEEa8ibMzFgg6Nl-8amgVj3qOc4hD44R89arHk,6134
127
+ vng_api_common/management/commands/generate_swagger.py,sha256=DyRRSDMFEZeXSJp_PWiMc6vYFK3GPEO4WIRWZV-tlm0,6120
126
128
  vng_api_common/management/commands/patch_error_contenttypes.py,sha256=xyFIQ4JRutLZCTcLrWqon0FDeAJCw1bkVg4jM8-EM24,2025
127
129
  vng_api_common/management/commands/use_external_components.py,sha256=FLkhZ5jAIZoAXQWwxqu4yT94LiBJ4GRr6g4ZRp7b_Eg,2936
128
130
  vng_api_common/migrations/0001_initial.py,sha256=frE83rux6uVHK4v-1fsb_1C00TBQQ6koFBA3DoVPHmk,895
@@ -130,15 +132,14 @@ vng_api_common/migrations/0002_apicredential.py,sha256=AYgkLMizI0g-Ogth8j_FVJHTD
130
132
  vng_api_common/migrations/0003_auto_20190417_1145.py,sha256=0ABqDggAdjmZxhSsLrMb2ZBwTcR1BOWo1Tn8StfiG0A,1001
131
133
  vng_api_common/migrations/0004_auto_20190517_0903.py,sha256=-Qcei-AJx4SXbEYMojmYInhV74uKtDncGP1PLs6hn0M,707
132
134
  vng_api_common/migrations/0005_auto_20190614_1346.py,sha256=5n8SVpbEJzLrSx-7d446Vy1--SaDfO05hAltrSRQx6o,2949
135
+ vng_api_common/migrations/0006_delete_apicredential.py,sha256=VMVwH4vj9Zq-oF_Z1Do9wobgePtkujwd8bO8M57ACt0,3621
133
136
  vng_api_common/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
134
137
  vng_api_common/notifications/__init__.py,sha256=UvHr_M8s-dZjCQOp60rLDqP_T3D0CgRIqc8ry5bIM8c,229
135
138
  vng_api_common/notifications/apps.py,sha256=7grk1hxQn5usAWV-sWATmNJ4TwSvT3izXVvV-L-zb_s,208
136
- vng_api_common/notifications/constants.py,sha256=R9c0Ejzw2nt-PRk0dCFbC8ugP_mTdgNJHnZ7gvzrTVE,156
137
- vng_api_common/notifications/handlers.py,sha256=-r52nIKbHdFUxE7hob9PiFRe0D3cizhkxbW8qhpK-Ks,2005
138
- vng_api_common/notifications/models.py,sha256=Ez3JYeM7BNSiFYtxAtVxWUWDc8t7jOFQlRTjrtE6SlI,2942
139
+ vng_api_common/notifications/handlers.py,sha256=xjuE_qn5h4gbRNGMsTaCTrHIjXTBcF8na6oMURGAuUU,2089
139
140
  vng_api_common/notifications/api/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
140
141
  vng_api_common/notifications/api/urls.py,sha256=c1T_6tuRpgN0V80Xg_ehZzGGlF1FNMmHdBEPAjR736I,164
141
- vng_api_common/notifications/api/views.py,sha256=8LMh4XU_qCQURx7BLn6HnaFt1auUXlF7PHlOrcJSZ0w,2303
142
+ vng_api_common/notifications/api/views.py,sha256=EvppqaANutuOIrkNepavmzFTX6fxo32c6l5c6DKHe8k,2326
142
143
  vng_api_common/notifications/migrations/0001_initial.py,sha256=zEIVIB--6FStaTl3r9GCXxHL1cSCWRO3T90rEpJr3hY,3049
143
144
  vng_api_common/notifications/migrations/0002_subscription__subscription.py,sha256=eqaj6-FAl7vuCswdvin9Lf8nzjP8U6WqVQO4opNKKCc,550
144
145
  vng_api_common/notifications/migrations/0003_auto_20190319_1048.py,sha256=C-ccYUwQPNCjTctpDDFhnQbm_OcpAlTjYQbEuiEi-ys,987
@@ -149,6 +150,7 @@ vng_api_common/notifications/migrations/0007_auto_20190429_1442.py,sha256=S9Q_WC
149
150
  vng_api_common/notifications/migrations/0008_auto_20190502_0415.py,sha256=Wwh3BsSg0r1I7HZZmrcCHEQMLmaeWEjbqYKapVafb2s,523
150
151
  vng_api_common/notifications/migrations/0009_auto_20190729_0427.py,sha256=Bfw-Axr9lNiXL8Ghns3RK33Dmjvpd_E5MWC740WJlb8,529
151
152
  vng_api_common/notifications/migrations/0010_auto_20220704_1419.py,sha256=Y57o91fbzWZyQO4tgg9Tn4r99hRErRlcrzpLii9Tm3I,1593
153
+ vng_api_common/notifications/migrations/0011_remove_subscription_config_and_more.py,sha256=3FA3rlpRDp8FB8Azl22pv7C4i1oUAQkRHHK9PRpNeJU,499
152
154
  vng_api_common/notifications/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
153
155
  vng_api_common/static/vng_api_common/css/screen.css,sha256=1Zt-yVFbm8qR9LakcYTF5x8p_91r8dMA-VRjIadYeP0,4674
154
156
  vng_api_common/static/vng_api_common/css/admin/admin_overrides.css,sha256=81xuIyYfiTgq8O9h5NsSf-v8Sx885ICSEx1f-5fWy4U,1748
@@ -187,9 +189,9 @@ vng_api_common/templatetags/vng_api_common.py,sha256=j4Fu4rNErLcXStBbuZ9Zd_xD_ft
187
189
  vng_api_common/tests/__init__.py,sha256=zBI2etwu2lEkKkIN1_cPuugJgPcoohQuJbdiUGtoceU,426
188
190
  vng_api_common/tests/auth.py,sha256=IKDWTEFv4Bign4F70-ibsFcnJqRxEJaXvqaPQJWa1xY,4544
189
191
  vng_api_common/tests/caching.py,sha256=zfIw5cRRvO9cekHZZKfRqZc8cx5IfJUYNmcH6cuIMg4,624
190
- vng_api_common/tests/schema.py,sha256=9LFgEXzcYGkHZtlpCiHNCWywR5lF7Pl0S43cwlwKDgQ,1954
192
+ vng_api_common/tests/schema.py,sha256=irt_kIp1uJsnYIJlrpk0-qM9W6i44WyX3GSiJvGUfdU,2288
191
193
  vng_api_common/tests/urls.py,sha256=PFrYzQbBC0TFPMEn3uPhcBG0IQs9JsEPqckicJT1UA4,2159
192
- commonground_api_common-1.13.4.dist-info/METADATA,sha256=Rvx7_yFH4rnQfECFINzFJxtkysviCExbotrYUUJ3yCc,6529
193
- commonground_api_common-1.13.4.dist-info/WHEEL,sha256=OVMc5UfuAQiSplgO0_WdW7vXVGAt9Hdd6qtN4HotdyA,91
194
- commonground_api_common-1.13.4.dist-info/top_level.txt,sha256=vPismc83zPzWXTmlNCCwfDlFV9iygJYxNJW5iDjKTgw,15
195
- commonground_api_common-1.13.4.dist-info/RECORD,,
194
+ commonground_api_common-2.0.0.dist-info/METADATA,sha256=hkvF9wlMO9qfbWy3oEhGKRQpuVRJUuzzODv5Rx4Ogic,6416
195
+ commonground_api_common-2.0.0.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
196
+ commonground_api_common-2.0.0.dist-info/top_level.txt,sha256=vPismc83zPzWXTmlNCCwfDlFV9iygJYxNJW5iDjKTgw,15
197
+ commonground_api_common-2.0.0.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (75.2.0)
2
+ Generator: setuptools (75.6.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -1 +1 @@
1
- __version__ = "1.13.4"
1
+ __version__ = "2.0.0"
vng_api_common/admin.py CHANGED
@@ -1,29 +1,10 @@
1
1
  from django.contrib import admin
2
2
  from django.utils.translation import gettext_lazy as _
3
3
 
4
- from .models import APICredential, JWTSecret
4
+ from .models import JWTSecret
5
5
 
6
6
 
7
7
  @admin.register(JWTSecret)
8
8
  class JWTSecretAdmin(admin.ModelAdmin):
9
9
  list_display = ("identifier",)
10
10
  search_fields = ("identifier",)
11
-
12
-
13
- @admin.register(APICredential)
14
- class APICredentialAdmin(admin.ModelAdmin):
15
- list_display = ("label", "api_root", "client_id", "user_id")
16
- search_fields = ("label", "api_root")
17
- fieldsets = (
18
- (_("external API"), {"fields": ["api_root", "label"]}),
19
- (
20
- _("credentials"),
21
- {
22
- "description": _(
23
- "Credentials that indicate how this API or application identifies itself at the external "
24
- "API."
25
- ),
26
- "fields": ["client_id", "secret", "user_id", "user_representation"],
27
- },
28
- ),
29
- )
@@ -7,7 +7,7 @@ from .models import Applicatie, AuthorizationsConfig, Autorisatie
7
7
 
8
8
  @admin.register(AuthorizationsConfig)
9
9
  class AuthorizationsConfigAdmin(SingletonModelAdmin):
10
- list_display = ("api_root", "component")
10
+ list_display = ("authorizations_api_service", "component")
11
11
 
12
12
 
13
13
  @admin.register(Autorisatie)
@@ -8,9 +8,10 @@ from django.utils.translation import gettext as _
8
8
 
9
9
  import jwt
10
10
  from djangorestframework_camel_case.util import underscoreize
11
+ from requests import RequestException
11
12
  from rest_framework.exceptions import PermissionDenied
12
- from zds_client.client import ClientError
13
13
 
14
+ from vng_api_common.client import ClientError, to_internal_data
14
15
  from vng_api_common.constants import VertrouwelijkheidsAanduiding
15
16
 
16
17
  from ..models import JWTSecret
@@ -51,10 +52,18 @@ class JWTAuth:
51
52
 
52
53
  def _request_auth(self) -> list:
53
54
  client = AuthorizationsConfig.get_client()
55
+
56
+ if not client:
57
+ logger.warning("Authorization component can't be accessed")
58
+ return []
59
+
54
60
  try:
55
- response = client.list(
56
- "applicatie", query_params={"clientIds": self.client_id}
57
- )
61
+ response = client.get("applicaties", params={"clientIds": self.client_id})
62
+
63
+ data = to_internal_data(response)
64
+ except RequestException:
65
+ logger.warning("Authorization component can't be accessed")
66
+ return []
58
67
  except ClientError as exc:
59
68
  response = exc.args[0]
60
69
  # friendly debug - hint at where the problem is located
@@ -64,10 +73,10 @@ class JWTAuth:
64
73
  "authorizations could not be retrieved"
65
74
  )
66
75
  raise PermissionDenied(detail=detail, code="not_authenticated_for_ac")
67
- logger.warn("Authorization component can't be accessed")
76
+ logger.warning("Authorization component can't be accessed")
68
77
  return []
69
78
 
70
- return underscoreize(response["results"])
79
+ return underscoreize(data["results"])
71
80
 
72
81
  def _get_auth(self):
73
82
  return Applicatie.objects.filter(client_ids__contains=[self.client_id])
@@ -0,0 +1,76 @@
1
+ # Generated by Django 5.1.2 on 2024-10-25 14:07
2
+
3
+ import logging
4
+
5
+ import django.db.models.deletion
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 migrate_authorization_config_to_service(apps, _) -> None:
15
+ AuthorizationsConfig = apps.get_model("authorizations", "AuthorizationsConfig")
16
+ Service = apps.get_model("zgw_consumers", "Service")
17
+
18
+ service_label = "Authorization API service"
19
+
20
+ config, _ = AuthorizationsConfig.objects.get_or_create()
21
+ service, created = Service.objects.get_or_create(
22
+ api_root=config.api_root,
23
+ defaults=dict(
24
+ label="Authorization API service",
25
+ slug=slugify(service_label),
26
+ api_type=APITypes.ac,
27
+ auth_type=AuthTypes.zgw,
28
+ ),
29
+ )
30
+ config.authorizations_api_service = service
31
+ config.save()
32
+
33
+ if created:
34
+ logger.info(f"Created new Service for {config.api_root}")
35
+ else:
36
+ logger.info(f"Existing service found for {config.api_root}")
37
+
38
+
39
+ def migrate_authorization_config_to_config(apps, _) -> None:
40
+ AuthorizationsConfig = apps.get_model("authorizations", "AuthorizationsConfig")
41
+
42
+ config, _ = AuthorizationsConfig.objects.get_or_create()
43
+ if config.authorizations_api_service:
44
+ config.api_root = config.authorizations_api_service.api_root
45
+ config.save()
46
+
47
+
48
+ class Migration(migrations.Migration):
49
+
50
+ dependencies = [
51
+ ("authorizations", "0015_auto_20220318_1608"),
52
+ ("zgw_consumers", "0022_set_default_service_slug"),
53
+ ]
54
+
55
+ operations = [
56
+ migrations.AddField(
57
+ model_name="authorizationsconfig",
58
+ name="authorizations_api_service",
59
+ field=models.ForeignKey(
60
+ blank=True,
61
+ limit_choices_to={"api_type": "ac", "auth_type": "zgw"},
62
+ null=True,
63
+ on_delete=django.db.models.deletion.SET_NULL,
64
+ to="zgw_consumers.service",
65
+ verbose_name="autorisations api service",
66
+ ),
67
+ ),
68
+ migrations.RunPython(
69
+ migrate_authorization_config_to_service,
70
+ reverse_code=migrate_authorization_config_to_config,
71
+ ),
72
+ migrations.RemoveField(
73
+ model_name="authorizationsconfig",
74
+ name="api_root",
75
+ ),
76
+ ]
@@ -1,17 +1,28 @@
1
1
  import uuid
2
+ from typing import Optional
2
3
 
3
4
  from django.contrib.postgres.fields import ArrayField
4
5
  from django.db import models
5
6
  from django.utils.translation import gettext_lazy as _
6
7
 
8
+ from solo.models import SingletonModel
9
+ from zgw_consumers.constants import APITypes, AuthTypes
10
+
11
+ from vng_api_common.client import Client, get_client
12
+
7
13
  from ..constants import ComponentTypes, VertrouwelijkheidsAanduiding
8
14
  from ..decorators import field_default
9
15
  from ..fields import VertrouwelijkheidsAanduidingField
10
- from ..models import APIMixin, ClientConfig
16
+ from ..models import APIMixin
17
+
11
18
 
19
+ class AuthorizationsConfigManager(models.Manager):
20
+ def get_queryset(self):
21
+ qs = super().get_queryset()
22
+ return qs.select_related("authorizations_api_service")
12
23
 
13
- @field_default("api_root", "https://autorisaties-api.vng.cloud/api/v1")
14
- class AuthorizationsConfig(ClientConfig):
24
+
25
+ class AuthorizationsConfig(SingletonModel):
15
26
  component = models.CharField(
16
27
  _("component"),
17
28
  max_length=50,
@@ -19,9 +30,33 @@ class AuthorizationsConfig(ClientConfig):
19
30
  default=ComponentTypes.zrc,
20
31
  )
21
32
 
33
+ authorizations_api_service = models.ForeignKey(
34
+ "zgw_consumers.Service",
35
+ limit_choices_to=dict(
36
+ api_type=APITypes.ac,
37
+ auth_type=AuthTypes.zgw,
38
+ ),
39
+ verbose_name=_("autorisations api service"),
40
+ on_delete=models.SET_NULL,
41
+ blank=True,
42
+ null=True,
43
+ )
44
+
45
+ objects = AuthorizationsConfigManager()
46
+
22
47
  class Meta:
23
48
  verbose_name = _("Autorisatiecomponentconfiguratie")
24
49
 
50
+ @classmethod
51
+ def get_client(cls) -> Optional[Client]:
52
+ """
53
+ Construct a client, prepared with the required auth.
54
+ """
55
+ config = cls.get_solo()
56
+ if config.authorizations_api_service:
57
+ return get_client(config.authorizations_api_service.api_root)
58
+ return None
59
+
25
60
 
26
61
  class ApplicatieManager(models.Manager):
27
62
  def get_by_natural_key(self, uuid):
@@ -0,0 +1,17 @@
1
+ def generate_jwt(client_id, secret, user_id, user_representation):
2
+ from zgw_consumers.client import ZGWAuth
3
+
4
+ class FakeService:
5
+ def __init__(self, **kwargs):
6
+ for key, value in kwargs.items():
7
+ setattr(self, key, value)
8
+
9
+ auth = ZGWAuth(
10
+ service=FakeService( # type: ignore
11
+ client_id=client_id,
12
+ secret=secret,
13
+ user_id=user_id,
14
+ user_representation=user_representation,
15
+ )
16
+ )
17
+ return f"Bearer {auth._token}"
vng_api_common/client.py CHANGED
@@ -1,44 +1,73 @@
1
1
  """
2
- Interface to get a zds_client object for a given URL.
2
+ Interface to get a client object for a given URL.
3
3
  """
4
4
 
5
- from typing import Optional
5
+ import logging
6
+ from typing import Any, Optional
6
7
 
7
- from django.apps import apps
8
8
  from django.conf import settings
9
9
  from django.utils.module_loading import import_string
10
10
 
11
- from zds_client import Client
11
+ from ape_pie import APIClient
12
+ from requests import JSONDecodeError, RequestException, Response
12
13
 
14
+ logger = logging.getLogger(__name__)
13
15
 
14
- def get_client(url: str, url_is_api_root=False) -> Optional[Client]:
15
- """
16
- Get a client instance for the given URL.
17
16
 
18
- If the setting CUSTOM_CLIENT_FETCHER is defined, then this callable is invoked.
19
- Otherwise we fall back on the default implementation.
17
+ class ClientError(RuntimeError):
18
+ pass
20
19
 
21
- If no suitable client is found, ``None`` is returned.
22
- """
23
- custom_client_fetcher = getattr(settings, "CUSTOM_CLIENT_FETCHER", None)
24
- if custom_client_fetcher:
25
- client_getter = import_string(custom_client_fetcher)
26
- return client_getter(url)
27
20
 
28
- # default implementation
29
- Client = import_string(settings.ZDS_CLIENT_CLASS)
21
+ # TODO: use more approriate method name
22
+ def to_internal_data(response: Response) -> dict | list | None:
23
+ try:
24
+ response_json = response.json()
25
+ except JSONDecodeError:
26
+ logger.exception("Unable to parse json from response")
27
+ response_json = None
30
28
 
31
- if url_is_api_root and not url.endswith("/"):
32
- url = f"{url}/"
29
+ try:
30
+ response.raise_for_status()
31
+ except RequestException as exc:
32
+ if response.status_code >= 500:
33
+ raise
34
+ raise ClientError(response_json if response_json is not None else {}) from exc
33
35
 
34
- client = Client.from_url(url)
35
- if client is None:
36
- return None
36
+ assert response_json
37
+ return response_json
38
+
39
+
40
+ class Client(APIClient):
41
+ def request(
42
+ self, method: str | bytes, url: str | bytes, *args, **kwargs
43
+ ) -> Response:
44
+
45
+ headers = kwargs.pop("headers", {})
46
+ headers.setdefault("Accept", "application/json")
47
+ headers.setdefault("Content-Type", "application/json")
48
+
49
+ kwargs["headers"] = headers
50
+
51
+ data = kwargs.get("data", {})
37
52
 
38
- APICredential = apps.get_model("vng_api_common", "APICredential")
53
+ if data:
54
+ kwargs["json"] = data
39
55
 
40
- if url_is_api_root:
41
- client.base_url = url
56
+ return super().request(method, url, *args, **kwargs)
57
+
58
+
59
+ def get_client(url: str) -> Client | None:
60
+ """
61
+ Get a client instance for the given URL.
62
+ If no suitable client is found, ``None`` is returned.
63
+ """
64
+ from zgw_consumers.client import build_client
65
+ from zgw_consumers.models import Service
66
+
67
+ service: Optional[Service] = Service.get_service(url)
68
+
69
+ if not service:
70
+ logger.warning(f"No service configured for {url}")
71
+ return None
42
72
 
43
- client.auth = APICredential.get_auth(url)
44
- return client
73
+ return build_client(service, client_factory=Client)
@@ -4,7 +4,6 @@ __all__ = [
4
4
  "BASE_SWAGGER_SETTINGS",
5
5
  "COMMON_SPEC",
6
6
  "LINK_FETCHER",
7
- "ZDS_CLIENT_CLASS",
8
7
  "GEMMA_URL_TEMPLATE",
9
8
  "GEMMA_URL_COMPONENTTYPE",
10
9
  "GEMMA_URL_INFORMATIEMODEL",
@@ -94,8 +93,6 @@ REDOC_SETTINGS = {"EXPAND_RESPONSES": "200,201", "SPEC_URL": "openapi.json"}
94
93
  # See: https://github.com/Rebilly/ReDoc#redoc-options-object
95
94
  LINK_FETCHER = "requests.get"
96
95
 
97
- ZDS_CLIENT_CLASS = "zds_client.Client"
98
-
99
96
  GEMMA_URL_TEMPLATE = "https://www.gemmaonline.nl/index.php/{informatiemodel}_{versie}/doc/{componenttype}/{component}"
100
97
  GEMMA_URL_COMPONENTTYPE = "objecttype"
101
98
  GEMMA_URL_INFORMATIEMODEL = "Rgbz"
@@ -47,7 +47,7 @@ class Row:
47
47
 
48
48
  class Command(generate_swagger.Command):
49
49
  """
50
- Patches to the provided command to modify the schema for ZDS needs.
50
+ Patches to the provided command to modify the schema.
51
51
  """
52
52
 
53
53
  leave_locale_alone = True
@@ -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"])