View functions — render article list and detail pages

In this chapter, you will learn to query data from the database using view functions, render the homepage and detail pages, and split routes using Blueprint.


The responsibilities of view functions

Flask view functions have the same responsibilities as Django:Receive request → Query data → Render template。

Example

# File path: app.py
from flask import Flask, render_template
from models import Post, Category

@app.route("/")
def index():
    """Blog home page: displays all articles"""
    posts = Post.query.order_by(Post.created_at.desc()).all()
    categories = Category.query.all()
    return render_template('index.html', posts=posts, categories=categories)

Path parameters and query parameters

Parameter TypeURL ExampleHow views are obtained
Path Parameter/post/3In routes, <type:variable_name> → function parameter
Query Parameter/?category=pythonrequest.args.get('key')

Flask routes support multiple type converters:<int:id>(integer),<string:slug>(default, does not include /),<path:path>(contains /),<float:price>etc.


Article Detail Page

Example

# File path: app.py append
from flask import abort

@app.route("/post/<int:post_id>")
def post_detail(post_id):
    """Article detail page"""
    # get_or_404 is equivalent to: Post.query.get(post_id) or abort(404)
    post = Post.query.get_or_404(post_id,
        description=f'Article #{post_id} does not exist')
    return render_template('post_detail.html', post=post)

Custom 404 error page

Example

@app.errorhandler(404)
def not_found(error):
    return render_template('404.html'), 404

Category Filtering

Example

# File path: refactor the index view in app.py
from flask import request

@app.route("/")
def index():
    category_slug = request.args.get('category', '')
    keyword = request.args.get('q', '')       # Search keyword (implemented in Chapter 6)

    posts_query = Post.query.order_by(Post.created_at.desc())

    # Filter by category
    if category_slug:
        posts_query = posts_query.join(Category).filter(Category.slug == category_slug)

    posts = posts_query.all()
    categories = Category.query.all()

    return render_template('index.html',
        posts=posts, categories=categories,
        category_slug=category_slug, keyword=keyword)

request.argsis an immutable MultiDict, used to read URL query parameters. Use.get('key', '默认值')Safe retrieval, returns a default value when the parameter does not exist instead of raising an error.


Blueprint — splitting routes

When view functions increase, stacking them all in app.py becomes unmanageable.

BlueprintIt is Flask's route grouping scheme: split views by functional modules and register via app.register_blueprint().

app/
├── blueprints/
│   ├── __init__.py
│   ├── main.py          # 首页、关于、搜索
│   └── posts.py         # 文章列表、详情
├── templates/
├── models.py
└── __init__.py

Example

# File path: app/blueprints/main.py
from flask import Blueprint, render_template, request
from app.models import Post, Category

# Blueprint('blueprint_name', __name__, url_prefix='/')
main_bp = Blueprint('main', __name__)

@main_bp.route("/")
def index():
    """Blog home page"""
    category_slug = request.args.get('category', '')
    posts_query = Post.query.order_by(Post.created_at.desc())
    if category_slug:
        posts_query = posts_query.join(Category).filter(Category.slug == category_slug)
    return render_template('index.html',
        posts=posts_query.all(),
        categories=Category.query.all(),
        category_slug=category_slug)

Example

# File path: app/blueprints/posts.py
from flask import Blueprint, render_template, abort
from app.models import Post

posts_bp = Blueprint('posts', __name__)

@posts_bp.route("/post/<int:post_id>")
def post_detail(post_id):
    post = Post.query.get_or_404(post_id)
    return render_template('post_detail.html', post=post)

Register the Blueprint at the application entry point:

Example

# File path: app.py
from flask import Flask
from app.blueprints.main import main_bp
from app.blueprints.posts import posts_bp

app = Flask(__name__)

# Register blueprints: all view functions of the blueprints will be merged into the same application
app.register_blueprint(main_bp)
app.register_blueprint(posts_bp)

A Blueprint can specifyurl_prefix(such asurl_prefix='/blog'), then all routes within the blueprint will automatically have this prefix added. A Blueprint can also have its own template and static file directories.


Creating templates for the homepage and detail page

index.html

Example

<!-- File path: app/templates/index.html -->
{% extends 'base.html' %}

{% block title %}EXAMPLE Blog - Home{% endblock %}

{% block content %}
<h2 class="section-title">Latest Articles</h2>

<!-- Category filter navigation -->
<div class="category-bar">
    <a href="{{ url_for('main.index') }}"
      class="{% if not category_slug %}active{% endif %}">All</a>
    {% for cat in categories %}
    <a href="{{ url_for('main.index', category=cat.slug) }}"
      class="{% if category_slug == cat.slug %}active{% endif %}">
        {{ cat.name }}
    </a>
    {% endfor %}
</div>

{% if posts %}
<div class="article-grid">
    {% for post in posts %}
    <a href="{{ url_for('posts.post_detail', post_id=post.id) }}" class="card-link">
        <div class="article-card">
            <div class="card-content">
                <span class="card-category">{{ post.category.name }}</span>
                <h3>{{ post.title }}</h3>
                <p>{{ post.summary|truncate(80) }}</p>
                <span class="card-date">{{ post.created_at.strftime('%Y-%m-%d') }}</span>
            </div>
        </div>
    </a>
    {% endfor %}
</div>
{% else %}
<p class="empty-tip">No articles yet in this category.</p>
{% endif %}
{% endblock %}

When using a Blueprint view in url_for(), the format isurl_for('蓝图名.函数名'), such asurl_for('posts.post_detail', post_id=3). Note the difference from Django's{% url %}tag comparison: Flask uses{{ url_for() }}, can be nested in Python expressions for more flexibility.


Chapter summary

In this chapter, you mastered Flask views and route organization: Post.query to query data, path parameter <int:post_id> and query parameter request.args, get_or_404 for safe queries, abort(404) and custom error pages, and Blueprint to split routes by module.

The blog now has three complete pages: the homepage article list, category filtering, and detail pages.

other extensions