diff --git a/.bumpversion.cfg b/.bumpversion.cfg index 2d08431c..a06a7555 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,7 +1,7 @@ [bumpversion] commit = False tag = False -current_version = 2.4.0 +current_version = 2.5.0 [bumpversion:file:README.rst] diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 1a60ccaf..fe46ef89 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -1,6 +1,7 @@ name: Probleem melden / Bug report description: Meldt een probleem om ons te helpen verbeteren / Create a report to help us improve title: "Title here" +type: "Bug" labels: ["bug", "triage"] projects: ["maykinmedia/15"] assignees: [] diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml index 81c734e6..5f5bbd98 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.yml +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -1,6 +1,7 @@ name: Verzoek tot verbetering / Feature request description: Stel een idee voor om het product beter te maken / Suggest an idea for this product title: "Title here" +type: "Feature" labels: ["enhancement", "triage"] projects: ["maykinmedia/15"] assignees: [] diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 231bfa8c..7f3d020d 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -2,6 +2,44 @@ Change history ============== +2.5.0 +===== +*January, 28, 2025* + +**New features** + +* Add support for setup configuration + * [#293] Configuring access tokens + * [#294] Admin authentication via OIDC + +**Bugfixes/QoL**: + +* Add UUID to Klantinteracties admin search fields and fieldsets +* [#254] Fix incorrect URLs being returned in API responses for ``Persoon``, ``Bijlage`` and ``CategorieRelatie`` +* [#265] Fix ``adres__icontains`` for GET requests on ``digitaleadressen`` endpoint +* [#272] Make ``digitaalAdres.omschrijving`` not required +* [#252] Make ``Persoon.overlijdensdatum`` optional via admin interface +* Point help text for ``DigitaalAdres.is_standaard_adres`` to correctly cased field name +* [maykinmedia/charts#148] Add timeouts to celery tasks + + +**Project maintenance** + +* [#66] Update zgw-consumers to 0.35.1 +* [#66] Update commonground-api-common to 2.1.2 +* [#66] Update notifications-api-common to 0.3.1 +* Update open-api-framework to 0.9.2 +* [maykinmedia/open-api-framework#92] Make sure documentation is built in CI +* [maykinmedia/open-api-framework#92] Fix pushing of Docker latest tag +* Fix code-analysis workflow +* [maykinmedia/open-api-framework#81] Switch from pip-compile to UV +* [maykinmedia/open-api-framework#93] Security updates for third party libraries + +**Documentation** + +* Add documentation for OpenKlant v2 semantic information model + + 2.4.0 ===== *November, 26, 2024* diff --git a/README.rst b/README.rst index 56e89ec3..7f1ddd88 100644 --- a/README.rst +++ b/README.rst @@ -2,7 +2,7 @@ Open Klant ========== -:Version: 2.4.0 +:Version: 2.5.0 :Source: https://github.com/maykinmedia/open-klant :Keywords: klanten, klantinteracties, contactmomenten, api, common ground :License: EUPL @@ -39,15 +39,17 @@ Open Klant versie API versie Release datum API specificatie ================== ============== ============= ================ master/latest n/a n/a `ReDoc `_, `Swagger `_, - (`diff `_) -2.4.0 0.0.3+ 2024-11-22 `ReDoc `_, + (`diff `_) +2.5.0 0.0.4 2025-01-28 `ReDoc `_, + `Swagger `_, + (`diff `_) +2.4.0 0.0.3 2024-11-22 `ReDoc `_, `Swagger `_, - (`diff `_) -2.3.0 0.0.3+ 2024-09-05 `ReDoc `_, +2.3.0 0.0.3 2024-09-05 `ReDoc `_, `Swagger `_, (`diff `_) -2.1.0 0.0.3+ 2024-07-16 `ReDoc `_, +2.1.0 0.0.3 2024-07-16 `ReDoc `_, `Swagger `_, (`diff `_) 2.0.0 0.0.3 2024-03-15 `ReDoc `_, diff --git a/bin/celery_worker.sh b/bin/celery_worker.sh index b2f295bc..0abc5720 100755 --- a/bin/celery_worker.sh +++ b/bin/celery_worker.sh @@ -5,8 +5,8 @@ set -e LOGLEVEL=${CELERY_LOGLEVEL:-INFO} CONCURRENCY=${CELERY_WORKER_CONCURRENCY:-1} -QUEUE=${1:-${CELERY_WORKER_QUEUE:=celery}} -WORKER_NAME=${2:-${CELERY_WORKER_NAME:="${QUEUE}"@%n}} +QUEUE=${CELERY_WORKER_QUEUE:=celery} +WORKER_NAME=${CELERY_WORKER_NAME:="${QUEUE}"@%n} echo "Starting celery worker $WORKER_NAME with queue $QUEUE" exec celery --workdir src --app openklant.celery worker \ diff --git a/docs/conf.py b/docs/conf.py index 0c582bb0..2d4c116c 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -9,6 +9,7 @@ import sys import django +from django.utils.translation import activate sys.path.insert(0, os.path.abspath("../src")) os.environ["LOG_REQUESTS"] = "false" @@ -39,6 +40,9 @@ "sphinx.ext.autodoc", "sphinx.ext.todo", "sphinx.ext.intersphinx", + "sphinx.ext.autodoc", + "django_setup_configuration.documentation.setup_config_example", + "django_setup_configuration.documentation.setup_config_usage", # "sphinx_tabs.tabs", # "recommonmark", # "sphinx_markdown_tables", @@ -54,6 +58,10 @@ # Usually you set "language" from the command line for these cases. language = "en" +# Also set the language to English for Django, to make sure that any translatable text +# is also shown in English (for instance the help texts for setup configuration examples) +activate("en") + # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. # This pattern also affects html_static_path and html_extra_path. diff --git a/docs/installation/setup_configuration.rst b/docs/installation/setup_configuration.rst index bf65f2e5..037bb4c0 100644 --- a/docs/installation/setup_configuration.rst +++ b/docs/installation/setup_configuration.rst @@ -4,84 +4,4 @@ Open Klant configuration (CLI) ============================== -After deploying Open Klant, it needs to be configured to be fully functional. -The django management command ``setup_configuration`` assist with this configuration. -You can get the full command documentation with: - -.. code-block:: bash - - python ./src/manage.py setup_configuration --help - -.. warning:: This command is declarative - if configuration is manually changed after - running the command and you then run the exact same command again, the manual - changes will be reverted. - -Preparation -=========== - -The command executes the list of pluggable configuration steps, and each step -requires specific configuration information, that should be prepared. -Here is the description of all available configuration steps and the configuration -format, used by each step. - -Token configuration ----------------------- - -Create a (single) YAML configuration file with your settings: - -.. code-block:: yaml - - tokenauth_config_enable: true - tokenauth: - items: - - identifier: token-1 - token: ba9d233e95e04c4a8a661a27daffe7c9bd019067 - contact_person: Person 1 - email: person-1@example.com - organization: Organization XYZ # optional - application: Application XYZ # optional - administration: Administration XYZ # optional - - - identifier: token-2 - token: 7b2b212d9f16d171a70a1d927cdcfbd5ca7a4799 - contact_person: Person 2 - email: person-2@example.com - - -Mozilla-django-oidc-db ----------------------- - -Create or update the (single) YAML configuration file with your settings: - -.. code-block:: yaml - - ... - oidc_db_config_enable: true - oidc_db_config_admin_auth: - items: - - identifier: admin-oidc - oidc_rp_client_id: client-id - oidc_rp_client_secret: secret - endpoint_config: - oidc_op_discovery_endpoint: https://keycloak.local/protocol/openid-connect/ - ... - -More details about configuring mozilla-django-oidc-db through ``setup_configuration`` -can be found at the _`documentation`: https://mozilla-django-oidc-db.readthedocs.io/en/latest/setup_configuration.html. - -Execution -========= - -Open Klant configuration ------------------------- - -With the full command invocation, everything is configured at once. Each configuration step -is idempotent, so any manual changes made via the admin interface will be updated if the command -is run afterwards. - -.. code-block:: bash - - python ./src/manage.py setup_configuration --yaml-file /path/to/config.yaml - -.. note:: Due to a cache-bug in the underlying framework, you need to restart all - replicas for part of this change to take effect everywhere. +.. setup-config-usage:: diff --git a/package-lock.json b/package-lock.json index 189ec8c8..a1bb87b8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "openklant", - "version": "2.2.0", + "version": "2.5.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "openklant", - "version": "2.2.0", + "version": "2.5.0", "license": "UNLICENSED", "dependencies": { "microscope-sass": "^2.0.0" diff --git a/package.json b/package.json index 2cf3d510..bbb2b583 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "openklant", - "version": "2.4.0", + "version": "2.5.0", "description": "openklant project", "main": "src/static/openklant/js/openklant.js", "directories": { diff --git a/requirements/base.in b/requirements/base.in index 34a2d811..bd639ef0 100644 --- a/requirements/base.in +++ b/requirements/base.in @@ -2,3 +2,5 @@ open-api-framework django-setup-configuration mozilla-django-oidc-db[setup-configuration] + +django-localflavor \ No newline at end of file diff --git a/requirements/base.txt b/requirements/base.txt index a4ed7b8a..13e9725e 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -81,6 +81,7 @@ django==4.2.17 # django-filter # django-formtools # django-jsonform + # django-localflavor # django-log-outgoing-requests # django-markup # django-otp @@ -126,6 +127,8 @@ django-jsonform==2.22.0 # via # mozilla-django-oidc-db # open-api-framework +django-localflavor==4.0 + # via -r requirements/base.in django-log-outgoing-requests==0.6.1 # via open-api-framework django-markup==1.8.1 @@ -148,7 +151,7 @@ django-sendfile2==0.7.1 # via django-privates django-sessionprofile==3.0.0 # via open-api-framework -django-setup-configuration==0.4.0 +django-setup-configuration==0.7.1 # via # -r requirements/base.in # mozilla-django-oidc-db @@ -181,6 +184,8 @@ djangorestframework-gis==1.0 # via open-api-framework djangorestframework-inclusions==1.2.0 # via open-api-framework +docutils==0.21.2 + # via django-setup-configuration drf-nested-routers==0.94.1 # via commonground-api-common drf-spectacular==0.27.2 @@ -229,7 +234,7 @@ maykin-2fa==1.0.1 # via open-api-framework mozilla-django-oidc==4.0.1 # via mozilla-django-oidc-db -mozilla-django-oidc-db==0.21.1 +mozilla-django-oidc-db==0.22.0 # via # -r requirements/base.in # open-api-framework @@ -279,6 +284,8 @@ python-dotenv==1.0.1 # via # open-api-framework # pydantic-settings +python-stdnum==1.20 + # via django-localflavor pytz==2024.1 # via flower pyyaml==6.0.1 @@ -310,6 +317,10 @@ rpds-py==0.19.1 # via # jsonschema # referencing +ruamel-yaml==0.18.10 + # via django-setup-configuration +ruamel-yaml-clib==0.2.12 + # via ruamel-yaml sentry-sdk==2.12.0 # via open-api-framework six==1.16.0 diff --git a/requirements/ci.txt b/requirements/ci.txt index 1f533b77..50390614 100644 --- a/requirements/ci.txt +++ b/requirements/ci.txt @@ -162,6 +162,7 @@ django==4.2.17 # django-filter # django-formtools # django-jsonform + # django-localflavor # django-log-outgoing-requests # django-markup # django-otp @@ -229,6 +230,10 @@ django-jsonform==2.22.0 # -r requirements/base.txt # mozilla-django-oidc-db # open-api-framework +django-localflavor==4.0 + # via + # -c requirements/base.txt + # -r requirements/base.txt django-log-outgoing-requests==0.6.1 # via # -c requirements/base.txt @@ -284,7 +289,7 @@ django-sessionprofile==3.0.0 # -c requirements/base.txt # -r requirements/base.txt # open-api-framework -django-setup-configuration==0.4.0 +django-setup-configuration==0.7.1 # via # -c requirements/base.txt # -r requirements/base.txt @@ -337,8 +342,11 @@ djangorestframework-inclusions==1.2.0 # -c requirements/base.txt # -r requirements/base.txt # open-api-framework -docutils==0.20.1 +docutils==0.21.2 # via + # -c requirements/base.txt + # -r requirements/base.txt + # django-setup-configuration # recommonmark # sphinx # sphinx-rtd-theme @@ -485,7 +493,7 @@ mozilla-django-oidc==4.0.1 # -c requirements/base.txt # -r requirements/base.txt # mozilla-django-oidc-db -mozilla-django-oidc-db==0.21.1 +mozilla-django-oidc-db==0.22.0 # via # -c requirements/base.txt # -r requirements/base.txt @@ -619,6 +627,11 @@ python-dotenv==1.0.1 # -r requirements/base.txt # open-api-framework # pydantic-settings +python-stdnum==1.20 + # via + # -c requirements/base.txt + # -r requirements/base.txt + # django-localflavor pytz==2024.1 # via # -c requirements/base.txt @@ -676,6 +689,16 @@ rpds-py==0.19.1 # -r requirements/base.txt # jsonschema # referencing +ruamel-yaml==0.18.10 + # via + # -c requirements/base.txt + # -r requirements/base.txt + # django-setup-configuration +ruamel-yaml-clib==0.2.12 + # via + # -c requirements/base.txt + # -r requirements/base.txt + # ruamel-yaml sentry-sdk==2.12.0 # via # -c requirements/base.txt @@ -703,7 +726,7 @@ sphinx==7.4.7 # sphinxcontrib-jquery sphinx-markdown-tables==0.0.17 # via -r requirements/docs.in -sphinx-rtd-theme==2.0.0 +sphinx-rtd-theme==3.0.2 # via -r requirements/docs.in sphinx-tabs==3.4.5 # via -r requirements/docs.in diff --git a/requirements/dev.txt b/requirements/dev.txt index f9135f69..edd748f0 100644 --- a/requirements/dev.txt +++ b/requirements/dev.txt @@ -197,6 +197,7 @@ django==4.2.17 # django-filter # django-formtools # django-jsonform + # django-localflavor # django-log-outgoing-requests # django-markup # django-otp @@ -268,6 +269,10 @@ django-jsonform==2.22.0 # -r requirements/ci.txt # mozilla-django-oidc-db # open-api-framework +django-localflavor==4.0 + # via + # -c requirements/ci.txt + # -r requirements/ci.txt django-log-outgoing-requests==0.6.1 # via # -c requirements/ci.txt @@ -323,7 +328,7 @@ django-sessionprofile==3.0.0 # -c requirements/ci.txt # -r requirements/ci.txt # open-api-framework -django-setup-configuration==0.4.0 +django-setup-configuration==0.7.1 # via # -c requirements/ci.txt # -r requirements/ci.txt @@ -378,10 +383,11 @@ djangorestframework-inclusions==1.2.0 # -c requirements/ci.txt # -r requirements/ci.txt # open-api-framework -docutils==0.20.1 +docutils==0.21.2 # via # -c requirements/ci.txt # -r requirements/ci.txt + # django-setup-configuration # recommonmark # sphinx # sphinx-rtd-theme @@ -556,7 +562,7 @@ mozilla-django-oidc==4.0.1 # -c requirements/ci.txt # -r requirements/ci.txt # mozilla-django-oidc-db -mozilla-django-oidc-db==0.21.1 +mozilla-django-oidc-db==0.22.0 # via # -c requirements/ci.txt # -r requirements/ci.txt @@ -731,6 +737,11 @@ python-dotenv==1.0.1 # -r requirements/ci.txt # open-api-framework # pydantic-settings +python-stdnum==1.20 + # via + # -c requirements/ci.txt + # -r requirements/ci.txt + # django-localflavor pytz==2024.1 # via # -c requirements/ci.txt @@ -789,6 +800,16 @@ rpds-py==0.19.1 # -r requirements/ci.txt # jsonschema # referencing +ruamel-yaml==0.18.10 + # via + # -c requirements/ci.txt + # -r requirements/ci.txt + # django-setup-configuration +ruamel-yaml-clib==0.2.12 + # via + # -c requirements/ci.txt + # -r requirements/ci.txt + # ruamel-yaml sentry-sdk==2.12.0 # via # -c requirements/ci.txt @@ -829,7 +850,7 @@ sphinx-markdown-tables==0.0.17 # via # -c requirements/ci.txt # -r requirements/ci.txt -sphinx-rtd-theme==2.0.0 +sphinx-rtd-theme==3.0.2 # via # -c requirements/ci.txt # -r requirements/ci.txt diff --git a/src/openklant/__init__.py b/src/openklant/__init__.py index efba30b6..4c55b101 100644 --- a/src/openklant/__init__.py +++ b/src/openklant/__init__.py @@ -1,6 +1,6 @@ from .celery import app as celery_app __all__ = ("celery_app",) -__version__ = "2.4.0" +__version__ = "2.5.0" __author__ = "Maykin" __homepage__ = "https://github.com/maykinmedia/open-klant" diff --git a/src/openklant/components/contactgegevens/api/tests/test_apis.py b/src/openklant/components/contactgegevens/api/tests/test_apis.py index 4e2ebdec..df130f51 100644 --- a/src/openklant/components/contactgegevens/api/tests/test_apis.py +++ b/src/openklant/components/contactgegevens/api/tests/test_apis.py @@ -17,12 +17,12 @@ def test_persoon_detail(self): geslacht="m", voorvoegsel="", voornamen="John", - adres_nummeraanduiding_id="nummeraanduiding_id", + adres_nummeraanduiding_id="1234567890000001", adres_adresregel1="adresregel1", adres_adresregel2="adresregel2", adres_adresregel3="adresregel3", - adres_land="5001", - land="5001", + adres_land="CA", + land="CA", ) detail_url = reverse( "contactgegevens:persoon-detail", @@ -30,11 +30,11 @@ def test_persoon_detail(self): ) expected_adres = { - "nummeraanduidingId": "nummeraanduiding_id", + "nummeraanduidingId": "1234567890000001", "adresregel1": "adresregel1", "adresregel2": "adresregel2", "adresregel3": "adresregel3", - "land": "5001", + "land": "CA", } expected_data = { "uuid": str(persoon.uuid), @@ -46,7 +46,7 @@ def test_persoon_detail(self): "voorvoegsel": "", "voornamen": "John", "adres": expected_adres, - "land": "5001", + "land": "CA", } response = self.client.get(detail_url) @@ -64,13 +64,13 @@ def test_create_persoon(self): "geslacht": "m", "voornamen": "Devin", "adres": { - "nummeraanduidingId": "nummeraanduiding_id", + "nummeraanduidingId": "1234567890000001", "adresregel1": "adresregel1", "adresregel2": "adresregel2", "adresregel3": "adresregel3", - "land": "5001", + "land": "CA", }, - "land": "5001", + "land": "CA", } response = self.client.post(list_url, data) @@ -88,14 +88,14 @@ def test_create_persoon(self): self.assertEqual( data["adres"], { - "nummeraanduidingId": "nummeraanduiding_id", + "nummeraanduidingId": "1234567890000001", "adresregel1": "adresregel1", "adresregel2": "adresregel2", "adresregel3": "adresregel3", - "land": "5001", + "land": "CA", }, ) - self.assertEqual(data["land"], "5001") + self.assertEqual(data["land"], "CA") def test_update_persoon(self): persoon = PersoonFactory.create( @@ -105,12 +105,12 @@ def test_update_persoon(self): geslacht="m", voorvoegsel="", voornamen="Devin", - adres_nummeraanduiding_id="nummeraanduiding_id", + adres_nummeraanduiding_id="1234567890000001", adres_adresregel1="adresregel1", adres_adresregel2="adresregel2", adres_adresregel3="adresregel3", - adres_land="5001", - land="5001", + adres_land="CA", + land="CA", ) detail_url = reverse( "contactgegevens:persoon-detail", @@ -132,14 +132,14 @@ def test_update_persoon(self): self.assertEqual( data["adres"], { - "nummeraanduidingId": "nummeraanduiding_id", + "nummeraanduidingId": "1234567890000001", "adresregel1": "adresregel1", "adresregel2": "adresregel2", "adresregel3": "adresregel3", - "land": "5001", + "land": "CA", }, ) - self.assertEqual(data["land"], "5001") + self.assertEqual(data["land"], "CA") data = { "geboortedatum": "1972-05-06", @@ -149,16 +149,17 @@ def test_update_persoon(self): "voorvoegsel": "changed", "voornamen": "changed", "adres": { - "nummeraanduidingId": "changed", + "nummeraanduidingId": "1234567890000002", "adresregel1": "changed", "adresregel2": "changed", "adresregel3": "changed", - "land": "6713", + "land": "FR", }, - "land": "6713", + "land": "FR", } response = self.client.put(detail_url, data) data = response.json() + self.assertEqual(data["geboortedatum"], "1972-05-06") self.assertEqual(data["overlijdensdatum"], "2023-11-22") self.assertEqual(data["geslachtsnaam"], "changed") @@ -168,14 +169,14 @@ def test_update_persoon(self): self.assertEqual( data["adres"], { - "nummeraanduidingId": "changed", + "nummeraanduidingId": "1234567890000002", "adresregel1": "changed", "adresregel2": "changed", "adresregel3": "changed", - "land": "6713", + "land": "FR", }, ) - self.assertEqual(data["land"], "6713") + self.assertEqual(data["land"], "FR") def test_update_partial_persoon(self): persoon = PersoonFactory.create( @@ -185,12 +186,12 @@ def test_update_partial_persoon(self): geslacht="m", voorvoegsel="", voornamen="Devin", - adres_nummeraanduiding_id="nummeraanduiding_id", + adres_nummeraanduiding_id="1234567890000001", adres_adresregel1="adresregel1", adres_adresregel2="adresregel2", adres_adresregel3="adresregel3", - adres_land="5001", - land="5001", + adres_land="CA", + land="CA", ) detail_url = reverse( "contactgegevens:persoon-detail", @@ -212,14 +213,14 @@ def test_update_partial_persoon(self): self.assertEqual( data["adres"], { - "nummeraanduidingId": "nummeraanduiding_id", + "nummeraanduidingId": "1234567890000001", "adresregel1": "adresregel1", "adresregel2": "adresregel2", "adresregel3": "adresregel3", - "land": "5001", + "land": "CA", }, ) - self.assertEqual(data["land"], "5001") + self.assertEqual(data["land"], "CA") data = { "overlijdensdatum": "2023-11-22", @@ -236,14 +237,14 @@ def test_update_partial_persoon(self): self.assertEqual( data["adres"], { - "nummeraanduidingId": "nummeraanduiding_id", + "nummeraanduidingId": "1234567890000001", "adresregel1": "adresregel1", "adresregel2": "adresregel2", "adresregel3": "adresregel3", - "land": "5001", + "land": "CA", }, ) - self.assertEqual(data["land"], "5001") + self.assertEqual(data["land"], "CA") def test_list_pagination_pagesize_param(self): list_url = reverse("contactgegevens:persoon-list") @@ -265,12 +266,12 @@ def test_organisatie_detail(self): handelsnaam="Devin Townsend", oprichtingsdatum="1980-02-23", opheffingsdatum="2020-09-05", - adres_nummeraanduiding_id="nummeraanduiding_id", + adres_nummeraanduiding_id="1234567890000001", adres_adresregel1="adresregel1", adres_adresregel2="adresregel2", adres_adresregel3="adresregel3", - adres_land="5001", - land="5001", + adres_land="CA", + land="CA", ) detail_url = reverse( "contactgegevens:organisatie-detail", @@ -278,11 +279,11 @@ def test_organisatie_detail(self): ) expected_adres = { - "nummeraanduidingId": "nummeraanduiding_id", + "nummeraanduidingId": "1234567890000001", "adresregel1": "adresregel1", "adresregel2": "adresregel2", "adresregel3": "adresregel3", - "land": "5001", + "land": "CA", } expected_data = { "uuid": str(organisatie.uuid), @@ -291,7 +292,7 @@ def test_organisatie_detail(self): "opheffingsdatum": "2020-09-05", "handelsnaam": "Devin Townsend", "adres": expected_adres, - "land": "5001", + "land": "CA", } response = self.client.get(detail_url) @@ -307,13 +308,13 @@ def test_create_organisatie(self): "handelsnaam": "Devin Townsend", "oprichtingsdatum": "1996-03-12", "adres": { - "nummeraanduidingId": "nummeraanduiding_id", + "nummeraanduidingId": "1234567890000001", "adresregel1": "adresregel1", "adresregel2": "adresregel2", "adresregel3": "adresregel3", - "land": "5001", + "land": "CA", }, - "land": "5001", + "land": "CA", } response = self.client.post(list_url, data) @@ -328,26 +329,26 @@ def test_create_organisatie(self): self.assertEqual( data["adres"], { - "nummeraanduidingId": "nummeraanduiding_id", + "nummeraanduidingId": "1234567890000001", "adresregel1": "adresregel1", "adresregel2": "adresregel2", "adresregel3": "adresregel3", - "land": "5001", + "land": "CA", }, ) - self.assertEqual(data["land"], "5001") + self.assertEqual(data["land"], "CA") def test_update_organisatie(self): organisatie = OrganisatieFactory.create( handelsnaam="Devin Townsend", oprichtingsdatum="1996-03-12", opheffingsdatum=None, - adres_nummeraanduiding_id="nummeraanduiding_id", + adres_nummeraanduiding_id="1234567890000001", adres_adresregel1="adresregel1", adres_adresregel2="adresregel2", adres_adresregel3="adresregel3", - adres_land="5001", - land="5001", + adres_land="CA", + land="CA", ) detail_url = reverse( "contactgegevens:organisatie-detail", @@ -366,27 +367,27 @@ def test_update_organisatie(self): self.assertEqual( data["adres"], { - "nummeraanduidingId": "nummeraanduiding_id", + "nummeraanduidingId": "1234567890000001", "adresregel1": "adresregel1", "adresregel2": "adresregel2", "adresregel3": "adresregel3", - "land": "5001", + "land": "CA", }, ) - self.assertEqual(data["land"], "5001") + self.assertEqual(data["land"], "CA") data = { "handelsnaam": "changed", "oprichtingsdatum": "1996-03-13", "opheffingsdatum": "2023-11-22", "adres": { - "nummeraanduidingId": "changed", + "nummeraanduidingId": "1234567890000002", "adresregel1": "changed", "adresregel2": "changed", "adresregel3": "changed", - "land": "6713", + "land": "FR", }, - "land": "6713", + "land": "FR", } response = self.client.put(detail_url, data) data = response.json() @@ -396,26 +397,26 @@ def test_update_organisatie(self): self.assertEqual( data["adres"], { - "nummeraanduidingId": "changed", + "nummeraanduidingId": "1234567890000002", "adresregel1": "changed", "adresregel2": "changed", "adresregel3": "changed", - "land": "6713", + "land": "FR", }, ) - self.assertEqual(data["land"], "6713") + self.assertEqual(data["land"], "FR") def test_update_partial_organisatie(self): organisatie = OrganisatieFactory.create( handelsnaam="Devin Townsend", oprichtingsdatum="1996-03-12", opheffingsdatum=None, - adres_nummeraanduiding_id="nummeraanduiding_id", + adres_nummeraanduiding_id="1234567890000001", adres_adresregel1="adresregel1", adres_adresregel2="adresregel2", adres_adresregel3="adresregel3", - adres_land="5001", - land="5001", + adres_land="CA", + land="CA", ) detail_url = reverse( "contactgegevens:organisatie-detail", @@ -434,14 +435,14 @@ def test_update_partial_organisatie(self): self.assertEqual( data["adres"], { - "nummeraanduidingId": "nummeraanduiding_id", + "nummeraanduidingId": "1234567890000001", "adresregel1": "adresregel1", "adresregel2": "adresregel2", "adresregel3": "adresregel3", - "land": "5001", + "land": "CA", }, ) - self.assertEqual(data["land"], "5001") + self.assertEqual(data["land"], "CA") data = { "opheffingsdatum": "2023-11-22", @@ -456,14 +457,14 @@ def test_update_partial_organisatie(self): self.assertEqual( data["adres"], { - "nummeraanduidingId": "nummeraanduiding_id", + "nummeraanduidingId": "1234567890000001", "adresregel1": "adresregel1", "adresregel2": "adresregel2", "adresregel3": "adresregel3", - "land": "5001", + "land": "CA", }, ) - self.assertEqual(data["land"], "5001") + self.assertEqual(data["land"], "CA") def test_list_pagination_pagesize_param(self): list_url = reverse("contactgegevens:organisatie-list") diff --git a/src/openklant/components/contactgegevens/migrations/0004_alter_organisatie_adres_land_alter_organisatie_land_and_more.py b/src/openklant/components/contactgegevens/migrations/0004_alter_organisatie_adres_land_alter_organisatie_land_and_more.py new file mode 100644 index 00000000..39afb751 --- /dev/null +++ b/src/openklant/components/contactgegevens/migrations/0004_alter_organisatie_adres_land_alter_organisatie_land_and_more.py @@ -0,0 +1,134 @@ +# Generated by Django 4.2.15 on 2025-01-09 15:19 +import logging +import django.core.validators +from django.db import migrations, models +import openklant.utils.validators +from django.db import IntegrityError + +from openklant.utils.constants import COUNTRIES_DICT + +logger = logging.getLogger(__name__) + +FIELDS = ["adres_land", "land"] + + +def _check_records(records): + total_failed_records = 0 + for record in records: + record_failed = False + for field_name in FIELDS: + field_value = getattr(record, field_name, None) + iso_code = COUNTRIES_DICT.get(field_value, "") + if field_value and not iso_code: + record_failed = True + logger.warning( + "%s(pk=%s, uuid=%s) Field: '%s'. No match found for nl_code (not found in Tabel 34 Landentabel): '%s'", + type(record).__qualname__, + record.pk, + record.uuid, + field_name, + field_value, + ) + + if record_failed: + total_failed_records += 1 + return total_failed_records + + +def _update_records(records): + for record in records: + updated = False + for field_name in FIELDS: + if field_value := getattr(record, field_name, None): + iso_code = COUNTRIES_DICT.get(field_value, "") + setattr(record, field_name, iso_code) + updated = True + + if updated: + record.save() + + +def _check_and_update_records(apps, schema_editor): + Organisatie = apps.get_model("contactgegevens", "Organisatie") + Persoon = apps.get_model("contactgegevens", "Persoon") + + for model in [Organisatie, Persoon]: + records = model.objects.all() + if total_failed_records := _check_records(records): + raise IntegrityError( + "The migration cannot proceed due to %s records that don't comply with the %s model's requirements. " + "Possible data inconsistency or mapping error." + % (total_failed_records, model.__qualname__) + ) + else: + _update_records(records) + + +class Migration(migrations.Migration): + + dependencies = [ + ("contactgegevens", "0003_alter_persoon_overlijdensdatum"), + ] + + operations = [ + migrations.RunPython( + code=_check_and_update_records, + reverse_code=migrations.RunPython.noop, + ), + migrations.AlterField( + model_name="organisatie", + name="adres_land", + field=models.CharField( + blank=True, + help_text="ISO 3166-code die het land (buiten Nederland) aangeeft alwaar de ingeschrevene verblijft.", + max_length=2, + validators=[ + django.core.validators.MinLengthValidator(limit_value=2), + openklant.utils.validators.validate_country, + ], + verbose_name="land", + ), + ), + migrations.AlterField( + model_name="organisatie", + name="land", + field=models.CharField( + blank=True, + help_text="ISO 3166-code die het land (buiten Nederland) aangeeft alwaar de ingeschrevene verblijft.", + max_length=2, + validators=[ + django.core.validators.MinLengthValidator(limit_value=2), + openklant.utils.validators.validate_country, + ], + verbose_name="land", + ), + ), + migrations.AlterField( + model_name="persoon", + name="adres_land", + field=models.CharField( + blank=True, + help_text="ISO 3166-code die het land (buiten Nederland) aangeeft alwaar de ingeschrevene verblijft.", + max_length=2, + validators=[ + django.core.validators.MinLengthValidator(limit_value=2), + openklant.utils.validators.validate_country, + ], + verbose_name="land", + ), + ), + migrations.AlterField( + model_name="persoon", + name="land", + field=models.CharField( + blank=True, + help_text="ISO 3166-code die het land (buiten Nederland) aangeeft alwaar de ingeschrevene verblijft.", + max_length=2, + validators=[ + django.core.validators.MinLengthValidator(limit_value=2), + openklant.utils.validators.validate_country, + ], + verbose_name="land", + ), + ), + ] diff --git a/src/openklant/components/contactgegevens/migrations/0005_alter_organisatie_adres_nummeraanduiding_id_and_more.py b/src/openklant/components/contactgegevens/migrations/0005_alter_organisatie_adres_nummeraanduiding_id_and_more.py new file mode 100644 index 00000000..cdb97b09 --- /dev/null +++ b/src/openklant/components/contactgegevens/migrations/0005_alter_organisatie_adres_nummeraanduiding_id_and_more.py @@ -0,0 +1,89 @@ +# Generated by Django 4.2.17 on 2025-01-28 09:10 + +import logging +import openklant.utils.validators +from django.core.exceptions import ValidationError +from django.db import IntegrityError +from django.db import migrations, models +from openklant.utils.validators import validate_bag_id + +logger = logging.getLogger(__name__) + + +def _check_records(records): + total_failed_records = 0 + for record in records: + if record.adres_nummeraanduiding_id: + try: + validate_bag_id(record.adres_nummeraanduiding_id) + except ValidationError: + logger.warning( + "%s(pk=%s, uuid=%s) Field: 'adres_nummeraanduiding_id'. Invalid BAG ID: %s", + type(record).__qualname__, + record.pk, + record.uuid, + record.adres_nummeraanduiding_id, + ) + total_failed_records += 1 + return total_failed_records + + +def _check_records_field_length(apps, schema_editor): + Organisatie = apps.get_model("contactgegevens", "Organisatie") + Persoon = apps.get_model("contactgegevens", "Persoon") + + for model in [Organisatie, Persoon]: + records = model.objects.all() + if total_failed_records := _check_records(records): + raise IntegrityError( + "The migration cannot proceed due to %s records that don't comply with the %s model's requirements. " + "Possible data inconsistency or mapping error." + % (total_failed_records, model.__qualname__) + ) + + +class Migration(migrations.Migration): + + dependencies = [ + ( + "contactgegevens", + "0004_alter_organisatie_adres_land_alter_organisatie_land_and_more", + ), + ] + + operations = [ + migrations.RunPython( + code=_check_records_field_length, + reverse_code=migrations.RunPython.noop, + ), + migrations.AlterField( + model_name="organisatie", + name="adres_nummeraanduiding_id", + field=models.CharField( + blank=True, + help_text="Identificatie van het adres bij de Basisregistratie Adressen en Gebouwen.", + max_length=16, + validators=[ + openklant.utils.validators.CustomRegexValidator( + message="Ongeldige nummeraanduiding BAG-ID", regex="^[0-9]{16}$" + ) + ], + verbose_name="nummeraanduiding ID", + ), + ), + migrations.AlterField( + model_name="persoon", + name="adres_nummeraanduiding_id", + field=models.CharField( + blank=True, + help_text="Identificatie van het adres bij de Basisregistratie Adressen en Gebouwen.", + max_length=16, + validators=[ + openklant.utils.validators.CustomRegexValidator( + message="Ongeldige nummeraanduiding BAG-ID", regex="^[0-9]{16}$" + ) + ], + verbose_name="nummeraanduiding ID", + ), + ), + ] diff --git a/src/openklant/components/contactgegevens/mixins.py b/src/openklant/components/contactgegevens/mixins.py index c9a3a6ce..c9fa5764 100644 --- a/src/openklant/components/contactgegevens/mixins.py +++ b/src/openklant/components/contactgegevens/mixins.py @@ -1,9 +1,11 @@ -from django.core.validators import MinLengthValidator, validate_integer +from django.core.validators import MinLengthValidator from django.db import models from django.utils.translation import gettext_lazy as _ from vng_api_common.descriptors import GegevensGroepType +from openklant.utils.validators import validate_bag_id, validate_country + class AdresMixin(models.Model): adres_nummeraanduiding_id = models.CharField( @@ -11,7 +13,8 @@ class AdresMixin(models.Model): help_text=_( "Identificatie van het adres bij de Basisregistratie Adressen en Gebouwen." ), - max_length=255, + max_length=16, + validators=[validate_bag_id], blank=True, ) adres_adresregel1 = models.CharField( @@ -41,14 +44,13 @@ class AdresMixin(models.Model): adres_land = models.CharField( _("land"), help_text=_( - "Een code, opgenomen in Tabel 34, Landentabel, die het land (buiten Nederland) " - "aangeeft alwaar de ingeschrevene verblijft." + "ISO 3166-code die het land (buiten Nederland) aangeeft alwaar de ingeschrevene verblijft." ), validators=[ - MinLengthValidator(limit_value=4), - validate_integer, + MinLengthValidator(limit_value=2), + validate_country, ], - max_length=4, + max_length=2, blank=True, ) diff --git a/src/openklant/components/contactgegevens/models.py b/src/openklant/components/contactgegevens/models.py index 33b98b63..c6321f84 100644 --- a/src/openklant/components/contactgegevens/models.py +++ b/src/openklant/components/contactgegevens/models.py @@ -1,11 +1,12 @@ import uuid -from django.core.validators import MinLengthValidator, validate_integer +from django.core.validators import MinLengthValidator from django.db import models from django.utils.translation import gettext_lazy as _ from openklant.components.contactgegevens.constants import GeslachtChoices from openklant.components.contactgegevens.mixins import AdresMixin +from openklant.utils.validators import validate_country class Organisatie(AdresMixin): @@ -42,14 +43,13 @@ class Organisatie(AdresMixin): land = models.CharField( _("land"), help_text=_( - "Een code, opgenomen in Tabel 34, Landentabel, die het land (buiten Nederland) " - "aangeeft alwaar de ingeschrevene verblijft." + "ISO 3166-code die het land (buiten Nederland) aangeeft alwaar de ingeschrevene verblijft." ), validators=[ - MinLengthValidator(limit_value=4), - validate_integer, + MinLengthValidator(limit_value=2), + validate_country, ], - max_length=4, + max_length=2, blank=True, ) @@ -128,14 +128,13 @@ class Persoon(AdresMixin): land = models.CharField( _("land"), help_text=_( - "Een code, opgenomen in Tabel 34, Landentabel, die het land (buiten Nederland) " - "aangeeft alwaar de ingeschrevene verblijft." + "ISO 3166-code die het land (buiten Nederland) aangeeft alwaar de ingeschrevene verblijft." ), validators=[ - MinLengthValidator(limit_value=4), - validate_integer, + MinLengthValidator(limit_value=2), + validate_country, ], - max_length=4, + max_length=2, blank=True, ) diff --git a/src/openklant/components/contactgegevens/openapi.yaml b/src/openklant/components/contactgegevens/openapi.yaml index ade2386b..060d9610 100644 --- a/src/openklant/components/contactgegevens/openapi.yaml +++ b/src/openklant/components/contactgegevens/openapi.yaml @@ -367,10 +367,10 @@ components: description: De adres gegevens van een organisatie. land: type: string - description: Een code, opgenomen in Tabel 34, Landentabel, die het land - (buiten Nederland) aangeeft alwaar de ingeschrevene verblijft. - maxLength: 4 - minLength: 4 + description: ISO 3166-code die het land (buiten Nederland) aangeeft alwaar + de ingeschrevene verblijft. + maxLength: 2 + minLength: 2 required: - handelsnaam - url @@ -382,7 +382,8 @@ components: type: string description: Identificatie van het adres bij de Basisregistratie Adressen en Gebouwen. - maxLength: 255 + pattern: ^[0-9]{16}$ + maxLength: 16 adresregel1: type: string description: Eerste deel van het adres dat niet voorkomt in de Basisregistratie @@ -400,10 +401,10 @@ components: maxLength: 80 land: type: string - description: Een code, opgenomen in Tabel 34, Landentabel, die het land - (buiten Nederland) aangeeft alwaar de ingeschrevene verblijft. - maxLength: 4 - minLength: 4 + description: ISO 3166-code die het land (buiten Nederland) aangeeft alwaar + de ingeschrevene verblijft. + maxLength: 2 + minLength: 2 PaginatedOrganisatieList: type: object required: @@ -494,10 +495,10 @@ components: description: De adres gegevens van een organisatie. land: type: string - description: Een code, opgenomen in Tabel 34, Landentabel, die het land - (buiten Nederland) aangeeft alwaar de ingeschrevene verblijft. - maxLength: 4 - minLength: 4 + description: ISO 3166-code die het land (buiten Nederland) aangeeft alwaar + de ingeschrevene verblijft. + maxLength: 2 + minLength: 2 PatchedPersoon: type: object description: |- @@ -560,10 +561,10 @@ components: description: De adres gegevens van een organisatie. land: type: string - description: Een code, opgenomen in Tabel 34, Landentabel, die het land - (buiten Nederland) aangeeft alwaar de ingeschrevene verblijft. - maxLength: 4 - minLength: 4 + description: ISO 3166-code die het land (buiten Nederland) aangeeft alwaar + de ingeschrevene verblijft. + maxLength: 2 + minLength: 2 Persoon: type: object description: |- @@ -626,10 +627,10 @@ components: description: De adres gegevens van een organisatie. land: type: string - description: Een code, opgenomen in Tabel 34, Landentabel, die het land - (buiten Nederland) aangeeft alwaar de ingeschrevene verblijft. - maxLength: 4 - minLength: 4 + description: ISO 3166-code die het land (buiten Nederland) aangeeft alwaar + de ingeschrevene verblijft. + maxLength: 2 + minLength: 2 required: - geboortedatum - geslachtsnaam @@ -642,7 +643,8 @@ components: type: string description: Identificatie van het adres bij de Basisregistratie Adressen en Gebouwen. - maxLength: 255 + pattern: ^[0-9]{16}$ + maxLength: 16 adresregel1: type: string description: Eerste deel van het adres dat niet voorkomt in de Basisregistratie @@ -660,10 +662,10 @@ components: maxLength: 80 land: type: string - description: Een code, opgenomen in Tabel 34, Landentabel, die het land - (buiten Nederland) aangeeft alwaar de ingeschrevene verblijft. - maxLength: 4 - minLength: 4 + description: ISO 3166-code die het land (buiten Nederland) aangeeft alwaar + de ingeschrevene verblijft. + maxLength: 2 + minLength: 2 securitySchemes: tokenAuth: type: apiKey diff --git a/src/openklant/components/contactgegevens/tests/__init__.py b/src/openklant/components/contactgegevens/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/openklant/components/contactgegevens/tests/test_migrations.py b/src/openklant/components/contactgegevens/tests/test_migrations.py new file mode 100644 index 00000000..379cef4d --- /dev/null +++ b/src/openklant/components/contactgegevens/tests/test_migrations.py @@ -0,0 +1,334 @@ +from django.db import IntegrityError + +from openklant.tests.test_migrate import BaseMigrationTest + + +class TestCountryConverter(BaseMigrationTest): + app = "contactgegevens" + migrate_from = "0003_alter_persoon_overlijdensdatum" + migrate_to = "0004_alter_organisatie_adres_land_alter_organisatie_land_and_more" + + def test_ok_migration_organisatie_model(self): + + Organisatie = self.old_app_state.get_model("contactgegevens", "Organisatie") + + Organisatie.objects.create(land="6030", adres_land="6030") + + self._perform_migration() + + Organisatie = self.apps.get_model("contactgegevens", "Organisatie") + + records = Organisatie.objects.all() + self.assertEqual(records.count(), 1) + self.assertEqual(records[0].land, "NL") + self.assertEqual(records[0].adres_land, "NL") + self.assertNotEqual(records[0].land, "6030") + self.assertNotEqual(records[0].adres_land, "6030") + + def test_ok_migration_organisatie_model_empty_code(self): + + Organisatie = self.old_app_state.get_model("contactgegevens", "Organisatie") + org1 = Organisatie.objects.create(land="", adres_land="") + org2 = Organisatie.objects.create(land="6030", adres_land="6030") + + self._perform_migration() + Organisatie = self.apps.get_model("contactgegevens", "Organisatie") + + records = Organisatie.objects.all() + org1 = records.get(pk=org1.pk) + org2 = records.get(pk=org2.pk) + + self.assertEqual(records.count(), 2) + self.assertEqual(org1.land, "") + self.assertEqual(org1.adres_land, "") + self.assertEqual(org2.land, "NL") + self.assertEqual(org2.adres_land, "NL") + self.assertNotEqual(org2.land, "6030") + self.assertNotEqual(org2.adres_land, "6030") + + def test_ko_migration_organisatie_model_wrong_code(self): + + Organisatie = self.old_app_state.get_model("contactgegevens", "Organisatie") + org1 = Organisatie.objects.create(land="9999", adres_land="9999") + org2 = Organisatie.objects.create(land="5001", adres_land="5001") + + with self.assertRaises(IntegrityError) as error: + self._perform_migration() + + self.assertEqual( + ( + "The migration cannot proceed due to 1 records that don't comply with the " + "Organisatie model's requirements. Possible data inconsistency or mapping error." + ), + str(error.exception), + ) + + records = Organisatie.objects.all() + org1 = records.get(pk=org1.pk) + org2 = records.get(pk=org2.pk) + + self.assertEqual(records.count(), 2) + self.assertEqual(org1.land, "9999") + self.assertEqual(org1.adres_land, "9999") + self.assertEqual(org2.land, "5001") + self.assertEqual(org2.adres_land, "5001") + + # Update manually + org1 = records.get(pk=org1.pk) + org1.land = "6030" + org1.adres_land = "6030" + org1.save() + + # Re-Run the migration + self._perform_migration() + Organisatie = self.apps.get_model("contactgegevens", "Organisatie") + + records = Organisatie.objects.all() + org1 = records.get(pk=org1.pk) + org2 = records.get(pk=org2.pk) + + self.assertEqual(records.count(), 2) + self.assertEqual(org1.land, "NL") + self.assertEqual(org1.adres_land, "NL") + self.assertEqual(org2.land, "CA") + self.assertEqual(org2.adres_land, "CA") + + self.assertNotEqual(org1.land, "6030") + self.assertNotEqual(org1.adres_land, "6030") + self.assertNotEqual(org2.land, "5001") + self.assertNotEqual(org2.adres_land, "5001") + + def test_ok_migrate_persoon_model(self): + + Persoon = self.old_app_state.get_model("contactgegevens", "Persoon") + + Persoon.objects.create( + land="6030", adres_land="6030", geboortedatum="1980-02-23" + ) + + self._perform_migration() + Persoon = self.apps.get_model("contactgegevens", "Persoon") + + records = Persoon.objects.all() + self.assertEqual(records.count(), 1) + self.assertEqual(records[0].land, "NL") + self.assertEqual(records[0].adres_land, "NL") + self.assertNotEqual(records[0].land, "6030") + self.assertNotEqual(records[0].adres_land, "6030") + + def test_ok_migration_persoon_model_empty_code(self): + + Persoon = self.old_app_state.get_model("contactgegevens", "Persoon") + + persoon1 = Persoon.objects.create( + land="", adres_land="", geboortedatum="1980-02-23" + ) + persoon2 = Persoon.objects.create( + land="6030", adres_land="6030", geboortedatum="1980-02-23" + ) + + self._perform_migration() + Persoon = self.apps.get_model("contactgegevens", "Persoon") + + records = Persoon.objects.all() + persoon1 = records.get(pk=persoon1.pk) + persoon2 = records.get(pk=persoon2.pk) + + self.assertEqual(records.count(), 2) + self.assertEqual(persoon1.land, "") + self.assertEqual(persoon1.adres_land, "") + self.assertEqual(persoon2.land, "NL") + self.assertEqual(persoon2.adres_land, "NL") + + def test_ko_migration_persoon_model_wrong_code(self): + + Persoon = self.old_app_state.get_model("contactgegevens", "Persoon") + persoon1 = Persoon.objects.create( + land="9999", adres_land="9999", geboortedatum="1980-02-23" + ) + persoon2 = Persoon.objects.create( + land="5001", adres_land="5001", geboortedatum="1980-02-23" + ) + + with self.assertRaises(IntegrityError) as error: + self._perform_migration() + + self.assertEqual( + ( + "The migration cannot proceed due to 1 records that don't comply with the " + "Persoon model's requirements. Possible data inconsistency or mapping error." + ), + str(error.exception), + ) + + records = Persoon.objects.all() + self.assertEqual(records.count(), 2) + self.assertEqual(persoon1.land, "9999") + self.assertEqual(persoon1.adres_land, "9999") + self.assertEqual(persoon2.land, "5001") + self.assertEqual(persoon2.adres_land, "5001") + + # Update manually + persoon1 = records.get(pk=persoon1.pk) + persoon2 = records.get(pk=persoon2.pk) + persoon1.land = "6030" + persoon1.adres_land = "6030" + persoon1.save() + + # Re-Run the migration + self._perform_migration() + Persoon = self.apps.get_model("contactgegevens", "Persoon") + + records = Persoon.objects.all() + persoon1 = records.get(pk=persoon1.pk) + persoon2 = records.get(pk=persoon2.pk) + + self.assertEqual(records.count(), 2) + self.assertEqual(persoon1.land, "NL") + self.assertEqual(persoon1.adres_land, "NL") + self.assertEqual(persoon2.land, "CA") + self.assertEqual(persoon2.adres_land, "CA") + + self.assertNotEqual(persoon1.land, "6030") + self.assertNotEqual(persoon1.adres_land, "6030") + self.assertNotEqual(persoon2.land, "5001") + self.assertNotEqual(persoon2.adres_land, "5001") + + +class TestValidateBagId(BaseMigrationTest): + app = "contactgegevens" + migrate_from = "0004_alter_organisatie_adres_land_alter_organisatie_land_and_more" + migrate_to = "0005_alter_organisatie_adres_nummeraanduiding_id_and_more" + + def test_ok_migration_organisatie_model(self): + + Organisatie = self.old_app_state.get_model("contactgegevens", "Organisatie") + + Organisatie.objects.create(adres_nummeraanduiding_id="1234567890000001") + + self._perform_migration() + + Organisatie = self.apps.get_model("contactgegevens", "Organisatie") + + records = Organisatie.objects.all() + self.assertEqual(records.count(), 1) + self.assertEqual(records[0].adres_nummeraanduiding_id, "1234567890000001") + + def test_ok_migration_organisatie_model_empty_value(self): + + Organisatie = self.old_app_state.get_model("contactgegevens", "Organisatie") + + Organisatie.objects.create(adres_nummeraanduiding_id="") + + self._perform_migration() + + Organisatie = self.apps.get_model("contactgegevens", "Organisatie") + + records = Organisatie.objects.all() + + self.assertEqual(records.count(), 1) + self.assertEqual(records[0].adres_nummeraanduiding_id, "") + + def test_ko_migration_organisatie_model_wrong_code(self): + + Organisatie = self.old_app_state.get_model("contactgegevens", "Organisatie") + org1 = Organisatie.objects.create(adres_nummeraanduiding_id="123456") + + with self.assertRaises(IntegrityError) as error: + self._perform_migration() + + self.assertEqual( + ( + "The migration cannot proceed due to 1 records that don't comply with the " + "Organisatie model's requirements. Possible data inconsistency or mapping error." + ), + str(error.exception), + ) + + records = Organisatie.objects.all() + + self.assertEqual(records.count(), 1) + self.assertEqual(records[0].adres_nummeraanduiding_id, "123456") + + # Update manually + org1 = records.get(pk=records[0].pk) + org1.adres_nummeraanduiding_id = "1234567890000001" + org1.save() + + # Re-Run the migration + self._perform_migration() + Organisatie = self.apps.get_model("contactgegevens", "Organisatie") + + records = Organisatie.objects.all() + org1 = records.get(pk=records[0].pk) + + self.assertEqual(records.count(), 1) + self.assertEqual(records[0].adres_nummeraanduiding_id, "1234567890000001") + self.assertNotEqual(records[0].adres_nummeraanduiding_id, "123456") + + def test_ok_migrate_persoon_model(self): + + Persoon = self.old_app_state.get_model("contactgegevens", "Persoon") + + Persoon.objects.create( + adres_nummeraanduiding_id="1234567890000001", geboortedatum="1980-02-23" + ) + + self._perform_migration() + Persoon = self.apps.get_model("contactgegevens", "Persoon") + + records = Persoon.objects.all() + self.assertEqual(records.count(), 1) + self.assertEqual(records[0].adres_nummeraanduiding_id, "1234567890000001") + + def test_ok_migration_persoon_model_empty_value(self): + + Persoon = self.old_app_state.get_model("contactgegevens", "Persoon") + + Persoon.objects.create(adres_nummeraanduiding_id="", geboortedatum="1980-02-23") + + self._perform_migration() + + Persoon = self.apps.get_model("contactgegevens", "Persoon") + + records = Persoon.objects.all() + + self.assertEqual(records.count(), 1) + self.assertEqual(records[0].adres_nummeraanduiding_id, "") + + def test_ko_migration_persoon_model_wrong_code(self): + + Persoon = self.old_app_state.get_model("contactgegevens", "Persoon") + persoon1 = Persoon.objects.create( + adres_nummeraanduiding_id="123456", geboortedatum="1980-02-23" + ) + + with self.assertRaises(IntegrityError) as error: + self._perform_migration() + + self.assertEqual( + ( + "The migration cannot proceed due to 1 records that don't comply with the " + "Persoon model's requirements. Possible data inconsistency or mapping error." + ), + str(error.exception), + ) + + records = Persoon.objects.all() + self.assertEqual(records.count(), 1) + self.assertEqual(records[0].adres_nummeraanduiding_id, "123456") + + # Update manually + persoon1 = records.get(pk=records[0].pk) + persoon1.adres_nummeraanduiding_id = "1234567890000001" + persoon1.save() + + # Re-Run the migration + self._perform_migration() + Persoon = self.apps.get_model("contactgegevens", "Persoon") + + records = Persoon.objects.all() + + self.assertEqual(records.count(), 1) + self.assertEqual(records[0].adres_nummeraanduiding_id, "1234567890000001") + self.assertNotEqual(records[0].adres_nummeraanduiding_id, "123456") diff --git a/src/openklant/components/klantinteracties/api/tests/factories.py b/src/openklant/components/klantinteracties/api/tests/factories.py index 3b1baf17..33648b70 100644 --- a/src/openklant/components/klantinteracties/api/tests/factories.py +++ b/src/openklant/components/klantinteracties/api/tests/factories.py @@ -13,19 +13,19 @@ class KlantContactDataFactory(factory.DictFactory): class BezoekAdresDataFactory(factory.DictFactory): - nummeraanduidingId = "4a282b5c-16d7-401d-9737-28e98c865ab2" + nummeraanduidingId = "1234567890000001" adresregel1 = "adres1" adresregel2 = "adres2" adresregel3 = "adres3" - land = "6030" + land = "NL" class CorrespondentieAdresDataFactory(factory.DictFactory): - nummeraanduidingId = "c06918d9-899b-4d98-a10d-08436ebc6c20" + nummeraanduidingId = "1234567890000002" adresregel1 = "adres1" adresregel2 = "adres2" adresregel3 = "adres3" - land = "6030" + land = "NL" class ContactNaamDataFactory(factory.DictFactory): diff --git a/src/openklant/components/klantinteracties/api/tests/test_klantcontacten.py b/src/openklant/components/klantinteracties/api/tests/test_klantcontacten.py index bb74129a..933ab11a 100644 --- a/src/openklant/components/klantinteracties/api/tests/test_klantcontacten.py +++ b/src/openklant/components/klantinteracties/api/tests/test_klantcontacten.py @@ -410,18 +410,18 @@ def test_create_betrokkene_with_partij(self): "hadKlantcontact": {"uuid": str(klantcontact.uuid)}, "wasPartij": {"uuid": str(partij.uuid)}, "bezoekadres": { - "nummeraanduidingId": "4a282b5c-16d7-401d-9737-28e98c865ab2", + "nummeraanduidingId": "1234567890000001", "adresregel1": "adres1", "adresregel2": "adres2", "adresregel3": "adres3", - "land": "6030", + "land": "NL", }, "correspondentieadres": { - "nummeraanduidingId": "c06918d9-899b-4d98-a10d-08436ebc6c20", + "nummeraanduidingId": "1234567890000002", "adresregel1": "adres1", "adresregel2": "adres2", "adresregel3": "adres3", - "land": "6030", + "land": "NL", }, "contactnaam": { "voorletters": "P", @@ -444,21 +444,21 @@ def test_create_betrokkene_with_partij(self): self.assertEqual( data["bezoekadres"], { - "nummeraanduidingId": "4a282b5c-16d7-401d-9737-28e98c865ab2", + "nummeraanduidingId": "1234567890000001", "adresregel1": "adres1", "adresregel2": "adres2", "adresregel3": "adres3", - "land": "6030", + "land": "NL", }, ) self.assertEqual( data["correspondentieadres"], { - "nummeraanduidingId": "c06918d9-899b-4d98-a10d-08436ebc6c20", + "nummeraanduidingId": "1234567890000002", "adresregel1": "adres1", "adresregel2": "adres2", "adresregel3": "adres3", - "land": "6030", + "land": "NL", }, ) self.assertEqual( @@ -483,18 +483,18 @@ def test_create_betrokkene(self): "hadKlantcontact": {"uuid": str(klantcontact.uuid)}, "wasPartij": None, "bezoekadres": { - "nummeraanduidingId": "4a282b5c-16d7-401d-9737-28e98c865ab2", + "nummeraanduidingId": "1234567890000001", "adresregel1": "adres1", "adresregel2": "adres2", "adresregel3": "adres3", - "land": "6030", + "land": "NL", }, "correspondentieadres": { - "nummeraanduidingId": "c06918d9-899b-4d98-a10d-08436ebc6c20", + "nummeraanduidingId": "1234567890000002", "adresregel1": "adres1", "adresregel2": "adres2", "adresregel3": "adres3", - "land": "6030", + "land": "NL", }, "contactnaam": { "voorletters": "P", @@ -517,21 +517,21 @@ def test_create_betrokkene(self): self.assertEqual( data["bezoekadres"], { - "nummeraanduidingId": "4a282b5c-16d7-401d-9737-28e98c865ab2", + "nummeraanduidingId": "1234567890000001", "adresregel1": "adres1", "adresregel2": "adres2", "adresregel3": "adres3", - "land": "6030", + "land": "NL", }, ) self.assertEqual( data["correspondentieadres"], { - "nummeraanduidingId": "c06918d9-899b-4d98-a10d-08436ebc6c20", + "nummeraanduidingId": "1234567890000002", "adresregel1": "adres1", "adresregel2": "adres2", "adresregel3": "adres3", - "land": "6030", + "land": "NL", }, ) self.assertEqual( @@ -555,16 +555,16 @@ def test_update_betrokkene(self): betrokkene = BetrokkeneFactory.create( klantcontact=klantcontact, partij=partij, - bezoekadres_nummeraanduiding_id="4a282b5c-16d7-401d-9737-28e98c865ab2", + bezoekadres_nummeraanduiding_id="1234567890000001", bezoekadres_adresregel1="adres1", bezoekadres_adresregel2="adres2", bezoekadres_adresregel3="adres3", - bezoekadres_land="6030", - correspondentieadres_nummeraanduiding_id="c06918d9-899b-4d98-a10d-08436ebc6c20", + bezoekadres_land="NL", + correspondentieadres_nummeraanduiding_id="1234567890000002", correspondentieadres_adresregel1="adres1", correspondentieadres_adresregel2="adres2", correspondentieadres_adresregel3="adres3", - correspondentieadres_land="6030", + correspondentieadres_land="NL", contactnaam_voorletters="P", contactnaam_voornaam="Phil", contactnaam_voorvoegsel_achternaam="", @@ -584,21 +584,21 @@ def test_update_betrokkene(self): self.assertEqual( data["bezoekadres"], { - "nummeraanduidingId": "4a282b5c-16d7-401d-9737-28e98c865ab2", + "nummeraanduidingId": "1234567890000001", "adresregel1": "adres1", "adresregel2": "adres2", "adresregel3": "adres3", - "land": "6030", + "land": "NL", }, ) self.assertEqual( data["correspondentieadres"], { - "nummeraanduidingId": "c06918d9-899b-4d98-a10d-08436ebc6c20", + "nummeraanduidingId": "1234567890000002", "adresregel1": "adres1", "adresregel2": "adres2", "adresregel3": "adres3", - "land": "6030", + "land": "NL", }, ) self.assertEqual( @@ -620,18 +620,18 @@ def test_update_betrokkene(self): "wasPartij": {"uuid": str(partij2.uuid)}, "hadKlantcontact": {"uuid": str(klantcontact2.uuid)}, "bezoekadres": { - "nummeraanduidingId": "4a282b5c-16d7-401d-9737-28e98c865ab2", + "nummeraanduidingId": "1234567890000001", "adresregel1": "changed", "adresregel2": "changed", "adresregel3": "changed", - "land": "3060", + "land": "NL", }, "correspondentieadres": { - "nummeraanduidingId": "c06918d9-899b-4d98-a10d-08436ebc6c20", + "nummeraanduidingId": "1234567890000002", "adresregel1": "changed", "adresregel2": "changed", "adresregel3": "changed", - "land": "3060", + "land": "NL", }, "contactnaam": { "voorletters": "changed", @@ -654,21 +654,21 @@ def test_update_betrokkene(self): self.assertEqual( data["bezoekadres"], { - "nummeraanduidingId": "4a282b5c-16d7-401d-9737-28e98c865ab2", + "nummeraanduidingId": "1234567890000001", "adresregel1": "changed", "adresregel2": "changed", "adresregel3": "changed", - "land": "3060", + "land": "NL", }, ) self.assertEqual( data["correspondentieadres"], { - "nummeraanduidingId": "c06918d9-899b-4d98-a10d-08436ebc6c20", + "nummeraanduidingId": "1234567890000002", "adresregel1": "changed", "adresregel2": "changed", "adresregel3": "changed", - "land": "3060", + "land": "NL", }, ) self.assertEqual( @@ -692,16 +692,16 @@ def test_partial_update_betrokkene(self): betrokkene = BetrokkeneFactory.create( klantcontact=klantcontact, partij=partij, - bezoekadres_nummeraanduiding_id="4a282b5c-16d7-401d-9737-28e98c865ab2", + bezoekadres_nummeraanduiding_id="1234567890000001", bezoekadres_adresregel1="adres1", bezoekadres_adresregel2="adres2", bezoekadres_adresregel3="adres3", - bezoekadres_land="6030", - correspondentieadres_nummeraanduiding_id="c06918d9-899b-4d98-a10d-08436ebc6c20", + bezoekadres_land="NL", + correspondentieadres_nummeraanduiding_id="1234567890000002", correspondentieadres_adresregel1="adres1", correspondentieadres_adresregel2="adres2", correspondentieadres_adresregel3="adres3", - correspondentieadres_land="6030", + correspondentieadres_land="NL", contactnaam_voorletters="P", contactnaam_voornaam="Phil", contactnaam_voorvoegsel_achternaam="", @@ -721,21 +721,21 @@ def test_partial_update_betrokkene(self): self.assertEqual( data["bezoekadres"], { - "nummeraanduidingId": "4a282b5c-16d7-401d-9737-28e98c865ab2", + "nummeraanduidingId": "1234567890000001", "adresregel1": "adres1", "adresregel2": "adres2", "adresregel3": "adres3", - "land": "6030", + "land": "NL", }, ) self.assertEqual( data["correspondentieadres"], { - "nummeraanduidingId": "c06918d9-899b-4d98-a10d-08436ebc6c20", + "nummeraanduidingId": "1234567890000002", "adresregel1": "adres1", "adresregel2": "adres2", "adresregel3": "adres3", - "land": "6030", + "land": "NL", }, ) self.assertEqual( @@ -755,11 +755,11 @@ def test_partial_update_betrokkene(self): data = { "bezoekadres": { - "nummeraanduidingId": "4a282b5c-16d7-401d-9737-28e98c865ab2", + "nummeraanduidingId": "1234567890000001", "adresregel1": "changed", "adresregel2": "changed", "adresregel3": "changed", - "land": "3060", + "land": "NL", }, } @@ -771,21 +771,21 @@ def test_partial_update_betrokkene(self): self.assertEqual( data["bezoekadres"], { - "nummeraanduidingId": "4a282b5c-16d7-401d-9737-28e98c865ab2", + "nummeraanduidingId": "1234567890000001", "adresregel1": "changed", "adresregel2": "changed", "adresregel3": "changed", - "land": "3060", + "land": "NL", }, ) self.assertEqual( data["correspondentieadres"], { - "nummeraanduidingId": "c06918d9-899b-4d98-a10d-08436ebc6c20", + "nummeraanduidingId": "1234567890000002", "adresregel1": "adres1", "adresregel2": "adres2", "adresregel3": "adres3", - "land": "6030", + "land": "NL", }, ) self.assertEqual( @@ -1574,18 +1574,18 @@ def test_create_success(self): "wasPartij": None, "digitaleAdressen": [], "bezoekadres": { - "nummeraanduidingId": "4a282b5c-16d7-401d-9737-28e98c865ab2", + "nummeraanduidingId": "1234567890000001", "adresregel1": "adres1", "adresregel2": "adres2", "adresregel3": "adres3", - "land": "6030", + "land": "NL", }, "correspondentieadres": { - "nummeraanduidingId": "c06918d9-899b-4d98-a10d-08436ebc6c20", + "nummeraanduidingId": "1234567890000002", "adresregel1": "adres1", "adresregel2": "adres2", "adresregel3": "adres3", - "land": "6030", + "land": "NL", }, "contactnaam": { "voorletters": "P", diff --git a/src/openklant/components/klantinteracties/api/tests/test_partijen.py b/src/openklant/components/klantinteracties/api/tests/test_partijen.py index fa925abd..5f36e120 100644 --- a/src/openklant/components/klantinteracties/api/tests/test_partijen.py +++ b/src/openklant/components/klantinteracties/api/tests/test_partijen.py @@ -110,18 +110,18 @@ def test_create_partij(self): "voorkeurstaal": "ndl", "indicatieActief": True, "bezoekadres": { - "nummeraanduidingId": "095be615-a8ad-4c33-8e9c-c7612fbf6c9f", + "nummeraanduidingId": "1234567890000001", "adresregel1": "adres1", "adresregel2": "adres2", "adresregel3": "adres3", - "land": "6030", + "land": "NL", }, "correspondentieadres": { - "nummeraanduidingId": "095be615-a8ad-4c33-8e9c-c7612fbf6c9f", + "nummeraanduidingId": "1234567890000001", "adresregel1": "adres1", "adresregel2": "adres2", "adresregel3": "adres3", - "land": "6030", + "land": "NL", }, "partijIdentificatie": { "contactnaam": { @@ -156,21 +156,21 @@ def test_create_partij(self): self.assertEqual( data["bezoekadres"], { - "nummeraanduidingId": "095be615-a8ad-4c33-8e9c-c7612fbf6c9f", + "nummeraanduidingId": "1234567890000001", "adresregel1": "adres1", "adresregel2": "adres2", "adresregel3": "adres3", - "land": "6030", + "land": "NL", }, ) self.assertEqual( data["correspondentieadres"], { - "nummeraanduidingId": "095be615-a8ad-4c33-8e9c-c7612fbf6c9f", + "nummeraanduidingId": "1234567890000001", "adresregel1": "adres1", "adresregel2": "adres2", "adresregel3": "adres3", - "land": "6030", + "land": "NL", }, ) self.assertEqual( @@ -212,21 +212,21 @@ def test_create_partij(self): self.assertEqual( response_data["bezoekadres"], { - "nummeraanduidingId": "095be615-a8ad-4c33-8e9c-c7612fbf6c9f", + "nummeraanduidingId": "1234567890000001", "adresregel1": "adres1", "adresregel2": "adres2", "adresregel3": "adres3", - "land": "6030", + "land": "NL", }, ) self.assertEqual( response_data["correspondentieadres"], { - "nummeraanduidingId": "095be615-a8ad-4c33-8e9c-c7612fbf6c9f", + "nummeraanduidingId": "1234567890000001", "adresregel1": "adres1", "adresregel2": "adres2", "adresregel3": "adres3", - "land": "6030", + "land": "NL", }, ) self.assertEqual( @@ -391,18 +391,18 @@ def test_create_persoon(self): "voorkeurstaal": "ndl", "indicatieActief": True, "bezoekadres": { - "nummeraanduidingId": "095be615-a8ad-4c33-8e9c-c7612fbf6c9f", + "nummeraanduidingId": "1234567890000001", "adresregel1": "adres1", "adresregel2": "adres2", "adresregel3": "adres3", - "land": "6030", + "land": "NL", }, "correspondentieadres": { - "nummeraanduidingId": "095be615-a8ad-4c33-8e9c-c7612fbf6c9f", + "nummeraanduidingId": "1234567890000001", "adresregel1": "adres1", "adresregel2": "adres2", "adresregel3": "adres3", - "land": "6030", + "land": "NL", }, "soortPartij": "persoon", "partijIdentificatie": { @@ -434,21 +434,21 @@ def test_create_persoon(self): self.assertEqual( response_data["bezoekadres"], { - "nummeraanduidingId": "095be615-a8ad-4c33-8e9c-c7612fbf6c9f", + "nummeraanduidingId": "1234567890000001", "adresregel1": "adres1", "adresregel2": "adres2", "adresregel3": "adres3", - "land": "6030", + "land": "NL", }, ) self.assertEqual( response_data["correspondentieadres"], { - "nummeraanduidingId": "095be615-a8ad-4c33-8e9c-c7612fbf6c9f", + "nummeraanduidingId": "1234567890000001", "adresregel1": "adres1", "adresregel2": "adres2", "adresregel3": "adres3", - "land": "6030", + "land": "NL", }, ) self.assertEqual( @@ -483,18 +483,18 @@ def test_create_organisatie(self): "voorkeurstaal": "ndl", "indicatieActief": True, "bezoekadres": { - "nummeraanduidingId": "095be615-a8ad-4c33-8e9c-c7612fbf6c9f", + "nummeraanduidingId": "1234567890000001", "adresregel1": "adres1", "adresregel2": "adres2", "adresregel3": "adres3", - "land": "6030", + "land": "NL", }, "correspondentieadres": { - "nummeraanduidingId": "095be615-a8ad-4c33-8e9c-c7612fbf6c9f", + "nummeraanduidingId": "1234567890000001", "adresregel1": "adres1", "adresregel2": "adres2", "adresregel3": "adres3", - "land": "6030", + "land": "NL", }, "soortPartij": "organisatie", "partijIdentificatie": { @@ -522,21 +522,21 @@ def test_create_organisatie(self): self.assertEqual( response_data["bezoekadres"], { - "nummeraanduidingId": "095be615-a8ad-4c33-8e9c-c7612fbf6c9f", + "nummeraanduidingId": "1234567890000001", "adresregel1": "adres1", "adresregel2": "adres2", "adresregel3": "adres3", - "land": "6030", + "land": "NL", }, ) self.assertEqual( response_data["correspondentieadres"], { - "nummeraanduidingId": "095be615-a8ad-4c33-8e9c-c7612fbf6c9f", + "nummeraanduidingId": "1234567890000001", "adresregel1": "adres1", "adresregel2": "adres2", "adresregel3": "adres3", - "land": "6030", + "land": "NL", }, ) self.assertEqual( @@ -558,18 +558,18 @@ def test_create_contactpersoon(self): "voorkeurstaal": "ndl", "indicatieActief": True, "bezoekadres": { - "nummeraanduidingId": "095be615-a8ad-4c33-8e9c-c7612fbf6c9f", + "nummeraanduidingId": "1234567890000001", "adresregel1": "adres1", "adresregel2": "adres2", "adresregel3": "adres3", - "land": "6030", + "land": "NL", }, "correspondentieadres": { - "nummeraanduidingId": "095be615-a8ad-4c33-8e9c-c7612fbf6c9f", + "nummeraanduidingId": "1234567890000001", "adresregel1": "adres1", "adresregel2": "adres2", "adresregel3": "adres3", - "land": "6030", + "land": "NL", }, "soortPartij": "contactpersoon", "partijIdentificatie": { @@ -604,21 +604,21 @@ def test_create_contactpersoon(self): self.assertEqual( response_data["bezoekadres"], { - "nummeraanduidingId": "095be615-a8ad-4c33-8e9c-c7612fbf6c9f", + "nummeraanduidingId": "1234567890000001", "adresregel1": "adres1", "adresregel2": "adres2", "adresregel3": "adres3", - "land": "6030", + "land": "NL", }, ) self.assertEqual( response_data["correspondentieadres"], { - "nummeraanduidingId": "095be615-a8ad-4c33-8e9c-c7612fbf6c9f", + "nummeraanduidingId": "1234567890000001", "adresregel1": "adres1", "adresregel2": "adres2", "adresregel3": "adres3", - "land": "6030", + "land": "NL", }, ) self.assertEqual( @@ -648,16 +648,16 @@ def test_update_partij(self): indicatie_geheimhouding=True, voorkeurstaal="ndl", indicatie_actief=True, - bezoekadres_nummeraanduiding_id="095be615-a8ad-4c33-8e9c-c7612fbf6c9f", + bezoekadres_nummeraanduiding_id="1234567890000001", bezoekadres_adresregel1="adres1", bezoekadres_adresregel2="adres2", bezoekadres_adresregel3="adres3", - bezoekadres_land="6030", - correspondentieadres_nummeraanduiding_id="095be615-a8ad-4c33-8e9c-c7612fbf6c9f", + bezoekadres_land="NL", + correspondentieadres_nummeraanduiding_id="1234567890000001", correspondentieadres_adresregel1="adres1", correspondentieadres_adresregel2="adres2", correspondentieadres_adresregel3="adres3", - correspondentieadres_land="6030", + correspondentieadres_land="NL", ) PersoonFactory.create( partij=partij, @@ -708,21 +708,21 @@ def test_update_partij(self): self.assertEqual( data["bezoekadres"], { - "nummeraanduidingId": "095be615-a8ad-4c33-8e9c-c7612fbf6c9f", + "nummeraanduidingId": "1234567890000001", "adresregel1": "adres1", "adresregel2": "adres2", "adresregel3": "adres3", - "land": "6030", + "land": "NL", }, ) self.assertEqual( data["correspondentieadres"], { - "nummeraanduidingId": "095be615-a8ad-4c33-8e9c-c7612fbf6c9f", + "nummeraanduidingId": "1234567890000001", "adresregel1": "adres1", "adresregel2": "adres2", "adresregel3": "adres3", - "land": "6030", + "land": "NL", }, ) self.assertEqual( @@ -750,18 +750,18 @@ def test_update_partij(self): "voorkeurstaal": "ger", "indicatieActief": False, "bezoekadres": { - "nummeraanduidingId": "f78sd8f-uh45-34km-2o3n-aasdasdasc9g", + "nummeraanduidingId": "1234567890000002", "adresregel1": "changed", "adresregel2": "changed", "adresregel3": "changed", - "land": "3060", + "land": "NL", }, "correspondentieadres": { - "nummeraanduidingId": "sd76f7sd-j4nr-a9s8-83ec-sad89f79a7sd", + "nummeraanduidingId": "1234567890000003", "adresregel1": "changed", "adresregel2": "changed", "adresregel3": "changed", - "land": "3060", + "land": "NL", }, "partijIdentificatie": { "contactnaam": { @@ -810,21 +810,21 @@ def test_update_partij(self): self.assertEqual( data["bezoekadres"], { - "nummeraanduidingId": "f78sd8f-uh45-34km-2o3n-aasdasdasc9g", + "nummeraanduidingId": "1234567890000002", "adresregel1": "changed", "adresregel2": "changed", "adresregel3": "changed", - "land": "3060", + "land": "NL", }, ) self.assertEqual( data["correspondentieadres"], { - "nummeraanduidingId": "sd76f7sd-j4nr-a9s8-83ec-sad89f79a7sd", + "nummeraanduidingId": "1234567890000003", "adresregel1": "changed", "adresregel2": "changed", "adresregel3": "changed", - "land": "3060", + "land": "NL", }, ) self.assertEqual( @@ -928,18 +928,18 @@ def test_update_partij(self): "voorkeurstaal": "ger", "indicatieActief": False, "bezoekadres": { - "nummeraanduidingId": "f78sd8f-uh45-34km-2o3n-aasdasdasc9g", + "nummeraanduidingId": "1234567890000002", "adresregel1": "changed", "adresregel2": "changed", "adresregel3": "changed", - "land": "3060", + "land": "NL", }, "correspondentieadres": { - "nummeraanduidingId": "sd76f7sd-j4nr-a9s8-83ec-sad89f79a7sd", + "nummeraanduidingId": "1234567890000003", "adresregel1": "changed", "adresregel2": "changed", "adresregel3": "changed", - "land": "3060", + "land": "NL", }, } @@ -960,21 +960,21 @@ def test_update_partij(self): self.assertEqual( data["bezoekadres"], { - "nummeraanduidingId": "f78sd8f-uh45-34km-2o3n-aasdasdasc9g", + "nummeraanduidingId": "1234567890000002", "adresregel1": "changed", "adresregel2": "changed", "adresregel3": "changed", - "land": "3060", + "land": "NL", }, ) self.assertEqual( data["correspondentieadres"], { - "nummeraanduidingId": "sd76f7sd-j4nr-a9s8-83ec-sad89f79a7sd", + "nummeraanduidingId": "1234567890000003", "adresregel1": "changed", "adresregel2": "changed", "adresregel3": "changed", - "land": "3060", + "land": "NL", }, ) @@ -988,16 +988,16 @@ def test_update_partij_persoon(self): indicatie_geheimhouding=True, voorkeurstaal="ndl", indicatie_actief=True, - bezoekadres_nummeraanduiding_id="095be615-a8ad-4c33-8e9c-c7612fbf6c9f", + bezoekadres_nummeraanduiding_id="1234567890000001", bezoekadres_adresregel1="adres1", bezoekadres_adresregel2="adres2", bezoekadres_adresregel3="adres3", - bezoekadres_land="6030", - correspondentieadres_nummeraanduiding_id="095be615-a8ad-4c33-8e9c-c7612fbf6c9f", + bezoekadres_land="NL", + correspondentieadres_nummeraanduiding_id="1234567890000001", correspondentieadres_adresregel1="adres1", correspondentieadres_adresregel2="adres2", correspondentieadres_adresregel3="adres3", - correspondentieadres_land="6030", + correspondentieadres_land="NL", ) PersoonFactory.create( partij=partij, @@ -1025,21 +1025,21 @@ def test_update_partij_persoon(self): self.assertEqual( data["bezoekadres"], { - "nummeraanduidingId": "095be615-a8ad-4c33-8e9c-c7612fbf6c9f", + "nummeraanduidingId": "1234567890000001", "adresregel1": "adres1", "adresregel2": "adres2", "adresregel3": "adres3", - "land": "6030", + "land": "NL", }, ) self.assertEqual( data["correspondentieadres"], { - "nummeraanduidingId": "095be615-a8ad-4c33-8e9c-c7612fbf6c9f", + "nummeraanduidingId": "1234567890000001", "adresregel1": "adres1", "adresregel2": "adres2", "adresregel3": "adres3", - "land": "6030", + "land": "NL", }, ) self.assertEqual( @@ -1067,18 +1067,18 @@ def test_update_partij_persoon(self): "voorkeurstaal": "ger", "indicatieActief": False, "bezoekadres": { - "nummeraanduidingId": "f78sd8f-uh45-34km-2o3n-aasdasdasc9g", + "nummeraanduidingId": "1234567890000002", "adresregel1": "changed", "adresregel2": "changed", "adresregel3": "changed", - "land": "3060", + "land": "NL", }, "correspondentieadres": { - "nummeraanduidingId": "sd76f7sd-j4nr-a9s8-83ec-sad89f79a7sd", + "nummeraanduidingId": "1234567890000003", "adresregel1": "changed", "adresregel2": "changed", "adresregel3": "changed", - "land": "3060", + "land": "NL", }, "partijIdentificatie": { "contactnaam": { @@ -1107,21 +1107,21 @@ def test_update_partij_persoon(self): self.assertEqual( data["bezoekadres"], { - "nummeraanduidingId": "f78sd8f-uh45-34km-2o3n-aasdasdasc9g", + "nummeraanduidingId": "1234567890000002", "adresregel1": "changed", "adresregel2": "changed", "adresregel3": "changed", - "land": "3060", + "land": "NL", }, ) self.assertEqual( data["correspondentieadres"], { - "nummeraanduidingId": "sd76f7sd-j4nr-a9s8-83ec-sad89f79a7sd", + "nummeraanduidingId": "1234567890000003", "adresregel1": "changed", "adresregel2": "changed", "adresregel3": "changed", - "land": "3060", + "land": "NL", }, ) self.assertEqual( @@ -1147,16 +1147,16 @@ def test_update_partij_organisatie(self): indicatie_geheimhouding=True, voorkeurstaal="ndl", indicatie_actief=True, - bezoekadres_nummeraanduiding_id="095be615-a8ad-4c33-8e9c-c7612fbf6c9f", + bezoekadres_nummeraanduiding_id="1234567890000001", bezoekadres_adresregel1="adres1", bezoekadres_adresregel2="adres2", bezoekadres_adresregel3="adres3", - bezoekadres_land="6030", - correspondentieadres_nummeraanduiding_id="095be615-a8ad-4c33-8e9c-c7612fbf6c9f", + bezoekadres_land="NL", + correspondentieadres_nummeraanduiding_id="1234567890000001", correspondentieadres_adresregel1="adres1", correspondentieadres_adresregel2="adres2", correspondentieadres_adresregel3="adres3", - correspondentieadres_land="6030", + correspondentieadres_land="NL", ) OrganisatieFactory(partij=partij, naam="Whitechapel") detail_url = reverse( @@ -1178,21 +1178,21 @@ def test_update_partij_organisatie(self): self.assertEqual( data["bezoekadres"], { - "nummeraanduidingId": "095be615-a8ad-4c33-8e9c-c7612fbf6c9f", + "nummeraanduidingId": "1234567890000001", "adresregel1": "adres1", "adresregel2": "adres2", "adresregel3": "adres3", - "land": "6030", + "land": "NL", }, ) self.assertEqual( data["correspondentieadres"], { - "nummeraanduidingId": "095be615-a8ad-4c33-8e9c-c7612fbf6c9f", + "nummeraanduidingId": "1234567890000001", "adresregel1": "adres1", "adresregel2": "adres2", "adresregel3": "adres3", - "land": "6030", + "land": "NL", }, ) self.assertEqual(data["partijIdentificatie"], {"naam": "Whitechapel"}) @@ -1209,18 +1209,18 @@ def test_update_partij_organisatie(self): "voorkeurstaal": "ger", "indicatieActief": False, "bezoekadres": { - "nummeraanduidingId": "f78sd8f-uh45-34km-2o3n-aasdasdasc9g", + "nummeraanduidingId": "1234567890000002", "adresregel1": "changed", "adresregel2": "changed", "adresregel3": "changed", - "land": "3060", + "land": "NL", }, "correspondentieadres": { - "nummeraanduidingId": "sd76f7sd-j4nr-a9s8-83ec-sad89f79a7sd", + "nummeraanduidingId": "1234567890000003", "adresregel1": "changed", "adresregel2": "changed", "adresregel3": "changed", - "land": "3060", + "land": "NL", }, "partijIdentificatie": { "naam": "The Acacia Strain", @@ -1244,21 +1244,21 @@ def test_update_partij_organisatie(self): self.assertEqual( data["bezoekadres"], { - "nummeraanduidingId": "f78sd8f-uh45-34km-2o3n-aasdasdasc9g", + "nummeraanduidingId": "1234567890000002", "adresregel1": "changed", "adresregel2": "changed", "adresregel3": "changed", - "land": "3060", + "land": "NL", }, ) self.assertEqual( data["correspondentieadres"], { - "nummeraanduidingId": "sd76f7sd-j4nr-a9s8-83ec-sad89f79a7sd", + "nummeraanduidingId": "1234567890000003", "adresregel1": "changed", "adresregel2": "changed", "adresregel3": "changed", - "land": "3060", + "land": "NL", }, ) self.assertEqual( @@ -1276,16 +1276,16 @@ def test_update_partij_contactpersoon(self): indicatie_geheimhouding=True, voorkeurstaal="ndl", indicatie_actief=True, - bezoekadres_nummeraanduiding_id="095be615-a8ad-4c33-8e9c-c7612fbf6c9f", + bezoekadres_nummeraanduiding_id="1234567890000001", bezoekadres_adresregel1="adres1", bezoekadres_adresregel2="adres2", bezoekadres_adresregel3="adres3", - bezoekadres_land="6030", - correspondentieadres_nummeraanduiding_id="095be615-a8ad-4c33-8e9c-c7612fbf6c9f", + bezoekadres_land="NL", + correspondentieadres_nummeraanduiding_id="1234567890000001", correspondentieadres_adresregel1="adres1", correspondentieadres_adresregel2="adres2", correspondentieadres_adresregel3="adres3", - correspondentieadres_land="6030", + correspondentieadres_land="NL", ) organisatie = PartijFactory.create(soort_partij="organisatie") ContactpersoonFactory.create( @@ -1323,21 +1323,21 @@ def test_update_partij_contactpersoon(self): self.assertEqual( data["bezoekadres"], { - "nummeraanduidingId": "095be615-a8ad-4c33-8e9c-c7612fbf6c9f", + "nummeraanduidingId": "1234567890000001", "adresregel1": "adres1", "adresregel2": "adres2", "adresregel3": "adres3", - "land": "6030", + "land": "NL", }, ) self.assertEqual( data["correspondentieadres"], { - "nummeraanduidingId": "095be615-a8ad-4c33-8e9c-c7612fbf6c9f", + "nummeraanduidingId": "1234567890000001", "adresregel1": "adres1", "adresregel2": "adres2", "adresregel3": "adres3", - "land": "6030", + "land": "NL", }, ) self.assertEqual( @@ -1369,18 +1369,18 @@ def test_update_partij_contactpersoon(self): "voorkeurstaal": "ger", "indicatieActief": False, "bezoekadres": { - "nummeraanduidingId": "f78sd8f-uh45-34km-2o3n-aasdasdasc9g", + "nummeraanduidingId": "1234567890000002", "adresregel1": "changed", "adresregel2": "changed", "adresregel3": "changed", - "land": "3060", + "land": "NL", }, "correspondentieadres": { - "nummeraanduidingId": "sd76f7sd-j4nr-a9s8-83ec-sad89f79a7sd", + "nummeraanduidingId": "1234567890000003", "adresregel1": "changed", "adresregel2": "changed", "adresregel3": "changed", - "land": "3060", + "land": "NL", }, "partijIdentificatie": { "werkteVoorPartij": {"uuid": str(organisatie2.uuid)}, @@ -1410,21 +1410,21 @@ def test_update_partij_contactpersoon(self): self.assertEqual( data["bezoekadres"], { - "nummeraanduidingId": "f78sd8f-uh45-34km-2o3n-aasdasdasc9g", + "nummeraanduidingId": "1234567890000002", "adresregel1": "changed", "adresregel2": "changed", "adresregel3": "changed", - "land": "3060", + "land": "NL", }, ) self.assertEqual( data["correspondentieadres"], { - "nummeraanduidingId": "sd76f7sd-j4nr-a9s8-83ec-sad89f79a7sd", + "nummeraanduidingId": "1234567890000003", "adresregel1": "changed", "adresregel2": "changed", "adresregel3": "changed", - "land": "3060", + "land": "NL", }, ) self.assertEqual( @@ -1454,16 +1454,16 @@ def test_update_partij_contactpersoon_to_persoon(self): indicatie_geheimhouding=True, voorkeurstaal="ndl", indicatie_actief=True, - bezoekadres_nummeraanduiding_id="095be615-a8ad-4c33-8e9c-c7612fbf6c9f", + bezoekadres_nummeraanduiding_id="1234567890000001", bezoekadres_adresregel1="adres1", bezoekadres_adresregel2="adres2", bezoekadres_adresregel3="adres3", - bezoekadres_land="6030", - correspondentieadres_nummeraanduiding_id="095be615-a8ad-4c33-8e9c-c7612fbf6c9f", + bezoekadres_land="NL", + correspondentieadres_nummeraanduiding_id="1234567890000001", correspondentieadres_adresregel1="adres1", correspondentieadres_adresregel2="adres2", correspondentieadres_adresregel3="adres3", - correspondentieadres_land="6030", + correspondentieadres_land="NL", ) organisatie = PartijFactory.create(soort_partij="organisatie") ContactpersoonFactory.create( @@ -1493,21 +1493,21 @@ def test_update_partij_contactpersoon_to_persoon(self): self.assertEqual( data["bezoekadres"], { - "nummeraanduidingId": "095be615-a8ad-4c33-8e9c-c7612fbf6c9f", + "nummeraanduidingId": "1234567890000001", "adresregel1": "adres1", "adresregel2": "adres2", "adresregel3": "adres3", - "land": "6030", + "land": "NL", }, ) self.assertEqual( data["correspondentieadres"], { - "nummeraanduidingId": "095be615-a8ad-4c33-8e9c-c7612fbf6c9f", + "nummeraanduidingId": "1234567890000001", "adresregel1": "adres1", "adresregel2": "adres2", "adresregel3": "adres3", - "land": "6030", + "land": "NL", }, ) self.assertEqual( @@ -1539,18 +1539,18 @@ def test_update_partij_contactpersoon_to_persoon(self): "voorkeurstaal": "ger", "indicatieActief": False, "bezoekadres": { - "nummeraanduidingId": "f78sd8f-uh45-34km-2o3n-aasdasdasc9g", + "nummeraanduidingId": "1234567890000002", "adresregel1": "changed", "adresregel2": "changed", "adresregel3": "changed", - "land": "3060", + "land": "NL", }, "correspondentieadres": { - "nummeraanduidingId": "sd76f7sd-j4nr-a9s8-83ec-sad89f79a7sd", + "nummeraanduidingId": "1234567890000003", "adresregel1": "changed", "adresregel2": "changed", "adresregel3": "changed", - "land": "3060", + "land": "NL", }, "partijIdentificatie": { "contactnaam": { @@ -1579,21 +1579,21 @@ def test_update_partij_contactpersoon_to_persoon(self): self.assertEqual( data["bezoekadres"], { - "nummeraanduidingId": "f78sd8f-uh45-34km-2o3n-aasdasdasc9g", + "nummeraanduidingId": "1234567890000002", "adresregel1": "changed", "adresregel2": "changed", "adresregel3": "changed", - "land": "3060", + "land": "NL", }, ) self.assertEqual( data["correspondentieadres"], { - "nummeraanduidingId": "sd76f7sd-j4nr-a9s8-83ec-sad89f79a7sd", + "nummeraanduidingId": "1234567890000003", "adresregel1": "changed", "adresregel2": "changed", "adresregel3": "changed", - "land": "3060", + "land": "NL", }, ) self.assertEqual( @@ -1619,16 +1619,16 @@ def test_partial_update_parij(self): indicatie_geheimhouding=True, voorkeurstaal="ndl", indicatie_actief=True, - bezoekadres_nummeraanduiding_id="095be615-a8ad-4c33-8e9c-c7612fbf6c9f", + bezoekadres_nummeraanduiding_id="1234567890000001", bezoekadres_adresregel1="adres1", bezoekadres_adresregel2="adres2", bezoekadres_adresregel3="adres3", - bezoekadres_land="6030", - correspondentieadres_nummeraanduiding_id="095be615-a8ad-4c33-8e9c-c7612fbf6c9f", + bezoekadres_land="NL", + correspondentieadres_nummeraanduiding_id="1234567890000001", correspondentieadres_adresregel1="adres1", correspondentieadres_adresregel2="adres2", correspondentieadres_adresregel3="adres3", - correspondentieadres_land="6030", + correspondentieadres_land="NL", ) digitaal_adres = DigitaalAdresFactory.create(partij=partij) digitaal_adres2 = DigitaalAdresFactory.create(partij=None) @@ -1662,21 +1662,21 @@ def test_partial_update_parij(self): self.assertEqual( data["bezoekadres"], { - "nummeraanduidingId": "095be615-a8ad-4c33-8e9c-c7612fbf6c9f", + "nummeraanduidingId": "1234567890000001", "adresregel1": "adres1", "adresregel2": "adres2", "adresregel3": "adres3", - "land": "6030", + "land": "NL", }, ) self.assertEqual( data["correspondentieadres"], { - "nummeraanduidingId": "095be615-a8ad-4c33-8e9c-c7612fbf6c9f", + "nummeraanduidingId": "1234567890000001", "adresregel1": "adres1", "adresregel2": "adres2", "adresregel3": "adres3", - "land": "6030", + "land": "NL", }, ) self.assertEqual( @@ -1719,21 +1719,21 @@ def test_partial_update_parij(self): self.assertEqual( data["bezoekadres"], { - "nummeraanduidingId": "095be615-a8ad-4c33-8e9c-c7612fbf6c9f", + "nummeraanduidingId": "1234567890000001", "adresregel1": "adres1", "adresregel2": "adres2", "adresregel3": "adres3", - "land": "6030", + "land": "NL", }, ) self.assertEqual( data["correspondentieadres"], { - "nummeraanduidingId": "095be615-a8ad-4c33-8e9c-c7612fbf6c9f", + "nummeraanduidingId": "1234567890000001", "adresregel1": "adres1", "adresregel2": "adres2", "adresregel3": "adres3", - "land": "6030", + "land": "NL", }, ) self.assertEqual( diff --git a/src/openklant/components/klantinteracties/migrations/0026_alter_betrokkene_bezoekadres_land_and_more.py b/src/openklant/components/klantinteracties/migrations/0026_alter_betrokkene_bezoekadres_land_and_more.py new file mode 100644 index 00000000..5254b4ea --- /dev/null +++ b/src/openklant/components/klantinteracties/migrations/0026_alter_betrokkene_bezoekadres_land_and_more.py @@ -0,0 +1,138 @@ +# Generated by Django 4.2.17 on 2025-01-27 14:35 + +import logging +import django.core.validators +import openklant.utils.validators +from django.db import migrations, models +import openklant.utils.validators +from openklant.utils.constants import COUNTRIES_DICT +from django.db import IntegrityError + +logger = logging.getLogger(__name__) + +FIELDS = ["bezoekadres_land", "correspondentieadres_land"] + + +def _check_records(records): + total_failed_records = 0 + for record in records: + record_failed = False + for field_name in FIELDS: + field_value = getattr(record, field_name, None) + iso_code = COUNTRIES_DICT.get(field_value, "") + if field_value and not iso_code: + record_failed = True + logger.warning( + "%s(pk=%s, uuid=%s) Field: '%s'. No match found for nl_code (not found in Tabel 34 Landentabel): '%s'", + type(record).__qualname__, + record.pk, + record.uuid, + field_name, + field_value, + ) + + if record_failed: + total_failed_records += 1 + return total_failed_records + + +def _update_records(records): + for record in records: + updated = False + for field_name in FIELDS: + if field_value := getattr(record, field_name, None): + iso_code = COUNTRIES_DICT.get(field_value, "") + setattr(record, field_name, iso_code) + updated = True + + if updated: + record.save() + + +def _check_and_update_records(apps, schema_editor): + Betrokkene = apps.get_model("klantinteracties", "Betrokkene") + Partij = apps.get_model("klantinteracties", "Partij") + + for model in [Betrokkene, Partij]: + records = model.objects.all() + if total_failed_records := _check_records(records): + raise IntegrityError( + "The migration cannot proceed due to %s records that don't comply with the %s model's requirements. " + "Possible data inconsistency or mapping error." + % (total_failed_records, model.__qualname__) + ) + else: + _update_records(records) + + +class Migration(migrations.Migration): + + dependencies = [ + ( + "klantinteracties", + "0025_alter_partijidentificator_partij_identificator_code_objecttype_and_more", + ), + ] + + operations = [ + migrations.RunPython( + code=_check_and_update_records, + reverse_code=migrations.RunPython.noop, + ), + migrations.AlterField( + model_name="betrokkene", + name="bezoekadres_land", + field=models.CharField( + blank=True, + help_text="ISO 3166-code die het land (buiten Nederland) aangeeft alwaar de ingeschrevene verblijft.", + max_length=2, + validators=[ + django.core.validators.MinLengthValidator(limit_value=2), + openklant.utils.validators.validate_country, + ], + verbose_name="land", + ), + ), + migrations.AlterField( + model_name="betrokkene", + name="correspondentieadres_land", + field=models.CharField( + blank=True, + help_text="ISO 3166-code die het land (buiten Nederland) aangeeft alwaar de ingeschrevene verblijft.", + max_length=2, + validators=[ + django.core.validators.MinLengthValidator(limit_value=2), + openklant.utils.validators.validate_country, + ], + verbose_name="land", + ), + ), + migrations.AlterField( + model_name="partij", + name="bezoekadres_land", + field=models.CharField( + blank=True, + help_text="ISO 3166-code die het land (buiten Nederland) aangeeft alwaar de ingeschrevene verblijft.", + max_length=2, + validators=[ + django.core.validators.MinLengthValidator(limit_value=2), + openklant.utils.validators.validate_country, + ], + verbose_name="land", + ), + ), + migrations.AlterField( + model_name="partij", + name="correspondentieadres_land", + field=models.CharField( + blank=True, + help_text="ISO 3166-code die het land (buiten Nederland) aangeeft alwaar de ingeschrevene verblijft.", + max_length=2, + validators=[ + django.core.validators.MinLengthValidator(limit_value=2), + openklant.utils.validators.validate_country, + ], + verbose_name="land", + ), + ), + ] diff --git a/src/openklant/components/klantinteracties/migrations/0027_alter_betrokkene_bezoekadres_nummeraanduiding_id_and_more.py b/src/openklant/components/klantinteracties/migrations/0027_alter_betrokkene_bezoekadres_nummeraanduiding_id_and_more.py new file mode 100644 index 00000000..1f8c0cdf --- /dev/null +++ b/src/openklant/components/klantinteracties/migrations/0027_alter_betrokkene_bezoekadres_nummeraanduiding_id_and_more.py @@ -0,0 +1,126 @@ +# Generated by Django 4.2.17 on 2025-01-28 09:10 + +import logging +import openklant.utils.validators +from django.core.exceptions import ValidationError +from django.db import IntegrityError +from django.db import migrations, models +from openklant.utils.validators import validate_bag_id + +logger = logging.getLogger(__name__) + + +def _check_records(records): + total_failed_records = 0 + for record in records: + record_failed = False + for field_name in ( + "bezoekadres_nummeraanduiding_id", + "correspondentieadres_nummeraanduiding_id", + ): + field_value = getattr(record, field_name, None) + if field_value: + try: + validate_bag_id(field_value) + except ValidationError: + logger.warning( + "%s(pk=%s, uuid=%s) Field: '%s'. Invalid BAG ID: %s", + type(record).__qualname__, + record.pk, + record.uuid, + field_name, + field_value, + ) + record_failed = True + + if record_failed: + total_failed_records += 1 + return total_failed_records + + +def _check_records_field_length(apps, schema_editor): + Betrokkene = apps.get_model("klantinteracties", "Betrokkene") + Partij = apps.get_model("klantinteracties", "Partij") + + for model in [Betrokkene, Partij]: + records = model.objects.all() + if total_failed_records := _check_records(records): + raise IntegrityError( + "The migration cannot proceed due to %s records that don't comply with the %s model's requirements. " + "Possible data inconsistency or mapping error." + % (total_failed_records, model.__qualname__) + ) + + +class Migration(migrations.Migration): + + dependencies = [ + ("klantinteracties", "0026_alter_betrokkene_bezoekadres_land_and_more"), + ] + + operations = [ + migrations.RunPython( + code=_check_records_field_length, + reverse_code=migrations.RunPython.noop, + ), + migrations.AlterField( + model_name="betrokkene", + name="bezoekadres_nummeraanduiding_id", + field=models.CharField( + blank=True, + help_text="Identificatie van het adres bij de Basisregistratie Adressen en Gebouwen.", + max_length=16, + validators=[ + openklant.utils.validators.CustomRegexValidator( + message="Ongeldige nummeraanduiding BAG-ID", regex="^[0-9]{16}$" + ) + ], + verbose_name="nummeraanduiding ID", + ), + ), + migrations.AlterField( + model_name="betrokkene", + name="correspondentieadres_nummeraanduiding_id", + field=models.CharField( + blank=True, + help_text="Identificatie van het adres bij de Basisregistratie Adressen en Gebouwen.", + max_length=16, + validators=[ + openklant.utils.validators.CustomRegexValidator( + message="Ongeldige nummeraanduiding BAG-ID", regex="^[0-9]{16}$" + ) + ], + verbose_name="nummeraanduiding ID", + ), + ), + migrations.AlterField( + model_name="partij", + name="bezoekadres_nummeraanduiding_id", + field=models.CharField( + blank=True, + help_text="Identificatie van het adres bij de Basisregistratie Adressen en Gebouwen.", + max_length=16, + validators=[ + openklant.utils.validators.CustomRegexValidator( + message="Ongeldige nummeraanduiding BAG-ID", regex="^[0-9]{16}$" + ) + ], + verbose_name="nummeraanduiding ID", + ), + ), + migrations.AlterField( + model_name="partij", + name="correspondentieadres_nummeraanduiding_id", + field=models.CharField( + blank=True, + help_text="Identificatie van het adres bij de Basisregistratie Adressen en Gebouwen.", + max_length=16, + validators=[ + openklant.utils.validators.CustomRegexValidator( + message="Ongeldige nummeraanduiding BAG-ID", regex="^[0-9]{16}$" + ) + ], + verbose_name="nummeraanduiding ID", + ), + ), + ] diff --git a/src/openklant/components/klantinteracties/models/mixins.py b/src/openklant/components/klantinteracties/models/mixins.py index 3ee69758..a2e13582 100644 --- a/src/openklant/components/klantinteracties/models/mixins.py +++ b/src/openklant/components/klantinteracties/models/mixins.py @@ -1,18 +1,20 @@ -from django.core.validators import MinLengthValidator, validate_integer +from django.core.validators import MinLengthValidator from django.db import models from django.utils.translation import gettext_lazy as _ from vng_api_common.descriptors import GegevensGroepType +from openklant.utils.validators import validate_bag_id, validate_country + class BezoekadresMixin(models.Model): - # TODO: Check if this is correct. bezoekadres_nummeraanduiding_id = models.CharField( _("nummeraanduiding ID"), help_text=_( "Identificatie van het adres bij de Basisregistratie Adressen en Gebouwen." ), - max_length=255, + max_length=16, + validators=[validate_bag_id], blank=True, ) bezoekadres_adresregel1 = models.CharField( @@ -42,14 +44,13 @@ class BezoekadresMixin(models.Model): bezoekadres_land = models.CharField( _("land"), help_text=_( - "Een code, opgenomen in Tabel 34, Landentabel, die het land (buiten Nederland) " - "aangeeft alwaar de ingeschrevene verblijft." + "ISO 3166-code die het land (buiten Nederland) aangeeft alwaar de ingeschrevene verblijft." ), validators=[ - MinLengthValidator(limit_value=4), - validate_integer, + MinLengthValidator(limit_value=2), + validate_country, ], - max_length=4, + max_length=2, blank=True, ) @@ -75,13 +76,13 @@ class Meta: class CorrespondentieadresMixin(models.Model): - # TODO: Check if this is correct. correspondentieadres_nummeraanduiding_id = models.CharField( _("nummeraanduiding ID"), help_text=_( "Identificatie van het adres bij de Basisregistratie Adressen en Gebouwen." ), - max_length=255, + max_length=16, + validators=[validate_bag_id], blank=True, ) correspondentieadres_adresregel1 = models.CharField( @@ -111,14 +112,13 @@ class CorrespondentieadresMixin(models.Model): correspondentieadres_land = models.CharField( _("land"), help_text=_( - "Een code, opgenomen in Tabel 34, Landentabel, die het land (buiten Nederland) " - "aangeeft alwaar de ingeschrevene verblijft." + "ISO 3166-code die het land (buiten Nederland) aangeeft alwaar de ingeschrevene verblijft." ), validators=[ - MinLengthValidator(limit_value=4), - validate_integer, + MinLengthValidator(limit_value=2), + validate_country, ], - max_length=4, + max_length=2, blank=True, ) diff --git a/src/openklant/components/klantinteracties/openapi.yaml b/src/openklant/components/klantinteracties/openapi.yaml index 415c1319..fb0120e5 100644 --- a/src/openklant/components/klantinteracties/openapi.yaml +++ b/src/openklant/components/klantinteracties/openapi.yaml @@ -1,7 +1,7 @@ openapi: 3.0.3 info: title: klantinteracties - version: 0.0.3 + version: 0.0.4 description: |2 Description WIP. @@ -3204,7 +3204,8 @@ components: type: string description: Identificatie van het adres bij de Basisregistratie Adressen en Gebouwen. - maxLength: 255 + pattern: ^[0-9]{16}$ + maxLength: 16 adresregel1: type: string description: Eerste deel van het adres dat niet voorkomt in de Basisregistratie @@ -3222,10 +3223,10 @@ components: maxLength: 80 land: type: string - description: Een code, opgenomen in Tabel 34, Landentabel, die het land - (buiten Nederland) aangeeft alwaar de ingeschrevene verblijft. - maxLength: 4 - minLength: 4 + description: ISO 3166-code die het land (buiten Nederland) aangeeft alwaar + de ingeschrevene verblijft. + maxLength: 2 + minLength: 2 BetrokkeneForeignKey: type: object properties: @@ -3334,7 +3335,8 @@ components: type: string description: Identificatie van het adres bij de Basisregistratie Adressen en Gebouwen. - maxLength: 255 + pattern: ^[0-9]{16}$ + maxLength: 16 adresregel1: type: string description: Eerste deel van het adres dat niet voorkomt in de Basisregistratie @@ -3352,10 +3354,10 @@ components: maxLength: 80 land: type: string - description: Een code, opgenomen in Tabel 34, Landentabel, die het land - (buiten Nederland) aangeeft alwaar de ingeschrevene verblijft. - maxLength: 4 - minLength: 4 + description: ISO 3166-code die het land (buiten Nederland) aangeeft alwaar + de ingeschrevene verblijft. + maxLength: 2 + minLength: 2 Bijlage: type: object description: |- @@ -4629,7 +4631,8 @@ components: type: string description: Identificatie van het adres bij de Basisregistratie Adressen en Gebouwen. - maxLength: 255 + pattern: ^[0-9]{16}$ + maxLength: 16 adresregel1: type: string description: Eerste deel van het adres dat niet voorkomt in de Basisregistratie @@ -4647,10 +4650,10 @@ components: maxLength: 80 land: type: string - description: Een code, opgenomen in Tabel 34, Landentabel, die het land - (buiten Nederland) aangeeft alwaar de ingeschrevene verblijft. - maxLength: 4 - minLength: 4 + description: ISO 3166-code die het land (buiten Nederland) aangeeft alwaar + de ingeschrevene verblijft. + maxLength: 2 + minLength: 2 PartijCorrespondentieadres: type: object properties: @@ -4658,7 +4661,8 @@ components: type: string description: Identificatie van het adres bij de Basisregistratie Adressen en Gebouwen. - maxLength: 255 + pattern: ^[0-9]{16}$ + maxLength: 16 adresregel1: type: string description: Eerste deel van het adres dat niet voorkomt in de Basisregistratie @@ -4676,10 +4680,10 @@ components: maxLength: 80 land: type: string - description: Een code, opgenomen in Tabel 34, Landentabel, die het land - (buiten Nederland) aangeeft alwaar de ingeschrevene verblijft. - maxLength: 4 - minLength: 4 + description: ISO 3166-code die het land (buiten Nederland) aangeeft alwaar + de ingeschrevene verblijft. + maxLength: 2 + minLength: 2 PartijForeignKey: type: object properties: diff --git a/src/openklant/components/klantinteracties/tests/test_migrations.py b/src/openklant/components/klantinteracties/tests/test_migrations.py new file mode 100644 index 00000000..0db64006 --- /dev/null +++ b/src/openklant/components/klantinteracties/tests/test_migrations.py @@ -0,0 +1,439 @@ +from django.db import IntegrityError + +from openklant.tests.test_migrate import BaseMigrationTest + + +class TestCountryConverter(BaseMigrationTest): + app = "klantinteracties" + migrate_from = ( + "0025_alter_partijidentificator_partij_identificator_code_objecttype_and_more" + ) + migrate_to = "0026_alter_betrokkene_bezoekadres_land_and_more" + + def test_ok_migration_betrokkene_model(self): + + Betrokkene = self.old_app_state.get_model("klantinteracties", "Betrokkene") + Klantcontact = self.old_app_state.get_model("klantinteracties", "Klantcontact") + + Betrokkene.objects.create( + partij=None, + klantcontact=Klantcontact.objects.create(vertrouwelijk=False), + initiator=False, + bezoekadres_land="6030", + correspondentieadres_land="6030", + ) + + self._perform_migration() + + Betrokkene = self.apps.get_model("klantinteracties", "Betrokkene") + + records = Betrokkene.objects.all() + self.assertEqual(records.count(), 1) + self.assertEqual(records[0].bezoekadres_land, "NL") + self.assertEqual(records[0].correspondentieadres_land, "NL") + self.assertNotEqual(records[0].bezoekadres_land, "6030") + self.assertNotEqual(records[0].correspondentieadres_land, "6030") + + def test_ok_migration_betrokkene_model_empty_code(self): + + Betrokkene = self.old_app_state.get_model("klantinteracties", "Betrokkene") + Klantcontact = self.old_app_state.get_model("klantinteracties", "Klantcontact") + + betrokken1 = Betrokkene.objects.create( + partij=None, + klantcontact=Klantcontact.objects.create(vertrouwelijk=False, nummer=123), + initiator=False, + bezoekadres_land="", + correspondentieadres_land="", + ) + + betrokken2 = Betrokkene.objects.create( + partij=None, + klantcontact=Klantcontact.objects.create(vertrouwelijk=False, nummer=456), + initiator=False, + bezoekadres_land="6030", + correspondentieadres_land="6030", + ) + + self._perform_migration() + + Betrokkene = self.apps.get_model("klantinteracties", "Betrokkene") + + records = Betrokkene.objects.all() + betrokken1 = records.get(pk=betrokken1.pk) + betrokken2 = records.get(pk=betrokken2.pk) + + self.assertEqual(records.count(), 2) + self.assertEqual(betrokken1.bezoekadres_land, "") + self.assertEqual(betrokken1.correspondentieadres_land, "") + self.assertEqual(betrokken2.bezoekadres_land, "NL") + self.assertEqual(betrokken2.correspondentieadres_land, "NL") + self.assertNotEqual(betrokken2.bezoekadres_land, "6030") + self.assertNotEqual(betrokken2.correspondentieadres_land, "6030") + + def test_ko_migration_betrokkene_model_wrong_code(self): + + Betrokkene = self.old_app_state.get_model("klantinteracties", "Betrokkene") + Klantcontact = self.old_app_state.get_model("klantinteracties", "Klantcontact") + + betrokken1 = Betrokkene.objects.create( + partij=None, + klantcontact=Klantcontact.objects.create(vertrouwelijk=False, nummer=123), + initiator=False, + bezoekadres_land="9999", + correspondentieadres_land="9999", + ) + + betrokken2 = Betrokkene.objects.create( + partij=None, + klantcontact=Klantcontact.objects.create(vertrouwelijk=False, nummer=456), + initiator=False, + bezoekadres_land="5001", + correspondentieadres_land="5001", + ) + + with self.assertRaises(IntegrityError) as error: + self._perform_migration() + + self.assertEqual( + ( + "The migration cannot proceed due to 1 records that don't comply with the " + "Betrokkene model's requirements. Possible data inconsistency or mapping error." + ), + str(error.exception), + ) + + records = Betrokkene.objects.all() + betrokken1 = records.get(pk=betrokken1.pk) + betrokken2 = records.get(pk=betrokken2.pk) + + self.assertEqual(records.count(), 2) + self.assertEqual(betrokken1.bezoekadres_land, "9999") + self.assertEqual(betrokken1.correspondentieadres_land, "9999") + self.assertEqual(betrokken2.bezoekadres_land, "5001") + self.assertEqual(betrokken2.correspondentieadres_land, "5001") + + # Update manually + betrokken1 = records.get(pk=betrokken1.pk) + betrokken1.bezoekadres_land = "6030" + betrokken1.correspondentieadres_land = "6030" + betrokken1.save() + + # Re-Run the migration + self._perform_migration() + + Betrokkene = self.apps.get_model("klantinteracties", "Betrokkene") + + records = Betrokkene.objects.all() + betrokken1 = records.get(pk=betrokken1.pk) + betrokken2 = records.get(pk=betrokken2.pk) + + self.assertEqual(records.count(), 2) + self.assertEqual(betrokken1.bezoekadres_land, "NL") + self.assertEqual(betrokken1.correspondentieadres_land, "NL") + self.assertEqual(betrokken2.bezoekadres_land, "CA") + self.assertEqual(betrokken2.correspondentieadres_land, "CA") + + def test_ok_migration_partij_model(self): + + Partij = self.old_app_state.get_model("klantinteracties", "Partij") + + Partij.objects.create( + indicatie_actief=True, + bezoekadres_land="6030", + correspondentieadres_land="6030", + ) + + self._perform_migration() + + Partij = self.apps.get_model("klantinteracties", "Partij") + + records = Partij.objects.all() + self.assertEqual(records.count(), 1) + self.assertEqual(records[0].bezoekadres_land, "NL") + self.assertEqual(records[0].correspondentieadres_land, "NL") + self.assertNotEqual(records[0].bezoekadres_land, "6030") + self.assertNotEqual(records[0].correspondentieadres_land, "6030") + + def test_ok_migration_partij_model_empty_code(self): + + Partij = self.old_app_state.get_model("klantinteracties", "Partij") + + partij1 = Partij.objects.create( + indicatie_actief=True, + bezoekadres_land="", + correspondentieadres_land="", + nummer=123, + ) + partij2 = Partij.objects.create( + indicatie_actief=True, + bezoekadres_land="6030", + correspondentieadres_land="6030", + nummer=456, + ) + + self._perform_migration() + + Partij = self.apps.get_model("klantinteracties", "Partij") + + records = Partij.objects.all() + partij1 = records.get(pk=partij1.pk) + partij2 = records.get(pk=partij2.pk) + + self.assertEqual(records.count(), 2) + self.assertEqual(partij1.bezoekadres_land, "") + self.assertEqual(partij1.correspondentieadres_land, "") + self.assertEqual(partij2.bezoekadres_land, "NL") + self.assertEqual(partij2.correspondentieadres_land, "NL") + self.assertNotEqual(partij2.bezoekadres_land, "6030") + self.assertNotEqual(partij2.correspondentieadres_land, "6030") + + def test_ko_migration_partij_model_wrong_code(self): + + Partij = self.old_app_state.get_model("klantinteracties", "Partij") + + partij1 = Partij.objects.create( + indicatie_actief=True, + bezoekadres_land="9999", + correspondentieadres_land="9999", + nummer=123, + ) + partij2 = Partij.objects.create( + indicatie_actief=True, + bezoekadres_land="5001", + correspondentieadres_land="5001", + nummer=456, + ) + + with self.assertRaises(IntegrityError) as error: + self._perform_migration() + + self.assertEqual( + ( + "The migration cannot proceed due to 1 records that don't comply with the " + "Partij model's requirements. Possible data inconsistency or mapping error." + ), + str(error.exception), + ) + + records = Partij.objects.all() + partij1 = records.get(pk=partij1.pk) + partij2 = records.get(pk=partij2.pk) + + self.assertEqual(records.count(), 2) + self.assertEqual(partij1.bezoekadres_land, "9999") + self.assertEqual(partij1.correspondentieadres_land, "9999") + self.assertEqual(partij2.bezoekadres_land, "5001") + self.assertEqual(partij2.correspondentieadres_land, "5001") + + # Update manually + partij1 = records.get(pk=partij1.pk) + partij1.bezoekadres_land = "6030" + partij1.correspondentieadres_land = "6030" + partij1.save() + + # Re-Run the migration + self._perform_migration() + + Partij = self.apps.get_model("klantinteracties", "Partij") + + records = Partij.objects.all() + partij1 = records.get(pk=partij1.pk) + partij2 = records.get(pk=partij2.pk) + + self.assertEqual(records.count(), 2) + self.assertEqual(partij1.bezoekadres_land, "NL") + self.assertEqual(partij1.correspondentieadres_land, "NL") + self.assertEqual(partij2.bezoekadres_land, "CA") + self.assertEqual(partij2.correspondentieadres_land, "CA") + + +class TestValidateBagId(BaseMigrationTest): + app = "klantinteracties" + migrate_from = "0026_alter_betrokkene_bezoekadres_land_and_more" + migrate_to = "0027_alter_betrokkene_bezoekadres_nummeraanduiding_id_and_more" + + def test_ok_migration_betrokkene_model(self): + + Betrokkene = self.old_app_state.get_model("klantinteracties", "Betrokkene") + Klantcontact = self.old_app_state.get_model("klantinteracties", "Klantcontact") + + Betrokkene.objects.create( + partij=None, + klantcontact=Klantcontact.objects.create(vertrouwelijk=False), + initiator=False, + bezoekadres_nummeraanduiding_id="1234567890000001", + correspondentieadres_nummeraanduiding_id="1234567890000002", + ) + + self._perform_migration() + + Betrokkene = self.apps.get_model("klantinteracties", "Betrokkene") + + records = Betrokkene.objects.all() + self.assertEqual(records.count(), 1) + self.assertEqual(records[0].bezoekadres_nummeraanduiding_id, "1234567890000001") + self.assertEqual( + records[0].correspondentieadres_nummeraanduiding_id, "1234567890000002" + ) + + def test_ok_migration_betrokkene_model_empty_value(self): + + Betrokkene = self.old_app_state.get_model("klantinteracties", "Betrokkene") + Klantcontact = self.old_app_state.get_model("klantinteracties", "Klantcontact") + + Betrokkene.objects.create( + partij=None, + klantcontact=Klantcontact.objects.create(vertrouwelijk=False), + initiator=False, + bezoekadres_nummeraanduiding_id="", + correspondentieadres_nummeraanduiding_id="", + ) + + self._perform_migration() + + Betrokkene = self.apps.get_model("klantinteracties", "Betrokkene") + + records = Betrokkene.objects.all() + self.assertEqual(records.count(), 1) + self.assertEqual(records[0].bezoekadres_nummeraanduiding_id, "") + self.assertEqual(records[0].correspondentieadres_nummeraanduiding_id, "") + + def test_ko_migration_betrokkene_model_wrong_code(self): + + Betrokkene = self.old_app_state.get_model("klantinteracties", "Betrokkene") + Klantcontact = self.old_app_state.get_model("klantinteracties", "Klantcontact") + + Betrokkene.objects.create( + partij=None, + klantcontact=Klantcontact.objects.create(vertrouwelijk=False), + initiator=False, + bezoekadres_nummeraanduiding_id="123456", + correspondentieadres_nummeraanduiding_id="789", + ) + + with self.assertRaises(IntegrityError) as error: + self._perform_migration() + + self.assertEqual( + ( + "The migration cannot proceed due to 1 records that don't comply with the " + "Betrokkene model's requirements. Possible data inconsistency or mapping error." + ), + str(error.exception), + ) + + records = Betrokkene.objects.all() + self.assertEqual(records.count(), 1) + self.assertEqual(records[0].bezoekadres_nummeraanduiding_id, "123456") + self.assertEqual(records[0].correspondentieadres_nummeraanduiding_id, "789") + + # Update manually + betrokken = records.get(pk=records[0].pk) + betrokken.bezoekadres_nummeraanduiding_id = "1234567890000001" + betrokken.correspondentieadres_nummeraanduiding_id = "1234567890000002" + betrokken.save() + + # Re-Run the migration + self._perform_migration() + + Betrokkene = self.apps.get_model("klantinteracties", "Betrokkene") + + records = Betrokkene.objects.all() + self.assertEqual(records.count(), 1) + self.assertEqual(records[0].bezoekadres_nummeraanduiding_id, "1234567890000001") + self.assertEqual( + records[0].correspondentieadres_nummeraanduiding_id, "1234567890000002" + ) + self.assertNotEqual(records[0].bezoekadres_nummeraanduiding_id, "123456") + self.assertNotEqual(records[0].correspondentieadres_nummeraanduiding_id, "789") + + def test_ok_migration_partij_model(self): + + Partij = self.old_app_state.get_model("klantinteracties", "Partij") + + Partij.objects.create( + indicatie_actief=True, + bezoekadres_nummeraanduiding_id="1234567890000001", + correspondentieadres_nummeraanduiding_id="1234567890000002", + ) + + self._perform_migration() + + Partij = self.apps.get_model("klantinteracties", "Partij") + + records = Partij.objects.all() + + self.assertEqual(records.count(), 1) + self.assertEqual(records[0].bezoekadres_nummeraanduiding_id, "1234567890000001") + self.assertEqual( + records[0].correspondentieadres_nummeraanduiding_id, "1234567890000002" + ) + + def test_ok_migration_partij_model_empty_value(self): + + Partij = self.old_app_state.get_model("klantinteracties", "Partij") + + Partij.objects.create( + indicatie_actief=True, + bezoekadres_nummeraanduiding_id="", + correspondentieadres_nummeraanduiding_id="", + ) + + self._perform_migration() + + Partij = self.apps.get_model("klantinteracties", "Partij") + + records = Partij.objects.all() + + self.assertEqual(records.count(), 1) + self.assertEqual(records[0].bezoekadres_nummeraanduiding_id, "") + self.assertEqual(records[0].correspondentieadres_nummeraanduiding_id, "") + + def test_ko_migration_partij_model_wrong_code(self): + + Partij = self.old_app_state.get_model("klantinteracties", "Partij") + + Partij.objects.create( + indicatie_actief=True, + bezoekadres_nummeraanduiding_id="ABC", + correspondentieadres_nummeraanduiding_id="DEF", + ) + + with self.assertRaises(IntegrityError) as error: + self._perform_migration() + + self.assertEqual( + ( + "The migration cannot proceed due to 1 records that don't comply with the " + "Partij model's requirements. Possible data inconsistency or mapping error." + ), + str(error.exception), + ) + + records = Partij.objects.all() + + self.assertEqual(records.count(), 1) + self.assertEqual(records[0].bezoekadres_nummeraanduiding_id, "ABC") + self.assertEqual(records[0].correspondentieadres_nummeraanduiding_id, "DEF") + + # Update manually + partij1 = records.get(pk=records[0].pk) + partij1.bezoekadres_nummeraanduiding_id = "1234567890000001" + partij1.correspondentieadres_nummeraanduiding_id = "1234567890000002" + partij1.save() + + # Re-Run the migration + self._perform_migration() + + Partij = self.apps.get_model("klantinteracties", "Partij") + + records = Partij.objects.all() + + self.assertEqual(records.count(), 1) + self.assertEqual(records[0].bezoekadres_nummeraanduiding_id, "1234567890000001") + self.assertEqual( + records[0].correspondentieadres_nummeraanduiding_id, "1234567890000002" + ) + self.assertNotEqual(records[0].bezoekadres_nummeraanduiding_id, "ABC") + self.assertNotEqual(records[0].correspondentieadres_nummeraanduiding_id, "DEF") diff --git a/src/openklant/components/token/tests/test_migrations.py b/src/openklant/components/token/tests/test_migrations.py index 47745a56..586669ee 100644 --- a/src/openklant/components/token/tests/test_migrations.py +++ b/src/openklant/components/token/tests/test_migrations.py @@ -1,53 +1,4 @@ -from django.core.management import call_command -from django.db import connection -from django.db.migrations.executor import MigrationExecutor -from django.db.migrations.state import StateApps -from django.test import TransactionTestCase - - -class BaseMigrationTest(TransactionTestCase): - app: str - migrate_from: str # The migration before the one we want to test - migrate_to: str # The migration we want to test - - setting_overrides: dict = {} - - old_app_state: StateApps - app_state: StateApps - - def setUp(self) -> None: - """ - Setup the migration test by reversing to `migrate_from` state, - then applying the `migrate_to` state. - """ - assert self.app is not None, "You must define the `app` attribute" - assert self.migrate_from is not None, "You must define `migrate_from`" - assert self.migrate_to is not None, "You must define `migrate_to`" - - # Step 1: Set up the MigrationExecutor - executor = MigrationExecutor(connection) - - # Step 2: Reverse to the starting migration state - migrate_from = [(self.app, self.migrate_from)] - old_migrate_state = executor.migrate(migrate_from) - - self.old_app_state = old_migrate_state.apps - - def _perform_migration(self) -> None: - migrate_to = [(self.app, self.migrate_to)] - - executor = MigrationExecutor(connection) - executor.loader.build_graph() # reload the graph in case of dependency changes - executor.migrate(migrate_to) - - self.apps = executor.loader.project_state(migrate_to).apps - - @classmethod - def tearDownClass(cls) -> None: - super().tearDownClass() - - # reset to latest migration - call_command("migrate", verbosity=0, database=connection._alias) +from openklant.tests.test_migrate import BaseMigrationTest class TestTokenAuthUniqueness(BaseMigrationTest): diff --git a/src/openklant/conf/api.py b/src/openklant/conf/api.py index 3e3b3ce7..6551ceeb 100644 --- a/src/openklant/conf/api.py +++ b/src/openklant/conf/api.py @@ -6,7 +6,7 @@ KLANTEN_API_VERSION = "1.0.0" CONTACTMOMENTEN_API_VERSION = "1.0.0" -KLANTINTERACTIES_API_VERSION = "0.0.3" +KLANTINTERACTIES_API_VERSION = "0.0.4" CONTACTGEGEVENS_API_VERSION = "1.0.0" REST_FRAMEWORK = BASE_REST_FRAMEWORK.copy() diff --git a/src/openklant/conf/base.py b/src/openklant/conf/base.py index 27679db8..f58d3f4d 100644 --- a/src/openklant/conf/base.py +++ b/src/openklant/conf/base.py @@ -15,6 +15,8 @@ "openklant.components.token", "openklant.components.klantinteracties", "openklant.components.contactgegevens", + # Django libraries + "localflavor", ] # `django.contrib.sites` is installed by Open API Framework by default # but we don't want to rely on it anymore (e.g. when generating the label for 2FA) diff --git a/src/openklant/fixtures/contactgegevens.json b/src/openklant/fixtures/contactgegevens.json index 4c3df531..a2833fcd 100644 --- a/src/openklant/fixtures/contactgegevens.json +++ b/src/openklant/fixtures/contactgegevens.json @@ -3,27 +3,27 @@ "model": "contactgegevens.organisatie", "pk": 1, "fields": { - "adres_nummeraanduiding_id": "e25988cd-daa7-43f7-ba8b-f226c5a3ec76", + "adres_nummeraanduiding_id": "1234567890000001", "adres_adresregel1": "Keizersgracht 117", "adres_adresregel2": "1015 CJ Amsterdam", "adres_adresregel3": "Noord-Holland", - "adres_land": "6030", + "adres_land": "NL", "uuid": "9df246f7-027b-4305-bf43-043b3b926ed3", "handelsnaam": "Maykin Media", "oprichtingsdatum": "2008-01-01", "opheffingsdatum": null, - "land": "6030" + "land": "NL" } }, { "model": "contactgegevens.persoon", "pk": 1, "fields": { - "adres_nummeraanduiding_id": "ed28d31b-8831-48c4-bf5b-ec312d149f72", + "adres_nummeraanduiding_id": "1234567890000002", "adres_adresregel1": "Keizersgracht 117", "adres_adresregel2": "1015 CJ Amsterdam", "adres_adresregel3": "Noord-Holland", - "adres_land": "6030", + "adres_land": "NL", "uuid": "729ff687-e6ab-4627-9a0b-5eca4098b746", "geboortedatum": "2000-01-01", "overlijdensdatum": null, @@ -31,7 +31,7 @@ "geslacht": "v", "voorvoegsel": "van", "voornamen": "Nagihan", - "land": "6030" + "land": "NL" } } ] diff --git a/src/openklant/fixtures/klantinteracties.json b/src/openklant/fixtures/klantinteracties.json index 36b1c834..8c0b7be7 100644 --- a/src/openklant/fixtures/klantinteracties.json +++ b/src/openklant/fixtures/klantinteracties.json @@ -151,11 +151,11 @@ "model": "klantinteracties.betrokkene", "pk": 3, "fields": { - "bezoekadres_nummeraanduiding_id": "ccbcee63-fb73-4c8b-9601-9d51d120a5b8", + "bezoekadres_nummeraanduiding_id": "1234567890000001", "bezoekadres_adresregel1": "Keizersgracht 117", "bezoekadres_adresregel2": "1015 CJ Amsterdam", "bezoekadres_adresregel3": "", - "bezoekadres_land": "6030", + "bezoekadres_land": "NL", "correspondentieadres_nummeraanduiding_id": "", "correspondentieadres_adresregel1": "", "correspondentieadres_adresregel2": "", @@ -227,11 +227,11 @@ "model": "klantinteracties.partij", "pk": 2, "fields": { - "bezoekadres_nummeraanduiding_id": "ae758bf9-6a34-4adc-8482-147738c9509d", + "bezoekadres_nummeraanduiding_id": "1234567890000002", "bezoekadres_adresregel1": "Keizersgracht 117", "bezoekadres_adresregel2": "1015 CJ Amsterdam", "bezoekadres_adresregel3": "", - "bezoekadres_land": "6030", + "bezoekadres_land": "NL", "correspondentieadres_nummeraanduiding_id": "", "correspondentieadres_adresregel1": "", "correspondentieadres_adresregel2": "", diff --git a/src/openklant/setup_configuration/models.py b/src/openklant/setup_configuration/models.py index 7d768d71..8ccb9379 100644 --- a/src/openklant/setup_configuration/models.py +++ b/src/openklant/setup_configuration/models.py @@ -16,6 +16,14 @@ class Meta: "administration", ) } + extra_kwargs = { + "identifier": {"examples": ["open-inwoner"]}, + "token": {"examples": ["modify-this"]}, + "contact_person": {"examples": ["John Doe"]}, + "email": {"examples": ["person@municipality.nl"]}, + "organization": {"examples": ["Municipality name"]}, + "application": {"examples": ["Open Inwoner"]}, + } class TokenAuthGroupConfigurationModel(ConfigurationModel): diff --git a/src/openklant/setup_configuration/steps.py b/src/openklant/setup_configuration/steps.py index 3a3df41e..96afb208 100644 --- a/src/openklant/setup_configuration/steps.py +++ b/src/openklant/setup_configuration/steps.py @@ -16,7 +16,7 @@ class TokenAuthConfigurationStep( BaseConfigurationStep[TokenAuthGroupConfigurationModel] ): """ - Configure configuration groups for the Objects API backend + Configure tokens for other applications to access the APIs provided by Open Klant """ namespace = "tokenauth" diff --git a/src/openklant/setup_configuration/tests/test_token_auth_config.py b/src/openklant/setup_configuration/tests/test_token_auth_config.py index 7a077172..825c409a 100644 --- a/src/openklant/setup_configuration/tests/test_token_auth_config.py +++ b/src/openklant/setup_configuration/tests/test_token_auth_config.py @@ -2,7 +2,10 @@ from django.test import TestCase -from django_setup_configuration.exceptions import ConfigurationRunFailed +from django_setup_configuration.exceptions import ( + ConfigurationRunFailed, + PrerequisiteFailed, +) from django_setup_configuration.test_utils import execute_single_step from openklant.components.token.models import TokenAuth @@ -218,7 +221,7 @@ def test_invalid_identifier(self): test_file_path = str(TEST_FILES / "token_invalid_identifier.yaml") - with self.assertRaises(ConfigurationRunFailed): + with self.assertRaises(PrerequisiteFailed): execute_single_step(TokenAuthConfigurationStep, yaml_source=test_file_path) tokens = TokenAuth.objects.order_by("created") diff --git a/src/openklant/tests/test_migrate.py b/src/openklant/tests/test_migrate.py index e6f73880..2d72a53e 100644 --- a/src/openklant/tests/test_migrate.py +++ b/src/openklant/tests/test_migrate.py @@ -5,7 +5,10 @@ from django.conf import settings from django.core.exceptions import ImproperlyConfigured from django.core.management import CommandError, call_command -from django.test import LiveServerTestCase +from django.db import connection +from django.db.migrations.executor import MigrationExecutor +from django.db.migrations.state import StateApps +from django.test import LiveServerTestCase, TransactionTestCase from requests import Request from vcr.config import RecordMode @@ -34,6 +37,51 @@ def vcr_request_filter(request: Request): return request +class BaseMigrationTest(TransactionTestCase): + app: str + migrate_from: str # The migration before the one we want to test + migrate_to: str # The migration we want to test + + setting_overrides: dict = {} + + old_app_state: StateApps + app_state: StateApps + + def setUp(self) -> None: + """ + Setup the migration test by reversing to `migrate_from` state, + then applying the `migrate_to` state. + """ + assert self.app is not None, "You must define the `app` attribute" + assert self.migrate_from is not None, "You must define `migrate_from`" + assert self.migrate_to is not None, "You must define `migrate_to`" + + # Step 1: Set up the MigrationExecutor + executor = MigrationExecutor(connection) + + # Step 2: Reverse to the starting migration state + migrate_from = [(self.app, self.migrate_from)] + old_migrate_state = executor.migrate(migrate_from) + + self.old_app_state = old_migrate_state.apps + + def _perform_migration(self) -> None: + migrate_to = [(self.app, self.migrate_to)] + + executor = MigrationExecutor(connection) + executor.loader.build_graph() # reload the graph in case of dependency changes + executor.migrate(migrate_to) + + self.apps = executor.loader.project_state(migrate_to).apps + + @classmethod + def tearDownClass(cls) -> None: + super().tearDownClass() + + # reset to latest migration + call_command("migrate", verbosity=0, database=connection._alias) + + class MigrateTestCase(VCRMixin, LiveServerTestCase): host = LIVE_SERVER_HOST port = LIVE_SERVER_PORT diff --git a/src/openklant/urls.py b/src/openklant/urls.py index 482449e6..9629a009 100644 --- a/src/openklant/urls.py +++ b/src/openklant/urls.py @@ -19,6 +19,7 @@ admin.site.site_header = "openklant admin" admin.site.site_title = "openklant admin" admin.site.index_title = "Welcome to the openklant admin" +admin.site.enable_nav_sidebar = False # # This will cause users not to be able to login any longer without the OTP setup. There are some # # issues in this package that need to be resolved. diff --git a/src/openklant/utils/constants.py b/src/openklant/utils/constants.py new file mode 100644 index 00000000..19d4c54a --- /dev/null +++ b/src/openklant/utils/constants.py @@ -0,0 +1,229 @@ +# Mapping from Tabel 34 landcodes to ISO 3166-1 alpha-2 country codes +# Source: +# https://publicaties.rvig.nl/Landelijke_tabellen/Landelijke_tabellen_32_t_m_61_excl_tabel_35/Landelijke_Tabellen_32_t_m_61_in_csv_formaat/Tabel_34_Landen_gesorteerd_op_omschrijving + +COUNTRIES_DICT = { + "5001": "CA", + "5002": "FR", + "5003": "CH", + "5005": "MW", + "5006": "CU", + "5007": "SR", + "5008": "TN", + "5009": "AT", + "5010": "BE", + "5011": "BW", + "5012": "IR", + "5013": "NZ", + "5014": "ZA", + "5015": "DK", + "5017": "HU", + "5018": "SA", + "5019": "LR", + "5020": "ET", + "5021": "CL", + "5022": "MA", + "5023": "TG", + "5024": "GH", + "5025": "LA", + "5026": "AO", + "5027": "PH", + "5028": "ZM", + "5029": "ML", + "5030": "CI", + "5032": "MC", + "5033": "CO", + "5034": "AL", + "5035": "CM", + "5037": "SG", + "5038": "PY", + "5039": "SE", + "5040": "CY", + "5042": "BN", + "5043": "IQ", + "5044": "MU", + "5045": "VA", + "5047": "MM", + "5048": "YE", + "5049": "SI", + "5051": "HR", + "5052": "TW", + "5053": "RU", + "5054": "AM", + "5057": "BH", + "5058": "BT", + "5060": "KM", + "5061": "FK", + "5062": "GF", + "5065": "GL", + "5066": "GP", + "5068": "MO", + "5069": "MQ", + "5070": "MZ", + "5071": "PN", + "5072": "GW", + "5073": "RE", + "5076": "TO", + "5077": "WF", + "5084": "YT", + "5092": "VC", + "5095": "AW", + "5096": "BF", + "5097": "AZ", + "5099": "KZ", + "5103": "RS", + "5104": "ME", + "5107": "CW", + "5110": "SX", + "5111": "SS", + "6000": "MD", + "6001": "BI", + "6002": "FI", + "6003": "GR", + "6004": "GT", + "6005": "NG", + "6006": "LY", + "6007": "IE", + "6008": "BR", + "6009": "RW", + "6010": "VE", + "6011": "IS", + "6012": "LI", + "6013": "SO", + "6015": "BO", + "6016": "AU", + "6017": "JM", + "6018": "LU", + "6019": "TD", + "6020": "MR", + "6021": "KG", + "6022": "CN", + "6023": "AF", + "6024": "ID", + "6025": "GY", + "6027": "NO", + "6028": "SM", + "6029": "DE", + "6030": "NL", + "6031": "KH", + "6032": "FJ", + "6033": "BS", + "6034": "IL", + "6035": "NP", + "6036": "KR", + "6037": "ES", + "6038": "UA", + "6040": "NE", + "6041": "HT", + "6042": "JO", + "6043": "TR", + "6044": "TT", + "6047": "DZ", + "6048": "GA", + "6049": "KP", + "6050": "UZ", + "6051": "SL", + "6054": "PF", + "6055": "GI", + "6057": "TJ", + "6063": "TM", + "6064": "GE", + "6066": "CZ", + "6067": "SK", + "7003": "MT", + "7004": "BB", + "7005": "AD", + "7006": "MX", + "7007": "CR", + "7008": "GM", + "7009": "SY", + "7014": "EG", + "7015": "AR", + "7016": "LS", + "7017": "HN", + "7018": "NI", + "7020": "PK", + "7021": "SN", + "7024": "BG", + "7026": "MY", + "7027": "DO", + "7028": "PL", + "7030": "VG", + "7031": "TZ", + "7032": "SV", + "7033": "LK", + "7034": "SD", + "7035": "JP", + "7036": "HK", + "7037": "PA", + "7038": "UY", + "7039": "EC", + "7040": "GN", + "7041": "MV", + "7042": "TH", + "7043": "LB", + "7044": "IT", + "7045": "KW", + "7046": "IN", + "7047": "RO", + "7049": "PE", + "7050": "PT", + "7051": "OM", + "7052": "MN", + "7053": "WS", + "7054": "AE", + "7057": "NR", + "7060": "PS", + "7064": "LV", + "7065": "EE", + "7066": "LT", + "7084": "BD", + "7088": "VI", + "7096": "IO", + "7097": "CK", + "7098": "TK", + "7099": "NC", + "8001": "GU", + "8002": "AS", + "8008": "GD", + "8012": "CX", + "8013": "CC", + "8014": "FO", + "8015": "MS", + "8016": "NF", + "8017": "BZ", + "8019": "TC", + "8020": "PR", + "8021": "PG", + "8022": "SB", + "8023": "BJ", + "8024": "VN", + "8025": "CV", + "8026": "SC", + "8027": "KI", + "8028": "TV", + "8029": "LC", + "8030": "DM", + "8031": "ZW", + "8035": "IM", + "8036": "AI", + "8037": "KN", + "8044": "PW", + "8045": "AG", + "9003": "ER", + "9009": "CD", + "9010": "MG", + "9013": "CG", + "9023": "NA", + "9036": "SZ", + "9037": "QA", + "9043": "GQ", + "9048": "BM", + "9056": "MH", + "9086": "CF", + "9087": "DJ", + "9090": "VU", + "9091": "NU", + "9093": "EH", + "9094": "FM", +} diff --git a/src/openklant/utils/tests/test_validators.py b/src/openklant/utils/tests/test_validators.py index 143dc9d5..ca86abba 100644 --- a/src/openklant/utils/tests/test_validators.py +++ b/src/openklant/utils/tests/test_validators.py @@ -2,7 +2,9 @@ from django.test import TestCase from openklant.utils.validators import ( + validate_bag_id, validate_charfield_entry, + validate_country, validate_iban, validate_no_space, validate_phone_number, @@ -147,3 +149,47 @@ def test_validate_iban(self): self.assertIsNone(validate_iban("ab1299999999999")) self.assertIsNone(validate_iban("ab129")) self.assertIsNone(validate_iban("ab12aaaaaaaaaa")) + + def test_validate_bag_id(self): + """ + Test Basisregistratie Adressen en Gebouwen ID + """ + invalid_ids = [ + "", + "1", + "1000 AAA", + "1000AAA", + "000000000000000", + "AAAAAAAAAAAAAAAA", + "1234-1234-1234-1234", + "123A123A123A123A", + ] + for bag_id in invalid_ids: + self.assertRaisesMessage( + ValidationError, + "Ongeldige nummeraanduiding BAG-ID", + validate_bag_id, + bag_id, + ) + + validate_bag_id("1234567890000001") + validate_bag_id("1111111111111111") + + def test_validate_country(self): + invalid_codes = [ + "", + "1", + "10", + "ZZ", + "1Z", + "nl", + ] + for code in invalid_codes: + self.assertRaisesMessage( + ValidationError, + "Ongeldige landcode, de code moet behoren tot de ISO 3166-standaard", + validate_country, + code, + ) + + validate_country("NL") diff --git a/src/openklant/utils/validators.py b/src/openklant/utils/validators.py index d69dbd05..32ba41ee 100644 --- a/src/openklant/utils/validators.py +++ b/src/openklant/utils/validators.py @@ -3,6 +3,21 @@ from django.utils.encoding import force_str from django.utils.translation import gettext_lazy as _ +from localflavor.generic.countries.iso_3166 import ISO_3166_1_ALPHA2_COUNTRY_CODES + + +def validate_country(value: str) -> None: + """ + Validate an ISO 3166-1 alpha-2 country code + + :param value: + :return: None if validation passed. Otherwise, raises a ``ValidationError`` exception. + """ + if value not in ISO_3166_1_ALPHA2_COUNTRY_CODES: + raise ValidationError( + _("Ongeldige landcode, de code moet behoren tot de ISO 3166-standaard") + ) + def validate_charfield_entry(value, allow_apostrophe=False): """ @@ -55,3 +70,7 @@ def __call__(self, value): validate_no_space = CustomRegexValidator( regex="^[\S]+$", message=_("Geen spaties toegestaan") # noqa ) + +validate_bag_id = CustomRegexValidator( + regex="^[0-9]{16}$", message=_("Ongeldige nummeraanduiding BAG-ID") +)