Skip to content

Instantly share code, notes, and snippets.

@eddiecorrigall
Last active May 6, 2021 13:35
Show Gist options
  • Save eddiecorrigall/ce0e475c9612ca750bcfb30cf108b763 to your computer and use it in GitHub Desktop.
Save eddiecorrigall/ce0e475c9612ca750bcfb30cf108b763 to your computer and use it in GitHub Desktop.
Using WTForms in a Flask project without Flask-WTF

This is a code snippet demonstrating using WTForms without Flask-WTF in a Flask project.

I found the extension: Flask-WTF problematic. So I decided to limit the number of dependencies and take control of this part of the project.

First define a base form to get the following features:

  • (csrf) cross-site request forgery protection
  • convenience method validate_on_submit()
from datetime import timedelta
from flask import current_app, request, session
from wtforms import Form
from wtforms.csrf.session import SessionCSRF


class BaseForm(Form):
    class Meta:
        csrf = True
        csrf_class = SessionCSRF
        csrf_time_limit = timedelta(minutes=15)
    
        @property
        def csrf_context(self):
            # https://wtforms.readthedocs.io/en/2.3.x/csrf/?highlight=csrf#example-integration
            return session
        
        @property
        def csrf_secret(self):
            csrf_secret_key = current_app.config['CSRF_SECRET_KEY']
            if isinstance(csrf_secret_key, str):
                return bytes(csrf_secret_key, encoding='utf-8')
            else:
                return csrf_secret_key

    def validate_on_submit(self):
        return request.method == 'POST' and self.validate()

Then, define a form:

from app.forms import BaseForm  # See above

from wtforms import StringField, SubmitField, PasswordField, validators


PASSWORD_LENGTH_MIN = 12
PASSWORD_LENGHT_MAX = 30


class UserRegistrationForm(BaseForm):
    first_name = StringField('First Name', [
        validators.DataRequired(),
        validators.InputRequired(message='First name is required'),
    ])
    last_name = StringField('Last Name', [
        validators.DataRequired(),
        validators.InputRequired(message='Last name is required'),
    ])
    email = StringField('Email Address', [
        validators.DataRequired(),
        validators.InputRequired(message='Email is required'),
        validators.Email(message='Email is invalid'),
    ])
    email_confirm = StringField('Confirm Email Address', [
        validators.DataRequired(),
        validators.InputRequired(message='Email confirmation is required'),
        validators.EqualTo('email', message='Emails must match'),
    ])
    password = PasswordField('Password', [
        validators.DataRequired(),
        validators.InputRequired(message='Password is required'),
        validators.Length(
            min=PASSWORD_LENGTH_MIN,
            max=PASSWORD_LENGHT_MAX,
            message='Password length must be between {min} and {max} characters in length'.format(
                min=PASSWORD_LENGTH_MIN,
                max=PASSWORD_LENGHT_MAX,
            )
        ),
    ])
    submit = SubmitField('Register')

Then, define a flask endpoint:

from app.forms import UserRegistrationForm
from app.models.base import db
from app.services.users import create_user
from app.www import blueprint

from flask import flash, redirect, render_template, request, url_for


@blueprint.route('/register', methods=['GET', 'POST'])
def register():
    form = UserRegistrationForm(request.form)
    if form.validate_on_submit():
        user = create_user(
            session=db.session,
            first_name=form.first_name.data,
            last_name=form.last_name.data,
            email=form.email.data,
            password=form.password.data)
        db.session.commit()
        flash('Thanks for registering')
        return redirect(url_for('www.login'))
    elif form.csrf_token.errors:
        # TODO: Log suspected attack
        pass
    return render_template('register.html', form=form)

And finally, define the template:

{% extends 'base.html' %}

{% block header %}
  <h1>{% block title %}Register{% endblock %}</h1>
{% endblock %}

{% block content %}
  {% if form.errors %}
  <ul class="errors">
      {% for field_name, field_errors in form.errors|dictsort if field_errors %}
          {% for error in field_errors %}
              <li>{{ form[field_name].label }}: {{ error }}</li>
          {% endfor %}
      {% endfor %}
      {% if form.csrf_token.errors %}
        <li>You have submitted an invalid CSRF token</li>
      {%endif%}
  </ul>
  {% endif %}
  <form method="post" action="{{ url_for('www.register') }}">
    {% for field in form %}
      {{ field.label }}
      {{ field }}
    {% endfor %}
  </form>
{% endblock %}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment