Flask传递数据到模板

本教程将详细介绍如何从Flask视图函数传递数据到Jinja2模板,包括各种数据类型和高级用法。
核心概念

在Flask中,通过 render_template() 函数将数据从视图传递到模板。数据以关键字参数的形式传递,在模板中作为变量使用。

1. 基本数据传递

1.1 传递简单变量

最基本的传递方式:在 render_template() 中传递关键字参数。

视图函数 (app.py)
from flask import Flask, render_template

app = Flask(__name__)

@app.route('/')
def index():
    # 传递简单变量
    username = "张三"
    age = 25
    score = 95.5
    is_vip = True

    return render_template(
        'index.html',
        username=username,
        age=age,
        score=score,
        is_vip=is_vip
    )

@app.route('/user/<user_id>')
def user_profile(user_id):
    # 直接传递参数
    return render_template(
        'profile.html',
        user_id=user_id,
        title=f"用户 {user_id} 的资料"
    )
模板文件 (index.html)
<!DOCTYPE html>
<html>
<head>
    <title>用户信息</title>
</head>
<body>
    <h1>用户信息</h1>

    <div class="user-info">
        <p>用户名: {{ username }}</p>
        <p>年龄: {{ age }}</p>
        <p>分数: {{ score }}</p>

        {% if is_vip %}
            <span class="badge vip">VIP会员</span>
        {% endif %}

        {% if score > 90 %}
            <p class="success">优秀成绩!</p>
        {% endif %}
    </div>

    <!-- 使用三元表达式 -->
    <p>状态: {{ "活跃" if is_vip else "普通" }}</p>
</body>
</html>

1.2 传递Python表达式结果

可以直接传递表达式的结果,而不是先计算再传递。

示例:动态计算并传递

# 视图函数
@app.route('/products')
def products():
    products = [
        {'name': '商品A', 'price': 100},
        {'name': '商品B', 'price': 200},
        {'name': '商品C', 'price': 150}
    ]

    # 计算并传递多个值
    return render_template(
        'products.html',
        products=products,
        total_products=len(products),  # 直接计算长度
        total_price=sum(p['price'] for p in products),  # 计算总价
        avg_price=sum(p['price'] for p in products) / len(products) if products else 0
    )
<!-- 模板中使用 -->
<h2>商品列表 (共 {{ total_products }} 件)</h2>
<p>总价值: ¥{{ total_price }}</p>
<p>平均价格: ¥{{ "%.2f"|format(avg_price) }}</p>

<ul>
{% for product in products %}
    <li>
        {{ product.name }} - ¥{{ product.price }}
        {% if product.price > avg_price %}
            <span class="text-warning">(高于平均)</span>
        {% endif %}
    </li>
{% endfor %}
</ul>

2. 复杂数据结构

2.1 传递列表和字典

可以传递任何Python数据结构,包括列表、字典、元组等。

视图函数示例
@app.route('/dashboard')
def dashboard():
    # 复杂数据结构示例
    user_data = {
        'profile': {
            'name': '李四',
            'email': 'lisi@example.com',
            'joined': '2023-01-15'
        },
        'preferences': {
            'theme': 'dark',
            'notifications': True,
            'language': 'zh-CN'
        },
        'stats': {
            'posts': 42,
            'followers': 156,
            'following': 89
        }
    }

    # 嵌套列表
    recent_activities = [
        {'type': 'post', 'title': '发布了新文章', 'time': '2小时前'},
        {'type': 'comment', 'title': '评论了帖子', 'time': '5小时前'},
        {'type': 'like', 'title': '点赞了照片', 'time': '1天前'}
    ]

    # 传递所有数据
    return render_template(
        'dashboard.html',
        user=user_data,
        activities=recent_activities,
        is_weekend=True
    )
模板中使用复杂数据
<!-- 访问嵌套字典 -->
<h2>{{ user.profile.name }} 的仪表板</h2>
<p>邮箱: {{ user.profile.email }}</p>

<!-- 访问嵌套字典的值 -->
{% if user.preferences.theme == 'dark' %}
    <body class="dark-theme">
{% endif %}

