4
votes

I'm using WTForms (together with Flask, flask-wtf, sqlalchemy) to validate incoming JSON for REST APIs. I realize that WTForms is aimed more towards HTML form rendering and validation, but I chose it because it can autogenerate forms out of my sqlalchemy models (thanks to wtforms.ext.sqlalchemy).

Anyway, here is the problem. One of my models includes boolean field which translates to wtforms.BooleanField with DataRequired validator. The issue is that validation fails with 'This field is required' error message even if I pass correct data. My Form:

class MyForm(Form):
    name = TextField('name', validators=[DataRequired()])
    disabled = BooleanField('disabled', validators=[DataRequired()])

JSON data is like this:

'{"name": "John", "disabled": "false"}'

What I'm expecting:

  1. {"disabled": "false"} -> validates successful, coerced Python data: {'disabled': False}
  2. {"disabled": "true"} -> validates successful, coerced Python data: {'disabled': True}
  3. {"disabled": ""} or '{"disabled": "foo"}' -> fails validation

Currently in first case validation is failed with {'disabled': [u'This field is required.']}

I know there is a note in docs that says DataRequired validator "require coerced data, not input data", but 1) the form is autogenerated by wtforms.ext.sqlalchemy and 2) how it is supposed to behave if I use InputRequired validator? Check (via form.validate()) that some data exists and then check that this data is "true" or "false"?

To summarize, my question is:

  1. What is the correct way of validating wtforms.BooleanField?
  2. Maybe there is some other framework that can validate incoming JSON against given sqlalchemy models?

Thanks.

1
have you tried default=('disabled', False) ? because default value of disabled field should be False. - mortymacs
Where exactly I'm supposed to pass this tuple? - Palasaty
sorry that tuple is for select tag. try it: disabled = BooleanField('disabled', validators=[DataRequired()],defaults=False) - mortymacs
You have a form class you have created, and then you say that the form class is generated by ext.sqlalchemy as your rationale for being unable to just omit the validator. Which one is it? If you have the field defined, you can just not use that validator (which is almost meaningless for a BooleanField), and otherwise, you can still override it in a subclass. - Crast
I use model_form function from ext.sqlalchemy to generate form from sqlalchemy model. I give MyForm definition here for simplicity and because it leads to same problem. Sure I can modify generated form (add/replace validators, etc) but my question was about how to generate forms I'm expecting and if that's possible at all. - Palasaty

1 Answers

0
votes

There are a number of ways of going about this. You could write your own converter to make of use a radiofield with true/false choices, you could use a data filter, you could set a default value, but I think the behavior you want will be possible with this:

MyForm = model_form(MyModel, db_session=db, field_args = {
    'disabled' : {
        'false_values': ['false'],
        'validators' : [InputRequired()] }
})

EDIT: If you wanted a stricter handler you could do the following:

class BooleanRequired(object):
    field_flags = ('required', )

    def __init__(self, message=None):
        self.message = message

    def __call__(self, form, field):
        if field.data is None:
            if self.message is None:
                message = field.gettext('This field is required.')
            else:
                message = self.message

            field.errors[:] = []
            raise StopValidation(message)


class StrictBooleanField(BooleanField):
    def process_formdata(self, valuelist):
        self.data = None
        if valuelist:
            if valuelist[0] == 'false':
                self.data = False
            elif valuelist[0] == 'true':
                self.data = True


class StrictModelConverter(ModelConverter):
    @converts('Boolean')
    def conv_Boolean(self, field_args, **extra):
        return StrictBooleanField(**field_args)

MyForm = model_form(MyModel, db_session=db, converter=StrictModelConverter(),
           field_args = { 'disabled' : { 'validators': [BooleanRequired()] }
})