pallets-eco / wtforms

A flexible forms validation and rendering library for Python.
https://wtforms.readthedocs.io
BSD 3-Clause "New" or "Revised" License
1.51k stars 395 forks source link

Allow invalid value error message to be customised #832

Open markhobson opened 8 months ago

markhobson commented 8 months ago

When a basic field coerces a string form data value into a stronger type it can raise an error message. For example, IntegerField adds an error message Not a valid integer value. if the value cannot be parsed as an integer. It would be useful to be able to customise this error message but it is hardcoded into the field.

Actual Behavior

>>> import wtforms
>>> class F(wtforms.Form):
...     foo = wtforms.IntegerField()
>>> f = F(foo="bar")
>>> f.validate()
False
>>> f.errors
{'foo': ['Not a valid integer value.']}

Expected Behavior

>>> import wtforms
>>> class F(wtforms.Form):
...     foo = wtforms.IntegerField(message="Enter foo as a number")
>>> f = F(foo="bar")
>>> f.validate()
False
>>> f.errors
{'foo': ['Enter foo as a number']}

This blog post details how the error message can be customised using i18n, but that is rather laborious. Another workaround is to extend the field class:

class CustomMessageIntegerField(IntegerField):
    """
    An integer field that allows the error message for invalid values to be customised.
    """

    def __init__(
        self,
        label: str | None = None,
        validators: tuple[Callable[[BaseForm, CustomMessageIntegerField], object], ...] | list[Any] | None = None,
        message: str = "Not a valid integer value.",
        **kwargs: Any,
    ):
        super().__init__(label, validators, **kwargs)
        self.message = message

    def process_data(self, value: Any) -> None:
        if value is not None:
            self._check_valid(value)

        super().process_data(value)

    def process_formdata(self, valuelist: list[Any]) -> None:
        if valuelist:
            self._check_valid(valuelist[0])

        super().process_formdata(valuelist)

    def _check_valid(self, value: Any) -> None:
        try:
            int(value)
        except ValueError:
            raise ValueError(self.message)

Although this feels like a lot of work for something that could be provided by the library.

This issue is applicable for the following basic fields:

Environment