Get None from a Fields data in instead of an empty string
Asked Answered
I

2

11

I have this field in the WTForms form

name = StringField('Name', validators = [Optional(), Length(max = 100)])

When the field is submitted empty then form.name.data will, as expected, contain an empty string.

Is there some way to make it return None in instead of an empty string? It is just because it is very convenient to deal with null in the database like in this update:

update t
set 
    name = coalesce(%(name)s, name),
    other = coalesce(%(other)s, other)

With the above code I don't need to check if the field is empty or not and take actions accordingly be it in the Python code on in the SQL code. The null with the coalesce solves that easily.

Ilona answered 17/2, 2014 at 14:8 Comment(4)
I'd say it's doubtful, as the actual data received from the browser will really contain an empty string for that field. What's wrong with checking your field values and replacing empty strings with None yourself? E.g. whatever_database.insert_record(form.name.data or None, form.other.data or None)Pandemonium
@Pandemonium What's wrong with... Nothing that wrong. It is just that I think it is cleaner to leave that logic to where (IMO) it belongs; the forms processorIlona
@Pandemonium If WTForms had any facility to automatically rewrite form data.... Indeed it has. Check my answer.Ilona
As a point of interest, if the form data is coming from a JSON POST (rather than an HTML form submission), then a missing key will result in an empty string from the StringField. This is counter-intuitive IMO, since it means there's no way to distinguish between 'user did not enter a value for this field' and 'user submitted an empty string', which might have very different semantics in an application (it does in mine).Usurer
I
28

There is the filters parameter to the Field constructor

name = StringField(
    'Name', 
    validators = [Optional(), Length(max = 100)], 
    filters = [lambda x: x or None]
)

http://wtforms.readthedocs.org/en/latest/fields.html#the-field-base-class

Ilona answered 18/2, 2014 at 12:22 Comment(0)
S
0

In my case I added a base class for my forms (all forms are inherited from this class):

class BaseFrom(FlaskForm):

    @property
    def fields(self) -> dict:
        """Returns a dict of fields with values, a value='' substitutes with None"""

        def process_item(item):
            if isinstance(item, dict):
                return process_dict(item)
            elif isinstance(item, list):
                return process_list(item)
            elif item == '':
                return None
            return item

        def process_dict(d: dict) -> dict:
            return {k: process_item(v) for k, v in d.items()}

        def process_list(l_: list) -> list:
            return [process_item(v) for v in l_]

        return process_dict({f: value.data for f, value in self._fields.items()})

So, when an object is being mapped from form input to dataclass, the mapper uses fields property to retrieve data from the form

@dataclass
class BaseModel:

    @classmethod
    def from_form(cls, obj: dict):
        self = object.__new__(cls)
        for f in dataclasses.fields(cls):
            setattr(self, f.name, obj.get(f.name))
        return self

d = BaseModel.from_form(form_instance.fields)

where form_instance is an instance of a class inherited from 'BaseForm'

I have nested forms, so this method will convert all empty inputs to None recursively

Subtropical answered 15/2, 2023 at 6:37 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.