<!-- 遍历列表中的字典 -->
<h3>最近活动</h3>
<ul class="activity-list">
{% for activity in activities %}
    <li class="{{ activity.type }}">
        <span>{{ activity.title }}</span>
        <small>{{ activity.time }}</small>
    </li>
{% endfor %}
</ul>

<!-- 使用字典的items()方法 -->
<div class="stats">
    {% for key, value in user.stats.items() %}
        <div class="stat-item">
            <strong>{{ key }}:</strong>
            <span>{{ value }}</span>
        </div>
    {% endfor %}
</div>

2.2 传递对象和类实例

可以直接传递Python对象,在模板中访问其属性和方法。

示例:传递自定义对象

# models.py
class User:
    def __init__(self, id, name, email):
        self.id = id
        self.name = name
        self.email = email
        self.created_at = datetime.now()

    def get_display_name(self):
        return f"{self.name} ({self.email})"

    def is_recent_user(self):
        # 检查是否在7天内注册
        delta = datetime.now() - self.created_at
        return delta.days < 7

# app.py
from models import User

@app.route('/user/<user_id>')
def user_detail(user_id):
    # 模拟从数据库获取用户
    user = User(
        id=user_id,
        name="王五",
        email="wangwu@example.com"
    )

    # 传递对象
    return render_template('user_detail.html', user=user)
<!-- 模板中访问对象 -->
<div class="user-card">
    <h2>{{ user.get_display_name() }}</h2>
    <p>用户ID: {{ user.id }}</p>
    <p>注册时间: {{ user.created_at.strftime('%Y-%m-%d') }}</p>

    {% if user.is_recent_user() %}
        <span class="badge bg-success">新用户</span>
    {% endif %}

    <!-- 调用带参数的方法 -->
    {% set greeting = user.say_hello("朋友") %}
    <p>{{ greeting }}</p>
</div>

3. 上下文处理器

上下文处理器允许你在所有模板中自动注入变量,无需在每个视图函数中重复传递。

3.1 基本上下文处理器

定义上下文处理器
from datetime import datetime
import os

@app.context_processor
def inject_common_variables():
    """注入所有模板共用的变量"""
    return {
        # 当前时间
        'current_time': datetime.now(),

        # 应用配置
        'app_name': '我的Flask应用',
        'app_version': '1.0.0',

        # 环境变量
        'debug_mode': app.debug,

        # 工具函数
        'get_year': lambda: datetime.now().year,

        # 常用常量
        'COMPANY_NAME': 'Acme Inc.',
        'SUPPORT_EMAIL': 'support@example.com'
    }

@app.context_processor
def inject_user_data():
    """注入用户相关数据"""
    # 假设有函数获取当前用户
    user = get_current_user()  # 自定义函数

    return {
        'current_user': user,
        'is_logged_in': user is not None,
        'is_admin': user.is_admin if user else False
    }
在模板中自动可用
<!-- 在任何模板中都可以直接使用 -->
<!DOCTYPE html>
<html>
<head>
    <title>{{ app_name }} - {{ page_title }}</title>
    <meta name="version" content="{{ app_version }}">
</head>
<body>
    <header>
        <h1>{{ app_name }}</h1>
        {% if is_logged_in %}
            <p>欢迎,{{ current_user.name }}!</p>
        {% endif %}
    </header>

    <main>
        <p>当前时间: {{ current_time.strftime('%Y-%m-%d %H:%M:%S') }}</p>
        <p>© {{ get_year() }} {{ COMPANY_NAME }}</p>

        {% if debug_mode %}
            <div class="debug-info">
                调试模式已开启
            </div>
        {% endif %}
    </main>

    <footer>
        <p>联系我们: {{ SUPPORT_EMAIL }}</p>
    </footer>
</body>
</html>

3.2 条件上下文处理器

可以根据请求或条件动态注入变量。

@app.context_processor
def inject_request_based_data():
    """基于请求的上下文处理器"""
    from flask import request

    # 获取请求信息
    path = request.path
    method = request.method

    # 根据路径决定显示的内容
    show_sidebar = not path.startswith('/admin/')
    show_analytics = 'dashboard' in path

    return {
        'current_path': path,
        'request_method': method,
        'show_sidebar': show_sidebar,
        'show_analytics': show_analytics,

        # 生成面包屑导航
        'breadcrumbs': generate_breadcrumbs(path)
    }

