Django模板系统允许你将业务逻辑与表现层分离,使用简单而强大的模板语言来创建动态HTML页面。
Django模板是包含特殊语法标记的文本文件,用于:
准备上下文数据
解析模板语法
生成最终页面
使用双花括号显示变量值
{{ variable }}
使用花括号和百分号执行逻辑
{% tag %}
使用管道符号修改变量显示
{{ variable|filter }}
使用花括号和井号添加注释
{# comment #}
from django.shortcuts import render
from datetime import datetime
def post_list(request):
context = {
'title': '我的博客',
'posts': [
{'title': '文章1', 'content': '这是第一篇文章'},
{'title': '文章2', 'content': '这是第二篇文章'},
],
'current_time': datetime.now(),
'user': {'name': '张三', 'is_authenticated': True},
'page_views': 1500,
}
return render(request, 'blog/post_list.html', context)
<!DOCTYPE html>
<html>
<head>
<title>{{ title }}</title>
</head>
<body>
<h1>欢迎来到{{ title }}</h1>
{# 条件判断 #}
{% if user.is_authenticated %}
<p>欢迎, {{ user.name }}!</p>
{% else %}
<p>请<a href="/login/">登录</a></p>
{% endif %}
{# 循环 #}
<div class="posts">
{% for post in posts %}
<article>
<h2>{{ post.title }}</h2>
<p>{{ post.content|truncatewords:10 }}</p>
</article>
{% empty %}
<p>暂无文章</p>
{% endfor %}
</div>
{# 使用过滤器 #}
<footer>
<p>当前时间: {{ current_time|date:"Y-m-d H:i" }}</p>
<p>访问量: {{ page_views|intcomma }}</p>
</footer>
</body>
</html>
模板继承允许你创建一个基础骨架模板,然后在子模板中覆盖特定的部分。
基础模板
博客应用基础模板
具体页面模板
{# templates/base.html #}
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}我的网站{% endblock %}</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
{% block extra_css %}{% endblock %}
</head>
<body>
<header>
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
<div class="container">
<a class="navbar-brand" href="/">我的网站</a>
{% block navigation %}{% endblock %}
</div>
</nav>
</header>
<main class="container my-4">
{% block content %}
<!-- 主要内容区域 -->
{% endblock %}
</main>
<footer class="bg-light py-4 mt-5">
<div class="container text-center">
{% block footer %}
<p>© 2023 我的网站. 版权所有.</p>
{% endblock %}
</div>
</footer>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
{% block extra_js %}{% endblock %}
</body>
</html>
{# templates/blog/post_list.html #}
{% extends "base.html" %}
{% load static %}
{% block title %}博客 - {{ block.super }}{% endblock %}
{% block extra_css %}
<link rel="stylesheet" href="{% static 'blog/css/blog.css' %}">
{% endblock %}
{% block navigation %}
<ul class="navbar-nav me-auto">
<li class="nav-item">
<a class="nav-link active" href="{% url 'post_list' %}">博客</a>
</li>
</ul>
{% endblock %}
{% block content %}
<h1 class="mb-4">博客文章</h1>
{% for post in posts %}
<article class="card mb-3">
<div class="card-body">
<h2 class="card-title">{{ post.title }}</h2>
<p class="card-text">{{ post.content|truncatewords:30 }}</p>
<a href="{% url 'post_detail' post.id %}" class="btn btn-primary">阅读更多</a>
</div>
</article>
{% empty %}
<div class="alert alert-info">暂无文章</div>
{% endfor %}
{% endblock %}
{% block footer %}
<p>© 2023 我的博客. 共 {{ posts|length }} 篇文章.</p>
{{ block.super }}
{% endblock %}
{# templates/blog/base_blog.html #}
{% extends "base.html" %}
{% block navigation %}
<ul class="navbar-nav me-auto">
<li class="nav-item">
<a class="nav-link active" href="{% url 'post_list' %}">博客</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{% url 'category_list' %}">分类</a>
</li>
</ul>
{% if user.is_authenticated %}
<div class="navbar-nav">
<a class="nav-link" href="{% url 'post_create' %}">写文章</a>
</div>
{% endif %}
{% endblock %}
{% block extra_css %}
{{ block.super }}
<link rel="stylesheet" href="{% static 'blog/css/blog.css' %}">
{% endblock %}
{# 条件判断 #}
{% if user.is_authenticated %}
欢迎回来!
{% elif user.is_anonymous %}
请登录
{% else %}
未知状态
{% endif %}
{# 循环 #}
{% for item in items %}
<li>{{ forloop.counter }}. {{ item.name }}</li>
{% empty %}
<li>没有项目</li>
{% endfor %}
{# URL反向解析 #}
<a href="{% url 'post_detail' post.id %}">详情</a>
<a href="{% url 'blog:post_detail' post.id %}">详情(命名空间)</a>
{# 静态文件 #}
{% load static %}
<img src="{% static 'images/logo.png' %}" alt="Logo">
{# 包含其他模板 #}
{% include "includes/header.html" %}
{% include "includes/footer.html" with year=2023 %}
{# 注释 #}
{# 这行不会在最终HTML中显示 #}
{# 分组内容 #}
{% with total=items|length %}
共有 {{ total }} 个项目
{% endwith %}
{# 字符串处理 #}
{{ name|lower }} {# 转为小写 #}
{{ title|upper }} {# 转为大写 #}
{{ content|truncatewords:30 }} {# 截断单词 #}
{{ text|truncatechars:100 }} {# 截断字符 #}
{{ slug|slugify }} {# 转为slug格式 #}
{# 日期时间处理 #}
{{ created_at|date:"Y-m-d" }} {# 日期格式化 #}
{{ updated_at|time:"H:i" }} {# 时间格式化 #}
{{ post_date|timesince }} {# 相对时间 #}
{# 数字处理 #}
{{ price|floatformat:2 }} {# 浮点数格式化 #}
{{ count|intcomma }} {# 千位分隔符 #}
{{ percentage|floatformat:"0" }}% {# 百分比 #}
{# 列表处理 #}
{{ items|length }} {# 列表长度 #}
{{ list|first }} {# 第一个元素 #}
{{ list|last }} {# 最后一个元素 #}
{{ list|join:", " }} {# 连接列表 #}
{{ list|slice:":3" }} {# 切片 #}
{# 布尔值处理 #}
{{ is_published|yesno:"是,否" }} {# 布尔值转中文 #}
{{ value|default:"暂无" }} {# 默认值 #}
{# 安全相关 #}
{{ user_input|escape }} {# HTML转义 #}
{{ html_content|safe }} {# 标记为安全HTML #}
{{ script|escapejs }} {# JavaScript转义 #}
{# 链式过滤器 #}
{{ name|title|truncatewords:2 }}
{{ content|striptags|truncatechars:100 }}
模板上下文是视图传递给模板的变量字典,决定了模板中可以访问哪些数据。
def post_detail(request, post_id):
post = get_object_or_404(Post, id=post_id)
comments = post.comments.all()
context = {
'post': post,
'comments': comments,
'comment_count': comments.count(),
'current_time': timezone.now(),
'user': request.user,
}
return render(request, 'blog/post_detail.html', context)
<h1>{{ post.title }}</h1>
<p>发布于 {{ post.created_at|date }}</p>
<div>{{ post.content }}</div>
{% if user.is_authenticated %}
<p>欢迎, {{ user.username }}!</p>
{% endif %}
<h2>评论 ({{ comment_count }})</h2>
{% for comment in comments %}
<div class="comment">
<strong>{{ comment.author }}</strong>
<p>{{ comment.content }}</p>
</div>
{% endfor %}
Django模板使用点号查找语法,按照特定顺序查找变量:
| 查找类型 | 示例 | 说明 |
|---|---|---|
| 字典查找 | {{ user.name }} |
查找字典的键 |
| 属性查找 | {{ post.title }} |
查找对象的属性 |
| 方法调用 | {{ post.get_absolute_url }} |
调用无参数方法 |
| 列表索引 | {{ items.0 }} |
列表索引查找 |
良好的模板组织结构可以提高项目的可维护性。
Django按照settings.TEMPLATES中定义的顺序查找模板:
# settings.py
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [
BASE_DIR / 'templates', # 项目级模板目录
],
'APP_DIRS': True, # 启用应用模板目录
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
'libraries': {
'blog_extras': 'blog.templatetags.blog_extras',
}
},
},
]
# 模板查找顺序:
# 1. 项目级 templates 目录
# 2. 已安装应用的 templates 目录(按 INSTALLED_APPS 顺序)
正确处理用户输入,防止XSS等安全漏洞。
{# 直接输出用户输入,存在XSS风险 #}
<div>{{ user_input }}</div>
{# 错误使用safe过滤器 #}
<div>{{ user_input|safe }}</div>
{# 在JavaScript中直接使用用户输入 #}
<script>
var username = "{{ username }}"; // 危险!
</script>
{# 自动转义用户输入 #}
<div>{{ user_input }}</div>
{# 明确标记可信内容为safe #}
<div>{{ trusted_html|safe }}</div>
{# 在JavaScript中正确转义 #}
<script>
var username = "{{ username|escapejs }}";
</script>
{# 使用专门的转义过滤器 #}
<div>{{ user_input|force_escape }}</div>
优化模板渲染性能,提高网站响应速度。
{# 避免在模板中进行复杂计算 #}
{# 不好的做法 #}
{% for post in posts %}
<div>{{ post.comments.count }} 条评论</div>
{% endfor %}
{# 好的做法 - 在视图中预计算 #}
{% for post in posts %}
<div>{{ post.comment_count }} 条评论</div>
{% endfor %}
{# 使用with标签缓存复杂查询 #}
{% with total=user.posts.count %}
<p>发表了 {{ total }} 篇文章</p>
{% endwith %}
{# 合理使用include标签 #}
{# 避免在循环中include复杂模板 #}
{% for post in posts %}
{# 避免这样做 #}
{% include "includes/post_detail.html" %}
{% endfor %}
{# 使用模板片段缓存 #}
{% load cache %}
{% cache 600 post_detail post.id %}
<div class="post-detail">
{# 复杂的渲染逻辑 #}
</div>
{% endcache %}
在下方编辑Django模板代码,实时查看渲染结果:
创建基础模板,避免代码重复
按功能模块组织模板文件
谨慎使用safe过滤器
避免在模板中进行复杂计算
复杂的逻辑放在视图或模型中
封装可重用的模板逻辑
{% with %}标签缓存复杂查询结果{% include %}拆分复杂模板{% block.super %}扩展父模板内容掌握模板调试技巧,快速定位和解决问题。
{# 1. 使用debug标签查看变量 #}
{% debug %}
{# 2. 检查变量是否存在 #}
{% if some_variable %}
<p>{{ some_variable }}</p>
{% else %}
<p>变量不存在或为空</p>
{% endif %}
{# 3. 使用default过滤器处理空值 #}
<p>{{ possibly_none_variable|default:"默认值" }}</p>
{# 4. 在开发环境中显示详细错误 #}
{# 在settings.py中设置: #}
{# DEBUG = True #}
{# TEMPLATES[0]['OPTIONS']['debug'] = True #}
{# 5. 使用Django Debug Toolbar #}
{# 安装并配置django-debug-toolbar #}
{# 6. 自定义模板错误页面 #}
{# 创建400.html, 403.html, 404.html, 500.html #}
| 错误类型 | 可能原因 | 解决方案 |
|---|---|---|
| TemplateDoesNotExist | 模板路径错误或模板不存在 | 检查模板路径和DIRS设置 |
| TemplateSyntaxError | 模板语法错误 | 检查标签和过滤器语法 |
| VariableDoesNotExist | 引用了不存在的变量 | 检查视图中的上下文变量 |
| Invalid block tag | 使用了未注册的标签 | 检查标签名称和{% load %} |