Django从零开始(四):模板系统与前端渲染
一、Django模板系统概述
Django模板引擎(Template Engine)是一个强大的文本渲染系统,用于将Python数据与HTML结合生成动态网页。它遵循"逻辑与展示分离"的设计理念,在模板中只能执行展示相关的逻辑,不能编写复杂的Python代码。
模板系统的核心概念
- 变量(Variables):从视图中传入的动态数据,使用
{{ variable }}语法 - 标签(Tags):控制模板逻辑,使用
{% tag %}语法 - 过滤器(Filters):对变量进行格式化处理,使用
{{ variable|filter }}语法 - 注释(Comments):模板注释,使用
{# comment #}语法
二、模板配置
2.1 模板目录结构
# 推荐的模板目录结构
mysite/
├── templates/ # 项目级模板目录
│ ├── base.html # 基础模板
│ ├── navbar.html # 导航栏组件
│ └── errors/ # 错误页面
│ ├── 404.html
│ └── 500.html
└── blog/
└── templates/ # App级模板目录
└── blog/
├── article_list.html
├── article_detail.html
├── article_form.html
└── includes/ # 可复用的模板片段
├── pagination.html
├── sidebar.html
└── article_card.html
2.2 settings.py配置
# settings.py
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [BASE_DIR / 'templates'], # 项目级模板目录
'APP_DIRS': True, # 自动搜索App的templates目录
'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',
# 自定义上下文处理器
'blog.context_processors.site_settings',
],
},
},
]
三、模板语法详解
3.1 变量输出
<!-- 基本变量 -->
<h1>{{ article.title }}</h1>
<p>作者:{{ article.author.username }}</p>
<span>{{ article.created_at|date:"Y-m-d" }}</span>
<!-- 字典取值 -->
<p>{{ settings.site_name }}</p>
<!-- 列表索引 -->
<p>{{ items.0 }}</p>
<p>{{ items.1 }}</p>
<!-- 方法调用(不需要加括号) -->
<p>{{ article.get_status_display }}</p>
3.2 模板标签
<!-- if/elif/else 条件判断 -->
{% if article.status == 'published' %}
<span class="badge badge-success">已发布</span>
{% elif article.status == 'draft' %}
<span class="badge badge-warning">草稿</span>
{% else %}
<span class="badge badge-secondary">已归档</span>
{% endif %}
<!-- ifequal/ifnotequal -->
{% ifequal article.status 'published' %}
<span>已发布</span>
{% endifequal %}
<!-- for 循环 -->
{% for article in articles %}
<div class="article-card">
<h2>{{ article.title }}</h2>
<p>{{ article.summary }}</p>
<!-- forloop内置变量 -->
<span>第 {{ forloop.counter }} 篇</span>
<span>共 {{ forloop.revcounter }} 篇剩余</span>
{% if forloop.first %}<span>第一篇</span>{% endif %}
{% if forloop.last %}<span>最后一篇</span>{% endif %}
</div>
{% empty %}
<p>暂无文章</p>
{% endfor %}
<!-- for...reverse 反向循环 -->
{% for article in articles reversed %}
<p>{{ article.title }}</p>
{% endfor %}
<!-- 遍历字典 -->
{% for key, value in settings.items %}
<p>{{ key }}: {{ value }}</p>
{% endfor %}
3.3 常用过滤器
<!-- 日期格式化 -->
{{ article.created_at|date:"Y年m月d日 H:i" }}
{{ article.created_at|timesince }} {# "3小时前" #}
<!-- 字符串处理 -->
{{ article.title|truncatewords:30 }} {# 截取30个单词 #}
{{ article.title|truncatechars:50 }} {# 截取50个字符 #}
{{ article.content|truncatewords_html:50 }} {# 安全截取HTML #}
{{ user.email|lower }} {# 转小写 #}
{{ user.username|upper }} {# 转大写 #}
{{ article.title|title }} {# 标题格式 #}
{{ text|capfirst }} {# 首字母大写 #}
{{ text|slugify }} {# 转为slug格式 #}
{{ text|striptags }} {# 去除HTML标签 #}
<!-- 数值处理 -->
{{ article.views|floatformat:2 }} {# 保留2位小数 #}
{{ price|floatformat }} {# 默认保留1位 #}
<!-- 默认值 -->
{{ article.summary|default:"暂无摘要" }}
{{ article.cover|default_if_none:"/static/default.jpg" }}
<!-- 安全输出 -->
{{ article.content|safe }} {# 不转义HTML #}
{{ article.content|escape }} {# 转义HTML(默认行为) #}
{{ article.content|escapejs }} {# 转义JS #}
<!-- 列表操作 -->
{{ articles|length }} {# 列表长度 #}
{{ articles|join:", " }} {# 用逗号连接 #}
{{ articles|first }} {# 第一个元素 #}
{{ articles|last }} {# 最后一个元素 #}
<!-- 链式使用过滤器 -->
{{ article.content|striptags|truncatewords:50|safe }}
<!-- 自定义过滤器参数 -->
{{ value|cut:" " }} {# 去除所有空格 #}
{{ value|add:5 }} {# 加5 #}
四、模板继承
模板继承是Django模板系统最强大的特性之一,它允许你定义一个基础模板,然后子模板只需要填充特定的区块。
4.1 基础模板(base.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>
{% block extra_css %}
<link rel="stylesheet" href="{% static 'css/style.css' %}">
{% endblock %}
</head>
<body>
<!-- 导航栏 -->
{% include "includes/navbar.html" %}
<!-- 主内容区 -->
<div class="container">
{% block content %}
{# 子模板将覆盖此区块 #}
{% endblock %}
</div>
<!-- 侧边栏 -->
{% block sidebar %}
{% include "includes/sidebar.html" %}
{% endblock %}
<!-- 页脚 -->
{% include "includes/footer.html" %}
{% block extra_js %}
<script src="{% static 'js/main.js' %}"></script>
{% endblock %}
</body>
</html>
4.2 子模板继承
<!-- blog/templates/blog/article_list.html -->
{% extends "base.html" %}
{% load static %}
{% block title %}文章列表 - {{ block.super }}{% endblock %}
{% block extra_css %}
{{ block.super }}
<link rel="stylesheet" href="{% static 'css/article.css' %}">
{% endblock %}
{% block content %}
<div class="article-list">
{% for article in articles %}
<article class="article-card">
{% include "blog/includes/article_card.html" with article=article show_category=True %}
</article>
{% empty %}
<div class="empty-state">
<p>暂无文章</p>
</div>
{% endfor %}
</div>
{% include "includes/pagination.html" with page_obj=page_obj %}
{% endblock %}
{% block sidebar %}
{{ block.super }}
<div class="widget">
<h3>热门标签</h3>
{% for tag in tags %}
<a href="{% url 'blog:tag_articles' tag.slug %}">{{ tag.name }}</a>
{% endfor %}
</div>
{% endblock %}
4.3 三层继承
<!-- templates/base.html -->
{# 第一层:全局基础 #}
{% block title %}{% endblock %}
{% block content %}{% endblock %}
<!-- templates/blog_base.html -->
{# 第二层:博客专用基础 #}
{% extends "base.html" %}
{% block content %}
<div class="blog-layout">
<main>{% block main_content %}{% endblock %}</main>
<aside>{% block blog_sidebar %}{% endblock %}</aside>
</div>
{% endblock %}
<!-- blog/templates/blog/article_detail.html -->
{# 第三层:具体页面 #}
{% extends "blog_base.html" %}
{% block main_content %}
<h1>{{ article.title }}</h1>
{{ article.content|safe }}
{% endblock %}
五、模板包含(include)
<!-- 基本包含 -->
{% include "includes/pagination.html" %}
<!-- 传递变量 -->
{% include "includes/article_card.html" with article=article %}
<!-- 传递多个变量 -->
{% include "includes/article_card.html" with article=article show_tags=True show_author=True %}
<!-- 只传递特定变量(隔离上下文) -->
{% include "includes/widget.html" with title="热门文章" items=hot_articles only %}
可复用模板片段示例:
<!-- templates/includes/pagination.html -->
{% if page_obj.has_other_pages %}
<nav class="pagination">
{% if page_obj.has_previous %}
<a href="?page={{ page_obj.previous_page_number }}">« 上一页</a>
{% endif %}
{% for num in page_obj.paginator.page_range %}
{% if page_obj.number == num %}
<span class="current">{{ num }}</span>
{% elif num > page_obj.number|add:'-3' and num < page_obj.number|add:'3' %}
<a href="?page={{ num }}">{{ num }}</a>
{% endif %}
{% endfor %}
{% if page_obj.has_next %}
<a href="?page={{ page_obj.next_page_number }}">下一页 »</a>
{% endif %}
</nav>
{% endif %}
六、自定义模板标签和过滤器
6.1 创建自定义标签库
# blog/templatetags/__init__.py (空文件)
# blog/templatetags/blog_extras.py
from django import template
from django.utils.safestring import mark_safe
import markdown
register = template.Library()
# ===== 自定义过滤器 =====
@register.filter(name='truncate_cn')
def truncate_cn(value, length):
"""截取中文字符串"""
if len(value) > length:
return value[:length] + '...'
return value
@register.filter
def multiply(value, arg):
"""乘法过滤器"""
try:
return float(value) * float(arg)
except (ValueError, TypeError):
return ''
@register.filter
def markdown_to_html(text):
"""Markdown转HTML"""
return mark_safe(markdown.markdown(text, extensions=['codehilite', 'fenced_code']))
# ===== 自定义标签 =====
@register.simple_tag
def get_recent_articles(count=5):
"""获取最近发布的文章"""
return Article.objects.filter(
status='published'
).order_by('-created_at')[:count]
@register.simple_tag(takes_context=True)
def total_articles(context):
"""获取当前用户的文章总数"""
user = context['request'].user
if user.is_authenticated:
return Article.objects.filter(author=user).count()
return 0
@register.inclusion_tag('blog/includes/article_card.html')
def show_article_card(article, show_tags=False):
"""渲染文章卡片组件"""
return {
'article': article,
'show_tags': show_tags,
}
@register.inclusion_tag('blog/includes/archive_widget.html')
def show_archive():
"""归档组件"""
from django.db.models import Count
archives = Article.objects.filter(
status='published'
).dates('created_at', 'month', order='DESC')
return {'archives': archives}
6.2 在模板中使用自定义标签
{% load blog_extras %}
{# 使用自定义过滤器 #}
<p>{{ article.content|truncate_cn:100 }}</p>
<p>{{ price|multiply:1.08 }}</p>
{# 使用simple_tag #}
{% get_recent_articles 5 as recent_articles %}
{% for article in recent_articles %}
<a href="{{ article.get_absolute_url }}">{{ article.title }}</a>
{% endfor %}
{# 使用inclusion_tag(自动渲染关联模板) #}
{% show_article_card article show_tags=True %}
七、静态文件管理
{% load static %}
<!-- 引用CSS -->
<link rel="stylesheet" href="{% static 'css/style.css' %}">
<!-- 引用JS -->
<script src="{% static 'js/main.js' %}"></script>
<!-- 引用图片 -->
<img src="{% static 'images/logo.png' %}" alt="Logo">
<!-- 动态静态文件路径 -->
{% static "css/" as css_path %}
<link rel="stylesheet" href="{{ css_path }}theme-{{ user.theme }}.css">
八、上下文处理器
上下文处理器可以让所有模板都自动获取某些变量,无需在每个视图中手动传递:
# blog/context_processors.py
from .models import Category, Tag
def site_settings(request):
"""全局站点设置(所有模板都可访问)"""
return {
'site_name': '我的博客',
'site_description': '一个关于技术的博客',
'categories': Category.objects.all(),
'popular_tags': Tag.objects.all()[:20],
}
def user_notifications(request):
"""用户通知"""
if request.user.is_authenticated:
unread_count = request.user.notifications.filter(read=False).count()
return {'unread_notifications': unread_count}
return {}
九、总结
本章我们学习了Django模板系统的核心知识:
- 模板语法:变量、标签、过滤器、注释
- 模板继承机制(extends/block)和三层继承策略
- 模板包含(include)和可复用组件
- 自定义模板标签和过滤器的开发
- 静态文件管理和上下文处理器
下一章我们将学习 Django表单处理与数据验证,掌握如何安全地处理用户输入。
声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。







