diff --git a/src/openklant/components/klantinteracties/admin/partijen.py b/src/openklant/components/klantinteracties/admin/partijen.py index 23c4ad7d..52af8faf 100644 --- a/src/openklant/components/klantinteracties/admin/partijen.py +++ b/src/openklant/components/klantinteracties/admin/partijen.py @@ -30,28 +30,20 @@ class Meta: def clean(self): cleaned_data = super().clean() - partij_identificator = { - "code_objecttype": cleaned_data["partij_identificator_code_objecttype"], - "code_soort_object_id": cleaned_data[ + PartijIdentificatorTypesValidator()( + code_register=cleaned_data["partij_identificator_code_register"], + code_objecttype=cleaned_data["partij_identificator_code_objecttype"], + code_soort_object_id=cleaned_data[ "partij_identificator_code_soort_object_id" ], - "object_id": cleaned_data["partij_identificator_object_id"], - "code_register": cleaned_data["partij_identificator_code_register"], - } - - PartijIdentificatorTypesValidator( - partij_identificator=partij_identificator - ).validate() - - queryset = PartijIdentificator.objects.exclude() - if self.instance: - queryset = queryset.exclude(pk=self.instance.pk) + object_id=cleaned_data["partij_identificator_object_id"], + ) PartijIdentificatorUniquenessValidator( - instance=self.instance, - partij_identificator=partij_identificator, + code_soort_object_id=cleaned_data[ + "partij_identificator_code_soort_object_id" + ], sub_identificator_van=cleaned_data["sub_identificator_van"], - partij=cleaned_data["partij"], )() return cleaned_data diff --git a/src/openklant/components/klantinteracties/api/serializers/partijen.py b/src/openklant/components/klantinteracties/api/serializers/partijen.py index 5bf788dc..c3b6869b 100644 --- a/src/openklant/components/klantinteracties/api/serializers/partijen.py +++ b/src/openklant/components/klantinteracties/api/serializers/partijen.py @@ -46,6 +46,7 @@ PartijIdentificatorTypesValidator, PartijIdentificatorUniquenessValidator, ) +from openklant.utils.decorators import handle_db_exceptions from openklant.utils.serializers import get_field_instance_by_uuid, get_field_value @@ -371,10 +372,7 @@ class Meta: "code_soort_object_id": {"required": True}, "object_id": {"required": True}, } - - def validate(self, attrs): - PartijIdentificatorTypesValidator(partij_identificator=attrs)() - return super().validate(attrs) + validators = [] class PartijIdentificatorSerializer( @@ -420,27 +418,38 @@ class Meta: } def validate(self, attrs): - instance = getattr(self, "instance", None) partij_identificator = get_field_value(self, attrs, "partij_identificator") - sub_identificator_van = get_field_instance_by_uuid( self, attrs, "sub_identificator_van", PartijIdentificator ) partij = get_field_instance_by_uuid(self, attrs, "partij", Partij) + PartijIdentificatorTypesValidator()( + code_objecttype=partij_identificator["code_objecttype"], + code_soort_object_id=partij_identificator["code_soort_object_id"], + object_id=partij_identificator["object_id"], + code_register=partij_identificator["code_register"], + ) PartijIdentificatorUniquenessValidator( - instance=instance, - partij_identificator=partij_identificator, + code_soort_object_id=partij_identificator["code_soort_object_id"], sub_identificator_van=sub_identificator_van, - partij=partij, )() - attrs["sub_identificator_van"] = get_field_instance_by_uuid( - self, attrs, "sub_identificator_van", PartijIdentificator - ) - attrs["partij"] = get_field_instance_by_uuid(self, attrs, "partij", Partij) + attrs["sub_identificator_van"] = sub_identificator_van + attrs["partij"] = partij + return super().validate(attrs) + @handle_db_exceptions + @transaction.atomic + def update(self, instance, validated_data): + return super().update(instance, validated_data) + + @handle_db_exceptions + @transaction.atomic + def create(self, validated_data): + return super().create(validated_data) + class PartijSerializer(NestedGegevensGroepMixin, PolymorphicSerializer): from openklant.components.klantinteracties.api.serializers.rekeningnummers import ( diff --git a/src/openklant/components/klantinteracties/api/tests/test_partijen.py b/src/openklant/components/klantinteracties/api/tests/test_partijen.py index 730bb1b2..761e7086 100644 --- a/src/openklant/components/klantinteracties/api/tests/test_partijen.py +++ b/src/openklant/components/klantinteracties/api/tests/test_partijen.py @@ -3,7 +3,7 @@ from django.utils.translation import gettext as _ from rest_framework import status -from vng_api_common.tests import reverse +from vng_api_common.tests import get_validation_errors, reverse, reverse_lazy from openklant.components.klantinteracties.models.constants import SoortPartij from openklant.components.klantinteracties.models.partijen import ( @@ -258,10 +258,10 @@ def test_create_partij(self): response = self.client.post(list_url, data) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) - response_data = response.json() + error = get_validation_errors(response, "nummer") + self.assertEqual(error["code"], "unique") self.assertEqual( - response_data["invalidParams"][0]["reason"], - "Er bestaat al een partij met eenzelfde nummer.", + error["reason"], "Er bestaat al een partij met eenzelfde nummer." ) with self.subTest("auto_generate_nummer_over_10_characters_error_message"): @@ -283,16 +283,11 @@ def test_create_partij(self): response = self.client.post(list_url, data) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) - response_data = response.json() + error = get_validation_errors(response, "voorkeursDigitaalAdres") + self.assertEqual(error["code"], "invalid") self.assertEqual( - response_data["invalidParams"], - [ - { - "name": "voorkeursDigitaalAdres", - "code": "invalid", - "reason": "Het voorkeurs adres moet een gelinkte digitaal adres zijn.", - } - ], + error["reason"], + "Het voorkeurs adres moet een gelinkte digitaal adres zijn.", ) with self.subTest("voorkeurs_adres_must_be_given_digitaal_adres_validation"): @@ -304,16 +299,11 @@ def test_create_partij(self): response = self.client.post(list_url, data) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) - response_data = response.json() + error = get_validation_errors(response, "voorkeursRekeningnummer") + self.assertEqual(error["code"], "invalid") self.assertEqual( - response_data["invalidParams"], - [ - { - "name": "voorkeursRekeningnummer", - "code": "invalid", - "reason": "Het voorkeurs rekeningnummer moet een gelinkte rekeningnummer zijn.", - } - ], + error["reason"], + "Het voorkeurs rekeningnummer moet een gelinkte rekeningnummer zijn.", ) def test_create_partij_only_required(self): @@ -849,16 +839,11 @@ def test_update_partij(self): data["voorkeursDigitaalAdres"] = {"uuid": str(digitaal_adres.uuid)} response = self.client.put(detail_url, data) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) - response_data = response.json() + error = get_validation_errors(response, "voorkeursDigitaalAdres") + self.assertEqual(error["code"], "invalid") self.assertEqual( - response_data["invalidParams"], - [ - { - "name": "voorkeursDigitaalAdres", - "code": "invalid", - "reason": "Het voorkeurs adres moet een gelinkte digitaal adres zijn.", - } - ], + error["reason"], + "Het voorkeurs adres moet een gelinkte digitaal adres zijn.", ) with self.subTest( @@ -867,18 +852,12 @@ def test_update_partij(self): data["digitaleAdressen"] = [] response = self.client.put(detail_url, data) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) - response_data = response.json() + error = get_validation_errors(response, "voorkeursDigitaalAdres") + self.assertEqual(error["code"], "invalid") self.assertEqual( - response_data["invalidParams"], - [ - { - "name": "voorkeursDigitaalAdres", - "code": "invalid", - "reason": "voorkeursDigitaalAdres mag niet meegegeven worden als digitaleAdressen leeg is.", - } - ], + error["reason"], + "voorkeursDigitaalAdres mag niet meegegeven worden als digitaleAdressen leeg is.", ) - with self.subTest( "test_voorkeurs_rekeningnummer_must_be_part_of_rekeningnummers" ): @@ -888,16 +867,11 @@ def test_update_partij(self): data["voorkeursRekeningnummer"] = {"uuid": str(rekeningnummer.uuid)} response = self.client.put(detail_url, data) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) - response_data = response.json() + error = get_validation_errors(response, "voorkeursRekeningnummer") + self.assertEqual(error["code"], "invalid") self.assertEqual( - response_data["invalidParams"], - [ - { - "name": "voorkeursRekeningnummer", - "code": "invalid", - "reason": "Het voorkeurs rekeningnummer moet een gelinkte rekeningnummer zijn.", - } - ], + error["reason"], + "Het voorkeurs rekeningnummer moet een gelinkte rekeningnummer zijn.", ) with self.subTest( @@ -906,18 +880,12 @@ def test_update_partij(self): data["rekeningnummers"] = [] response = self.client.put(detail_url, data) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) - response_data = response.json() + error = get_validation_errors(response, "voorkeursRekeningnummer") + self.assertEqual(error["code"], "invalid") self.assertEqual( - response_data["invalidParams"], - [ - { - "name": "voorkeursRekeningnummer", - "code": "invalid", - "reason": "voorkeursRekeningnummer mag niet meegegeven worden als rekeningnummers leeg is.", - } - ], + error["reason"], + "voorkeursRekeningnummer mag niet meegegeven worden als rekeningnummers leeg is.", ) - with self.subTest("set_foreignkey_fields_to_none"): data = { "nummer": "6427834668", @@ -1757,16 +1725,11 @@ def test_partial_update_parij(self): response = self.client.patch(detail_url, data) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) - response_data = response.json() + error = get_validation_errors(response, "voorkeursDigitaalAdres") + self.assertEqual(error["code"], "invalid") self.assertEqual( - response_data["invalidParams"], - [ - { - "name": "voorkeursDigitaalAdres", - "code": "invalid", - "reason": "Het voorkeurs adres moet een gelinkte digitaal adres zijn.", - } - ], + error["reason"], + "Het voorkeurs adres moet een gelinkte digitaal adres zijn.", ) with self.subTest( @@ -1779,16 +1742,11 @@ def test_partial_update_parij(self): response = self.client.patch(detail_url, data) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) - response_data = response.json() + error = get_validation_errors(response, "voorkeursRekeningnummer") + self.assertEqual(error["code"], "invalid") self.assertEqual( - response_data["invalidParams"], - [ - { - "name": "voorkeursRekeningnummer", - "code": "invalid", - "reason": "Het voorkeurs rekeningnummer moet een gelinkte rekeningnummer zijn.", - } - ], + error["reason"], + "Het voorkeurs rekeningnummer moet een gelinkte rekeningnummer zijn.", ) def test_destroy_partij(self): @@ -1851,13 +1809,16 @@ def _get_detail_url(partij: Partij) -> str: class PartijIdentificatorTests(APITestCase): def test_list_partij_identificator(self): list_url = reverse("klantinteracties:partijidentificator-list") + partij = PartijFactory.create() PartijIdentificator.objects.create( + partij=partij, partij_identificator_code_objecttype="natuurlijk_persoon", partij_identificator_code_soort_object_id="bsn", partij_identificator_object_id="296648875", partij_identificator_code_register="brp", ) PartijIdentificator.objects.create( + partij=partij, partij_identificator_code_objecttype="niet_natuurlijk_persoon", partij_identificator_code_soort_object_id="rsin", partij_identificator_object_id="296648875", @@ -2076,15 +2037,10 @@ def test_invalid_validation_partij_identificator_code_objecttype(self): } response = self.client.post(url, data) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) - self.assertEqual(response.data["code"], "invalid") - self.assertEqual(response.data["title"], "Invalid input.") + error = get_validation_errors(response, "partijIdentificatorCodeObjecttype") + self.assertEqual(error["code"], "invalid") self.assertEqual( - response.data["invalid_params"][0]["name"], - "partijIdentificator.partijIdentificatorCodeObjecttype", - ) - self.assertEqual(response.data["invalid_params"][0]["code"], "invalid") - self.assertEqual( - response.data["invalid_params"][0]["reason"], + error["reason"], "voor `codeRegister` brp zijn alleen deze waarden toegestaan: ['natuurlijk_persoon']", ) @@ -2104,15 +2060,11 @@ def test_invalid_validation_partij_identificator_code_soort_object_id(self): response = self.client.post(url, data) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) - self.assertEqual(response.data["code"], "invalid") - self.assertEqual(response.data["title"], "Invalid input.") - self.assertEqual( - response.data["invalid_params"][0]["name"], - "partijIdentificator.partijIdentificatorCodeSoortObjectId", - ) - self.assertEqual(response.data["invalid_params"][0]["code"], "invalid") + error = get_validation_errors(response, "partijIdentificatorCodeSoortObjectId") + self.assertEqual(error["code"], "invalid") + self.assertEqual( - response.data["invalid_params"][0]["reason"], + error["reason"], "voor `codeObjecttype` natuurlijk_persoon zijn alleen deze waarden toegestaan: ['bsn', 'overig']", ) @@ -2132,15 +2084,11 @@ def test_invalid_validation_partij_identificator_object_id(self): response = self.client.post(url, data) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) - self.assertEqual(response.data["code"], "invalid") - self.assertEqual(response.data["title"], "Invalid input.") - self.assertEqual( - response.data["invalid_params"][0]["name"], - "partijIdentificator.partijIdentificatorObjectId", - ) - self.assertEqual(response.data["invalid_params"][0]["code"], "invalid") + + error = get_validation_errors(response, "partijIdentificatorObjectId") + self.assertEqual(error["code"], "invalid") self.assertEqual( - response.data["invalid_params"][0]["reason"], + error["reason"], "Deze waarde is ongeldig, reden: Waarde moet 9 tekens lang zijn", ) @@ -2156,9 +2104,11 @@ def test_invalid_create_empty(self): response = self.client.post(list_url, data) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) - self.assertEqual(response.data["code"], "invalid") - self.assertEqual(response.data["title"], "Invalid input.") self.assertEqual(len(response.data["invalid_params"]), 4) + get_validation_errors(response, "partijIdentificator.objectId") + get_validation_errors(response, "partijIdentificator.codeSoortObjectId") + get_validation_errors(response, "partijIdentificator.codeObjecttype") + get_validation_errors(response, "partijIdentificator.codeRegister") def test_invalid_create_partial(self): # all partij_identificator fields required @@ -2176,15 +2126,9 @@ def test_invalid_create_partial(self): response = self.client.post(list_url, data) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) - self.assertEqual(response.data["code"], "invalid") - self.assertEqual(response.data["title"], "Invalid input.") - self.assertEqual( - response.data["invalid_params"][0]["name"], "partijIdentificator.objectId" - ) - self.assertEqual(response.data["invalid_params"][0]["code"], "required") - self.assertEqual( - response.data["invalid_params"][0]["reason"], "Dit veld is vereist." - ) + error = get_validation_errors(response, "partijIdentificator.objectId") + self.assertEqual(error["code"], "required") + self.assertEqual(error["reason"], "Dit veld is vereist.") self.assertEqual(PartijIdentificator.objects.all().count(), 0) def test_invalid_update_partial(self): @@ -2217,21 +2161,16 @@ def test_invalid_update_partial(self): response = self.client.post(list_url, data) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) - self.assertEqual(response.data["code"], "invalid") - self.assertEqual(response.data["title"], "Invalid input.") - self.assertEqual( - response.data["invalid_params"][0]["name"], "partijIdentificator.objectId" - ) - self.assertEqual(response.data["invalid_params"][0]["code"], "required") - self.assertEqual( - response.data["invalid_params"][0]["reason"], "Dit veld is vereist." - ) + error = get_validation_errors(response, "partijIdentificator.objectId") + self.assertEqual(error["code"], "required") + self.assertEqual(error["reason"], "Dit veld is vereist.") self.assertEqual(PartijIdentificator.objects.all().count(), 1) class PartijIdentificatorUniquenessTests(APITestCase): + list_url = reverse_lazy("klantinteracties:partijidentificator-list") + def setUp(self): - self.list_url = reverse("klantinteracties:partijidentificator-list") self.partij = PartijFactory.create() super().setUp() @@ -2251,16 +2190,15 @@ def test_valid_create(self): self.assertEqual(response.status_code, status.HTTP_201_CREATED) self.assertEqual(PartijIdentificator.objects.all().count(), 1) - def test_valid_create_sub_identificator_van(self): + def test_valid_create_with_sub_identificator_van(self): partij_identificator = PartijIdentificatorFactory.create( partij=self.partij, andere_partij_identificator="anderePartijIdentificator", - partij_identificator_code_objecttype="natuurlijk_persoon", - partij_identificator_code_soort_object_id="bsn", - partij_identificator_object_id="123456782", - partij_identificator_code_register="brp", + partij_identificator_code_objecttype="niet_natuurlijk_persoon", + partij_identificator_code_soort_object_id="kvk_nummer", + partij_identificator_object_id="12345678", + partij_identificator_code_register="hr", ) - data = { "identificeerdePartij": {"uuid": str(self.partij.uuid)}, "sub_identificator_van": {"uuid": str(partij_identificator.uuid)}, @@ -2275,13 +2213,25 @@ def test_valid_create_sub_identificator_van(self): self.assertEqual(response.status_code, status.HTTP_201_CREATED) self.assertEqual(PartijIdentificator.objects.all().count(), 2) - def test_invalid_create_global_unique(self): + def test_invalid_create_partij_required(self): + data = { + "partijIdentificator": { + "codeObjecttype": "natuurlijk_persoon", + "codeSoortObjectId": "bsn", + "objectId": "296648875", + "codeRegister": "brp", + }, + } + response = self.client.post(self.list_url, data) + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + error = get_validation_errors(response, "identificeerdePartij") + self.assertEqual(error["code"], "required") + self.assertEqual(error["reason"], "Dit veld is vereist.") + + def test_invalid_create_duplicate_code_soort_object_id_for_partij(self): PartijIdentificatorFactory.create( partij=self.partij, - partij_identificator_code_objecttype="natuurlijk_persoon", partij_identificator_code_soort_object_id="bsn", - partij_identificator_object_id="296648875", - partij_identificator_code_register="brp", ) data = { @@ -2293,86 +2243,43 @@ def test_invalid_create_global_unique(self): "codeRegister": "brp", }, } - sub_identificator_van = PartijIdentificatorFactory.create( - partij=self.partij, - partij_identificator_code_objecttype="niet_natuurlijk_persoon", - partij_identificator_code_soort_object_id="kvk_nummer", - partij_identificator_object_id="12345678", - partij_identificator_code_register="hr", + response = self.client.post(self.list_url, data) + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + error = get_validation_errors(response, "__all__") + self.assertEqual(error["code"], "unique_together") + self.assertEqual( + error["reason"], + "Partij identificator met deze Partij en Soort object ID bestaat al.", ) - with self.subTest("ok_subtest_global_1"): - # sub_identificator_van is set - data["sub_identificator_van"] = {"uuid": str(sub_identificator_van.uuid)} - response = self.client.post(self.list_url, data) - self.assertEqual(response.status_code, status.HTTP_201_CREATED) - with self.subTest("failed_subtest_global_1"): - # combination sub_identificator_van and partij_identificator arleady exists - data["sub_identificator_van"] = {"uuid": str(sub_identificator_van.uuid)} - response = self.client.post(self.list_url, data) - self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) - self.assertEqual(response.data["code"], "invalid") - self.assertEqual(response.data["title"], "Invalid input.") - self.assertEqual( - response.data["invalid_params"][0]["reason"], - "`PartijIdentificator` moet uniek zijn, er bestaat er al een met deze gegevenscombinatie.", - ) - with self.subTest("failed_subtest_global_2"): - # same partij, same data_values - response = self.client.post(self.list_url, data) - self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) - self.assertEqual(response.data["code"], "invalid") - self.assertEqual(response.data["title"], "Invalid input.") - self.assertEqual( - response.data["invalid_params"][0]["reason"], - "`PartijIdentificator` moet uniek zijn, er bestaat er al een met deze gegevenscombinatie.", - ) - with self.subTest("failed_subtest_global_3"): - # partij = None, same data_values - data["identificeerdePartij"] = None - response = self.client.post(self.list_url, data) - self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) - self.assertEqual(response.data["code"], "invalid") - self.assertEqual(response.data["title"], "Invalid input.") - self.assertEqual( - response.data["invalid_params"][0]["reason"], - "`PartijIdentificator` moet uniek zijn, er bestaat er al een met deze gegevenscombinatie.", - ) - def test_valid_update_simple_field(self): + def test_valid_update_partij(self): partij_identificator = PartijIdentificatorFactory.create( - partij=self.partij, + partij=PartijFactory.create(), andere_partij_identificator="anderePartijIdentificator", partij_identificator_code_objecttype="natuurlijk_persoon", partij_identificator_code_soort_object_id="bsn", partij_identificator_object_id="123456782", partij_identificator_code_register="brp", ) - data = { - "anderePartijIdentificator": "changed", + "identificeerdePartij": {"uuid": str(self.partij.uuid)}, } detail_url = reverse( "klantinteracties:partijidentificator-detail", kwargs={"uuid": str(partij_identificator.uuid)}, ) + # check if this partij_identificator can be assigned to self.partij + self.assertEqual(self.partij.partijidentificator_set.count(), 0) response = self.client.patch(detail_url, data) self.assertEqual(response.status_code, status.HTTP_200_OK) data = response.json() self.assertEqual(data["identificeerdePartij"]["uuid"], str(self.partij.uuid)) - self.assertEqual(data["anderePartijIdentificator"], "changed") - self.assertEqual( - data["partijIdentificator"], - { - "codeObjecttype": "natuurlijk_persoon", - "codeSoortObjectId": "bsn", - "objectId": "123456782", - "codeRegister": "brp", - }, - ) - def test_valid_update_partij(self): - partij_identificator = PartijIdentificatorFactory.create( + def test_invalid_update_partij(self): + new_partij = PartijFactory.create() + PartijIdentificatorFactory.create( + partij=new_partij, andere_partij_identificator="anderePartijIdentificator", partij_identificator_code_objecttype="natuurlijk_persoon", partij_identificator_code_soort_object_id="bsn", @@ -2380,20 +2287,49 @@ def test_valid_update_partij(self): partij_identificator_code_register="brp", ) + partij_identificator = PartijIdentificatorFactory.create( + partij=self.partij, + andere_partij_identificator="anderePartijIdentificator", + partij_identificator_code_objecttype="natuurlijk_persoon", + partij_identificator_code_soort_object_id="bsn", + partij_identificator_object_id="296648875", + partij_identificator_code_register="brp", + ) data = { - "identificeerdePartij": {"uuid": str(self.partij.uuid)}, + "identificeerdePartij": {"uuid": str(new_partij.uuid)}, } detail_url = reverse( "klantinteracties:partijidentificator-detail", kwargs={"uuid": str(partij_identificator.uuid)}, ) + # check if this partij_identificator can be assigned to self.partij + self.assertEqual(new_partij.partijidentificator_set.count(), 1) + self.assertEqual( + new_partij.partijidentificator_set.filter( + partij_identificator_code_soort_object_id="bsn" + ).count(), + 1, + ) + response = self.client.patch(detail_url, data) - self.assertEqual(response.status_code, status.HTTP_200_OK) - data = response.json() - self.assertEqual(data["identificeerdePartij"]["uuid"], str(self.partij.uuid)) + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) - def test_valid_update_unique(self): + error = get_validation_errors(response, "__all__") + self.assertEqual(error["code"], "unique_together") + self.assertEqual( + error["reason"], + "Partij identificator met deze Partij en Soort object ID bestaat al.", + ) + self.assertEqual(new_partij.partijidentificator_set.count(), 1) + self.assertEqual( + new_partij.partijidentificator_set.filter( + partij_identificator_code_soort_object_id="bsn" + ).count(), + 1, + ) + + def test_valid_update_check_uniqueness_values(self): partij_identificator = PartijIdentificatorFactory.create( partij=self.partij, andere_partij_identificator="anderePartijIdentificator", @@ -2433,7 +2369,7 @@ def test_valid_update_unique(self): }, ) - def test_invalid_update_unique_already_exists(self): + def test_invalid_update_check_uniqueness_exists(self): partij_identificator_a = PartijIdentificatorFactory.create( partij=self.partij, partij_identificator_code_objecttype="natuurlijk_persoon", @@ -2443,7 +2379,7 @@ def test_invalid_update_unique_already_exists(self): ) partij_identificator_b = PartijIdentificatorFactory.create( - partij=self.partij, + partij=PartijFactory.create(), partij_identificator_code_objecttype="natuurlijk_persoon", partij_identificator_code_soort_object_id="bsn", partij_identificator_object_id="296648875", @@ -2468,41 +2404,82 @@ def test_invalid_update_unique_already_exists(self): # partij_identificator already exists response = self.client.put(detail_url, data) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) - self.assertEqual(response.data["code"], "invalid") - self.assertEqual(response.data["title"], "Invalid input.") + error = get_validation_errors(response, "__all__") + self.assertEqual(error["code"], "invalid") self.assertEqual( - response.data["invalid_params"][0]["reason"], + error["reason"], "`PartijIdentificator` moet uniek zijn, er bestaat er al een met deze gegevenscombinatie.", ) - def test_invalid_update_unique_set_sub_identificator_van_self(self): - partij_identificator_a = PartijIdentificatorFactory.create( + def test_valid_check_uniqueness_sub_identificator_van(self): + PartijIdentificatorFactory.create( partij=self.partij, partij_identificator_code_objecttype="natuurlijk_persoon", partij_identificator_code_soort_object_id="bsn", - partij_identificator_object_id="123456782", + partij_identificator_object_id="296648875", partij_identificator_code_register="brp", ) - # update partij_identificator_a with partij_identificator_b data - detail_url = reverse( - "klantinteracties:partijidentificator-detail", - kwargs={"uuid": str(partij_identificator_a.uuid)}, + # Same values, but sub_identifier_van is set + partij = PartijFactory.create() + sub_identificator_van = PartijIdentificatorFactory.create( + partij=partij, + partij_identificator_code_objecttype="niet_natuurlijk_persoon", + partij_identificator_code_soort_object_id="kvk_nummer", + partij_identificator_object_id="12345678", + partij_identificator_code_register="hr", ) + self.assertEqual(PartijIdentificator.objects.all().count(), 2) + data = { + "identificeerdePartij": {"uuid": str(partij.uuid)}, + "sub_identificator_van": {"uuid": str(sub_identificator_van.uuid)}, + "partijIdentificator": { + "codeObjecttype": "natuurlijk_persoon", + "codeSoortObjectId": "bsn", + "objectId": "296648875", + "codeRegister": "brp", + }, + } + response = self.client.post(self.list_url, data) + self.assertEqual(response.status_code, status.HTTP_201_CREATED) + self.assertEqual(PartijIdentificator.objects.all().count(), 3) + def test_invalid_check_uniqueness_sub_identificator_van(self): + sub_identificator_van = PartijIdentificatorFactory.create( + partij=self.partij, + partij_identificator_code_objecttype="niet_natuurlijk_persoon", + partij_identificator_code_soort_object_id="kvk_nummer", + partij_identificator_object_id="12345678", + partij_identificator_code_register="hr", + ) + PartijIdentificatorFactory.create( + partij=self.partij, + sub_identificator_van=sub_identificator_van, + partij_identificator_code_objecttype="natuurlijk_persoon", + partij_identificator_code_soort_object_id="bsn", + partij_identificator_object_id="296648875", + partij_identificator_code_register="brp", + ) + self.assertEqual(PartijIdentificator.objects.all().count(), 2) + # Same values and same sub_identificator_van data = { "identificeerdePartij": {"uuid": str(self.partij.uuid)}, - "sub_identificator_van": {"uuid": str(partij_identificator_a.uuid)}, + "sub_identificator_van": {"uuid": str(sub_identificator_van.uuid)}, + "partijIdentificator": { + "codeObjecttype": "natuurlijk_persoon", + "codeSoortObjectId": "bsn", + "objectId": "296648875", + "codeRegister": "brp", + }, } - - # set sub_identificator_van self relation - response = self.client.patch(detail_url, data) + response = self.client.post(self.list_url, data) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) - self.assertEqual(response.data["code"], "invalid") - self.assertEqual(response.data["title"], "Invalid input.") + error = get_validation_errors(response, "__all__") + self.assertEqual(error["code"], "unique_together") self.assertEqual( - response.data["invalid_params"][0]["reason"], - "Kan zichzelf niet selecteren als `subIdentificatorVan`.", + error["reason"], + "Partij identificator met deze Partij en Soort object ID bestaat al.", ) + self.assertEqual(PartijIdentificator.objects.all().count(), 2) def test_vestigingsnummer_valid_create(self): sub_identificator_van = PartijIdentificatorFactory.create( @@ -2542,11 +2519,15 @@ def test_vestigingsnummer_invalid_create_without_sub_identificator_van(self): response = self.client.post(self.list_url, data) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) - self.assertEqual(response.data["code"], "invalid") - self.assertEqual(response.data["title"], "Invalid input.") - self.assertTrue( - "`sub_identifier_van` met codeSoortObjectId = `kvk_nummer` te kiezen." - in response.data["invalid_params"][0]["reason"] + error = get_validation_errors(response, "subIdentificatorVan") + self.assertEqual(error["code"], "invalid") + self.assertEqual( + error["reason"], + ( + "Voor een PartijIdentificator met codeSoortObjectId = `vestigingsnummer` " + "is het verplicht om een `sub_identifier_van` met codeSoortObjectId = " + "`kvk_nummer` te kiezen." + ), ) def test_vestigingsnummer_invalid_create_without_partij(self): @@ -2559,7 +2540,6 @@ def test_vestigingsnummer_invalid_create_without_partij(self): ) data = { - "identificeerdePartij": None, "sub_identificator_van": {"uuid": str(sub_identificator_van.uuid)}, "partijIdentificator": { "codeObjecttype": "vestiging", @@ -2571,14 +2551,11 @@ def test_vestigingsnummer_invalid_create_without_partij(self): response = self.client.post(self.list_url, data) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) - self.assertEqual(response.data["code"], "invalid") - self.assertEqual(response.data["title"], "Invalid input.") - self.assertTrue( - "Het is niet mogelijk om een partij_identificator te maken zonder de partijwaartoe" - in response.data["invalid_params"][0]["reason"] - ) + error = get_validation_errors(response, "identificeerdePartij") + self.assertEqual(error["code"], "required") + self.assertEqual(error["reason"], "Dit veld is vereist.") - def test_vestigingsnummer_invalid_create_external_partij(self): + def test_vestigingsnummer_valid_create_external_partij(self): partij = PartijFactory.create() sub_identificator_van = PartijIdentificatorFactory.create( partij=partij, @@ -2588,7 +2565,7 @@ def test_vestigingsnummer_invalid_create_external_partij(self): partij_identificator_code_register="hr", ) - # sub_identificator_van partij is different from vestigingsnummer + # sub_identificator_van partij is different from vestigingsnummer partij data = { "identificeerdePartij": {"uuid": str(self.partij.uuid)}, "sub_identificator_van": {"uuid": str(sub_identificator_van.uuid)}, @@ -2599,19 +2576,6 @@ def test_vestigingsnummer_invalid_create_external_partij(self): "codeRegister": "hr", }, } - - response = self.client.post(self.list_url, data) - self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) - self.assertEqual(response.data["code"], "invalid") - self.assertEqual(response.data["title"], "Invalid input.") - self.assertEqual( - response.data["invalid_params"][0]["reason"], - "Je moet een `sub_identifier_van` selecteren die tot dezelfde partij behoort.", - ) - - # valida case sub_identificator_van partij equal vestigingsnummer partij - sub_identificator_van.partij = self.partij - sub_identificator_van.save() response = self.client.post(self.list_url, data) self.assertEqual(response.status_code, status.HTTP_201_CREATED) self.assertEqual(PartijIdentificator.objects.all().count(), 2) @@ -2637,49 +2601,13 @@ def test_vestigingsnummer_invalid_create_invalid_sub_identificator_van(self): response = self.client.post(self.list_url, data) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) - self.assertEqual(response.data["code"], "invalid") - self.assertEqual(response.data["title"], "Invalid input.") + error = get_validation_errors(response, "subIdentificatorVan") + self.assertEqual(error["code"], "invalid") self.assertEqual( - response.data["invalid_params"][0]["reason"], - "Het is alleen mogelijk om sub_identifier_vans te selecteren die CodeSoortObjectId = `kvk_nummer` hebben.", + error["reason"], + "Het is alleen mogelijk om een subIdentifierVan te selecteren met codeSoortObjectId = `kvk_nummer`.", ) - def test_vestigingsnummer_invalid_create(self): - sub_identificator_van = PartijIdentificatorFactory.create( - partij=self.partij, - partij_identificator_code_objecttype="niet_natuurlijk_persoon", - partij_identificator_code_soort_object_id="kvk_nummer", - partij_identificator_object_id="12345678", - partij_identificator_code_register="hr", - ) - - data = { - "identificeerdePartij": {"uuid": str(self.partij.uuid)}, - "sub_identificator_van": {"uuid": str(sub_identificator_van.uuid)}, - "partijIdentificator": { - "codeObjecttype": "vestiging", - "codeSoortObjectId": "vestigingsnummer", - "objectId": "296648875154", - "codeRegister": "hr", - }, - } - - response = self.client.post(self.list_url, data) - self.assertEqual(response.status_code, status.HTTP_201_CREATED) - partij_identificatoren = PartijIdentificator.objects.all() - self.assertEqual(partij_identificatoren.count(), 2) - - # create new same data - response = self.client.post(self.list_url, data) - self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) - self.assertEqual(response.data["code"], "invalid") - self.assertEqual(response.data["title"], "Invalid input.") - self.assertEqual( - response.data["invalid_params"][0]["reason"], - "`PartijIdentificator` moet uniek zijn, er bestaat er al een met deze gegevenscombinatie.", - ) - self.assertEqual(partij_identificatoren.count(), 2) - def test_vestigingsnummer_valid_update(self): sub_identificator_van = PartijIdentificatorFactory.create( partij=self.partij, @@ -2693,7 +2621,7 @@ def test_vestigingsnummer_valid_update(self): sub_identificator_van=sub_identificator_van, partij_identificator_code_objecttype="vestiging", partij_identificator_code_soort_object_id="vestigingsnummer", - partij_identificator_object_id="12345678", + partij_identificator_object_id="111122223333", partij_identificator_code_register="hr", ) @@ -2717,7 +2645,7 @@ def test_vestigingsnummer_valid_update(self): { "codeObjecttype": "vestiging", "codeSoortObjectId": "vestigingsnummer", - "objectId": "12345678", + "objectId": "111122223333", "codeRegister": "hr", }, ) @@ -2735,7 +2663,7 @@ def test_vestigingsnummer_invalid_update_set_sub_identificator_van_null(self): sub_identificator_van=sub_identificator_van, partij_identificator_code_objecttype="vestiging", partij_identificator_code_soort_object_id="vestigingsnummer", - partij_identificator_object_id="12345678", + partij_identificator_object_id="111122223333", partij_identificator_code_register="hr", ) @@ -2751,11 +2679,14 @@ def test_vestigingsnummer_invalid_update_set_sub_identificator_van_null(self): response = self.client.patch(detail_url, data) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) - self.assertEqual(response.data["code"], "invalid") - self.assertEqual(response.data["title"], "Invalid input.") - self.assertTrue( - "`sub_identifier_van` met codeSoortObjectId = `kvk_nummer` te kiezen." - in response.data["invalid_params"][0]["reason"] + error = get_validation_errors(response, "subIdentificatorVan") + self.assertEqual(error["code"], "invalid") + self.assertEqual( + error["reason"], + ( + "Voor een PartijIdentificator met codeSoortObjectId = `vestigingsnummer` is het verplicht om" + " een `sub_identifier_van` met codeSoortObjectId = `kvk_nummer` te kiezen." + ), ) def test_invalid_vestigingsnummer_and_kvk_nummer_combination_unique(self): @@ -2788,65 +2719,70 @@ def test_invalid_vestigingsnummer_and_kvk_nummer_combination_unique(self): response = self.client.post(self.list_url, data) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) - self.assertEqual(response.data["code"], "invalid") - self.assertEqual(response.data["title"], "Invalid input.") + self.assertEqual(len(response.data["invalid_params"]), 2) + self.assertEqual(response.data["invalid_params"][0]["code"], "unique_together") self.assertEqual( response.data["invalid_params"][0]["reason"], + "Partij identificator met deze Partij en Soort object ID bestaat al.", + ) + self.assertEqual(response.data["invalid_params"][1]["code"], "invalid") + self.assertEqual( + response.data["invalid_params"][1]["reason"], "`PartijIdentificator` moet uniek zijn, er bestaat er al een met deze gegevenscombinatie.", ) - def test_vestigingsnummer_invalid_update_unique(self): - sub_identificator_van_a = PartijIdentificatorFactory.create( + def test_valid_protect_delete(self): + partij_identificator = PartijIdentificatorFactory.create( partij=self.partij, partij_identificator_code_objecttype="niet_natuurlijk_persoon", partij_identificator_code_soort_object_id="kvk_nummer", partij_identificator_object_id="12345678", partij_identificator_code_register="hr", ) - sub_identificator_van_b = PartijIdentificatorFactory.create( + detail_url = reverse( + "klantinteracties:partijidentificator-detail", + kwargs={"uuid": str(partij_identificator.uuid)}, + ) + self.assertEqual(PartijIdentificator.objects.all().count(), 1) + response = self.client.delete(detail_url) + self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) + self.assertEqual(PartijIdentificator.objects.all().count(), 0) + + def test_invalid_protect_delete(self): + sub_identificator_van = PartijIdentificatorFactory.create( partij=self.partij, partij_identificator_code_objecttype="niet_natuurlijk_persoon", partij_identificator_code_soort_object_id="kvk_nummer", partij_identificator_object_id="12345678", partij_identificator_code_register="hr", ) - partij_identificator_a = PartijIdentificatorFactory.create( - partij=self.partij, - sub_identificator_van=sub_identificator_van_a, - partij_identificator_code_objecttype="vestiging", - partij_identificator_code_soort_object_id="vestigingsnummer", - partij_identificator_object_id="12345678", - partij_identificator_code_register="hr", - ) PartijIdentificatorFactory.create( partij=self.partij, - sub_identificator_van=sub_identificator_van_b, + sub_identificator_van=sub_identificator_van, partij_identificator_code_objecttype="vestiging", partij_identificator_code_soort_object_id="vestigingsnummer", - partij_identificator_object_id="12345678", + partij_identificator_object_id="296648875154", partij_identificator_code_register="hr", ) - - self.assertEqual(PartijIdentificator.objects.count(), 4) - - data = { - "sub_identificator_van": {"uuid": str(sub_identificator_van_b.uuid)}, - } detail_url = reverse( "klantinteracties:partijidentificator-detail", - kwargs={"uuid": str(partij_identificator_a.uuid)}, + kwargs={"uuid": str(sub_identificator_van.uuid)}, ) - - response = self.client.patch(detail_url, data) + self.assertEqual(PartijIdentificator.objects.all().count(), 2) + response = self.client.delete(detail_url) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) - self.assertEqual(response.data["code"], "invalid") - self.assertEqual(response.data["title"], "Invalid input.") - + error = get_validation_errors(response, "nonFieldErrors") + self.assertEqual(error["code"], "invalid") self.assertEqual( - response.data["invalid_params"][0]["reason"], - "`PartijIdentificator` moet uniek zijn, er bestaat er al een met deze gegevenscombinatie.", + error["reason"], + ( + "Cannot delete some instances of model 'PartijIdentificator' because they are" + " referenced through protected foreign keys: 'PartijIdentificator.sub_identificator_van'." + ), ) + self.assertEqual(PartijIdentificator.objects.all().count(), 2) + class CategorieRelatieTests(APITestCase): def test_list_categorie_relatie(self): @@ -3132,17 +3068,11 @@ def test_create_vertegenwoordigden(self): with self.subTest("test_unique_together"): response = self.client.post(list_url, data) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) - data = response.json() + error = get_validation_errors(response, "nonFieldErrors") + self.assertEqual(error["code"], "unique") self.assertEqual( - data["invalidParams"], - [ - { - "name": "nonFieldErrors", - "code": "unique", - "reason": "De velden vertegenwoordigende_partij, vertegenwoordigde_partij " - "moeten een unieke set zijn.", - } - ], + error["reason"], + "De velden vertegenwoordigende_partij, vertegenwoordigde_partij moeten een unieke set zijn.", ) with self.subTest("test_partij_can_not_vertegenwoordig_it_self"): @@ -3152,16 +3082,11 @@ def test_create_vertegenwoordigden(self): } response = self.client.post(list_url, data) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) - data = response.json() + error = get_validation_errors(response, "vertegenwoordigdePartij") + self.assertEqual(error["code"], "invalid") self.assertEqual( - data["invalidParams"], - [ - { - "name": "vertegenwoordigdePartij", - "code": "invalid", - "reason": "De partij kan niet zichzelf vertegenwoordigen.", - } - ], + error["reason"], + "De partij kan niet zichzelf vertegenwoordigen.", ) def test_update_vertegenwoordigden(self): @@ -3225,16 +3150,11 @@ def test_update_partial_vertegenwoordigden(self): } response = self.client.patch(detail_url, data) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) - data = response.json() + error = get_validation_errors(response, "vertegenwoordigdePartij") + self.assertEqual(error["code"], "invalid") self.assertEqual( - data["invalidParams"], - [ - { - "code": "invalid", - "name": "vertegenwoordigdePartij", - "reason": "De partij kan niet zichzelf vertegenwoordigen.", - } - ], + error["reason"], + "De partij kan niet zichzelf vertegenwoordigen.", ) with self.subTest("test_unique_together"): @@ -3253,17 +3173,11 @@ def test_update_partial_vertegenwoordigden(self): # update new vertegenwoordigde object to have same data as the existing one. response = self.client.patch(detail_url, data) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) - data = response.json() + error = get_validation_errors(response, "nonFieldErrors") + self.assertEqual(error["code"], "unique") self.assertEqual( - data["invalidParams"], - [ - { - "name": "nonFieldErrors", - "code": "unique", - "reason": "De velden vertegenwoordigende_partij, vertegenwoordigde_partij " - "moeten een unieke set zijn.", - } - ], + error["reason"], + "De velden vertegenwoordigende_partij, vertegenwoordigde_partij moeten een unieke set zijn.", ) def test_destroy_vertegenwoordigden(self): diff --git a/src/openklant/components/klantinteracties/api/viewsets/partijen.py b/src/openklant/components/klantinteracties/api/viewsets/partijen.py index 46624d77..6f8d0e78 100644 --- a/src/openklant/components/klantinteracties/api/viewsets/partijen.py +++ b/src/openklant/components/klantinteracties/api/viewsets/partijen.py @@ -26,6 +26,7 @@ from openklant.components.token.authentication import TokenAuthentication from openklant.components.token.permission import TokenPermissions from openklant.components.utils.mixins import ExpandMixin +from openklant.utils.decorators import handle_db_exceptions @extend_schema(tags=["partijen"]) @@ -252,3 +253,7 @@ class PartijIdentificatorViewSet(viewsets.ModelViewSet): ] authentication_classes = (TokenAuthentication,) permission_classes = (TokenPermissions,) + + @handle_db_exceptions + def destroy(self, request, *args, **kwargs): + return super().destroy(request, *args, **kwargs) diff --git a/src/openklant/components/klantinteracties/migrations/0028_partijidentificator_sub_identificator_van.py b/src/openklant/components/klantinteracties/migrations/0028_partijidentificator_sub_identificator_van.py deleted file mode 100644 index 9f16f6ad..00000000 --- a/src/openklant/components/klantinteracties/migrations/0028_partijidentificator_sub_identificator_van.py +++ /dev/null @@ -1,34 +0,0 @@ -# Generated by Django 4.2.17 on 2025-02-20 09:51 - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ( - "klantinteracties", - "0027_alter_betrokkene_bezoekadres_nummeraanduiding_id_and_more", - ), - ] - - operations = [ - migrations.AddField( - model_name="partijidentificator", - name="sub_identificator_van", - field=models.ForeignKey( - blank=True, - help_text=( - "The parent PartijIdentificator under which this PartijIdentificator is unique " - "(e.g. the parent identificator could specify a KVK number and the child identificator " - "could specify a vestigingsnummer that is unique for the KVK number)." - ), - null=True, - on_delete=django.db.models.deletion.SET_NULL, - related_name="parent_partij_identificator", - to="klantinteracties.partijidentificator", - verbose_name="sub identificator van", - ), - ), - ] diff --git a/src/openklant/components/klantinteracties/migrations/0028_partijidentificator_sub_identificator_van_and_more.py b/src/openklant/components/klantinteracties/migrations/0028_partijidentificator_sub_identificator_van_and_more.py new file mode 100644 index 00000000..fba66f98 --- /dev/null +++ b/src/openklant/components/klantinteracties/migrations/0028_partijidentificator_sub_identificator_van_and_more.py @@ -0,0 +1,67 @@ +# Generated by Django 4.2.17 on 2025-02-26 13:21 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ( + "klantinteracties", + "0027_alter_betrokkene_bezoekadres_nummeraanduiding_id_and_more", + ), + ] + + operations = [ + migrations.AddField( + model_name="partijidentificator", + name="sub_identificator_van", + field=models.ForeignKey( + blank=True, + help_text="The parent PartijIdentificator under which this PartijIdentificator is unique (e.g. the parent identificator could specify a KVK number and the child identificator could specify a vestigingsnummer that is unique for the KVK number).", + null=True, + on_delete=django.db.models.deletion.PROTECT, + related_name="parent_partij_identificator", + to="klantinteracties.partijidentificator", + verbose_name="sub identificator van", + ), + ), + migrations.AddConstraint( + model_name="partijidentificator", + constraint=models.UniqueConstraint( + condition=models.Q(("sub_identificator_van__isnull", True)), + fields=( + "partij_identificator_code_objecttype", + "partij_identificator_code_soort_object_id", + "partij_identificator_object_id", + "partij_identificator_code_register", + ), + name="non_scoped_identificator_globally_unique", + violation_error_message="`PartijIdentificator` moet uniek zijn, er bestaat er al een met deze gegevenscombinatie.", + ), + ), + migrations.AddConstraint( + model_name="partijidentificator", + constraint=models.UniqueConstraint( + fields=("partij", "partij_identificator_code_soort_object_id"), + name="non_scoped_identificator_locally_unique", + violation_error_message="`CodeSoortObjectId` moet uniek zijn voor de Partij.", + ), + ), + migrations.AddConstraint( + model_name="partijidentificator", + constraint=models.UniqueConstraint( + condition=models.Q(("sub_identificator_van__isnull", False)), + fields=( + "sub_identificator_van", + "partij_identificator_code_objecttype", + "partij_identificator_code_soort_object_id", + "partij_identificator_object_id", + "partij_identificator_code_register", + ), + name="scoped_identificator_globally_unique", + violation_error_message="`PartijIdentificator` moet uniek zijn, er bestaat er al een met deze gegevenscombinatie.", + ), + ), + ] diff --git a/src/openklant/components/klantinteracties/models/partijen.py b/src/openklant/components/klantinteracties/models/partijen.py index b7cdb5ac..e2e78629 100644 --- a/src/openklant/components/klantinteracties/models/partijen.py +++ b/src/openklant/components/klantinteracties/models/partijen.py @@ -336,7 +336,7 @@ class PartijIdentificator(models.Model): ) sub_identificator_van = models.ForeignKey( "self", - on_delete=models.SET_NULL, + on_delete=models.PROTECT, verbose_name=_("sub identificator van"), help_text=_( "The parent PartijIdentificator under which this PartijIdentificator is unique " @@ -419,8 +419,64 @@ class Meta: verbose_name = _("partij identificator") verbose_name_plural = _("partij identificatoren") - def __str__(self): - soort_object = self.partij_identificator_code_soort_object_id - object = self.partij_identificator_object_id + constraints = [ + models.UniqueConstraint( + fields=[ + "partij_identificator_code_objecttype", + "partij_identificator_code_soort_object_id", + "partij_identificator_object_id", + "partij_identificator_code_register", + ], + condition=models.Q(sub_identificator_van__isnull=True), + name="non_scoped_identificator_globally_unique", + violation_error_message=_( + "`PartijIdentificator` moet uniek zijn, er bestaat er al een met deze gegevenscombinatie." + ), + ), + models.UniqueConstraint( + fields=[ + "partij", + "partij_identificator_code_soort_object_id", + ], + name="non_scoped_identificator_locally_unique", + violation_error_message=_( + "`CodeSoortObjectId` moet uniek zijn voor de Partij." + ), + ), + models.UniqueConstraint( + fields=[ + "sub_identificator_van", + "partij_identificator_code_objecttype", + "partij_identificator_code_soort_object_id", + "partij_identificator_object_id", + "partij_identificator_code_register", + ], + condition=models.Q(sub_identificator_van__isnull=False), + name="scoped_identificator_globally_unique", + violation_error_message=_( + "`PartijIdentificator` moet uniek zijn, er bestaat er al een met deze gegevenscombinatie." + ), + ), + ] + + def clean_sub_identificator_van(self): + if self.sub_identificator_van: + if self.sub_identificator_van == self: + raise ValidationError( + { + "sub_identificator_van": _( + "Een `Partijidentificator` kan geen `subIdentificatorVan` zijn van zichzelf." + ) + } + ) + + def save(self, *args, **kwargs): + self.full_clean() + super().save(*args, **kwargs) - return f"{soort_object} - {object}" + def clean(self): + super().clean() + self.clean_sub_identificator_van() + + def __str__(self): + return f"{self.partij_identificator_code_soort_object_id} - {self.partij_identificator_object_id}" diff --git a/src/openklant/components/klantinteracties/models/tests/test_partijen.py b/src/openklant/components/klantinteracties/models/tests/test_partijen.py new file mode 100644 index 00000000..a395f95c --- /dev/null +++ b/src/openklant/components/klantinteracties/models/tests/test_partijen.py @@ -0,0 +1,209 @@ +from django.core.exceptions import ValidationError +from django.db.models import ProtectedError +from django.test import TestCase + +from openklant.components.klantinteracties.models.partijen import PartijIdentificator +from openklant.components.klantinteracties.models.tests.factories.partijen import ( + PartijFactory, +) + + +class PartijIdentificatorModelConstraints(TestCase): + + def test_valid_globally_unique(self): + partij_a = PartijFactory.create() + partij_identificator_a = PartijIdentificator.objects.create( + partij=partij_a, + partij_identificator_code_objecttype="niet_natuurlijk_persoon", + partij_identificator_code_soort_object_id="kvk_nummer", + partij_identificator_object_id="12345678", + partij_identificator_code_register="hr", + ) + + partij_b = PartijFactory.create() + partij_identificator_b = PartijIdentificator.objects.create( + partij=partij_b, + partij_identificator_code_objecttype="niet_natuurlijk_persoon", + partij_identificator_code_soort_object_id="kvk_nummer", + partij_identificator_object_id="87654321", + partij_identificator_code_register="hr", + ) + + # different sub_identificator_van same values + PartijIdentificator.objects.create( + partij=partij_a, + sub_identificator_van=partij_identificator_a, + partij_identificator_code_objecttype="vestiging", + partij_identificator_code_soort_object_id="vestigingsnummer", + partij_identificator_object_id="123412341234", + partij_identificator_code_register="hr", + ) + + PartijIdentificator.objects.create( + partij=partij_b, + sub_identificator_van=partij_identificator_b, + partij_identificator_code_objecttype="vestiging", + partij_identificator_code_soort_object_id="vestigingsnummer", + partij_identificator_object_id="123412341234", + partij_identificator_code_register="hr", + ) + + # different values same sub_identificator_van + PartijIdentificator.objects.create( + partij=partij_a, + sub_identificator_van=partij_identificator_a, + partij_identificator_code_objecttype="natuurlijk_persoon", + partij_identificator_code_soort_object_id="bsn", + partij_identificator_object_id="123456782", + partij_identificator_code_register="brp", + ) + PartijIdentificator.objects.create( + partij=partij_b, + sub_identificator_van=partij_identificator_b, + partij_identificator_code_objecttype="natuurlijk_persoon", + partij_identificator_code_soort_object_id="bsn", + partij_identificator_object_id="123456782", + partij_identificator_code_register="brp", + ) + + def test_non_scoped_identificator_globally_unique(self): + partij = PartijFactory.create() + PartijIdentificator.objects.create( + partij=partij, + sub_identificator_van=None, + partij_identificator_code_objecttype="niet_natuurlijk_persoon", + partij_identificator_code_soort_object_id="kvk_nummer", + partij_identificator_object_id="87654321", + partij_identificator_code_register="hr", + ) + with self.assertRaises(ValidationError) as error: + PartijIdentificator.objects.create( + partij=partij, + sub_identificator_van=None, + partij_identificator_code_objecttype="niet_natuurlijk_persoon", + partij_identificator_code_soort_object_id="kvk_nummer", + partij_identificator_object_id="87654321", + partij_identificator_code_register="hr", + ) + self.assertEqual( + error.exception.message_dict, + { + "__all__": [ + "`PartijIdentificator` moet uniek zijn, er bestaat er al een met deze gegevenscombinatie.", + "Partij identificator met deze Partij en Soort object ID bestaat al.", + ] + }, + ) + + def test_scoped_identificator_globally_unique(self): + partij = PartijFactory.create() + sub_identificator_van = PartijIdentificator.objects.create( + partij=partij, + partij_identificator_code_objecttype="niet_natuurlijk_persoon", + partij_identificator_code_soort_object_id="kvk_nummer", + partij_identificator_object_id="87654321", + partij_identificator_code_register="hr", + ) + PartijIdentificator.objects.create( + partij=partij, + sub_identificator_van=sub_identificator_van, + partij_identificator_code_objecttype="natuurlijk_persoon", + partij_identificator_code_soort_object_id="bsn", + partij_identificator_object_id="123456782", + partij_identificator_code_register="brp", + ) + + with self.assertRaises(ValidationError) as error: + PartijIdentificator.objects.create( + partij=partij, + sub_identificator_van=sub_identificator_van, + partij_identificator_code_objecttype="natuurlijk_persoon", + partij_identificator_code_soort_object_id="bsn", + partij_identificator_object_id="123456782", + partij_identificator_code_register="brp", + ) + self.assertEqual( + error.exception.message_dict, + { + "__all__": [ + "Partij identificator met deze Partij en Soort object ID bestaat al.", + "`PartijIdentificator` moet uniek zijn, er bestaat er al een met deze gegevenscombinatie.", + ] + }, + ) + + def test_non_scoped_identificator_locally_unique(self): + partij = PartijFactory.create() + PartijIdentificator.objects.create( + partij=partij, + partij_identificator_code_objecttype="niet_natuurlijk_persoon", + partij_identificator_code_soort_object_id="kvk_nummer", + partij_identificator_object_id="87654321", + partij_identificator_code_register="hr", + ) + + with self.assertRaises(ValidationError) as error: + PartijIdentificator.objects.create( + partij=partij, + partij_identificator_code_objecttype="niet_natuurlijk_persoon", + partij_identificator_code_soort_object_id="kvk_nummer", + partij_identificator_object_id="11112222", + partij_identificator_code_register="hr", + ) + + self.assertEqual( + error.exception.message_dict, + { + "__all__": [ + "Partij identificator met deze Partij en Soort object ID bestaat al." + ] + }, + ) + + def test_protected_delete(self): + partij = PartijFactory.create() + sub_identificator_van = PartijIdentificator.objects.create( + partij=partij, + partij_identificator_code_objecttype="niet_natuurlijk_persoon", + partij_identificator_code_soort_object_id="kvk_nummer", + partij_identificator_object_id="87654321", + partij_identificator_code_register="hr", + ) + PartijIdentificator.objects.create( + partij=partij, + sub_identificator_van=sub_identificator_van, + partij_identificator_code_objecttype="natuurlijk_persoon", + partij_identificator_code_soort_object_id="bsn", + partij_identificator_object_id="123456782", + partij_identificator_code_register="brp", + ) + with self.assertRaises(ProtectedError) as error: + PartijIdentificator.objects.filter(uuid=sub_identificator_van.uuid).delete() + + self.assertTrue( + "Cannot delete some instances of model 'PartijIdentificator'" + in error.exception.args[0], + ) + + +class PartijIdentificatorModelClean(TestCase): + + def test_clean_sub_identificator_van_different_self_related(self): + partij_identificator = PartijIdentificator.objects.create( + partij=PartijFactory.create(), + partij_identificator_code_objecttype="niet_natuurlijk_persoon", + partij_identificator_code_soort_object_id="kvk_nummer", + partij_identificator_object_id="87654321", + partij_identificator_code_register="hr", + ) + with self.assertRaises(ValidationError) as error: + partij_identificator.sub_identificator_van = partij_identificator + partij_identificator.save() + self.assertEqual( + error.exception.message_dict, + { + "sub_identificator_van": [ + "Een `Partijidentificator` kan geen `subIdentificatorVan` zijn van zichzelf." + ] + }, + ) diff --git a/src/openklant/components/klantinteracties/models/validators.py b/src/openklant/components/klantinteracties/models/validators.py index bd491c97..e670c03e 100644 --- a/src/openklant/components/klantinteracties/models/validators.py +++ b/src/openklant/components/klantinteracties/models/validators.py @@ -7,10 +7,7 @@ validate_rsin, ) -from openklant.components.klantinteracties.models.partijen import ( - Partij, - PartijIdentificator, -) +from openklant.components.klantinteracties.models.partijen import PartijIdentificator from .constants import ( PartijIdentificatorCodeObjectType, @@ -25,61 +22,30 @@ class PartijIdentificatorUniquenessValidator: def __init__( self, - partij_identificator: dict | None = {}, + code_soort_object_id: str | None = "", sub_identificator_van: PartijIdentificator | None = None, - instance: PartijIdentificator | None = None, - partij: Partij | None = None, ): - self.partij_identificator = partij_identificator + self.code_soort_object_id = code_soort_object_id self.sub_identificator_van = sub_identificator_van - self.instance = instance - self.partij = partij - self.queryset = PartijIdentificator.objects.all() - - if self.instance: - self.queryset = self.queryset.exclude(pk=self.instance.pk) - - if not self.partij_identificator: - raise ValueError("partij_identificator is required") def __call__(self): - self.validate_not_self_assigned() if ( - self.partij_identificator["code_soort_object_id"] + self.code_soort_object_id == PartijIdentificatorCodeSoortObjectId.vestigingsnummer.value ): self.validate_sub_identificator_van_for_vestigingsnummer() - self.validate_unique_partij_identificator_locally() - self.validate_unique_partij_identificator_globally() - - def validate_not_self_assigned(self): - """ - Validation that the current instance does not assign itself as 'sub_identificator_van'. - """ - if self.sub_identificator_van and self.sub_identificator_van == self.instance: - raise ValidationError( - { - "sub_identificator_van": _( - "Kan zichzelf niet selecteren als `subIdentificatorVan`." - ) - } - ) def validate_sub_identificator_van_for_vestigingsnummer(self): """ - Validation that when the partij_identificator has CodeSoortObjectId = `vestigingsnummer`: - - if the `sub_identificator_van` is not selected - - the `sub_identificator_van` must have CodeSoortObjectId = `kvk_nummer`. - - cannot be assigned to a null Partij - - cannot be assigned to a Partij that doesn't have another partij_identificator - with CodeSoortObjectId = `kvk_nummer` - + - `sub_identificator_van` is required + - `sub_identificator_van` must have CodeSoortObjectId = `kvk_nummer` """ if not self.sub_identificator_van: raise ValidationError( { "sub_identificator_van": _( - "Voor de Identificator met codeSoortObjectId = `vestigingsnummer` is het verplicht om" + "Voor een PartijIdentificator met codeSoortObjectId = `vestigingsnummer` is het verplicht om" " een `sub_identifier_van` met codeSoortObjectId = `kvk_nummer` te kiezen." ) } @@ -92,93 +58,12 @@ def validate_sub_identificator_van_for_vestigingsnummer(self): raise ValidationError( { "sub_identificator_van": _( - "Het is alleen mogelijk om sub_identifier_vans te selecteren" - " die CodeSoortObjectId = `kvk_nummer` hebben." + "Het is alleen mogelijk om een subIdentifierVan te selecteren met " + "codeSoortObjectId = `kvk_nummer`." ) } ) - if not self.partij: - raise ValidationError( - { - "sub_identificator_van": _( - "Het is niet mogelijk om een partij_identificator te maken zonder de partij" - "waartoe deze behoort te specificeren." - ) - } - ) - - if ( - not self.queryset.filter(partij=self.partij) - .filter( - partij_identificator_code_soort_object_id=PartijIdentificatorCodeSoortObjectId.kvk_nummer.value - ) - .exists() - ): - raise ValidationError( - { - "sub_identificator_van": _( - "Je moet een `sub_identifier_van` selecteren die tot dezelfde partij behoort." - ) - } - ) - - def validate_unique_partij_identificator_locally(self): - """ - Validation that a single Partij can only have a single partij_identificator_code_soort_object_id type locally - """ - return # TODO unlock after feedback - if ( - self.partij - and self.queryset.filter(partij=self.partij) - .filter( - partij_identificator_code_soort_object_id=self.partij_identificator[ - "code_soort_object_id" - ] - ) - .exists() - ): - raise ValidationError( - { - "partij_identificator_code_soort_object_id": _( - "Er is al een PartyIdentificator met dit CodeSoortObjectId = '%s' voor deze Partij." - % (self.partij_identificator["code_soort_object_id"]) - ) - } - ) - - def validate_unique_partij_identificator_globally(self): - """ - Validation that a single partij_identifier combination occurs only once globally - """ - filters = { - "partij_identificator_code_objecttype": self.partij_identificator[ - "code_objecttype" - ], - "partij_identificator_code_soort_object_id": self.partij_identificator[ - "code_soort_object_id" - ], - "partij_identificator_object_id": self.partij_identificator["object_id"], - "partij_identificator_code_register": self.partij_identificator[ - "code_register" - ], - } - - if self.sub_identificator_van is None: - filters["sub_identificator_van__isnull"] = True - else: - filters["sub_identificator_van"] = self.sub_identificator_van - - if self.queryset.filter(**filters).exists(): - raise ValidationError( - { - "__all__": _( - "`PartijIdentificator` moet uniek zijn, er bestaat er al een met deze gegevenscombinatie." - ) - } - ) - return - class PartijIdentificatorTypesValidator: """ @@ -226,15 +111,19 @@ class PartijIdentificatorTypesValidator: ], } - def __init__(self, partij_identificator: dict) -> None: + def __call__( + self, + code_register: str, + code_objecttype: str, + code_soort_object_id: str, + object_id: str, + ) -> None: """Initialize validator""" - self.code_register = partij_identificator["code_register"] - self.code_objecttype = partij_identificator["code_objecttype"] - self.code_soort_object_id = partij_identificator["code_soort_object_id"] - self.object_id = partij_identificator["object_id"] + self.code_register = code_register + self.code_objecttype = code_objecttype + self.code_soort_object_id = code_soort_object_id + self.object_id = object_id - def __call__(self) -> None: - """Run all validations""" self.validate_code_objecttype() self.validate_code_soort_object_id() self.validate_object_id() diff --git a/src/openklant/components/klantinteracties/openapi.yaml b/src/openklant/components/klantinteracties/openapi.yaml index fb0120e5..c36eaa53 100644 --- a/src/openklant/components/klantinteracties/openapi.yaml +++ b/src/openklant/components/klantinteracties/openapi.yaml @@ -4754,6 +4754,11 @@ components: nullable: true description: Gegevens die een partij in een basisregistratie of ander extern register uniek identificeren. + subIdentificatorVan: + allOf: + - $ref: '#/components/schemas/PartijIdentificatorForeignkey' + nullable: true + description: Relatie sub_identificator_van required: - identificeerdePartij - partijIdentificator @@ -4804,6 +4809,11 @@ components: oneOf: - $ref: '#/components/schemas/CodeRegisterEnum' - $ref: '#/components/schemas/BlankEnum' + required: + - codeObjecttype + - codeRegister + - codeSoortObjectId + - objectId PartijPolymorphic: type: object properties: @@ -5296,6 +5306,11 @@ components: nullable: true description: Gegevens die een partij in een basisregistratie of ander extern register uniek identificeren. + subIdentificatorVan: + allOf: + - $ref: '#/components/schemas/PartijIdentificatorForeignkey' + nullable: true + description: Relatie sub_identificator_van PatchedRekeningnummer: type: object properties: diff --git a/src/openklant/components/klantinteracties/tests/test_validators.py b/src/openklant/components/klantinteracties/tests/test_validators.py index 43395f0e..2532b0de 100644 --- a/src/openklant/components/klantinteracties/tests/test_validators.py +++ b/src/openklant/components/klantinteracties/tests/test_validators.py @@ -6,72 +6,53 @@ PartijIdentificatorCodeRegister, PartijIdentificatorCodeSoortObjectId, ) -from openklant.components.klantinteracties.models.tests.factories.partijen import ( - PartijFactory, - PartijIdentificatorFactory, -) from openklant.components.klantinteracties.models.validators import ( PartijIdentificatorTypesValidator, - PartijIdentificatorUniquenessValidator, ) class PartijIdentificatorTypesValidatorTests(TestCase): def test_valid(self): - PartijIdentificatorTypesValidator( - partij_identificator={ - "code_objecttype": PartijIdentificatorCodeObjectType.natuurlijk_persoon.value, - "code_soort_object_id": PartijIdentificatorCodeSoortObjectId.bsn.value, - "object_id": "296648875", - "code_register": PartijIdentificatorCodeRegister.brp.value, - } - )() + PartijIdentificatorTypesValidator()( + code_objecttype=PartijIdentificatorCodeObjectType.natuurlijk_persoon.value, + code_soort_object_id=PartijIdentificatorCodeSoortObjectId.bsn.value, + object_id="296648875", + code_register=PartijIdentificatorCodeRegister.brp.value, + ) # Start section validate_code_objecttype def test_valid_code_objecttype_null(self): - validator = PartijIdentificatorTypesValidator( - partij_identificator={ - "code_objecttype": "", - "code_soort_object_id": PartijIdentificatorCodeSoortObjectId.bsn.value, - "object_id": "296648875", - "code_register": PartijIdentificatorCodeRegister.brp.value, - } + PartijIdentificatorTypesValidator()( + code_objecttype="", + code_soort_object_id=PartijIdentificatorCodeSoortObjectId.bsn.value, + object_id="296648875", + code_register=PartijIdentificatorCodeRegister.brp.value, ) - validator.validate_code_objecttype() def test_valid_code_objecttype_top_level_null_or_overig(self): - validator = PartijIdentificatorTypesValidator( - partij_identificator={ - "code_objecttype": PartijIdentificatorCodeObjectType.natuurlijk_persoon.value, - "code_soort_object_id": PartijIdentificatorCodeSoortObjectId.bsn.value, - "object_id": "296648875", - "code_register": "", - } + PartijIdentificatorTypesValidator()( + code_objecttype=PartijIdentificatorCodeObjectType.natuurlijk_persoon.value, + code_soort_object_id=PartijIdentificatorCodeSoortObjectId.bsn.value, + object_id="296648875", + code_register="", ) - validator.validate_code_objecttype() - validator = PartijIdentificatorTypesValidator( - partij_identificator={ - "code_objecttype": PartijIdentificatorCodeObjectType.natuurlijk_persoon.value, - "code_soort_object_id": PartijIdentificatorCodeSoortObjectId.bsn.value, - "object_id": "296648875", - "code_register": PartijIdentificatorCodeRegister.overig.value, - } + PartijIdentificatorTypesValidator()( + code_objecttype=PartijIdentificatorCodeObjectType.natuurlijk_persoon.value, + code_soort_object_id=PartijIdentificatorCodeSoortObjectId.bsn.value, + object_id="296648875", + code_register=PartijIdentificatorCodeRegister.overig.value, ) - validator.validate_code_objecttype() def test_invalid_code_objecttype_not_found_in_top_level(self): with self.assertRaises(ValidationError) as error: - validator = PartijIdentificatorTypesValidator( - partij_identificator={ - "code_objecttype": PartijIdentificatorCodeObjectType.niet_natuurlijk_persoon.value, - "code_soort_object_id": PartijIdentificatorCodeSoortObjectId.bsn.value, - "object_id": "296648875", - "code_register": PartijIdentificatorCodeRegister.brp.value, - } + PartijIdentificatorTypesValidator()( + code_objecttype=PartijIdentificatorCodeObjectType.niet_natuurlijk_persoon.value, + code_soort_object_id=PartijIdentificatorCodeSoortObjectId.bsn.value, + object_id="296648875", + code_register=PartijIdentificatorCodeRegister.brp.value, ) - validator.validate_code_objecttype() details = error.exception.message_dict self.assertEqual( @@ -82,27 +63,21 @@ def test_invalid_code_objecttype_not_found_in_top_level(self): # Start section validate_code_soort_object_id def test_valid_code_soort_object_id_null(self): - validator = PartijIdentificatorTypesValidator( - partij_identificator={ - "code_objecttype": PartijIdentificatorCodeObjectType.natuurlijk_persoon.value, - "code_soort_object_id": "", - "object_id": "12345678", - "code_register": PartijIdentificatorCodeRegister.brp.value, - } + PartijIdentificatorTypesValidator()( + code_objecttype=PartijIdentificatorCodeObjectType.natuurlijk_persoon.value, + code_soort_object_id="", + object_id="12345678", + code_register=PartijIdentificatorCodeRegister.brp.value, ) - validator.validate_code_soort_object_id() def test_invalid_code_soort_object_id_not_found_in_top_level(self): with self.assertRaises(ValidationError) as error: - validator = PartijIdentificatorTypesValidator( - partij_identificator={ - "code_objecttype": PartijIdentificatorCodeObjectType.natuurlijk_persoon.value, - "code_soort_object_id": PartijIdentificatorCodeSoortObjectId.rsin.value, - "object_id": "296648875", - "code_register": PartijIdentificatorCodeRegister.brp.value, - } + PartijIdentificatorTypesValidator()( + code_objecttype=PartijIdentificatorCodeObjectType.natuurlijk_persoon.value, + code_soort_object_id=PartijIdentificatorCodeSoortObjectId.rsin.value, + object_id="296648875", + code_register=PartijIdentificatorCodeRegister.brp.value, ) - validator.validate_code_soort_object_id() details = error.exception.message_dict self.assertEqual( @@ -113,92 +88,68 @@ def test_invalid_code_soort_object_id_not_found_in_top_level(self): # Start section validate_object_id def test_valid_object_id_null(self): - validator = PartijIdentificatorTypesValidator( - partij_identificator={ - "code_objecttype": PartijIdentificatorCodeObjectType.natuurlijk_persoon.value, - "code_soort_object_id": PartijIdentificatorCodeSoortObjectId.bsn.value, - "object_id": "", - "code_register": PartijIdentificatorCodeRegister.brp.value, - } + PartijIdentificatorTypesValidator()( + code_objecttype=PartijIdentificatorCodeObjectType.natuurlijk_persoon.value, + code_soort_object_id=PartijIdentificatorCodeSoortObjectId.bsn.value, + object_id="", + code_register=PartijIdentificatorCodeRegister.brp.value, ) - validator.validate_object_id() def test_valid_object_id_top_level_null_or_overig(self): - validator = PartijIdentificatorTypesValidator( - partij_identificator={ - "code_objecttype": PartijIdentificatorCodeObjectType.natuurlijk_persoon.value, - "code_soort_object_id": "", - "object_id": "1123", - "code_register": PartijIdentificatorCodeRegister.brp.value, - } + PartijIdentificatorTypesValidator()( + code_objecttype=PartijIdentificatorCodeObjectType.natuurlijk_persoon.value, + code_soort_object_id="", + object_id="1123", + code_register=PartijIdentificatorCodeRegister.brp.value, ) - validator.validate_object_id() - validator = PartijIdentificatorTypesValidator( - partij_identificator={ - "code_objecttype": PartijIdentificatorCodeObjectType.natuurlijk_persoon.value, - "code_soort_object_id": PartijIdentificatorCodeSoortObjectId.overig.value, - "object_id": "1123", - "code_register": PartijIdentificatorCodeRegister.brp.value, - } + PartijIdentificatorTypesValidator()( + code_objecttype=PartijIdentificatorCodeObjectType.natuurlijk_persoon.value, + code_soort_object_id=PartijIdentificatorCodeSoortObjectId.overig.value, + object_id="1123", + code_register=PartijIdentificatorCodeRegister.brp.value, ) - validator.validate_object_id() def test_valid_object_id_bsn(self): - validator = PartijIdentificatorTypesValidator( - partij_identificator={ - "code_objecttype": PartijIdentificatorCodeObjectType.natuurlijk_persoon.value, - "code_soort_object_id": PartijIdentificatorCodeSoortObjectId.bsn.value, - "object_id": "296648875", - "code_register": PartijIdentificatorCodeRegister.brp.value, - } + PartijIdentificatorTypesValidator()( + code_objecttype=PartijIdentificatorCodeObjectType.natuurlijk_persoon.value, + code_soort_object_id=PartijIdentificatorCodeSoortObjectId.bsn.value, + object_id="296648875", + code_register=PartijIdentificatorCodeRegister.brp.value, ) - validator.validate_object_id() def test_valid_object_id_vestigingsnummer(self): - validator = PartijIdentificatorTypesValidator( - partij_identificator={ - "code_objecttype": PartijIdentificatorCodeObjectType.vestiging.value, - "code_soort_object_id": PartijIdentificatorCodeSoortObjectId.vestigingsnummer.value, - "object_id": "296648875154", - "code_register": PartijIdentificatorCodeRegister.hr.value, - } + PartijIdentificatorTypesValidator()( + code_objecttype=PartijIdentificatorCodeObjectType.vestiging.value, + code_soort_object_id=PartijIdentificatorCodeSoortObjectId.vestigingsnummer.value, + object_id="296648875154", + code_register=PartijIdentificatorCodeRegister.hr.value, ) - validator.validate_object_id() def test_valid_object_id_rsin(self): - validator = PartijIdentificatorTypesValidator( - partij_identificator={ - "code_objecttype": PartijIdentificatorCodeObjectType.niet_natuurlijk_persoon.value, - "code_soort_object_id": PartijIdentificatorCodeSoortObjectId.rsin.value, - "object_id": "296648875", - "code_register": PartijIdentificatorCodeRegister.hr.value, - } + PartijIdentificatorTypesValidator()( + code_objecttype=PartijIdentificatorCodeObjectType.niet_natuurlijk_persoon.value, + code_soort_object_id=PartijIdentificatorCodeSoortObjectId.rsin.value, + object_id="296648875", + code_register=PartijIdentificatorCodeRegister.hr.value, ) - validator.validate_object_id() def test_valid_object_id_kvk_nummer(self): - validator = PartijIdentificatorTypesValidator( - partij_identificator={ - "code_objecttype": PartijIdentificatorCodeObjectType.niet_natuurlijk_persoon.value, - "code_soort_object_id": PartijIdentificatorCodeSoortObjectId.kvk_nummer.value, - "object_id": "12345678", - "code_register": PartijIdentificatorCodeRegister.hr.value, - } + PartijIdentificatorTypesValidator()( + code_objecttype=PartijIdentificatorCodeObjectType.niet_natuurlijk_persoon.value, + code_soort_object_id=PartijIdentificatorCodeSoortObjectId.kvk_nummer.value, + object_id="12345678", + code_register=PartijIdentificatorCodeRegister.hr.value, ) - validator.validate_object_id() def test_invalid_object_id_len_bsn(self): with self.assertRaises(ValidationError) as error: - validator = PartijIdentificatorTypesValidator( - partij_identificator={ - "code_objecttype": PartijIdentificatorCodeObjectType.natuurlijk_persoon.value, - "code_soort_object_id": PartijIdentificatorCodeSoortObjectId.bsn.value, - "object_id": "123", - "code_register": PartijIdentificatorCodeRegister.brp.value, - } + PartijIdentificatorTypesValidator()( + code_objecttype=PartijIdentificatorCodeObjectType.natuurlijk_persoon.value, + code_soort_object_id=PartijIdentificatorCodeSoortObjectId.bsn.value, + object_id="123", + code_register=PartijIdentificatorCodeRegister.brp.value, ) - validator.validate_object_id() details = error.exception.message_dict self.assertEqual( @@ -208,15 +159,12 @@ def test_invalid_object_id_len_bsn(self): def test_invalid_object_id_digit_bsn(self): with self.assertRaises(ValidationError) as error: - validator = PartijIdentificatorTypesValidator( - partij_identificator={ - "code_objecttype": PartijIdentificatorCodeObjectType.natuurlijk_persoon.value, - "code_soort_object_id": PartijIdentificatorCodeSoortObjectId.bsn.value, - "object_id": "123TEST123", - "code_register": PartijIdentificatorCodeRegister.brp.value, - } + PartijIdentificatorTypesValidator()( + code_objecttype=PartijIdentificatorCodeObjectType.natuurlijk_persoon.value, + code_soort_object_id=PartijIdentificatorCodeSoortObjectId.bsn.value, + object_id="123TEST123", + code_register=PartijIdentificatorCodeRegister.brp.value, ) - validator.validate_object_id() details = error.exception.message_dict self.assertEqual( @@ -226,15 +174,12 @@ def test_invalid_object_id_digit_bsn(self): def test_invalid_object_id_proef11_bsn(self): with self.assertRaises(ValidationError) as error: - validator = PartijIdentificatorTypesValidator( - partij_identificator={ - "code_objecttype": PartijIdentificatorCodeObjectType.natuurlijk_persoon.value, - "code_soort_object_id": PartijIdentificatorCodeSoortObjectId.bsn.value, - "object_id": "123456789", - "code_register": PartijIdentificatorCodeRegister.brp.value, - } + PartijIdentificatorTypesValidator()( + code_objecttype=PartijIdentificatorCodeObjectType.natuurlijk_persoon.value, + code_soort_object_id=PartijIdentificatorCodeSoortObjectId.bsn.value, + object_id="123456789", + code_register=PartijIdentificatorCodeRegister.brp.value, ) - validator.validate_object_id() details = error.exception.message_dict self.assertEqual( @@ -324,14 +269,12 @@ def test_allowed_cases(self): ], ] for case in valid_cases: - PartijIdentificatorTypesValidator( - partij_identificator={ - "code_register": case[0], - "code_objecttype": case[1], - "code_soort_object_id": case[2], - "object_id": case[3], - }, - )() + PartijIdentificatorTypesValidator()( + code_register=case[0], + code_objecttype=case[1], + code_soort_object_id=case[2], + object_id=case[3], + ) def test_not_allowed_cases(self): invalid_cases = [ @@ -356,120 +299,9 @@ def test_not_allowed_cases(self): ] for case in invalid_cases: with self.assertRaises(ValidationError): - PartijIdentificatorTypesValidator( - partij_identificator={ - "code_register": case[0], - "code_objecttype": case[1], - "code_soort_object_id": case[2], - "object_id": case[3], - } - )() - - -class PartijIdentificatorUniquenessValidatorTests(TestCase): - - def test_valid_global_uniqueness(self): - PartijIdentificatorUniquenessValidator( - partij_identificator={ - "code_objecttype": PartijIdentificatorCodeObjectType.natuurlijk_persoon.value, - "code_soort_object_id": PartijIdentificatorCodeSoortObjectId.bsn.value, - "object_id": "296648875", - "code_register": PartijIdentificatorCodeRegister.brp.value, - }, - sub_identificator_van=None, - instance=None, - )() - - def test_valid_relation_sub_identificator_van(self): - # check self relation and sub_identificator_van allowed cases - partij = PartijFactory.create() - sub_identificator_van = PartijIdentificatorFactory.create( - partij=partij, - partij_identificator_code_objecttype=PartijIdentificatorCodeObjectType.niet_natuurlijk_persoon.value, - partij_identificator_code_soort_object_id=PartijIdentificatorCodeSoortObjectId.kvk_nummer.value, - partij_identificator_object_id="12345678", - partij_identificator_code_register=PartijIdentificatorCodeRegister.hr.value, - ) - PartijIdentificatorUniquenessValidator( - partij_identificator={ - "code_objecttype": PartijIdentificatorCodeObjectType.vestiging.value, - "code_soort_object_id": PartijIdentificatorCodeSoortObjectId.vestigingsnummer.value, - "object_id": "296648875154", - "code_register": PartijIdentificatorCodeRegister.hr.value, - }, - sub_identificator_van=sub_identificator_van, - instance=None, - partij=partij, - )() - - def test_invalid_self_relation_sub_identificator_van(self): - partij_identificator = PartijIdentificatorFactory.create( - partij_identificator_code_objecttype=PartijIdentificatorCodeObjectType.niet_natuurlijk_persoon.value, - partij_identificator_code_soort_object_id=PartijIdentificatorCodeSoortObjectId.kvk_nummer.value, - partij_identificator_object_id="12345678", - partij_identificator_code_register=PartijIdentificatorCodeRegister.hr.value, - ) - with self.assertRaises(ValidationError) as error: - PartijIdentificatorUniquenessValidator( - partij_identificator={ - "code_objecttype": PartijIdentificatorCodeObjectType.vestiging.value, - "code_soort_object_id": PartijIdentificatorCodeSoortObjectId.vestigingsnummer.value, - "object_id": "296648875154", - "code_register": PartijIdentificatorCodeRegister.hr.value, - }, - sub_identificator_van=partij_identificator, - instance=partij_identificator, - )() - - details = error.exception.message_dict - self.assertEqual( - details["sub_identificator_van"][0], - "Kan zichzelf niet selecteren als `subIdentificatorVan`.", - ) - - def test_invalid_sub_identificator_van_type_not_allowed(self): - # sub_identificator_van type not allowed - sub_identificator_van = PartijIdentificatorFactory.create( - partij_identificator_code_objecttype=PartijIdentificatorCodeObjectType.natuurlijk_persoon.value, - partij_identificator_code_soort_object_id=PartijIdentificatorCodeSoortObjectId.bsn.value, - partij_identificator_object_id="296648875", - partij_identificator_code_register=PartijIdentificatorCodeRegister.brp.value, - ) - with self.assertRaises(ValidationError) as error: - PartijIdentificatorUniquenessValidator( - partij_identificator={ - "code_objecttype": PartijIdentificatorCodeObjectType.vestiging.value, - "code_soort_object_id": PartijIdentificatorCodeSoortObjectId.vestigingsnummer.value, - "object_id": "296648875154", - "code_register": PartijIdentificatorCodeRegister.hr.value, - }, - sub_identificator_van=sub_identificator_van, - instance=None, - )() - - details = error.exception.message_dict - self.assertEqual( - details["sub_identificator_van"][0], - "Het is alleen mogelijk om sub_identifier_vans te selecteren die CodeSoortObjectId = `kvk_nummer` hebben.", - ) - - def test_invalid_partij_identificator_vestigingsnummer_require_sub_identificator_van( - self, - ): - with self.assertRaises(ValidationError) as error: - PartijIdentificatorUniquenessValidator( - partij_identificator={ - "code_objecttype": PartijIdentificatorCodeObjectType.vestiging.value, - "code_soort_object_id": PartijIdentificatorCodeSoortObjectId.vestigingsnummer.value, - "object_id": "296648875154", - "code_register": PartijIdentificatorCodeRegister.hr.value, - }, - sub_identificator_van=None, - instance=None, - )() - - details = error.exception.message_dict - self.assertTrue( - "`sub_identifier_van` met codeSoortObjectId = `kvk_nummer` te kiezen." - in details["sub_identificator_van"][0] - ) + PartijIdentificatorTypesValidator()( + code_register=case[0], + code_objecttype=case[1], + code_soort_object_id=case[2], + object_id=case[3], + ) diff --git a/src/openklant/utils/decorators.py b/src/openklant/utils/decorators.py new file mode 100644 index 00000000..8b33cad2 --- /dev/null +++ b/src/openklant/utils/decorators.py @@ -0,0 +1,23 @@ +from functools import wraps + +from django.core.exceptions import ValidationError as DjangoValidationError +from django.db import models + +from rest_framework.exceptions import ValidationError +from rest_framework.fields import get_error_detail + + +def handle_db_exceptions(func): + @wraps(func) + def wrapper(*args, **kwargs): + try: + return func(*args, **kwargs) + except models.ProtectedError as protected_err: + raise ValidationError( + {"nonFieldErrors": protected_err.args[0]}, + code="invalid", + ) + except DjangoValidationError as validation_err: + raise ValidationError(get_error_detail(validation_err)) + + return wrapper