Django + Vue 学习日志( PART03:Django 模板)
PART03:Django 模板系统(Template System)
记录时间:2026年8月14日
环境:Fedora 44 + Python 3.12.13 + Django 5.2.16
一、今日核心问题溯源
在搭建文章列表页的过程中,触发了对 Django 模板系统的一系列追问:
- 模板查找机制:Django 到底去哪里找模板文件?
DIRS和APP_DIRS是什么关系? - 模板继承:
{% extends %}和{% block %}是怎么工作的?为什么endblock之后的内容不显示? - 模板包含:
{% include %}不显示内容是什么原因? - 模板过滤器:为什么
| random不生效?过滤器不存在时 Django 会怎样? cycle标签:如何实现列表交替变色?- 变量渲染:
{{ }}的查找规则是什么?字典、列表、对象属性怎么取?
二、关键认知突破
1️⃣ 模板查找机制:Django 怎么找到 HTML 文件的
Django 按固定顺序查找模板,这是你反复踩坑后彻底搞懂的核心机制。
查找顺序
第 1 步:DIRS 里的目录(项目级,按列表顺序)
第 2 步:INSTALLED_APPS 中注册的 App(按注册顺序)
settings.py 配置
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [BASE_DIR / 'templates'], # ← 项目级模板目录
'APP_DIRS': True, # ← 是否搜索 App 内模板
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
...
],
},
},
]
INSTALLED_APPS 决定 App 级模板的查找权
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
...
'app01', # ← 注册了才能找到 app01/templates/
]
两种推荐的项目结构
方式 A:App 级模板(推荐,支持命名空间)
app01/
├── templates/
│ └── app01/
│ ├── list.html
│ └── page.html
视图里写:
render(request, 'app01/list.html', context)
方式 B:项目级模板
demo/
├── templates/
│ └── list.html
settings.py 里:
'DIRS': [BASE_DIR / 'templates'],
视图里写:
render(request, 'list.html', context)
模板命名空间的意义
app01/templates/app01/list.html
blog/templates/blog/list.html
两个 App 都有 list.html,如果不加命名空间(app01/ 前缀),先注册的 App 会覆盖后注册的。
关键心法:
INSTALLED_APPS的注册顺序就是模板查找的优先级顺序。
2️⃣ 模板继承:{% extends %} + {% block %}
这是 Django 模板最强大的功能,也是你踩坑最多的地方。
基本结构
base.html(父模板)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>{% block title %}默认标题{% endblock %}</title>
{% block extra_css %}{% endblock %}
</head>
<body>
<header>
<h1>我的博客</h1>
<hr>
</header>
<main>
{% block content %}{% endblock %}
</main>
<footer>
<hr>
<p>© 2026 我的博客</p>
</footer>
</body>
</html>
list.html(子模板)
{% extends 'base.html' %}
{% block title %}文章列表{% endblock %}
{% block content %}
<h1>文章列表</h1>
<ul>
{% for article in article_list %}
<li>{{ article }}</li>
{% endfor %}
</ul>
{% endblock %}
渲染规则(铁律)
| 规则 | 说明 |
|---|---|
extends 必须是模板第一行 |
前面只能有空白和注释 |
子模板只能覆盖 block 内的内容 |
block 外的内容会被忽略 |
endblock 之后的内容不渲染 |
直接丢弃,不报错 |
| 未覆盖的 block 显示默认值 | 父模板里写的默认内容 |
{{ block.super }} 可继承父模板内容 |
不是替换,是追加 |
你踩过的坑:endblock 之后写内容
{% extends 'base.html' %}
{% block content %}
<p>这是内容</p>
{% include 'app01/page.html' %}
{% endblock %}
{# ❌ 这里的内容永远不会显示! #}
<p>我放在了 endblock 之后</p>
✅ 原因:extends 之后,所有内容必须包在 {% block %} 里,否则直接被丢弃。
✅ 解决:养成习惯,所有内容都在 block 内。
3️⃣ 模板包含:{% include %}
基本用法
{% include 'app01/page.html' %}
include 的查找规则
和模板查找规则完全一致:
- 先查
DIRS - 再查
INSTALLED_APPS
include 的静默失败(大坑)
| 情况 | Django 行为 |
|---|---|
| 文件存在 | ✅ 正常渲染 |
| 文件不存在 | ❌ 静默失败,不报错,不显示 |
| 路径写错 | ❌ 静默失败,不报错,不显示 |
| App 没注册 | ❌ 静默失败,不报错,不显示 |
调试技巧
{# 测试文件是否存在 #}
{% include 'app01/page.html' %}
{# 如果没显示,说明文件找不到或路径不对 #}
更安全的写法(Django 提供 with 传参):
{% include 'app01/page.html' with author=author %}
include vs extends 的区别
| 特性 | extends |
include |
|---|---|---|
| 用途 | 继承整个页面骨架 | 嵌入一段 HTML 片段 |
| 是否支持 block | ✅ 是 | ❌ 否 |
| 是否可多层嵌套 | ✅ 可以 | ✅ 可以 |
| 典型场景 | 全站布局(导航/页脚) | 重复组件(评论框/分页条) |
4️⃣ 模板变量:{{ }} 的查找规则
基本语法
{{ variable }}
点号查找规则(LEGB 风格的模板版)
Django 模板遇到 {{ info.name }} 时,按以下顺序查找:
| 顺序 | 查找方式 | 示例 |
|---|---|---|
| 1 | 字典 key | info['name'] |
| 2 | 对象属性 | info.name |
| 3 | 对象方法(无参) | info.get_name() |
| 4 | 列表索引 | info[0] |
你的代码验证
视图传参:
info = {
'name': 'andy',
'age': 18,
'gender': '男',
'programming_languages': ['Python', 'JavaScript', 'C++']
}
return render(request, 'app01/list.html', {
'author': author,
'article_numbers': article_numbers,
'article_list': article_list,
'info': info
})
模板取值:
<p>姓名:{{ info.name }}</p> {# 字典 key 查找 ✅ #}
<p>年龄:{{ info.age }}</p> {# 字典 key 查找 ✅ #}
<p>第一门语言:{{ info.programming_languages.0 }}</p> {# 列表索引 ✅ #}
5️⃣ 模板过滤器(Filter)
基本语法
{{ variable | filter }}
{{ variable | filter:"参数" }}
常用内置过滤器
| 过滤器 | 作用 | 示例 | 输出 |
|---|---|---|---|
default |
变量为空时显示默认值 | `{{ name | default:"匿名" }}` |
length |
获取长度 | `{{ article_list | length }}` |
upper |
转大写 | `{{ name | upper }}` |
lower |
转小写 | `{{ name | lower }}` |
date |
格式化日期 | `{{ now | date:"Y-m-d" }}` |
first |
取第一个元素 | `{{ languages | first }}` |
last |
取最后一个元素 | `{{ languages | last }}` |
join |
用字符串连接 | `{{ languages | join:", " }}` |
safe |
关闭 HTML 转义 | `{{ html | safe }}` |
过滤器链
{{ name|default:"匿名"|upper }}
执行顺序:从左到右。
❌ random 过滤器不存在(你踩的大坑)
{{ info.programming_languages | random }} {# ❌ 不显示 #}
原因:Django 没有内置 random 过滤器。
Django 的行为:
- ❌ 不报错
- ❌ 不渲染
- ✅ 静默失败
正确做法(在视图里 random):
# views.py
import random
def list(request):
...
random_language = random.choice(info['programming_languages'])
return render(request, 'app01/list.html', {
...
'random_language': random_language
})
{# list.html #}
<p>掌握的编程语言:{{ random_language }}</p>
关键心法:模板是"展示层",不是"逻辑层"。
random是业务逻辑,必须放在视图里。
6️⃣ 模板标签(Tag):{% %}
循环:{% for %}
<ul>
{% for article in article_list %}
<li>{{ article }}</li>
{% empty %}
<li>暂无文章</li>
{% endfor %}
</ul>
| 变量 | 含义 |
|---|---|
forloop.counter |
从 1 开始的序号 |
forloop.counter0 |
从 0 开始的序号 |
forloop.first |
是否是第一次循环(bool) |
forloop.last |
是否是最后一次循环(bool) |
forloop.parentloop |
嵌套循环时访问外层 |
条件:{% if %}
{% if article_numbers > 10 %}
<p>文章很多!</p>
{% elif article_numbers > 5 %}
<p>文章还行</p>
{% else %}
<p>文章太少</p>
{% endif %}
交替样式:{% cycle %}(你用过的)
<li class="{% cycle 'red_row' 'blue_row' %}">{{ article }}</li>
渲染结果:
<li class="red_row">第一篇文章</li>
<li class="blue_row">第二篇文章</li>
<li class="red_row">第三篇文章</li>
...
对应的 CSS:
.red_row { color: red; }
.blue_row { color: blue; }
注意:
cycle输出的是字符串,被塞进class="..."里,所以 CSS 要用类选择器.red_row,不是标签选择器。
三、完整项目代码(你的最终版本)
views.py
import random
from django.shortcuts import render
from django.http import HttpResponse
def helloworld(request):
return HttpResponse('Hello World!')
def article_create(request):
return HttpResponse('这是文章1的内容')
def article_detail(request, article_id):
return HttpResponse(f'这是第{article_id}篇文章的内容')
def list(request):
author = 'andy'
article_numbers = 20
article_list = [
'第一篇文章:什么是Django',
'第二篇文章:C语言的艺术',
'第三篇文章:JavaScript高级程序设计',
'第四篇文章:Python编程:从入门到实践',
'第五篇文章:深入理解计算机系统'
]
info = {
'name': 'andy',
'age': 18,
'gender': '男',
'programming_languages': ['Python', 'JavaScript', 'C++']
}
# 在视图里做 random,不在模板里做
random_language = random.choice(info['programming_languages'])
return render(request, 'app01/list.html', {
'author': author,
'article_numbers': article_numbers,
'article_list': article_list,
'info': info,
'random_language': random_language
})
base.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}我的博客{% endblock %}</title>
{% block extra_css %}{% endblock %}
</head>
<body>
<header>
<h1>我的博客</h1>
<hr>
</header>
<main>
{% block content %}{% endblock %}
</main>
<footer>
<hr>
<p>© 2026 我的博客</p>
</footer>
</body>
</html>
list.html
{% extends 'base.html' %}
{% block title %}文章列表{% endblock %}
{% block extra_css %}
<style>
.red_row {
color: red;
}
.blue_row {
color: blue;
}
</style>
{% endblock %}
{% block content %}
<h1>文章列表</h1>
<h2>作者:{{ author }} 文章数量:{{ article_numbers }}</h2>
<ul>
{% for article in article_list %}
<li class="{% cycle 'red_row' 'blue_row' %}">{{ article }}</li>
{% empty %}
<li>暂无文章</li>
{% endfor %}
</ul>
<p>作者简介:</p>
<p>姓名:{{ info.name }},年龄:{{ info.age }},性别:{{ info.gender }}</p>
<p>掌握的编程语言:{{ random_language }}</p>
{% include 'app01/page.html' %}
{% endblock %}
page.html(被 include 的片段)
<div class="page-info">
<p>这是被引入的页面片段</p>
</div>
四、常见报错与解决方案速查表
| 报错 | 原因 | 解决方案 |
|---|---|---|
TemplateDoesNotExist: list.html |
文件不存在 / 路径不对 / App 未注册 | 检查文件位置、DIRS、INSTALLED_APPS |
TemplateDoesNotExist: base.html |
extends 引用的父模板找不到 |
同上,检查 base.html 是否存在 |
TemplateDoesNotExist: page.html |
include 引用的文件找不到 |
同上,且 include 静默失败不报错 |
{{ variable }} 不显示 |
变量没传到模板 / 过滤器不存在 | 检查 render() 的 context / 过滤器拼写 |
| ` | random` 不显示 | Django 没有内置 random 过滤器 |
endblock 后的内容不显示 |
不在任何 block 内,被丢弃 | 把所有内容移到 block 内 |
| CSS 类名不生效 | .h1 是类选择器,不是 <h1> 标签 |
用 .red 代替 .h1,避免和标签混淆 |
cycle 不交替 |
写在了 for 循环外面 |
cycle 必须在 for 循环内部 |
五、Django 模板设计哲学
| 原则 | 说明 |
|---|---|
| 展示与逻辑分离 | 模板只负责展示,业务逻辑放视图 |
| 静默失败 | 变量/过滤器/文件找不到时不报错,输出空 |
| 可预测性 | 模板渲染应该是确定性的(无随机性) |
| 继承优先 | extends + block 是页面布局的首选方式 |
| 命名空间 | App 级模板用 app01/template.html 防止覆盖 |
六、学习金句
- "模板是展示层,不是逻辑层;
random是逻辑,不是展示。"- "
extends之后,所有内容必须包在 block 里,否则直接被丢弃。"- "Django 模板的静默失败是双刃剑:不炸页面,但调试时让你怀疑人生。"
- "
.red_row的.是类选择器的标志,不是装饰;class 是属性,不是标签。"- "
INSTALLED_APPS的注册顺序就是模板查找的优先级顺序。"
七、学习总结
- 模板查找:掌握了
DIRS+INSTALLED_APPS的两级查找机制,理解了命名空间的意义。 - 模板继承:彻底搞懂了
extends+block的工作原理,踩坑后明白了endblock之后内容被丢弃的规则。 - 模板包含:理解了
include的静默失败特性,学会了调试方法。 - 变量渲染:掌握了点号查找规则(字典 → 属性 → 方法 → 索引)。
- 过滤器:学会了常用内置过滤器,理解了"模板无 random"的设计哲学。
- 标签:熟练使用
for/if/cycle/empty等核心标签。 - CSS 联动:理解了 Django
cycle输出字符串 → class 属性 → CSS 类选择器的完整链路。

浙公网安备 33010602011771号