Menu

Django & Flask Topic

Using Flask-WTF for Form Validation

What to learn: Creating form classes with Flask-WTF, adding validators (e.g., DataRequired(), Email()), and rendering forms safely in templates with CSRF protection. Django Connection: This is a direct parallel to Django's Form class. The concepts of defining form fields, validation, and CSRF protection are core to Django.

Introduction to Flask-WTF

Flask-WTF is an extension that integrates WTForms with Flask, providing:

  • CSRF protection

  • Form validation

  • File upload handling

  • Security features

Installation and Setup

bash

pip install flask-wtf

python

from flask import Flask, render_template
from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField, TextAreaField, SelectField, BooleanField, FileField
from wtforms.validators import DataRequired, Email, Length, EqualTo, ValidationError

app = Flask(__name__)
app.config['SECRET_KEY'] = 'your-secret-key-here'  # Required for CSRF

Creating Form Classes

This is where Flask starts to look very much like Django!

Basic Registration Form:

python

from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField, SubmitField
from wtforms.validators import DataRequired, Email, Length, EqualTo

class RegistrationForm(FlaskForm):
    username = StringField('Username', validators=[
        DataRequired(message='Username is required'),
        Length(min=3, max=50, message='Username must be between 3-50 characters')
    ])
    
    email = StringField('Email', validators=[
        DataRequired(message='Email is required'),
        Email(message='Please enter a valid email address')
    ])
    
    password = PasswordField('Password', validators=[
        DataRequired(message='Password is required'),
        Length(min=8, message='Password must be at least 8 characters'),
        EqualTo('confirm_password', message='Passwords must match')
    ])
    
    confirm_password = PasswordField('Confirm Password')
    submit = SubmitField('Register')

More Complex Form Example:

python

class UserProfileForm(FlaskForm):
    first_name = StringField('First Name', validators=[DataRequired()])
    last_name = StringField('Last Name', validators=[DataRequired()])
    bio = TextAreaField('Biography', validators=[Length(max=500)])
    
    country = SelectField('Country', choices=[
        ('', 'Select Country'),
        ('us', 'United States'),
        ('uk', 'United Kingdom'),
        ('ca', 'Canada')
    ])
    
    newsletter = BooleanField('Subscribe to newsletter')
    avatar = FileField('Profile Picture')
    submit = SubmitField('Update Profile')

Custom Validators

python

def validate_username(form, field):
    """Custom validator to check if username contains only letters and numbers"""
    if not field.data.isalnum():
        raise ValidationError('Username can only contain letters and numbers')

class RegistrationForm(FlaskForm):
    username = StringField('Username', validators=[
        DataRequired(),
        Length(min=3, max=50),
        validate_username  # Custom validator
    ])
    # ... other fields

Using Forms in Views

python

@app.route('/register', methods=['GET', 'POST'])
def register():
    form = RegistrationForm()
    
    if form.validate_on_submit():  # Checks if it's a POST request and validates
        # Form is valid - process the data
        username = form.username.data
        email = form.email.data
        password = form.password.data
        
        # In a real app, you'd save to database here
        print(f"Creating user: {username}, {email}")
        
        return f'Registration successful for {username}!'
    
    # If GET request or validation failed, show the form
    return render_template('register.html', form=form)

Rendering Forms in Templates with CSRF Protection

Template (register.html):

html

<!DOCTYPE html>
<html>
<head>
    <title>Register</title>
</head>
<body>
    <h2>Register</h2>
    
    <form method="POST">
        {{ form.hidden_tag() }}  <!-- CSRF Token -->
        
        <div>
            {{ form.username.label }}<br>
            {{ form.username(size=32) }}<br>
            {% for error in form.username.errors %}
                <span style="color: red;">{{ error }}</span><br>
            {% endfor %}
        </div>
        
        <div>
            {{ form.email.label }}<br>
            {{ form.email(size=32) }}<br>
            {% for error in form.email.errors %}
                <span style="color: red;">{{ error }}</span><br>
            {% endfor %}
        </div>
        
        <div>
            {{ form.password.label }}<br>
            {{ form.password(size=32) }}<br>
            {% for error in form.password.errors %}
                <span style="color: red;">{{ error }}</span><br>
            {% endfor %}
        </div>
        
        <div>
            {{ form.confirm_password.label }}<br>
            {{ form.confirm_password(size=32) }}<br>
            {% for error in form.confirm_password.errors %}
                <span style="color: red;">{{ error }}</span><br>
            {% endfor %}
        </div>
        
        <div>{{ form.submit() }}</div>
    </form>
</body>
</html>

Quick Form Rendering

You can also render forms more quickly:

html

