0 is a valid integer value. In the latest version of WTForms, version 3.0.1, IntegerField and InputRequired don't accept 0 as valid. This appears to be an ongoing issue dating back several years. I am proposing a patch, which seems to be working for me.
WTForms 3.0.1 is a great validation library. And I think it is also framework-independent: we can implement our own generic business rules classes, and use it as a basic data validation engine, we can then use these business rules classes in any framework of our own choosing.
I have recently found an issue with it, my form has an IntegerField, and the InputRequired validator, whereby 0
is an acceptable value.
-- But 0
gets rejected!
This issue has been reported over the years, but so far, it is still in this latest version:
- WTForms IntegerField doesn't work properly with NumberRange as validator
- Flask-WTF: How to allow zero upon DataRequired() validation
- InputRequired doesn't accept 0 as valid #100
Following is my attempt to reproduce this issue, and how to get around it. ❶ Create project virtual environment. ❷ Write a test script.
❶ Create project virtual environment. We will need Werkzeug and WTForms 3.0.1 packages.
The test project lives under /webwork/wtforms_test
:
$ cd webwork/
$ mkdir wtforms_test
$ cd wtforms_test/
behai@HP-Pavilion-15:~/webwork/wtforms_test$ virtualenv venv
behai@HP-Pavilion-15:~/webwork/wtforms_test$ source venv/bin/activate
(venv) behai@HP-Pavilion-15:~/webwork/wtforms_test$ ./venv/bin/pip install werkzeug WTForms
❷ Test script.
Content of /webwork/wtforms_test$/wtforms_bug.py
from pprint import pprint
from werkzeug.datastructures import MultiDict
from wtforms import (
Form,
IntegerField,
)
from wtforms.validators import (
InputRequired,
NumberRange,
)
BREAK_HOUR_01_MSG = "Break Hour must have a value."
BREAK_HOUR_02_MSG = "Break Hour is between 0 and 23."
class TestForm(Form):
break_hour = IntegerField('Break Hour', validators=[InputRequired(BREAK_HOUR_01_MSG),
NumberRange(0, 23, BREAK_HOUR_02_MSG)])
def validate(data: dict, form: Form) -> tuple():
form_data = MultiDict(mapping=data)
f = form(form_data)
res = f.validate()
return res, f.errors
print("\n--break_hour: -1")
res, errors = validate({'break_hour': -1}, TestForm)
print(res)
pprint(errors)
print("\n--break_hour: 'xx'")
res, errors = validate({'break_hour': 'xx'}, TestForm)
print(res)
pprint(errors)
print("\n--break_hour: 0")
res, errors = validate({'break_hour': 0}, TestForm)
print(res)
pprint(errors)
print("\n--break_hour: 1")
res, errors = validate({'break_hour': 1}, TestForm)
print(res)
pprint(errors)
It is simple, the form has only a single integer field break_hour
, it is a required field, and accepts any value in the range of 0
to 23
-- and follows by 4 ( four ) tests.
To run:
(venv) behai@HP-Pavilion-15:~/webwork/wtforms_test$ venv/bin/python wtforms_bug.py
Output:
--break_hour: -1
False
{'break_hour': ['Break Hour is between 0 and 23.']}
--break_hour: 'xx'
False
{'break_hour': ['Not a valid integer value.',
'Break Hour is between 0 and 23.']}
--break_hour: 0
False
{'break_hour': ['Break Hour must have a value.']}
--break_hour: 1
True
{}
-1
and 'xx'
get rejected, which are correct. But 0
gets rejected is a bug: 0
is a valid value.
I traced the issue to InputRequired
, I am printing the code for this class below:
InputRequired in ./venv/lib/python3.10/site-packages/wtforms/validators.py
class InputRequired:
"""
Validates that input was provided for this field.
Note there is a distinction between this and DataRequired in that
InputRequired looks that form-input data was provided, and DataRequired
looks at the post-coercion data.
Sets the `required` attribute on widgets.
"""
def __init__(self, message=None):
self.message = message
self.field_flags = {"required": True}
def __call__(self, form, field):
if field.raw_data and field.raw_data[0]:
return
if self.message is None:
message = field.gettext("This field is required.")
else:
message = self.message
field.errors[:] = []
raise StopValidation(message)
The issue is caused by the first line in def call(self, form, field)
:
def __call__(self, form, field):
if field.raw_data and field.raw_data[0]:
return
field.raw_data[0]
is evaluated to 0
, and it is a False
, causing the whole if
statement to evaluate to False
.
Change it to:
if field.raw_data and len(field.raw_data):
And it will accept 0
as a value. The correct expected output is now:
--break_hour: -1
False
{'break_hour': ['Break Hour is between 0 and 23.']}
--break_hour: 'xx'
False
{'break_hour': ['Not a valid integer value.',
'Break Hour is between 0 and 23.']}
--break_hour: 0
True
{}
--break_hour: 1
True
{}
Right now, I am just having the change made locally. I am not sure what to do with it just yet. Thank you for reading. I hope this info is useful. Stay safe as always.
✿✿✿
Feature image sources:
- https://in.pinterest.com/pin/337277459600111737/
- https://www.omgubuntu.co.uk/2022/09/ubuntu-2210-kinetic-kudu-default-wallpaper
- https://seeklogo.com/vector-logo/332789/python
- https://github.com/wtforms/wtforms/issues/569, https://user-images.githubusercontent.com/19359364/116413884-4b4e7500-a838-11eb-83b0-704ebb3454b0.png
Top comments (1)
I ended up subclass
InputRequired
, and useInputRequiredEx
in place ofInputRequired
: