Application Structure with Blueprints

Module 23: Web Frameworks II (Python) - Tuesday, Lecture 3

The Need for Structure in Web Applications

As Flask applications grow, organizing everything in a single file becomes unwieldy and introduces several problems:

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.

graph TD A[Monolithic App] --> B[Hard to maintain] A --> C[Difficult to test] A --> D[Team conflicts] A --> E[Poor code reuse] A --> F[Complex dependencies] G[Modular App] --> H[Easy to maintain] G --> I[Testable components] G --> J[Team collaboration] G --> K[Reusable modules] G --> L[Clear dependencies]

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

When to Use Blueprints

Blueprints are particularly useful in the following scenarios:

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:

Blueprint Template Resolution

Flask looks for templates in multiple locations, with a specific order of precedence:

  1. The application's template folder
  2. The current Blueprint's template folder
  3. 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
graph TD A[Flask Application] --> B[Main Blueprint] A --> C[Auth Blueprint] A --> D[Admin Blueprint] A --> E[API Blueprint] A --> F[Models] A --> G[Utils] B --> B1[Routes] B --> B2[Forms] B --> B3[Templates] C --> C1[Routes] C --> C2[Forms] C --> C3[Templates] D --> D1[Routes] D --> D2[Forms] D --> D3[Templates] D --> D4[Static Files] E --> E1[Routes] E --> E2[Helpers]

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

Blueprint Design

Code Organization

Configuration and Extensions

Practice Activity

Create a Flask application for a basic e-commerce site using Blueprints to organize the code:

  1. Set up a Flask application with the application factory pattern
  2. 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
  3. 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)
  4. Implement the following features:
    • User registration and login
    • Product listing and filtering
    • Shopping cart functionality
    • Admin area for managing products

Bonus challenges:

Further Topics to Explore