The Need for Structure in Web Applications
As Flask applications grow, organizing everything in a single file becomes unwieldy and introduces several problems:
- Code maintainability: A large, monolithic file is hard to navigate and understand
- Code reusability: Tightly coupled code is difficult to reuse in other projects
- Team collaboration: Multiple developers working on the same file leads to conflicts
- Testing complexity: Monolithic applications are harder to test in isolation
- Scaling challenges: Adding new features to an unstructured application becomes increasingly difficult
The City Planning Analogy
Think of application structure like city planning. A small village might work fine with just one main street where everything is located. But as the village grows into a town and then a city, you need to organize it into districts (modules) with specific purposes, connected by roads (interfaces). Without proper planning, you end up with a chaotic, unnavigable mess that's difficult to maintain and expand.
Blueprints in Flask are like zoning permits in city planning—they define distinct areas with specific purposes while maintaining a cohesive overall structure.
Introduction to Flask Blueprints
Blueprints are Flask's solution for modularizing applications. A Blueprint is a way to organize a group of related views, templates, static files, and other code into a self-contained component that can be registered with a Flask application.
Key Features of Blueprints
- Modular Structure: Group related functionality into separate modules
- Reusable Components: Create reusable application components
- URL Prefixing: Register a Blueprint with a URL prefix to namespace all its routes
- Template and Static File Separation: Each Blueprint can have its own templates and static files
- Own Error Handlers: Define Blueprint-specific error handlers
- Extension Points: Register functions that run when specific events occur
When to Use Blueprints
Blueprints are particularly useful in the following scenarios:
- Applications with multiple distinct sections (e.g., admin panel, user area, API)
- Large applications that need to be broken down into manageable pieces
- When developing reusable components for multiple Flask applications
- When multiple developers need to work on different parts of the application
- When you want to package functionality for distribution
Basic Blueprint Structure
Let's look at how to create and use a basic blueprint:
Creating a Blueprint
from flask import Blueprint, render_template
# Create a blueprint object
admin_bp = Blueprint('admin', __name__, url_prefix='/admin')
# Define routes using the blueprint's route decorator
@admin_bp.route('/')
def admin_index():
return render_template('admin/index.html')
@admin_bp.route('/users')
def admin_users():
return render_template('admin/users.html')
@admin_bp.route('/settings')
def admin_settings():
return render_template('admin/settings.html')
Registering a Blueprint with the Application
from flask import Flask
from admin import admin_bp
app = Flask(__name__)
# Register the blueprint with the application
app.register_blueprint(admin_bp)
# Define main application routes
@app.route('/')
def index():
return render_template('index.html')
if __name__ == '__main__':
app.run(debug=True)
In this example, all routes defined in the admin Blueprint will be prefixed with /admin, so admin_index() will be available at /admin/, admin_users() at /admin/users, and so on.
Blueprint Configuration Options
When creating a Blueprint, you can provide several parameters to customize its behavior:
Blueprint(name, import_name, static_folder=None, static_url_path=None,
template_folder=None, url_prefix=None, subdomain=None,
url_defaults=None, root_path=None)
| Parameter | Description | Example |
|---|---|---|
name |
The name of the Blueprint (used in url_for()) |
'admin' |
import_name |
The name of the package where the Blueprint is defined | __name__ |
static_folder |
Path to the Blueprint's static files folder | 'static' |
static_url_path |
URL path for the Blueprint's static files | '/admin/static' |
template_folder |
Path to the Blueprint's template folder | 'templates' |
url_prefix |
Prefix for all URLs defined in the Blueprint | '/admin' |
subdomain |
Subdomain for all routes in the Blueprint | 'admin' |
url_defaults |
Default values for view arguments | {'lang': 'en'} |
root_path |
Override the Blueprint's root path | '/path/to/blueprint' |
Example with Extended Configuration
admin_bp = Blueprint('admin', __name__,
template_folder='templates/admin',
static_folder='static/admin',
static_url_path='/admin/static',
url_prefix='/admin')
With this configuration:
- Templates will be found in the
templates/admindirectory relative to the Blueprint's location - Static files will be found in the
static/admindirectory relative to the Blueprint's location - Static files will be accessible at URLs starting with
/admin/static - All routes defined in the Blueprint will have URLs starting with
/admin
Blueprint Template Resolution
Flask looks for templates in multiple locations, with a specific order of precedence:
- The application's template folder
- The current Blueprint's template folder
- Other registered Blueprints' template folders (in registration order)
This allows both application-wide templates and Blueprint-specific templates:
project/
│
├── app.py # Flask application
├── templates/ # Application templates
│ ├── base.html # Base template for all pages
│ ├── index.html # Main application template
│ └── admin/ # Admin-specific templates in app folder
│ └── dashboard.html # Can override Blueprint template
│
└── admin/ # Admin Blueprint package
├── __init__.py # Blueprint definition
├── views.py # Blueprint views
└── templates/ # Blueprint-specific templates
└── admin/ # Namespace to avoid conflicts
├── index.html # Admin index template
├── users.html # Users management template
└── dashboard.html # Would be overridden by app template
Best Practice: Template Namespacing
It's a good practice to namespace Blueprint templates by putting them in a subdirectory with the same name as the Blueprint. This helps avoid naming conflicts between different Blueprints and the main application.
For example, if you have an 'admin' Blueprint, put its templates in a directory structure like templates/admin/ within the Blueprint's template folder.
URL Generation with Blueprints
When using url_for() with Blueprints, you need to prefix the view function name with the Blueprint name:
# Without Blueprint
url_for('index') # Refers to the main app's index view
# With Blueprint
url_for('admin.index') # Refers to the admin Blueprint's index view
Example with Templates
<!-- In a template file -->
<nav>
<ul>
<li><a href="{{ url_for('index') }}">Home</a></li>
<li><a href="{{ url_for('admin.index') }}">Admin Dashboard</a></li>
<li><a href="{{ url_for('admin.users') }}">User Management</a></li>
<li><a href="{{ url_for('blog.index') }}">Blog</a></li>
</ul>
</nav>
Dynamic URL Prefixing
One advantage of using url_for() is that it automatically includes the Blueprint's URL prefix, which means if you change the prefix when registering the Blueprint, all your URLs will update correctly:
# If you change this...
app.register_blueprint(admin_bp, url_prefix='/administration')
# ...all these URLs change automatically
url_for('admin.index') # Now points to '/administration/'
url_for('admin.users') # Now points to '/administration/users'
Blueprint-Specific Error Handlers
Each Blueprint can define its own error handlers that apply only to errors that occur within the Blueprint:
admin_bp = Blueprint('admin', __name__, url_prefix='/admin')
@admin_bp.errorhandler(404)
def admin_404_error(error):
return render_template('admin/errors/404.html'), 404
@admin_bp.errorhandler(403)
def admin_403_error(error):
return render_template('admin/errors/403.html'), 403
Blueprint-specific error handlers only apply to errors raised within the Blueprint. If you need application-wide error handling, you should register those handlers on the application object.
# In your main app.py file
@app.errorhandler(404)
def page_not_found(error):
return render_template('errors/404.html'), 404
@app.errorhandler(500)
def server_error(error):
return render_template('errors/500.html'), 500
Blueprint Before and After Request Hooks
Blueprints can register functions to run before and after each request, similar to the application-wide hooks:
@admin_bp.before_request
def admin_before_request():
"""Run before each request in the admin Blueprint."""
# Check if user is authenticated and has admin privileges
if not current_user.is_authenticated or not current_user.is_admin:
abort(403) # Forbidden
@admin_bp.after_request
def admin_after_request(response):
"""Run after each request in the admin Blueprint."""
# Add custom headers to admin responses
response.headers['X-Admin-Area'] = 'True'
return response
These hooks only run for requests to routes defined in the Blueprint. To run a function before all requests, register it with the application:
@app.before_request
def before_request():
"""Run before each request in the application."""
# Set up g object with request-specific data
g.user = get_current_user()
Organizing a Flask Application with Blueprints
Let's examine a comprehensive directory structure for a Flask application organized with Blueprints:
myapp/
│
├── run.py # Application entry point
├── config.py # Configuration settings
├── requirements.txt # Dependencies
├── README.md # Documentation
│
├── app/ # Application package
│ ├── __init__.py # Initialize application
│ ├── models/ # Database models
│ │ ├── __init__.py
│ │ ├── user.py
│ │ └── post.py
│ │
│ ├── static/ # Main application static files
│ │ ├── css/
│ │ ├── js/
│ │ └── images/
│ │
│ ├── templates/ # Main application templates
│ │ ├── base.html
│ │ ├── macros/
│ │ └── errors/
│ │
│ ├── utils/ # Utility functions
│ │ ├── __init__.py
│ │ ├── decorators.py
│ │ └── helpers.py
│ │
│ └── blueprints/ # Application blueprints
│ ├── main/ # Main pages Blueprint
│ │ ├── __init__.py
│ │ ├── routes.py
│ │ ├── forms.py
│ │ └── templates/
│ │ └── main/
│ │
│ ├── auth/ # Authentication Blueprint
│ │ ├── __init__.py
│ │ ├── routes.py
│ │ ├── forms.py
│ │ └── templates/
│ │ └── auth/
│ │
│ ├── admin/ # Admin Blueprint
│ │ ├── __init__.py
│ │ ├── routes.py
│ │ ├── forms.py
│ │ ├── static/ # Blueprint-specific static files
│ │ └── templates/
│ │ └── admin/
│ │
│ └── api/ # API Blueprint
│ ├── __init__.py
│ ├── routes.py
│ └── helpers.py
│
├── migrations/ # Database migrations
│
└── tests/ # Test suite
├── __init__.py
├── conftest.py
├── test_models.py
└── test_views.py
Creating the Application Factory Pattern
The Application Factory Pattern is a way to create multiple instances of the Flask application, which is particularly useful for testing and when using different configurations:
The Factory Function
# app/__init__.py
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_migrate import Migrate
from flask_login import LoginManager
from config import config
# Initialize extensions
db = SQLAlchemy()
migrate = Migrate()
login_manager = LoginManager()
login_manager.login_view = 'auth.login'
def create_app(config_name='default'):
"""Create and configure the Flask application."""
app = Flask(__name__)
app.config.from_object(config[config_name])
# Initialize extensions with the app
db.init_app(app)
migrate.init_app(app, db)
login_manager.init_app(app)
# Register blueprints
from app.blueprints.main import main_bp
app.register_blueprint(main_bp)
from app.blueprints.auth import auth_bp
app.register_blueprint(auth_bp, url_prefix='/auth')
from app.blueprints.admin import admin_bp
app.register_blueprint(admin_bp, url_prefix='/admin')
from app.blueprints.api import api_bp
app.register_blueprint(api_bp, url_prefix='/api/v1')
# Register error handlers
from app.utils.error_handlers import register_error_handlers
register_error_handlers(app)
return app
Configuration File
# config.py
import os
class Config:
"""Base configuration."""
SECRET_KEY = os.environ.get('SECRET_KEY', 'dev-key-please-change')
SQLALCHEMY_TRACK_MODIFICATIONS = False
class DevelopmentConfig(Config):
"""Development configuration."""
DEBUG = True
SQLALCHEMY_DATABASE_URI = os.environ.get('DEV_DATABASE_URL',
'sqlite:///dev.db')
class TestingConfig(Config):
"""Testing configuration."""
TESTING = True
SQLALCHEMY_DATABASE_URI = os.environ.get('TEST_DATABASE_URL',
'sqlite:///test.db')
class ProductionConfig(Config):
"""Production configuration."""
SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URL',
'sqlite:///prod.db')
config = {
'development': DevelopmentConfig,
'testing': TestingConfig,
'production': ProductionConfig,
'default': DevelopmentConfig
}
Application Entry Point
# run.py
import os
from app import create_app
app = create_app(os.environ.get('FLASK_CONFIG', 'default'))
if __name__ == '__main__':
app.run()
Benefits of the Application Factory Pattern
- Multiple instances: Create different instances of the application for testing, development, or production
- Delayed configuration: Load configuration only when the application is created
- Extension initialization: Initialize extensions with the application instance
- Blueprint registration: Register Blueprints when the application is created
- Testability: Easy to create test instances with different configurations
Implementing Blueprints
Let's see how to implement a Blueprint in a modular application structure:
Blueprint Definition
# app/blueprints/admin/__init__.py
from flask import Blueprint
admin_bp = Blueprint('admin', __name__,
template_folder='templates',
static_folder='static')
# Import views at the end to avoid circular imports
from app.blueprints.admin import routes
Blueprint Routes
# app/blueprints/admin/routes.py
from flask import render_template, redirect, url_for, flash, request, current_app
from flask_login import login_required, current_user
from app.models.user import User
from app.blueprints.admin import admin_bp
from app.blueprints.admin.forms import UserForm
from app.utils.decorators import admin_required
from app import db
@admin_bp.route('/')
@login_required
@admin_required
def index():
"""Admin dashboard."""
user_count = User.query.count()
recent_users = User.query.order_by(User.created_at.desc()).limit(5).all()
return render_template('admin/index.html',
user_count=user_count,
recent_users=recent_users)
@admin_bp.route('/users')
@login_required
@admin_required
def user_list():
"""List all users."""
page = request.args.get('page', 1, type=int)
per_page = current_app.config.get('ADMIN_USERS_PER_PAGE', 20)
users = User.query.order_by(User.username).paginate(
page=page, per_page=per_page
)
return render_template('admin/users.html', users=users)
@admin_bp.route('/users/', methods=['GET', 'POST'])
@login_required
@admin_required
def edit_user(user_id):
"""Edit a user."""
user = User.query.get_or_404(user_id)
form = UserForm(obj=user)
if form.validate_on_submit():
form.populate_obj(user)
db.session.commit()
flash('User updated successfully.', 'success')
return redirect(url_for('admin.user_list'))
return render_template('admin/edit_user.html', form=form, user=user)
Blueprint Forms
# app/blueprints/admin/forms.py
from flask_wtf import FlaskForm
from wtforms import StringField, BooleanField, SelectField, SubmitField
from wtforms.validators import DataRequired, Email, Length, Optional
from app.models.user import User
class UserForm(FlaskForm):
"""Form for editing users."""
username = StringField('Username', validators=[
DataRequired(), Length(min=3, max=64)
])
email = StringField('Email', validators=[
DataRequired(), Email(), Length(max=120)
])
active = BooleanField('Active')
role = SelectField('Role', choices=[
('user', 'Regular User'),
('editor', 'Editor'),
('admin', 'Administrator')
])
submit = SubmitField('Save')
Blueprint Template Example
<!-- app/blueprints/admin/templates/admin/index.html -->
{% extends "base.html" %}
{% block title %}Admin Dashboard{% endblock %}
{% block content %}
<div class="container">
<h1>Admin Dashboard</h1>
<div class="row">
<div class="col-md-4">
<div class="card">
<div class="card-body">
<h5 class="card-title">Total Users</h5>
<p class="card-text display-4">{{ user_count }}</p>
<a href="{{ url_for('admin.user_list') }}" class="btn btn-primary">View All Users</a>
</div>
</div>
</div>
<!-- More dashboard cards -->
</div>
<h2 class="mt-4">Recent Users</h2>
<table class="table">
<thead>
<tr>
<th>Username</th>
<th>Email</th>
<th>Joined</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
{% for user in recent_users %}
<tr>
<td>{{ user.username }}</td>
<td>{{ user.email }}</td>
<td>{{ user.created_at.strftime('%Y-%m-%d') }}</td>
<td>
<a href="{{ url_for('admin.edit_user', user_id=user.id) }}" class="btn btn-sm btn-outline-primary">Edit</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% endblock %}
Reusable Blueprints
One of the key advantages of Blueprints is the ability to create reusable components that can be used across different Flask applications:
Creating a Reusable Blueprint Package
flask_admin/
│
├── __init__.py # Package initialization
├── decorators.py # Admin-specific decorators
├── forms.py # Admin forms
├── models.py # Admin-specific models
├── routes.py # Admin routes
│
├── static/ # Admin static files
│ ├── css/
│ ├── js/
│ └── images/
│
└── templates/ # Admin templates
└── admin/
Blueprint Package Initialization
# flask_admin/__init__.py
from flask import Blueprint
class AdminBlueprint(Blueprint):
"""Enhanced Blueprint for admin functionality."""
def __init__(self, models=None, **kwargs):
"""Initialize the admin Blueprint.
Args:
models: Dictionary of model classes to manage
**kwargs: Additional arguments for the Blueprint
"""
name = kwargs.pop('name', 'admin')
import_name = kwargs.pop('import_name', __name__)
static_folder = kwargs.pop('static_folder', 'static')
template_folder = kwargs.pop('template_folder', 'templates')
super().__init__(name, import_name,
static_folder=static_folder,
template_folder=template_folder,
**kwargs)
self.models = models or {}
# Import routes to register them with the Blueprint
from flask_admin import routes
# Register model-specific views
self._register_model_views()
def _register_model_views(self):
"""Register generic CRUD views for each model."""
for model_name, model_class in self.models.items():
# Register list view
self.add_url_rule(
f'/{model_name}',
f'{model_name}_list',
lambda m=model_class: routes.model_list(m),
methods=['GET']
)
# Register create view
self.add_url_rule(
f'/{model_name}/create',
f'{model_name}_create',
lambda m=model_class: routes.model_create(m),
methods=['GET', 'POST']
)
# Register edit view
self.add_url_rule(
f'/{model_name}//edit',
f'{model_name}_edit',
lambda id, m=model_class: routes.model_edit(m, id),
methods=['GET', 'POST']
)
# Register delete view
self.add_url_rule(
f'/{model_name}//delete',
f'{model_name}_delete',
lambda id, m=model_class: routes.model_delete(m, id),
methods=['POST']
)
def create_admin_blueprint(models=None, **kwargs):
"""Factory function to create an admin Blueprint.
Args:
models: Dictionary of model classes to manage
**kwargs: Additional arguments for the Blueprint
Returns:
An AdminBlueprint instance
"""
return AdminBlueprint(models=models, **kwargs)
Using the Reusable Blueprint
# In your application
from flask import Flask
from flask_admin import create_admin_blueprint
from app.models import User, Post, Category
app = Flask(__name__)
# Create admin Blueprint with your models
admin_bp = create_admin_blueprint(
models={
'users': User,
'posts': Post,
'categories': Category
},
url_prefix='/admin'
)
# Register the Blueprint
app.register_blueprint(admin_bp)
Real-World Example: Flask-Admin
The Flask-Admin extension is a real-world example of a reusable Blueprint. It provides a complete admin interface for your Flask applications with minimal setup:
from flask import Flask
from flask_admin import Admin
from flask_admin.contrib.sqla import ModelView
from app.models import User, Post, Category
from app import db
app = Flask(__name__)
# Create admin interface
admin = Admin(app, name='My Admin', template_mode='bootstrap4')
# Add model views
admin.add_view(ModelView(User, db.session))
admin.add_view(ModelView(Post, db.session))
admin.add_view(ModelView(Category, db.session))
Flask-Admin uses Blueprints internally to provide a complete admin interface that can be customized and extended. This is a perfect example of how Blueprints enable reusable components in Flask.
Nested Blueprints
While Flask doesn't directly support nesting Blueprints, you can simulate this behavior by carefully structuring your URL prefixes:
# Parent Blueprint (admin)
admin_bp = Blueprint('admin', __name__, url_prefix='/admin')
# Child Blueprint (users)
users_bp = Blueprint('admin_users', __name__, url_prefix='/users')
# Child Blueprint (settings)
settings_bp = Blueprint('admin_settings', __name__, url_prefix='/settings')
# Register child Blueprints with the parent
admin_bp.register_blueprint(users_bp)
admin_bp.register_blueprint(settings_bp)
# Register parent Blueprint with the application
app.register_blueprint(admin_bp)
# This creates the following URL structure:
# /admin/users/...
# /admin/settings/...
Each "child" Blueprint maintains its own routes, templates, and static files, allowing for a modular structure within a larger Blueprint.
Comprehensive Example: Blog Application
Let's examine a full implementation of a blog application using Blueprints for modularity:
Directory Structure
blog_app/
│
├── run.py # Application entry point
├── config.py # Configuration
├── requirements.txt # Dependencies
│
├── app/ # Application package
│ ├── __init__.py # Application factory
│ │
│ ├── models/ # Database models
│ │ ├── __init__.py
│ │ ├── user.py
│ │ └── blog.py
│ │
│ ├── extensions.py # Flask extensions
│ │
│ ├── blueprints/ # Application blueprints
│ │ ├── main/ # Main pages
│ │ │ ├── __init__.py
│ │ │ └── routes.py
│ │ │
│ │ ├── auth/ # Authentication
│ │ │ ├── __init__.py
│ │ │ ├── routes.py
│ │ │ └── forms.py
│ │ │
│ │ ├── blog/ # Blog functionality
│ │ │ ├── __init__.py
│ │ │ ├── routes.py
│ │ │ └── forms.py
│ │ │
│ │ └── admin/ # Admin panel
│ │ ├── __init__.py
│ │ ├── routes.py
│ │ └── forms.py
│ │
│ ├── templates/ # Application templates
│ │ ├── base.html
│ │ ├── macros/
│ │ ├── main/
│ │ ├── auth/
│ │ ├── blog/
│ │ └── admin/
│ │
│ └── static/ # Static files
│ ├── css/
│ ├── js/
│ └── images/
│
└── migrations/ # Database migrations
Application Factory
# app/__init__.py
from flask import Flask
from app.extensions import db, migrate, login_manager
from config import config
def create_app(config_name='default'):
"""Create and configure the Flask application."""
app = Flask(__name__)
app.config.from_object(config[config_name])
# Initialize extensions
db.init_app(app)
migrate.init_app(app, db)
login_manager.init_app(app)
# Register blueprints
from app.blueprints.main import main_bp
app.register_blueprint(main_bp)
from app.blueprints.auth import auth_bp
app.register_blueprint(auth_bp, url_prefix='/auth')
from app.blueprints.blog import blog_bp
app.register_blueprint(blog_bp, url_prefix='/blog')
from app.blueprints.admin import admin_bp
app.register_blueprint(admin_bp, url_prefix='/admin')
# Setup error handlers
register_error_handlers(app)
return app
def register_error_handlers(app):
"""Register error handlers."""
@app.errorhandler(404)
def page_not_found(error):
return render_template('errors/404.html'), 404
@app.errorhandler(500)
def internal_server_error(error):
return render_template('errors/500.html'), 500
Extensions
# app/extensions.py
from flask_sqlalchemy import SQLAlchemy
from flask_migrate import Migrate
from flask_login import LoginManager
# Initialize extensions
db = SQLAlchemy()
migrate = Migrate()
login_manager = LoginManager()
# Configure login manager
login_manager.login_view = 'auth.login'
login_manager.login_message_category = 'info'
@login_manager.user_loader
def load_user(user_id):
from app.models.user import User
return User.query.get(int(user_id))
Main Blueprint
# app/blueprints/main/__init__.py
from flask import Blueprint
main_bp = Blueprint('main', __name__)
from app.blueprints.main import routes
# app/blueprints/main/routes.py
from flask import render_template, current_app
from app.blueprints.main import main_bp
from app.models.blog import Post
@main_bp.route('/')
def index():
"""Homepage."""
recent_posts = Post.query.filter_by(published=True).order_by(
Post.created_at.desc()
).limit(5).all()
return render_template('main/index.html', recent_posts=recent_posts)
@main_bp.route('/about')
def about():
"""About page."""
return render_template('main/about.html')
@main_bp.route('/contact')
def contact():
"""Contact page."""
return render_template('main/contact.html')
Blog Blueprint
# app/blueprints/blog/__init__.py
from flask import Blueprint
blog_bp = Blueprint('blog', __name__)
from app.blueprints.blog import routes
# app/blueprints/blog/routes.py
from flask import render_template, redirect, url_for, flash, request, abort
from flask_login import login_required, current_user
from app.blueprints.blog import blog_bp
from app.blueprints.blog.forms import PostForm, CommentForm
from app.models.blog import Post, Comment, Category, Tag
from app.extensions import db
@blog_bp.route('/')
def index():
"""Blog index page."""
page = request.args.get('page', 1, type=int)
per_page = 10
posts = Post.query.filter_by(published=True).order_by(
Post.created_at.desc()
).paginate(page=page, per_page=per_page)
categories = Category.query.all()
return render_template('blog/index.html',
posts=posts,
categories=categories)
@blog_bp.route('/post/')
def post_detail(slug):
"""Show a blog post."""
post = Post.query.filter_by(slug=slug, published=True).first_or_404()
form = CommentForm()
return render_template('blog/post_detail.html',
post=post,
form=form)
@blog_bp.route('/post//comment', methods=['POST'])
@login_required
def add_comment(slug):
"""Add a comment to a post."""
post = Post.query.filter_by(slug=slug, published=True).first_or_404()
form = CommentForm()
if form.validate_on_submit():
comment = Comment(
content=form.content.data,
post=post,
author=current_user
)
db.session.add(comment)
db.session.commit()
flash('Your comment has been added!', 'success')
return redirect(url_for('blog.post_detail', slug=post.slug))
@blog_bp.route('/category/')
def category_posts(slug):
"""Show posts in a category."""
category = Category.query.filter_by(slug=slug).first_or_404()
page = request.args.get('page', 1, type=int)
per_page = 10
posts = Post.query.filter_by(
category=category, published=True
).order_by(
Post.created_at.desc()
).paginate(page=page, per_page=per_page)
return render_template('blog/category.html',
category=category,
posts=posts)
@blog_bp.route('/create', methods=['GET', 'POST'])
@login_required
def create_post():
"""Create a new blog post."""
form = PostForm()
# Populate form choices
form.category_id.choices = [
(c.id, c.name) for c in Category.query.order_by(Category.name).all()
]
form.tags.choices = [
(t.id, t.name) for t in Tag.query.order_by(Tag.name).all()
]
if form.validate_on_submit():
post = Post(
title=form.title.data,
content=form.content.data,
published=form.publish.data,
author=current_user,
category_id=form.category_id.data
)
# Add selected tags
selected_tags = Tag.query.filter(Tag.id.in_(form.tags.data)).all()
post.tags = selected_tags
db.session.add(post)
db.session.commit()
flash('Your post has been created!', 'success')
return redirect(url_for('blog.post_detail', slug=post.slug))
return render_template('blog/create_post.html', form=form)
Best Practices for Blueprint Organization
Here are some best practices to follow when organizing your Flask application with Blueprints:
Structure and Organization
- Separation by function: Create different Blueprints for different functional areas (e.g., auth, admin, api)
- Consistent naming: Follow a consistent naming convention for Blueprint variables and URL prefixes
- Template namespacing: Put Blueprint templates in subdirectories named after the Blueprint
- Avoid circular imports: Import views at the end of Blueprint __init__.py to avoid circular imports
Blueprint Design
- Single responsibility: Each Blueprint should have a clear, focused purpose
- Reusability: Design Blueprints to be potentially reusable in other projects
- Independence: Minimize dependencies between Blueprints when possible
- Consistent interfaces: Keep interfaces consistent across different Blueprints
Code Organization
- Models separation: Keep models separate from Blueprints unless they're specific to that Blueprint
- Forms in Blueprints: Keep forms.py in the Blueprint if they're specific to that Blueprint
- Blueprint's static files: Use Blueprint-specific static files only when necessary
- Common utilities: Move shared code to utility modules at the application level
Configuration and Extensions
- App factory pattern: Use the application factory pattern for flexibility
- Initialize extensions outside Blueprints: Initialize extensions at the application level
- Consistent error handling: Use both application-level and Blueprint-specific error handlers as needed
- Environment-specific configuration: Use different configurations for development, testing, and production
Practice Activity
Create a Flask application for a basic e-commerce site using Blueprints to organize the code:
- Set up a Flask application with the application factory pattern
- Create the following Blueprints:
- Main Blueprint (main) - Homepage, about, contact
- Auth Blueprint (auth) - Login, register, logout
- Product Blueprint (products) - Product listing, product detail
- Cart Blueprint (cart) - Shopping cart, checkout
- Admin Blueprint (admin) - Product management, order management
- Design the following models:
- User (id, username, email, password_hash)
- Product (id, name, description, price, image_url)
- Category (id, name, description)
- Order (id, user_id, status, created_at)
- OrderItem (id, order_id, product_id, quantity, price)
- Implement the following features:
- User registration and login
- Product listing and filtering
- Shopping cart functionality
- Admin area for managing products
Bonus challenges:
- Implement Blueprint-specific error handlers
- Add before_request hooks for authentication
- Create a reusable admin interface with generic CRUD views
- Implement an API Blueprint for a RESTful API
Further Topics to Explore
- Advanced Blueprint customization
- Integrating Blueprints with third-party extensions
- Creating reusable Blueprint packages for distribution
- Scalable blueprint architecture for large applications
- Testing strategies for Blueprint-based applications
- Blueprints for API versioning
- Subdomain-specific Blueprints