def generate_breadcrumbs(path):
    """生成面包屑导航数据"""
    parts = path.strip('/').split('/')
    breadcrumbs = []

    for i, part in enumerate(parts):
        url = '/' + '/'.join(parts[:i+1])
        name = part.replace('-', ' ').title()
        breadcrumbs.append({'name': name, 'url': url})

    return breadcrumbs

4. 全局模板变量

4.1 内置全局变量

Flask和Jinja2提供了一些内置的全局变量,无需传递即可使用。

变量 描述 示例
request 当前请求对象 {{ request.url }}
session 会话对象 {{ session.get('user_id') }}
g 请求全局对象 {{ g.user }}
config 应用配置 {{ config['DEBUG'] }}
url_for() URL生成函数 {{ url_for('index') }}
get_flashed_messages() 获取闪现消息 {{ get_flashed_messages() }}

4.2 使用内置全局变量

<!-- 使用request对象 -->
<div class="request-info">
    <p>当前URL: {{ request.url }}</p>
    <p>请求方法: {{ request.method }}</p>
    <p>用户代理: {{ request.user_agent.browser }}</p>

    {% if request.args.get('page') %}
        <p>页码: {{ request.args.get('page') }}</p>
    {% endif %}
</div>

<!-- 使用session -->
{% if session.logged_in %}
    <p>欢迎用户 {{ session.username }}</p>
    <a href="{{ url_for('logout') }}">退出登录</a>
{% else %}
    <a href="{{ url_for('login') }}">请登录</a>
{% endif %}

<!-- 使用config -->
{% if config.DEBUG %}
    <div class="debug-notice">
        调试模式已开启
    </div>
{% endif %}

<!-- 使用url_for生成URL -->
<nav>
    <a href="{{ url_for('home') }}">首页</a>
    <a href="{{ url_for('profile', username='john') }}">John的资料</a>
    <a href="{{ url_for('static', filename='css/style.css') }}">CSS文件</a>
</nav>

<!-- 显示闪现消息 -->
{% with messages = get_flashed_messages() %}
    {% if messages %}
        <div class="flash-messages">
            {% for message in messages %}
                <div class="alert alert-info">{{ message }}</div>
            {% endfor %}
        </div>
    {% endif %}
{% endwith %}

5. 模板函数

5.1 传递函数到模板

可以传递Python函数到模板中直接调用。

定义模板函数
# 定义一些工具函数
def format_price(price):
    """格式化价格显示"""
    return f"¥{price:,.2f}"

def truncate_text(text, length=100):
    """截断文本并添加省略号"""
    if len(text) <= length:
        return text
    return text[:length] + "..."

def get_gravatar_url(email, size=80):
    """根据邮箱获取Gravatar头像URL"""
    import hashlib
    email_hash = hashlib.md5(email.lower().encode()).hexdigest()
    return f"https://www.gravatar.com/avatar/{email_hash}?s={size}&d=identicon"

def is_featured(product):
    """判断商品是否推荐"""
    return product.get('featured', False) or product['sales'] > 100

@app.route('/shop')
def shop():
    products = [
        {'name': '商品A', 'price': 1234.56, 'description': '这是一个很长的商品描述...', 'sales': 150},
        {'name': '商品B', 'price': 89.99, 'description': '短描述', 'sales': 80}
    ]

    # 传递函数到模板
    return render_template(
        'shop.html',
        products=products,
        format_price=format_price,
        truncate_text=truncate_text,
        get_gravatar_url=get_gravatar_url,
        is_featured=is_featured
    )
在模板中调用函数
<div class="products">
    {% for product in products %}
        <div class="product-card {{ 'featured' if is_featured(product) else '' }}">
            <h3>{{ product.name }}</h3>

            <!-- 调用传递的函数 -->
            <p class="price">{{ format_price(product.price) }}</p>

            <p class="description">
                {{ truncate_text(product.description, 50) }}
            </p>

            {% if is_featured(product) %}
                <span class="badge bg-warning">推荐商品</span>
            {% endif %}

            <!-- 调用带参数的函数 -->
            <img src="{{ get_gravatar_url('user@example.com', 40) }}"
                 alt="头像">
        </div>
    {% endfor %}
