Flask Form Macros
When building web applications with Flask, you'll often find yourself creating many forms that share similar structures and styling. Writing the same HTML for each form field can quickly become repetitive and hard to maintain. This is where Flask Form Macros come to the rescue, allowing you to define reusable form components that can be used across your application.
What Are Macros in Flask?
Macros in Flask are powered by the Jinja2 templating engine and function similarly to functions in programming languages. They allow you to define reusable pieces of template code that can be called with different parameters, making them perfect for form elements that follow a consistent pattern but need different field names, labels, and values.
Benefits of Using Form Macros
- Reduces code duplication - Write form field markup once, use it everywhere
- Consistent styling - Ensure all forms look and behave the same across your application
- Easier maintenance - When you need to update form styling, you only need to change it in one place
- Improved readability - Templates become cleaner and more focused on content rather than form structure
Creating Your First Form Macro
Let's start by creating a simple macro for rendering form fields. First, we'll create a file called macros.html
in your templates folder:
{# templates/macros.html #}
{% macro render_field(field) %}
<div class="form-group">
{{ field.label(class="form-control-label") }}
{{ field(class="form-control") }}
{% if field.errors %}
<div class="invalid-feedback d-block">
{% for error in field.errors %}
<span>{{ error }}</span>
{% endfor %}
</div>
{% endif %}
</div>
{% endmacro %}
This macro takes a form field as input and renders it with appropriate labels and error messages, all wrapped in a consistent structure.
Using Form Macros in Templates
Now that we've defined our first macro, let's use it in a template:
{# templates/register.html #}
{% extends "base.html" %}
{% from "macros.html" import render_field %}
{% block content %}
<div class="container">
<h1>Register</h1>
<form method="POST" action="{{ url_for('register') }}">
{{ form.csrf_token }}
{{ render_field(form.username) }}
{{ render_field(form.email) }}
{{ render_field(form.password) }}
{{ render_field(form.confirm_password) }}
<button type="submit" class="btn btn-primary">Register</button>
</form>
</div>
{% endblock %}
Notice how we import the macro using the {% from "macros.html" import render_field %}
statement, and then call it for each form field.
Creating a Complete Form Example
Let's see a complete example with a Flask route that renders a form using our macros:
# app.py
from flask import Flask, render_template
from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField, SubmitField
from wtforms.validators import DataRequired, Email, Length, EqualTo
app = Flask(__name__)
app.config['SECRET_KEY'] = 'your-secret-key'
class RegistrationForm(FlaskForm):
username = StringField('Username', validators=[DataRequired(), Length(min=2, max=20)])
email = StringField('Email', validators=[DataRequired(), Email()])
password = PasswordField('Password', validators=[DataRequired()])
confirm_password = PasswordField('Confirm Password', validators=[DataRequired(), EqualTo('password')])
submit = SubmitField('Sign Up')
@app.route('/register', methods=['GET', 'POST'])
def register():
form = RegistrationForm()
if form.validate_on_submit():
# Process form data here
return redirect(url_for('home'))
return render_template('register.html', form=form)
if __name__ == '__main__':
app.run(debug=True)
Advanced Form Macros
Let's create more advanced macros to handle different types of form inputs:
{# templates/macros.html #}
{# Basic input field #}
{% macro render_field(field) %}
<div class="form-group">
{{ field.label(class="form-control-label") }}
{{ field(class="form-control") }}
{% if field.errors %}
<div class="invalid-feedback d-block">
{% for error in field.errors %}
<span>{{ error }}</span>
{% endfor %}
</div>
{% endif %}
</div>
{% endmacro %}
{# Checkbox field #}
{% macro render_checkbox(field) %}
<div class="form-check">
{{ field(class="form-check-input") }}
{{ field.label(class="form-check-label") }}
{% if field.errors %}
<div class="invalid-feedback d-block">
{% for error in field.errors %}
<span>{{ error }}</span>
{% endfor %}
</div>
{% endif %}
</div>
{% endmacro %}
{# Select/dropdown field #}
{% macro render_select(field) %}
<div class="form-group">
{{ field.label(class="form-control-label") }}
{{ field(class="form-select") }}
{% if field.errors %}
<div class="invalid-feedback d-block">
{% for error in field.errors %}
<span>{{ error }}</span>
{% endfor %}
</div>
{% endif %}
</div>
{% endmacro %}
Creating a Complete Form with Different Input Types
Now let's use these macros in a more complex form:
{# templates/profile.html #}
{% extends "base.html" %}
{% from "macros.html" import render_field, render_checkbox, render_select %}
{% block content %}
<div class="container">
<h1>Edit Profile</h1>
<form method="POST" action="{{ url_for('edit_profile') }}">
{{ form.csrf_token }}
{{ render_field(form.first_name) }}
{{ render_field(form.last_name) }}
{{ render_field(form.bio) }}
{{ render_select(form.country) }}
{{ render_checkbox(form.newsletter) }}
{{ render_checkbox(form.terms) }}
<button type="submit" class="btn btn-primary">Save Profile</button>
</form>
</div>
{% endblock %}
And the corresponding Flask code:
from flask import Flask, render_template, redirect, url_for
from flask_wtf import FlaskForm
from wtforms import StringField, TextAreaField, SelectField, BooleanField, SubmitField
from wtforms.validators import DataRequired
class ProfileForm(FlaskForm):
first_name = StringField('First Name', validators=[DataRequired()])
last_name = StringField('Last Name', validators=[DataRequired()])
bio = TextAreaField('Biography')
country = SelectField('Country', choices=[
('', 'Select a country'),
('us', 'United States'),
('uk', 'United Kingdom'),
('ca', 'Canada'),
('au', 'Australia')
])
newsletter = BooleanField('Subscribe to newsletter')
terms = BooleanField('I agree to terms', validators=[DataRequired()])
submit = SubmitField('Save Profile')
@app.route('/profile/edit', methods=['GET', 'POST'])
def edit_profile():
form = ProfileForm()
if form.validate_on_submit():
# Process the form data
return redirect(url_for('profile'))
return render_template('profile.html', form=form)
Creating a Macro for an Entire Form
You can take macros a step further by creating one that renders an entire form:
{# templates/macros.html #}
{% macro render_form(form, action_url, cancel_url=None, btn_text='Submit') %}
<form method="POST" action="{{ action_url }}">
{{ form.csrf_token }}
{% for field in form if field.name != 'csrf_token' and field.type != 'SubmitField' %}
{% if field.type == 'BooleanField' %}
{{ render_checkbox(field) }}
{% elif field.type == 'SelectField' %}
{{ render_select(field) }}
{% else %}
{{ render_field(field) }}
{% endif %}
{% endfor %}
<div class="form-group">
<button type="submit" class="btn btn-primary">{{ btn_text }}</button>
{% if cancel_url %}
<a href="{{ cancel_url }}" class="btn btn-secondary">Cancel</a>
{% endif %}
</div>
</form>
{% endmacro %}
Now you can render an entire form with a single line:
{# templates/login.html #}
{% extends "base.html" %}
{% from "macros.html" import render_form %}
{% block content %}
<div class="container">
<h1>Login</h1>
{{ render_form(form, url_for('login'), url_for('home'), 'Log In') }}
</div>
{% endblock %}
Real-World Application: Contact Form
Let's create a practical example of a contact form using our macros:
{# templates/contact.html #}
{% extends "base.html" %}
{% from "macros.html" import render_form %}
{% block content %}
<div class="container">
<div class="row">
<div class="col-md-8 offset-md-2">
<h1>Contact Us</h1>
<p class="lead">Have a question or feedback? Fill out the form below and we'll get back to you.</p>
{{ render_form(form, url_for('contact'), url_for('home'), 'Send Message') }}
</div>
</div>
</div>
{% endblock %}
The corresponding Flask code:
from flask import Flask, render_template, flash, redirect, url_for
from flask_wtf import FlaskForm
from wtforms import StringField, TextAreaField, SelectField, SubmitField
from wtforms.validators import DataRequired, Email
class ContactForm(FlaskForm):
name = StringField('Name', validators=[DataRequired()])
email = StringField('Email', validators=[DataRequired(), Email()])
subject = SelectField('Subject', choices=[
('', 'Select a subject'),
('general', 'General Inquiry'),
('support', 'Technical Support'),
('feedback', 'Feedback'),
('other', 'Other')
])
message = TextAreaField('Message', validators=[DataRequired()])
submit = SubmitField('Send Message')
@app.route('/contact', methods=['GET', 'POST'])
def contact():
form = ContactForm()
if form.validate_on_submit():
# Send email with form data
flash('Your message has been sent! We will respond shortly.', 'success')
return redirect(url_for('home'))
return render_template('contact.html', form=form)
Customizing Macros with Parameters
You can make your macros even more flexible by adding parameters:
{# templates/macros.html #}
{% macro render_field(field, label_class='form-control-label', input_class='form-control') %}
<div class="form-group">
{{ field.label(class=label_class) }}
{{ field(class=input_class) }}
{% if field.errors %}
<div class="invalid-feedback d-block">
{% for error in field.errors %}
<span>{{ error }}</span>
{% endfor %}
</div>
{% endif %}
</div>
{% endmacro %}
Now you can customize the classes:
{{ render_field(form.username, label_class='custom-label', input_class='custom-input') }}
Summary
Form macros in Flask provide an elegant solution to reduce code duplication and maintain consistent form styling across your application. By using Jinja2's powerful macro system, you can:
- Create reusable form components that maintain consistent styling
- Reduce template code duplication significantly
- Make your templates more readable and maintainable
- Easily update form styling across your entire application in one place
Once you start using form macros, you'll find them invaluable for keeping your Flask templates clean, consistent, and maintainable.
Additional Resources and Exercises
Resources
Exercises
-
Basic Macro Practice: Create a macro for rendering a form field with a floating label (label that sits inside the input field and moves up when the field is focused).
-
Advanced Form Styling: Extend the form macros to include different styling for invalid fields, providing visual feedback to users.
-
Form Layout Macro: Create a macro that arranges form fields in a two-column layout for larger screens but stacks them vertically on mobile devices.
-
Complex Form: Create a multi-step form wizard using macros to maintain consistent styling across all steps.
-
Form Accessibility: Enhance the macros to improve form accessibility by adding proper ARIA attributes and ensuring they work well with screen readers.
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)