WTForms-JSON with optional nesting using FormField

2.4k views Asked by At

I'm using WTForms-JSON and processing nested forms. I'd like to make an inner form optional, but if the inner form is present, I'd like its fields to be required. The problem I'm running into is that FormField doesn't accept validators.

(Although I'm using WTForms-JSON, I believe this applies to vanilla WTForms as well.)

This code works but doesn't behave the way I want:

class InnerForm(Form):
    foo_id = IntegerField("Foo ID", [Required()])

class OuterForm(Form):
    inner = FormField(InnerForm)

The problem with the above code is that inner is implicitly required. Oddly, while validate() returns False when inner is omitted, errors is empty.

This code doesn't doesn't work:

class InnerForm(Form):
    foo_id = IntegerField("Foo ID", [Required()])

class OuterForm(Form):
    inner = FormField(InnerForm, "Inner", [Optional()])

The latter produces this error:

TypeError: FormField does not accept any validators. Instead, define them on the enclosed form.

My question is: how can I make inner optional, but require foo_id if inner is present?

3

There are 3 answers

3
Sean Vieira On BEST ANSWER

The easiest way is to wrap the FormField in a FieldList, with max_entries set to 1. FieldList also supports validators, but since min_entries is 0 by default you should not need any. The only annoyance will be that you will have to unwrap the inner form's data if it is available.

0
aryeh On

In case anyone comes here looking for a solution to this, here is a simple one:

from wtforms.fields import FormField, _unset_value

class OptionalFormField(FormField):

    def process(self, formdata, data=_unset_value):
        self._formdata = formdata
        return super(OptionalFormField, self).process(formdata, data=data)

    def validate(self, form, extra_validators=tuple()):
        if extra_validators:
            raise TypeError('FormField does not accept in-line validators, as it gets errors from the enclosed form.')

        # Run normal validation only if there is data for this form
        for field_name in self._formdata.keys():
            if field_name.find(self.name) == 0:
                return self.form.validate()

        return True

What this does is it only runs the form validation if it finds keys in the formdata that pertain to that form.

0
grubberr On

Thanks @aryeh for OptionalFormField. I just put here my slightly improved (in my opinion) version:

class OptionalFormField(FormField):

    def process(self, formdata, *args, **kwargs):
        self._formdata = formdata
        return super(OptionalFormField, self).process(formdata, *args, **kwargs)

    def validate(self, *args, **kwargs):
        if self._formdata:
            for field_name in self._formdata.keys():
                if field_name.startswith(self.name + self.separator):
                    return super(OptionalFormField, self).validate(*args, **kwargs)
        return True