Flask Admin Custom Form Validation for Multiple Fields?
Asked Answered
L

3

8

I have been intending to create a custom form validation logic that's based on multiple field values (in my case, 2 fields to ensure date range data integrity is ensured, I.E. start_time < end_time). However, flipping through the documentation on flask admin site, I couldn't find anywhere to do this sort of stuff. I am aware of that you can pretty trivially pass a list of validation functions to validators argument for form_args property in a subclass of a BaseModelView class, but again, that's per-field validation not exactly what I want.

So my question is this: how does one validate multiple fields together at once?

Also, I don't see any pre-save hook event function to tap into do do this. I am aware of on_model_change but then it's a post-save hook, it would defeat the purpose of validation to put the validation in there. What would be the appropriate the way go about doing pre-save actions?

Leotie answered 27/7, 2015 at 21:26 Comment(1)
Seems to be exact solution for your problem: #21815567Homo
E
8

You can introduce custom form validation code to your flask admin model views by using inheritance and a custom "validate_form" method that incorporates your validation code before calling the parent "validate form".

If your validation logic finds a problem, your validate_form should display an appropriate error message and return False, otherwise it should continue by running the original flask admin validation form code.

from flask_admin.contrib.sqla import ModelView
from flask import flash

class MyModelView(ModelView):
    """ My model admin model view """

    def validate_form(self, form):
        """ Custom validation code that checks dates """
        if form.start_time.data > form.end_time.data:
            flash("start time cannot be greater than end time!")
            return False
        return super(MyModelView, self).validate_form(form)

This is a much more natural place to do form validation than at the change_model_model method, which shouldn't be concerned with model logic, not form validation logic. Also, note we don't need to use an exception and rely on reversing the transaction. We simply catch the error before any transaction has occurred, flash an error message and gracefully return a "False" state.

Relevant links:

flask admin documentation

flask admin source code

Easing answered 7/7, 2017 at 15:18 Comment(3)
Sad thing that this method is also getting called upon the delete action in the flask-admin and when you go to the "create" page as well.Swill
@scythargon, you can solve that by wrapping your additional validation code in if is_form_submitted(): (import that function from flask_admin.helpers).Inez
If you want to attach the error to a specific form field rather than showing a flash message, see here: https://mcmap.net/q/960420/-wtforms-raise-a-validation-error-after-the-form-is-validatedInez
L
7

So after experimenting and trying out a few different approaches, the way I did multiple form field validation is still to peg along on_model_change I know it says the event hook is called after the changes have been made - however, since it's been wrapped in a transaction, one can raise any exception to roll back the transaction.

Here is my sample code to make it work.

from flask.ext.admin.form import rules
from wtforms import validators

class TimeWindowView(LoggedInView):
    column_filters = ('scheduled_start', 'scheduled_end')
    form_create_rules = [
        rules.Field('scheduled_start'),
        rules.Field('scheduled_end'),
    ]

    def on_model_change(self, form, model, is_created):
        # if end date before start date or end date in the past, flag them invalid
        if (form.scheduled_end.data <= form.scheduled_start.data or
            form.scheduled_end.data <= datetime.datetime.utcnow()):
            raise validators.ValidationError('Invalid schedule start and end time!')
        else:
            super().on_model_change(form, model, is_created)
Leotie answered 6/8, 2015 at 14:59 Comment(4)
Another option would be using WTForms validation machinery. You can either contribute a function to a form class after its constructed or supply a custom base class with a validate_scheduled_end function.Itinerary
@Itinerary How do I access model instance within such validator function? For example, I need check if field.data != model.email within validate_email(), but model is accessible only within on_model_change()Exanthema
Unfortunately model is not passed to the validator - you can only check individual values. If you need model, you'll have to do it in the on_model_change.Itinerary
also if you use inline editing it's better to check if 'scheduled_end' in form before use it or check if it is_createdHermaherman
B
1

Here's another solution using form_base_class letting wtforms handle all validation:

from flask import flash
from flask_admin.contrib.sqla import ModelView
from flask_admin.form import BaseForm


class MyForm(BaseForm):
    def validate(self, *args, **kwargs):
        is_valid = super().validate(*args, **kwargs)

        if self.scheduled_end.data <= self.scheduled_start.data:
            flash("Invalid schedule start and end time", "error")

            # or instead of flash(), show the error at the scheduled_end field
            self.scheduled_end.errors.append("Should be after schedule start")
               
            is_valid = False

        return is_valid

class MyModelView(ModelView):
    form_base_class = MyForm
Balls answered 22/9, 2021 at 12:32 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.