Don’t repeat yourself

Flask-wtf: Flask-WTF: Flask-WTF: Flask-WTF

from flask import Flask

app = Flask(__name__)

@app.route('/', methods=['GET', 'POST'])
def index(a):
	form = TestForm()
	# Determine whether it is legal
	if not form.validate_on_submit():
		return 'err'.400
	# Main logic
Copy the code

For projects with many commit interfaces, the same logic needs to be written under each route, resulting in a lot of code duplication. In flask-login, to set up a route to be accessed after Login, you simply add a @login_required decorator to the route, no extra code required. Could it be possible to encapsulate the validation logic for forms with a decorator, like flask-login?

Implement the form validation decorator

Because different routes use different form classes, you need to pass a form-class parameter to the decorator, and you need to use the values from the form in the routing function, so you also need to pass the validated form to the routing function.

The code:

def validate_form(self, form_cls):
    def decorator(fn):
        @wraps(fn)
        def wrapper(*args, **kwargs):
            if not form.validate_on_submit():
                return 'error'.400
            return fn(form, *args, **kwargs)
        return wrapper
    return decorator
Copy the code

The usage is as follows:

@validate_form(TestForm) # Pass in the form class to validate
@app.route('/', methods=['GET', 'POST'])
def index(form):
    The form has been verified
Copy the code

After application in the project, it was found that the decorator still has some defects:

  • Unable to customize logic for handling illegal forms
  • Validate_on_submit () validate_on_submit() validates only post and PUT forms.

Enrich your

To customize the logic that handles illegal forms, you need to add an interface that can pass in custom logic. The interface tends to return the same thing when the form is invalid, so we pass in a uniform processing logic for all routes that apply decorators. Encapsulate the decorator in a class that adds a method to configure the processing logic.

from functools import wraps

from flask import request


class FormValidator(object):

    def __init__(self, error_handler=None):
        self._error_handler = error_handler

    def validate_form(self, form_cls):
        def decorator(fn):
            @wraps(fn)
            def wrapper(*args, **kwargs):
                if not form.validate_on_submit() and self._error_handler:
                    return self._error_handler(form.errors)
                return fn(form, *args, **kwargs)
            return wrapper
        return decorator

    def error_handler(self, fn):
        self._error_handler = fn
        return fn
Copy the code

Error_handler is also a decorator, and the methods it decorates are those that handle illegal forms.

@form_validator.error_handler
def error_handler(errors):
    return jsonify({'errors': errors}), 400
Copy the code

In flask, we can retrieve the parameters submitted by the get method via request.args. The idea is to generate an instance of the form class with the parameters, which can then be checked by calling the form class’s validate() method. Modify the Validate_FORM decorator:

def validate_form(self, form_cls):
    def decorator(fn):
        @wraps(fn)
        def wrapper(*args, **kwargs):
            if request.method == 'GET':
                form = form_cls(formdata=request.args)
            elif request.method in ('POST'.'PUT'):
                form = form_cls()
            else:
                return fn(*args, **kwargs)
            if not form.validate() and self._error_handler:
                return self._error_handler(form.errors)
            return fn(form, *args, **kwargs)
        return wrapper
    return decorator
Copy the code

And you’re done! Using the decorator above, you can eliminate the need to write form validation logic repeatedly in routing functions and support forms submitted by the PUT, POST, and GET methods at the same time.

# out of the box

PIP Install Flask-wtF-decorators is available for direct use. The source code for the project has also been posted to Github.

Check the PyPI

To check the lot