<!-- Quick rendering -->
<form method="POST">
    {{ form.hidden_tag() }}
    
    <p>
        {{ form.username.label }}<br>
        {{ form.username() }}<br>
        {% for error in form.username.errors %}
            <span style="color: red;">[{{ error }}]</span>
        {% endfor %}
    </p>
    
    <p>{{ form.submit() }}</p>
</form>

<!-- Or even quicker -->
<form method="POST">
    {{ form.hidden_tag() }}
    {% for field in form if field.type != 'HiddenField' and field.type != 'CSRFTokenField' %}
        <p>
            {{ field.label }}<br>
            {{ field() }}<br>
            {% for error in field.errors %}
                <span style="color: red;">[{{ error }}]</span>
            {% endfor %}
        </p>
    {% endfor %}
    <p>{{ form.submit() }}</p>
</form>

Handling File Uploads with Flask-WTF

python

from flask_wtf.file import FileField, FileAllowed, FileRequired

class UploadForm(FlaskForm):
    document = FileField('Document', validators=[
        FileRequired(),
        FileAllowed(['pdf', 'doc', 'docx', 'txt'], 'Document files only!')
    ])
    submit = SubmitField('Upload')

@app.route('/upload', methods=['GET', 'POST'])
def upload():
    form = UploadForm()
    
    if form.validate_on_submit():
        file = form.document.data
        filename = secure_filename(file.filename)
        file.save(os.path.join('uploads', filename))
        return f'File {filename} uploaded successfully!'
    
    return render_template('upload.html', form=form)

Pre-populating Forms

python

@app.route('/profile/edit', methods=['GET', 'POST'])
def edit_profile():
    form = UserProfileForm()
    
    # Pre-populate form with existing data (from database)
    if request.method == 'GET':
        # In real app, you'd get this from your database
        user_data = {
            'first_name': 'John',
            'last_name': 'Doe',
            'bio': 'Software developer',
            'country': 'us',
            'newsletter': True
        }
        
        form.process(**user_data)  # Pre-populate the form
    
    if form.validate_on_submit():
        # Process the updated data
        first_name = form.first_name.data
        # ... save to database
        return 'Profile updated!'
    
    return render_template('edit_profile.html', form=form)

Django Connection: Direct Parallels

1. Form Class Definition

Flask-WTF:

python

from flask_wtf import FlaskForm
from wtforms import StringField, validators

class RegistrationForm(FlaskForm):
    username = StringField('Username', validators=[
        validators.DataRequired(),
        validators.Length(min=3, max=50)
    ])

Django:

python

from django import forms

class RegistrationForm(forms.Form):
    username = forms.CharField(
        label='Username',
        max_length=50,
        min_length=3,
        required=True
    )

2. Validation

Flask-WTF:

python

email = StringField('Email', validators=[
    DataRequired(),
    Email()
])

Django:

python

email = forms.EmailField(
    label='Email',
    required=True
)

3. CSRF Protection

Flask-WTF:

html

<form method="POST">
    {{ form.hidden_tag() }}  <!-- Automatically includes CSRF token -->
    <!-- form fields -->
</form>

Django:

html

<form method="POST">
    {% csrf_token %}  <!-- Django's CSRF token -->
    <!-- form fields -->
</form>

4. Form Handling in Views

Flask-WTF:

python

@app.route('/register', methods=['GET', 'POST'])
def register():
    form = RegistrationForm()
    if form.validate_on_submit():
        # Process valid form
        return redirect('/success')
    return render_template('register.html', form=form)

Django:

python

def register(request):
    if request.method == 'POST':
        form = RegistrationForm(request.POST)
        if form.is_valid():
            # Process valid form
            return redirect('/success')
    else:
        form = RegistrationForm()
    return render(request, 'register.html', {'form': form})

5. Template Rendering

Flask-WTF:

html

{{ form.username.label }}
{{ form.username() }}
{% for error in form.username.errors %}
    <span class="error">{{ error }}</span>
{% endfor %}

Django:

html

{{ form.username.label_tag }}
{{ form.username }}
{% for error in form.username.errors %}
    <span class="error">{{ error }}</span>
{% endfor %}

Key Parallels to Understand:

  1. Form Class Structure: Both use Python classes to define form structure and validation

  2. Field Types: Similar field types (StringField/CharField, EmailField, etc.)

  3. Validation Logic: Centralized validation in the form class

  4. CSRF Protection: Automatic CSRF protection

  5. Template Rendering: Similar template syntax for rendering forms and errors

  6. Form Handling Pattern: Same basic pattern in views (GET=show form, POST=validate+process)

 

Comments (0)

No comments yet. Be the first to comment!

LecturreAI Assistant

Hello! I'm LecturreAI

What would you like to learn today?