Flask: conditional validation on multiple form fields

Sergiusz picture Sergiusz · Apr 17, 2015 · Viewed 7.1k times · Source

Let me start with usual - I'm new to both Python & Flask. Before posting this question (my first ever here) I have spent number of hours searching and experimenting, unfortunately with no luck so far.

I am building web form where user can define firewall rules, which subsequently get recorded in the database. I am at the validation stage and I came up to a wall here... hopefully someone will be able to help me out.

My (simplified here) form has 2 fields - src_ip and dst_ip:

class FirewallRule(Form)
    src_ip = StringField('Source IP')
    dst_ip = StringField('Destination IP')

and my validation requirements are:

  1. At least 1 field must be filled (both are also fine)
  2. Fields that are filled must contain valid IP address

wtforms.validators.IPAddress() and custom validation function seems to be my friend, but I am struggling to find a way to plug them together.

Essentially I am trying to build conditional validation:

  1. If not (src_ip OR dst_ip); return False
  2. If src_ip AND src_ip not valid IP address; return False
  3. If dst_ip AND dst_ip not valid IP address; return False
  4. Return True

Obviously I would like to re-use IPAddress() [or generally any of the built-in validators] rather then write my own.

I am sure someone must have done it before.. unfortunately I could not find any pointers in the right direction.

Thanks in advance.

Answer

Miguel picture Miguel · Apr 17, 2015

You are missing one very useful validator: Optional. This validator allows you to say that a field can be empty, but if it isn't empty then other validators should be used.

The part about having at least one of the fields filled out I would do with a custom validation method, I don't think there is any stock validators that can help with that.

So it would be something like this:

class FirewallRule(Form)
    src_ip = StringField('Source IP', validators=[Optional(), IPAddress()])
    dst_ip = StringField('Destination IP', validators=[Optional(), IPAddress()])

    def validate(self):
        if not super(FirewallRule, self).validate():
            return False
        if not self.src_ip.data and not self.dst_ip.data:
            msg = 'At least one of Source and Destination IP must be set'
            self.src_ip.errors.append(msg)
            self.dst_ip.errors.append(msg)
            return False
        return True

If you want to avoid a custom validation function, then consider that it would be fairly easy to create a validator class to check that at least one of a list of fields are set. You can look at the implementation of the EqualTo validator for inspiration if you would like to follow this route.