1. 这篇学完你能做什么
- 用
{% extends %}继承一个 基础布局 - 用
{% block %}在子页面里 只写变化的部分 - 公共导航、Flash 提示 只维护一份
- 静态 CSS 挂到布局里,全站生效
2. 为什么需要模板继承
第二篇、第三篇的页面可能是这样的:
<!-- note_list.html -->
<!DOCTYPE html>
<html>
<head>...</head>
<body>
<nav>...</nav>
<!-- 列表内容 -->
</body>
</html>
<!-- note_form.html -->
<!DOCTYPE html>
<html>
<head>...</head>
<body>
<nav>...</nav> <!-- 又写一遍 -->
<!-- 表单内容 -->
</body>
</html>
改一次导航,要改 N 个文件。
Flask 用的是 Jinja2 模板引擎,继承语法就是来解决这个问题的。
3. 先建一个「母版」:base.html
在 app/templates/ 下新建 base.html:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>{% block title %}我的备忘录{% endblock %}</title>
<link rel="stylesheet" href="{{ url_for('static', filename='css/site.css') }}">
{% block css %}{% endblock %}
</head>
<body>
<header class="site-header">
<a href="{{ url_for('home.index') }}">首页</a>
<a href="{{ url_for('home.note_list') }}">备忘录</a>
<a href="{{ url_for('home.note_add') }}">新增</a>
</header>
<main class="site-main">
{% with messages = get_flashed_messages() %}
{% if messages %}
<ul class="flash-list">
{% for msg in messages %}
<li class="flash-item">{{ msg }}</li>
{% endfor %}
</ul>
{% endif %}
{% endwith %}
{% block content %}{% endblock %}
</main>
<footer class="site-footer">
<small>Flask 练习项目</small>
</footer>
{% block js %}{% endblock %}
</body>
</html>
几个关键点:
| 写法 | 作用 |
|---|---|
{% block title %} |
子页面可改浏览器标题 |
{% block content %} |
子页面填主要内容 |
{% block css %} / {% block js %} |
个别页面额外引 CSS/JS |
get_flashed_messages() |
Flash 提示写一次,全站都有 |
4. 子页面怎么写:列表页
app/templates/home/note_list.html 改成:
{% extends "base.html" %}
{% block title %}备忘录列表 · 我的备忘录{% endblock %}
{% block content %}
<h1>备忘录列表</h1>
<form method="get" action="{{ url_for('home.note_list') }}">
<input type="text" name="q" value="{{ q or '' }}" placeholder="搜标题或内容">
<button type="submit">搜索</button>
</form>
{% for note in page_data.items %}
<article class="note-item">
<h3>{{ note.title }}</h3>
<p>{{ note.content or '(无内容)' }}</p>
<a href="{{ url_for('home.note_edit', note_id=note.id) }}">编辑</a>
</article>
{% endfor %}
{% if page_data.has_prev %}
<a href="{{ url_for('home.note_list', page=page_data.prev_num, q=q) }}">上一页</a>
{% endif %}
{% if page_data.has_next %}
<a href="{{ url_for('home.note_list', page=page_data.next_num, q=q) }}">下一页</a>
{% endif %}
{% endblock %}
注意第一行:必须先 extends,再写 block。
子页面里 不用 再写 <html>、<head>、导航------母版已经有了。
5. 表单页同样继承
app/templates/home/note_form.html:
{% extends "base.html" %}
{% block title %}{{ title }} · 我的备忘录{% endblock %}
{% block content %}
<h1>{{ title }}</h1>
<form method="post">
{{ form.csrf_token }}
<p>
{{ form.title.label }}<br>
{{ form.title(size=40) }}
{% for err in form.title.errors %}
<span class="error">{{ err }}</span>
{% endfor %}
</p>
<p>
{{ form.content.label }}<br>
{{ form.content(rows=5, cols=40) }}
</p>
<p>{{ form.submit }}</p>
<a href="{{ url_for('home.note_list') }}">返回列表</a>
</form>
{% endblock %}
新增页和编辑页 共用一个模板,和第三篇一样;
区别只是母版帮你包好了外壳。
6. 静态文件挂到布局里
第五篇之前,CSS 可能散落在各个页面。
现在只在 base.html 引一次:
<link rel="stylesheet" href="{{ url_for('static', filename='css/site.css') }}">
文件放在 app/static/css/site.css。
Flask 会自动从 static 目录提供静态资源。
某个页面需要额外样式时,用 {% block css %}:
{% extends "base.html" %}
{% block css %}
<link rel="stylesheet" href="{{ url_for('static', filename='css/note-chart.css') }}">
{% endblock %}
{% block content %}
...
{% endblock %}
7. 多层继承(了解即可)
项目变大后,可以 母版套母版:
base.html ← 全站:head、footer
└── admin_base.html ← 后台:侧栏、面包屑
└── note_list.html
admin_base.html:
{% extends "base.html" %}
{% block content %}
<aside class="admin-sidebar">...</aside>
<section class="admin-content">
{% block admin_content %}{% endblock %}
</section>
{% endblock %}
具体列表页:
{% extends "admin/admin_base.html" %}
{% block admin_content %}
<h1>备忘录管理</h1>
...
{% endblock %}
入门阶段一层 base.html 就够;
知道可以嵌套,以后拆 home / admin 两套布局时会用到。
8. include 和 extends 的区别
| 语法 | 适合什么 |
|---|---|
{% extends %} |
整页有统一骨架,子页填 block |
{% include "xxx.html" %} |
抽一小段复用,如分页条、删除按钮 |
分页条可以单独做成 includes/pagination.html:
{# includes/pagination.html #}
{% if page_data.has_prev %}
<a href="{{ url_for(endpoint, page=page_data.prev_num, q=q) }}">上一页</a>
{% endif %}
列表页里:
{% include "includes/pagination.html" %}
规则:整页结构用 extends,小组件用 include。
9. 一张图串起来
浏览器请求 /notes/
│
▼
render_template("home/note_list.html", ...)
│
▼
note_list.html {% extends "base.html" %}
│
▼
base.html 输出完整 HTML
├─ head + site.css
├─ 导航
├─ Flash 区域
├─ block content ← 列表内容填在这里
└─ footer
10. 新手常踩的 4 个坑
坑 1:extends 不在文件最前面
{% extends %} 必须是模板 第一行(前面不能有空行以外的内容),否则报错。
坑 2:block 名字对不上
母版是 {% block content %},子页面写成 {% block main %},内容不会显示。
名字必须一致。
坑 3:子页面又写了一遍 <html>
继承后子页面 只写 block 里的片段,不要再包一层完整 HTML。
坑 4:Flash 每个页面各写一份
应放在 base.html 里 写一次;
个别页面要分类提示可以用 get_flashed_messages(with_categories=true),第六篇先知道有这能力即可。