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

Using str.split as a filter causes an error when the field value is not set #849

Open raxod502 opened 3 months ago

raxod502 commented 3 months ago

I am creating a basic web-form following the WTForms documentation. I have a field where whitespace should be stripped from its beginning and end. The "filters" feature appears to be designed for this, and even has an example in the documentation that is explicitly for string stripping:

filters – A sequence of callable which are run by process() to filter or transform the input data. For example StringForm(filters=[str.strip, str.upper]). Note that filters are applied after processing the default and incoming data, but before validation.

(That documentation was added by https://github.com/wtforms/wtforms/pull/796.)

However, I found that implementing this as written causes an error. When submitting the form (e.g. for a POST request), it works fine. But when trying to render the form (e.g. for a GET request), the filter is still applied, even though there isn't any value provided for the field, so it errors out calling str.strip on None.

Presumably, the correct thing is for me to define my own function that checks for None before attempting to strip, but since the WTForms documentation explicitly provides str.strip as an example value for filters, I assume this is supposed to work - so what am I doing wrong?

Is the assumption that if filters are used, then I also need to set a default value for my field? Or was this an oversight and filters should not be run when no data is provided by the user? (I get the impression that WTForms intentionally wants to keep "no data" and "empty string" as distinct, since this distinction is also explained in the documentation.)

I tried to search for existing use cases of filters with WTForms on GitHub, but didn't find any that use str.split - perhaps it's not a commonly used feature, or perhaps I didn't do a good job searching. I also checked the issue trackers for WTForms as well as Flask-WTF, but didn't find anything relevant except this seemingly identical report https://github.com/wtforms/wtforms/issues/65 from 2014 which was closed without comment.

Actual Behavior

import wtforms
from wtforms import StringField

class MyForm(wtforms.Form):
    username = StringField("Username", filters=[str.strip])

form_post = MyForm(username="raxod502")  # works fine
form_get = MyForm()  # errors

Output:

Traceback (most recent call last):
  File "/home/raxod502/files/temp/wtf/./main.py", line 8, in <module>
    form_get = MyForm()  # errors
  File "/home/raxod502/.cache/pypoetry/virtualenvs/wtf-cTKJ0gKr-py3.10/lib/python3.10/site-packages/wtforms/form.py", line 208, in __call__
    return type.__call__(cls, *args, **kwargs)
  File "/home/raxod502/.cache/pypoetry/virtualenvs/wtf-cTKJ0gKr-py3.10/lib/python3.10/site-packages/wtforms/form.py", line 286, in __init__
    self.process(formdata, obj, data=data, **kwargs)
  File "/home/raxod502/.cache/pypoetry/virtualenvs/wtf-cTKJ0gKr-py3.10/lib/python3.10/site-packages/wtforms/form.py", line 127, in process
    field.process(formdata, data, extra_filters=field_extra_filters)
  File "/home/raxod502/.cache/pypoetry/virtualenvs/wtf-cTKJ0gKr-py3.10/lib/python3.10/site-packages/wtforms/fields/core.py", line 338, in process
    self.data = filter(self.data)
TypeError: descriptor 'strip' for 'str' objects doesn't apply to a 'NoneType' object

Expected Behavior

With the example code above, I might naively expect that str.split would be called to transform the field value if and only if a value was provided. The filter would be applied even if an empty string is submitted, but it would not be applied if the field is entirely missing from the form submission.

However, I am fine with the current behavior, it would just be nice if the documentation explained the expected usage pattern since what it currently suggests does not seem to work. I am happy to submit an improvement to the documentation if someone can help me understand what is intended by the developers.

Environment