diff --git a/src/openforms/utils/checks.py b/src/openforms/utils/checks.py index d50d2aab22..dd5d85699a 100644 --- a/src/openforms/utils/checks.py +++ b/src/openforms/utils/checks.py @@ -1,9 +1,15 @@ +import inspect import os +from pathlib import Path from django.conf import settings +from django.contrib.auth.models import AnonymousUser +from django.contrib.sessions.backends.cache import SessionStore from django.core.checks import Error, Warning, register from django.forms import ModelForm +from rest_framework.serializers import CharField, Serializer, empty +from rest_framework.test import APIRequestFactory from treebeard.forms import MoveNodeForm @@ -81,3 +87,57 @@ def check_missing_init_files(app_configs, **kwargs): ) return errors + + +@register +def check_serializer_non_required_charfield_allow_blank_true( # pragma: no cover + app_configs, **kwargs +): + """ + Check for serializers.CharFields that have ``required=False``, but not ``allow_blank=True`` + to avoid bogus validation errors occurring when empty strings are provided by the frontend. + """ + src_dir = Path(__file__).parent.parent.parent + modules = [str(x).split("/")[-1] for x in src_dir.iterdir() if x.is_dir()] + request = APIRequestFactory().get("/") + request.user = AnonymousUser() + request.session = SessionStore() + + errors = [] + serializers = get_subclasses(Serializer) + for serializer_class in serializers: + # We're only interested in classes defined in this codebase, not in classes + # defined in libraries + if serializer_class.__module__.split(".")[0] not in modules: + continue + + if hasattr(serializer_class, "Meta") and not hasattr( + serializer_class.Meta, "model" + ): + continue + + serializer = serializer_class(context={"request": request}) + fields = serializer.fields + + for field_name, field in fields.items(): + if not isinstance(field, CharField) or field.read_only: + continue + + if ( + not field.required + and field.default in ("", None, empty) + and not field.allow_blank + ): + file_path = inspect.getfile(serializer_class) + + errors.append( + Warning( + ( + f"{serializer_class.__module__}.{serializer_class.__qualname__}.{field_name} does not have `allow_blank=True`\n" + f"{file_path}" + ), + hint="Consider setting `allow_blank=True` to allow passing of empty strings", + id="utils.W002", + ) + ) + return errors