</div>

<!-- 直接调用函数 -->
<p>格式化测试: {{ format_price(1234567.89) }}</p>

6. 最佳实践与技巧

推荐做法
  • 使用有意义的变量名:让模板更易读
  • 保持视图函数简洁:将复杂逻辑移到业务层
  • 使用上下文处理器:减少重复代码
  • 类型检查:传递前确保数据格式正确
  • 文档化:为复杂数据结构添加注释
  • 错误处理:处理可能为None的变量
避免做法
  • 不要传递敏感数据:密码、API密钥等
  • 避免过度复杂:模板中不应有复杂业务逻辑
  • 不要信任用户输入:始终进行转义
  • 避免大型数据集:分页或懒加载
  • 不要硬编码:使用配置文件
  • 避免深层嵌套:保持数据结构扁平化

6.1 实用技巧

实用技巧示例

# 技巧1: 使用字典展开简化代码
user_data = {
    'username': 'john',
    'email': 'john@example.com',
    'age': 30
}
config = {
    'site_name': '我的网站',
    'debug': True
}

# 传统方式
return render_template(
    'index.html',
    username=user_data['username'],
    email=user_data['email'],
    age=user_data['age'],
    site_name=config['site_name'],
    debug=config['debug']
)

# 简化方式(Python 3.5+)
return render_template(
    'index.html',
    **user_data,  # 展开字典
    **config      # 展开另一个字典
)

# 技巧2: 条件传递
def user_profile():
    user = get_current_user()
    extra_data = {}

    if user and user.is_admin:
        extra_data = {
            'admin_stats': get_admin_stats(),
            'recent_logs': get_recent_logs()
        }

    return render_template(
        'profile.html',
        user=user,
        **extra_data  # 只有管理员才传递这些数据
    )
<!-- 技巧3: 模板中的默认值 -->
<p>用户名: {{ username|default('匿名用户') }}</p>
<p>邮箱: {{ email|default('未设置', true) }}</p>

<!-- 技巧4: 使用set创建局部变量 -->
{% set full_name = user.first_name ~ ' ' ~ user.last_name %}
<h2>{{ full_name }}</h2>

<!-- 技巧5: 处理可能不存在的变量 -->
{% if user.metadata is defined and user.metadata.theme %}
    <body class="{{ user.metadata.theme }}-theme">
{% else %}
    <body class="default-theme">
{% endif %}

<!-- 技巧6: 使用with简化嵌套访问 -->
{% with messages = get_flashed_messages(with_categories=true) %}
    {% if messages %}
        {% for category, message in messages %}
            <div class="alert alert-{{ category }}">{{ message }}</div>
        {% endfor %}
    {% endif %}
{% endwith %}

7. 实战示例:博客系统

完整博客页面示例
# blog_views.py
from flask import render_template, request
from models import Post, User, Comment
from utils import paginate_query

@app.route('/blog')
def blog_index():
    """博客首页"""
    page = request.args.get('page', 1, type=int)

    # 获取分页数据
    posts_query = Post.query.filter_by(published=True).order_by(Post.created_at.desc())
    pagination = paginate_query(posts_query, page, per_page=10)

    # 获取热门文章
    popular_posts = Post.get_popular_posts(limit=5)

    # 获取标签云
    tags = Post.get_tag_cloud()

    # 获取统计数据
    stats = {
        'total_posts': Post.query.count(),
        'total_comments': Comment.query.count(),
        'total_authors': User.query.count()
    }

    return render_template(
        'blog/index.html',
        posts=pagination.items,  # 当前页的文章
        pagination=pagination,   # 分页对象
        popular_posts=popular_posts,
        tags=tags,
        stats=stats,
        current_year=datetime.now().year,
        page_title='博客首页',
        meta_description='欢迎访问我们的技术博客,分享编程技巧和开发经验。'
    )

