diff --git a/src/openforms/contrib/brk/tests/test_integration.py b/src/openforms/contrib/brk/tests/test_integration.py index f09e54834c..df49769d34 100644 --- a/src/openforms/contrib/brk/tests/test_integration.py +++ b/src/openforms/contrib/brk/tests/test_integration.py @@ -23,11 +23,7 @@ class BRKValidatorIntegrationTestCase( def setUp(self) -> None: super().setUp() register = Registry() - register( - "brk-zakelijk-gerechtigd", - verbose_name="dummy", - for_components=("addressNL",), - )(BRKZakelijkGerechtigdeValidator) + register("brk-zakelijk-gerechtigd")(BRKZakelijkGerechtigdeValidator) patcher = patch("openforms.validations.api.views.register", new=register) patcher.start() diff --git a/src/openforms/contrib/brk/tests/test_validators.py b/src/openforms/contrib/brk/tests/test_validators.py index a86ae4acec..60297ad85e 100644 --- a/src/openforms/contrib/brk/tests/test_validators.py +++ b/src/openforms/contrib/brk/tests/test_validators.py @@ -21,7 +21,7 @@ class BRKValidatorTestCase(OFVCRMixin, BRKTestMixin, TestCase): VCR_TEST_FILES = TEST_FILES def test_brk_validator_no_auth(self): - validator = BRKZakelijkGerechtigdeValidator() + validator = BRKZakelijkGerechtigdeValidator("brk_validator") submission_no_auth = SubmissionFactory.create( form__generate_minimal_setup=True, @@ -35,7 +35,7 @@ def test_brk_validator_no_auth(self): ) def test_brk_validator_no_bsn(self): - validator = BRKZakelijkGerechtigdeValidator() + validator = BRKZakelijkGerechtigdeValidator("brk_validator") submission_no_bsn = SubmissionFactory.create( form__generate_minimal_setup=True, @@ -52,7 +52,7 @@ def test_brk_validator_no_bsn(self): ) def test_brk_validator_wrong_bsn(self): - validator = BRKZakelijkGerechtigdeValidator() + validator = BRKZakelijkGerechtigdeValidator("brk_validator") submission_wrong_bsn = SubmissionFactory.create( form__generate_minimal_setup=True, @@ -73,7 +73,7 @@ def test_brk_validator_wrong_bsn(self): ) def test_brk_validator_bsn(self): - validator = BRKZakelijkGerechtigdeValidator() + validator = BRKZakelijkGerechtigdeValidator("brk_validator") submission_bsn = SubmissionFactory.create( form__generate_minimal_setup=True, @@ -112,7 +112,7 @@ def test_brk_validator_bsn(self): @requests_mock.Mocker() def test_brk_validator_requests_error(self, m: requests_mock.Mocker): - validator = BRKZakelijkGerechtigdeValidator() + validator = BRKZakelijkGerechtigdeValidator("brk_validator") submission_bsn = SubmissionFactory.create( form__generate_minimal_setup=True, @@ -150,7 +150,7 @@ def setUp(self): self.addCleanup(patcher.stop) def test_brk_validator_not_configured(self): - validator = BRKZakelijkGerechtigdeValidator() + validator = BRKZakelijkGerechtigdeValidator("brk_validator") submission_bsn = SubmissionFactory.create( form__generate_minimal_setup=True, diff --git a/src/openforms/contrib/brk/validators.py b/src/openforms/contrib/brk/validators.py index 3baaec1a18..5e4f75e31a 100644 --- a/src/openforms/contrib/brk/validators.py +++ b/src/openforms/contrib/brk/validators.py @@ -57,12 +57,12 @@ class ValueSerializer(serializers.Serializer): value = AddressValueSerializer() -@register("brk-Zaakgerechtigde") +@register("brk-zakelijk-gerechtigd") @deconstructible -class BRKZaakgerechtigdeValidator(BasePlugin[AddressValue]): +class BRKZakelijkGerechtigdeValidator(BasePlugin[AddressValue]): value_serializer = ValueSerializer - verbose_name = _("BRK - Zaakgerechtigde") + verbose_name = _("BRK - Zakelijk gerechtigd") for_components = ("addressNL",) error_messages = { diff --git a/src/openforms/contrib/kvk/tests/test_validators.py b/src/openforms/contrib/kvk/tests/test_validators.py index bc4b8c9e8e..06030471f1 100644 --- a/src/openforms/contrib/kvk/tests/test_validators.py +++ b/src/openforms/contrib/kvk/tests/test_validators.py @@ -67,7 +67,7 @@ def test_kvkNumber_validator(self, m): status_code=500, ) - validator = partial(KVKNumberRemoteValidator(), submission=Submission()) + validator = partial(KVKNumberRemoteValidator("id"), submission=Submission()) validator("69599084") with self.assertRaisesMessage( @@ -96,7 +96,7 @@ def test_kvkNumber_validator_emptyish_results(self, m): {"resultaten": []}, {}, ) - validate = partial(KVKNumberRemoteValidator(), submission=Submission()) + validate = partial(KVKNumberRemoteValidator("id"), submission=Submission()) for response_json in bad_responses: with self.subTest(response_json=response_json): @@ -120,7 +120,7 @@ def test_rsin_validator(self, m): status_code=404, ) - validator = partial(KVKRSINRemoteValidator(), submission=Submission()) + validator = partial(KVKRSINRemoteValidator("id"), submission=Submission()) validator("111222333") with self.assertRaisesMessage( @@ -151,7 +151,7 @@ def test_branchNumber_validator(self, m): status_code=404, ) - validator = partial(KVKBranchNumberRemoteValidator(), submission=Submission()) + validator = partial(KVKBranchNumberRemoteValidator(""), submission=Submission()) validator("112233445566") with self.assertRaisesMessage( diff --git a/src/openforms/contrib/kvk/validators.py b/src/openforms/contrib/kvk/validators.py index 4111fbde5f..38e623ee81 100644 --- a/src/openforms/contrib/kvk/validators.py +++ b/src/openforms/contrib/kvk/validators.py @@ -94,7 +94,7 @@ class KVKNumberRemoteValidator(BasePlugin[str], KVKRemoteBaseValidator): def __call__(self, value: str, submission): validate_kvk(value) - super().__call__(value) + KVKRemoteBaseValidator.__call__(self, value) @register("kvk-rsin") @@ -108,12 +108,12 @@ class KVKRSINRemoteValidator(BasePlugin[str], KVKRemoteBaseValidator): def __call__(self, value: str, submission): validate_rsin(value) - super().__call__(value) + KVKRemoteBaseValidator.__call__(self, value) @register("kvk-branchNumber") @deconstructible -class KVKBranchNumberRemoteValidator(KVKRemoteBaseValidator): +class KVKBranchNumberRemoteValidator(BasePlugin[str], KVKRemoteBaseValidator): query_param = "vestigingsnummer" value_label = _("Branch number") @@ -122,4 +122,4 @@ class KVKBranchNumberRemoteValidator(KVKRemoteBaseValidator): def __call__(self, value: str, submission): validate_branchNumber(value) - super().__call__(value) + KVKRemoteBaseValidator.__call__(self, value) diff --git a/src/openforms/validations/api/views.py b/src/openforms/validations/api/views.py index a8ef2d44a3..37ba64e8a4 100644 --- a/src/openforms/validations/api/views.py +++ b/src/openforms/validations/api/views.py @@ -1,5 +1,3 @@ -from typing import Iterable - from django.utils.translation import gettext_lazy as _ from drf_spectacular.types import OpenApiTypes @@ -21,7 +19,8 @@ ValidatorsFilterSerializer, ) -from ..registry import RegisteredValidator, register +from ..base import BasePlugin +from ..registry import register @extend_schema_view( @@ -39,16 +38,18 @@ class ValidatorsListView(ListMixin, APIView): permission_classes = (permissions.IsAdminUser,) serializer_class = ValidationPluginSerializer - def get_objects(self) -> list[RegisteredValidator]: + def get_objects(self) -> list[BasePlugin]: filter_serializer = ValidatorsFilterSerializer(data=self.request.query_params) if not filter_serializer.is_valid(raise_exception=False): return [] - plugins: Iterable[RegisteredValidator] = register.iter_enabled_plugins() - for_component = filter_serializer.validated_data.get("component_type") or "" + plugins = register.iter_enabled_plugins() + for_component: str = ( + filter_serializer.validated_data.get("component_type") or "" + ) if not for_component: - return plugins + return list(plugins) return [plugin for plugin in plugins if for_component in plugin.for_components] diff --git a/src/openforms/validations/base.py b/src/openforms/validations/base.py index 3d40c69bb4..501c4137d9 100644 --- a/src/openforms/validations/base.py +++ b/src/openforms/validations/base.py @@ -1,12 +1,13 @@ from abc import ABC, abstractmethod -from typing import ClassVar, Generic, TypeVar +from typing import ClassVar, Generic from rest_framework import serializers +from typing_extensions import TypeVar from openforms.plugins.plugin import AbstractBasePlugin from openforms.submissions.models import Submission -T = TypeVar("T") +T = TypeVar("T", default=str) """A type variable representing the type of the value being validated by the plugin.""" @@ -17,6 +18,10 @@ class StringValueSerializer(serializers.Serializer): class BasePlugin(ABC, AbstractBasePlugin, Generic[T]): + """The base class for validation plugins. + + This class is generic over the type of the validated value, defaulting to ``str``. + """ value_serializer: ClassVar[type[serializers.BaseSerializer]] = StringValueSerializer """The serializer to be used to validate the value.""" diff --git a/src/openforms/validations/registry.py b/src/openforms/validations/registry.py index b9c2f7cee4..58adfc876d 100644 --- a/src/openforms/validations/registry.py +++ b/src/openforms/validations/registry.py @@ -1,6 +1,6 @@ import dataclasses import logging -from typing import Callable, Iterable, List, Type, TypeVar, Union +from typing import Iterable from django.core.exceptions import ValidationError as DJ_ValidationError from django.utils.translation import gettext_lazy as _ @@ -8,23 +8,25 @@ import elasticapm from rest_framework import serializers from rest_framework.exceptions import ValidationError as DRF_ValidationError +from typing_extensions import TypeVar from openforms.plugins.registry import BaseRegistry from openforms.submissions.models import Submission -from openforms.typing import JSONValue from .base import BasePlugin logger = logging.getLogger(__name__) +T = TypeVar("T", default=str) + @dataclasses.dataclass() class ValidationResult: is_valid: bool - messages: List[str] = dataclasses.field(default_factory=list) + messages: list[str] = dataclasses.field(default_factory=list) -def flatten(iterables: Iterable) -> List[str]: +def flatten(iterables: Iterable[str]) -> list[str]: def _flat(it): if isinstance(it, str): yield it @@ -38,9 +40,6 @@ def _flat(it): return list(_flat(iterables)) -T = TypeVar("T") - - class Registry(BaseRegistry[BasePlugin[T]]): """ A registry for the validations module plugins. diff --git a/src/openforms/validations/tests/test_api.py b/src/openforms/validations/tests/test_api.py index 64f1ecd4b9..c7ff3dc3e7 100644 --- a/src/openforms/validations/tests/test_api.py +++ b/src/openforms/validations/tests/test_api.py @@ -9,12 +9,9 @@ from openforms.config.models import GlobalConfiguration from openforms.submissions.tests.factories import SubmissionFactory from openforms.submissions.tests.mixins import SubmissionsMixin -from openforms.validations.registry import Registry, StringValueSerializer -from openforms.validations.tests.test_registry import ( - DjangoValidator, - DRFValidator, - function_validator, -) +from openforms.validations.base import StringValueSerializer +from openforms.validations.registry import Registry +from openforms.validations.tests.test_registry import DjangoValidator, DRFValidator class ValidationsAPITests(SubmissionsMixin, APITestCase): @@ -23,20 +20,8 @@ def setUp(self): self.client.force_login(self.user) register = Registry() - register( - "django", - verbose_name="Django Test Validator", - for_components=("textfield",), - )(DjangoValidator) - register( - "drf", verbose_name="DRF Test Validator", for_components=("phoneNumber",) - )(DRFValidator) - register("func", verbose_name="Django function Validator", for_components=())( - function_validator - ) - register( - "demo", verbose_name="Demo function", for_components=(), is_demo_plugin=True - )(function_validator) + register("django")(DjangoValidator) + register("drf")(DRFValidator) patcher = patch("openforms.validations.api.views.register", new=register) patcher.start() @@ -71,11 +56,6 @@ def test_validations_list(self): "label": "DRF Test Validator", "for_components": ["phoneNumber"], }, - { - "id": "func", - "label": "Django function Validator", - "for_components": [], - }, ], ) @@ -132,11 +112,6 @@ def test_validations_list(self): "label": "DRF Test Validator", "for_components": ["phoneNumber"], }, - { - "id": "func", - "label": "Django function Validator", - "for_components": [], - }, ], ) diff --git a/src/openforms/validations/tests/test_registry.py b/src/openforms/validations/tests/test_registry.py index 1ddc7bef5d..17d5138c6e 100644 --- a/src/openforms/validations/tests/test_registry.py +++ b/src/openforms/validations/tests/test_registry.py @@ -5,86 +5,63 @@ from rest_framework.exceptions import ValidationError as DRFValidationError from openforms.submissions.models import Submission -from openforms.validations.registry import Registry, StringValueSerializer +from openforms.validations.base import BasePlugin +from openforms.validations.registry import Registry -class DjangoValidator: - is_enabled = True - components = ("textfield",) - value_serializer = StringValueSerializer +class DjangoValidator(BasePlugin): + for_components = ("textfield",) + verbose_name = "Django Test Validator" def __call__(self, value, submission): if value != "VALID": raise DjangoValidationError("not VALID value") -class DRFValidator: - is_enabled = True - components = ("phoneNumber",) - value_serializer = StringValueSerializer +class DRFValidator(BasePlugin): + for_components = ("phoneNumber",) + verbose_name = "DRF Test Validator" def __call__(self, value, submission): if value != "VALID": raise DRFValidationError("not VALID value") -class DisabledValidator: - is_enabled = False - components = ("textfield",) - value_serializer = StringValueSerializer +class DisabledValidator(BasePlugin): + for_components = ("textfield",) def __call__(self, value, submission): if value != "VALID": raise DRFValidationError("not VALID value") - -def function_validator(value, submission): - if value != "VALID": - raise DjangoValidationError("not VALID value") - - -function_validator.value_serializer = StringValueSerializer + @property + def is_enabled(self) -> bool: + return False class RegistryTest(TestCase): def test_register_function(self): register = Registry() - register("plugin", "Plugin")(DjangoValidator) + register("plugin")(DjangoValidator) plugin = register["plugin"] - self.assertNotIsInstance(plugin, DjangoValidator) - self.assertIsInstance(plugin.callable, DjangoValidator) + self.assertIsInstance(plugin, DjangoValidator) def test_duplicate_identifier(self): register = Registry() - register("plugin", "Plugin")(DjangoValidator) + register("plugin")(DjangoValidator) with self.assertRaisesMessage( ValueError, - "The unique identifier 'plugin' is already present in the registry", + "The unique identifier 'plugin' is already present in the registry.", ): - register("plugin", "Plugin")(DjangoValidator) - - def test_decorator(self): - registry = Registry() - - def decorated(value, submission): - pass - - decorated.value_serializer = StringValueSerializer - registry("func", verbose_name="Function")(decorated) - - wrapped = list(registry)[0] - self.assertEqual(wrapped.identifier, "func") - self.assertEqual(wrapped.verbose_name, "Function") - self.assertEqual(wrapped.callable, decorated) + register("plugin")(DjangoValidator) def test_validate(self): registry = Registry() - registry("django", "Django")(DjangoValidator) - registry("drf", "DRF")(DRFValidator) - registry("func", "Function")(function_validator) + registry("django")(DjangoValidator) + registry("drf")(DRFValidator) - # The submission object is not relevant for these validators, we use a dummy string instead + # The submission object is not relevant for these validators, we use a dummy object instead res = registry.validate("django", "VALID", Submission()) self.assertEqual(res.is_valid, True) self.assertEqual(res.messages, []) @@ -99,13 +76,6 @@ def test_validate(self): self.assertEqual(res.is_valid, False) self.assertEqual(res.messages, ["not VALID value"]) - res = registry.validate("func", "VALID", Submission()) - self.assertEqual(res.is_valid, True) - self.assertEqual(res.messages, []) - res = registry.validate("func", "INVALID", Submission()) - self.assertEqual(res.is_valid, False) - self.assertEqual(res.messages, ["not VALID value"]) - res = registry.validate("NOT_REGISTERED", "VALID", Submission()) self.assertEqual(res.is_valid, False) self.assertEqual( @@ -119,10 +89,10 @@ def test_validate(self): def test_validate_plugin_not_enabled(self): registry = Registry() - registry("disabled", "Disabled")(DisabledValidator()) + registry("disabled")(DisabledValidator) res = registry.validate("disabled", "VALID", Submission()) - self.assertEqual(res.is_valid, False) + self.assertFalse(res.is_valid) self.assertEqual( res.messages, [_("plugin '{plugin_id}' not enabled").format(plugin_id="disabled")], diff --git a/src/openforms/validations/validators/formats.py b/src/openforms/validations/validators/formats.py index 435701b933..262d1dc119 100644 --- a/src/openforms/validations/validators/formats.py +++ b/src/openforms/validations/validators/formats.py @@ -48,7 +48,7 @@ def __call__(self, value: str, submission): @register("phonenumber-international") -class InternationalPhoneNumberValidator(BasePlugin[str], PhoneNumberBaseValidator): +class InternationalPhoneNumberValidator(PhoneNumberBaseValidator, BasePlugin[str]): country = None country_name = _("international") error_message = _( @@ -74,7 +74,7 @@ def _parse_phonenumber(self, value: str) -> "PhoneNumber": @register("phonenumber-nl") -class DutchPhoneNumberValidator(BasePlugin[str], PhoneNumberBaseValidator): +class DutchPhoneNumberValidator(PhoneNumberBaseValidator, BasePlugin[str]): country = "NL" country_name = _("Dutch") error_message = _(