@app.route('/blog/<slug>')
def blog_post(slug):
    """博客文章详情页"""
    post = Post.query.filter_by(slug=slug).first_or_404()

    # 增加阅读量
    post.increment_views()

    # 获取相关文章
    related_posts = post.get_related_posts(limit=3)

    # 获取文章评论
    comments = post.get_comments(approved_only=True)

    # 获取作者信息
    author = post.author

    return render_template(
        'blog/post.html',
        post=post,
        related_posts=related_posts,
        comments=comments,
        author=author,
        page_title=post.title,
        meta_description=post.excerpt,
        meta_keywords=','.join([tag.name for tag in post.tags])
    )
<!-- blog/index.html -->
{% extends "base.html" %}

{% block title %}{{ page_title }}{% endblock %}

{% block content %}
<div class="row">
    <!-- 左侧:文章列表 -->
    <div class="col-md-8">
        <h1>技术博客</h1>
        <p class="lead">共有 {{ stats.total_posts }} 篇文章</p>

        {% for post in posts %}
            <article class="blog-post">
                <h2>
                    <a href="{{ url_for('blog_post', slug=post.slug) }}">
                        {{ post.title }}
                    </a>
                </h2>

                <div class="post-meta">
                    <span>
                        <i class="fas fa-user"></i>
                        {{ post.author.username }}
                    </span>
                    <span>
                        <i class="fas fa-calendar"></i>
                        {{ post.created_at|date('Y-m-d') }}
                    </span>
                    <span>
                        <i class="fas fa-eye"></i>
                        {{ post.views }} 阅读
                    </span>
                </div>

                <p>{{ post.excerpt|truncate(200) }}</p>

                <div class="post-tags">
                    {% for tag in post.tags %}
                        <a href="{{ url_for('tag_posts', tag=tag.slug) }}"
                           class="badge bg-secondary">
                            {{ tag.name }}
                        </a>
                    {% endfor %}
                </div>
            </article>
        {% endfor %}

        <!-- 分页导航 -->
        {% if pagination.pages > 1 %}
            <nav aria-label="Page navigation">
                <ul class="pagination">
                    {% if pagination.has_prev %}
                        <li class="page-item">
                            <a class="page-link"
                               href="?page={{ pagination.prev_num }}">
                                上一页
                            </a>
                        </li>
                    {% endif %}

                    {% for page_num in pagination.iter_pages() %}
                        <li class="page-item {{ 'active' if page_num == pagination.page else '' }}">
                            <a class="page-link" href="?page={{ page_num }}">
                                {{ page_num }}
                            </a>
                        </li>
                    {% endfor %}

                    {% if pagination.has_next %}
                        <li class="page-item">
                            <a class="page-link"
                               href="?page={{ pagination.next_num }}">
                                下一页
                            </a>
                        </li>
                    {% endif %}
                </ul>
            </nav>
        {% endif %}
    </div>

    <!-- 右侧:侧边栏 -->
    <div class="col-md-4">
        <!-- 热门文章 -->
        <div class="sidebar-widget">
            <h3>热门文章</h3>
            <ul>
                {% for post in popular_posts %}
                    <li>
                        <a href="{{ url_for('blog_post', slug=post.slug) }}">
                            {{ post.title }}
                        </a>
                    </li>
                {% endfor %}
            </ul>
        </div>

        <!-- 标签云 -->
        <div class="sidebar-widget">
            <h3>标签云</h3>
            <div class="tag-cloud">
                {% for tag in tags %}
                    <a href="{{ url_for('tag_posts', tag=tag.slug) }}"
                       style="font-size: {{ tag.font_size }}px;">
                        {{ tag.name }}
                    </a>
                {% endfor %}
            </div>
        </div>
    </div>
</div>
{% endblock %}
学习总结

通过本教程,你已经掌握了Flask中传递数据到模板的各种方法:

  • 基本传递:使用 render_template() 传递简单变量
  • 复杂数据:传递列表、字典、对象等复杂结构
  • 上下文处理器:在所有模板中自动注入变量
  • 全局变量:使用内置的全局模板变量
  • 模板函数:传递Python函数在模板中调用
  • 最佳实践:保持代码简洁和安全
  • 实战技巧:使用各种技巧提高开发效率
  • 完整示例:实际项目中的数据传递方案
下一步学习建议
  • 学习Flask表单处理与验证
  • 掌握Flask数据库集成(SQLAlchemy)
  • 学习Flask用户认证与授权
  • 了解Flask REST API开发
  • 实践Flask项目部署