第六篇-Django模板系统架构、Context处理器与渲染机制深度解析
·
Django模板系统架构、Context处理器与渲染机制深度解析
本文深入剖析Django模板系统的核心机制,涵盖模板架构、语法体系、内置标签与过滤器、Context处理器、自定义组件及渲染机制,帮助开发者掌握模板层的核心开发技能。
一、模板系统架构概述
1.1 模板系统核心概念
Django模板系统是一个强大的文本渲染引擎,负责将动态数据与静态模板结合生成最终的HTML输出。
1.2 模板配置详解
# settings.py
TEMPLATES = [
{
# 模板引擎后端
'BACKEND': 'django.template.backends.django.DjangoTemplates',
# 模板目录
'DIRS': [
BASE_DIR / 'templates',
],
# 是否在应用目录中查找模板
'APP_DIRS': True,
# 模板选项
'OPTIONS': {
# Context处理器
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
# 调试模式
'debug': DEBUG,
# 自动转义
'autoescape': True,
# 无效变量显示值
'string_if_invalid': '',
# 文件字符集
'file_charset': 'utf-8',
},
},
]
1.3 核心类关系
二、模板语法体系详解
2.1 变量语法
<!-- 基本变量 -->
<h1>{{ title }}</h1>
<!-- 属性访问 -->
<p>{{ user.username }}</p>
<p>{{ article.author.name }}</p>
<!-- 方法调用 -->
<p>{{ user.get_full_name }}</p>
<p>{{ user.get_age }}</p>
<!-- 字典访问 -->
<p>{{ data.key }}</p>
<p>{{ data.items.0 }}</p>
<!-- 列表索引 -->
<p>{{ items.0 }}</p>
<p>{{ items|first }}</p>
2.2 标签语法
<!-- 注释 -->
{# 这是注释,不会显示 #}
<!-- for循环 -->
{% for article in articles %}
<div class="article">
<h2>{{ article.title }}</h2>
<p>{{ article.summary }}</p>
</div>
{% empty %}
<p>暂无文章</p>
{% endfor %}
<!-- if条件 -->
{% if user.is_authenticated %}
<p>欢迎,{{ user.username }}</p>
{% elif user.is_guest %}
<p>访客用户</p>
{% else %}
<p>请登录</p>
{% endif %}
<!-- block块 -->
{% block content %}
<p>默认内容</p>
{% endblock %}
<!-- extends继承 -->
{% extends "base.html" %}
<!-- include包含 -->
{% include "sidebar.html" %}
<!-- with上下文 -->
{% with title="页面标题" %}
<h1>{{ title }}</h1>
{% endwith %}
<!-- url反向解析 -->
<a href="{% url 'article_detail' article.id %}">{{ article.title }}</a>
<!-- load加载标签库 -->
{% load static i18n %}
2.3 过滤器语法
<!-- 基本过滤器 -->
<p>{{ name|upper }}</p>
<p>{{ name|lower }}</p>
<p>{{ name|title }}</p>
<!-- 链式过滤器 -->
<p>{{ text|lower|truncate:50 }}</p>
<!-- 带参数的过滤器 -->
<p>{{ price|floatformat:2 }}</p>
<p>{{ date|date:"Y-m-d" }}</p>
<!-- 默认值 -->
<p>{{ name|default:"匿名" }}</p>
<!-- 安全转义 -->
<p>{{ html|safe }}</p>
<p>{{ html|escape }}</p>
三、内置标签详解
3.1 控制流标签
<!-- for标签 -->
{% for item in items %}
<p>{{ forloop.counter }}: {{ item.name }}</p>
<!-- forloop变量 -->
<p>索引: {{ forloop.counter0 }}</p> <!-- 从0开始 -->
<p>序号: {{ forloop.counter }}</p> <!-- 从1开始 -->
<p>是否第一: {{ forloop.first }}</p>
<p>是否最后: {{ forloop.last }}</p>
<p>父循环计数: {{ forloop.parentloop.counter }}</p>
<p>循环次数: {{ forloop.revcounter }}</p>
<p>循环次数(0): {{ forloop.revcounter0 }}</p>
{% empty %}
<p>列表为空</p>
{% endfor %}
<!-- if/elif/else标签 -->
{% if score >= 90 %}
<p>优秀</p>
{% elif score >= 60 %}
<p>及格</p>
{% else %}
<p>不及格</p>
{% endif %}
<!-- 条件运算符 -->
{% if user.is_authenticated and user.is_staff %}
<p>管理员</p>
{% endif %}
{% if user.is_superuser or user.is_staff %}
<p>有权限</p>
{% endif %}
{% if not user.is_banned %}
<p>正常用户</p>
{% endif %}
{% if user.age|add:1 > 18 %}
<p>成年人</p>
{% endif %}
3.2 模板继承标签
<!-- base.html - 基础模板 -->
<!DOCTYPE html>
<html>
<head>
<title>{% block title %}默认标题{% endblock %}</title>
{% block meta %}
<meta charset="UTF-8">
{% endblock %}
{% block stylesheets %}
<link rel="stylesheet" href="{% static 'css/base.css' %}">
{% endblock %}
</head>
<body>
<header>
{% block header %}
<nav>导航栏</nav>
{% endblock %}
</header>
<main>
{% block content %}{% endblock %}
</main>
<footer>
{% block footer %}
<p>版权所有</p>
{% endblock %}
</footer>
{% block scripts %}
<script src="{% static 'js/base.js' %}"></script>
{% endblock %}
</body>
</html>
<!-- article.html - 继承模板 -->
{% extends "base.html" %}
{% load static %}
{% block title %}{{ article.title }}{% endblock %}
{% block stylesheets %}
{{ block.super }} <!-- 保留父模板内容 -->
<link rel="stylesheet" href="{% static 'css/article.css' %}">
{% endblock %}
{% block content %}
<article>
<h1>{{ article.title }}</h1>
<div class="content">{{ article.content|safe }}</div>
</article>
{% endblock %}
3.3 包含与导入标签
<!-- include标签 -->
{% include "header.html" %}
<!-- 带上下文的include -->
{% include "article_card.html" with article=featured_article show_image=True %}
<!-- only参数(不继承上下文) -->
{% include "widget.html" with title="组件标题" only %}
<!-- load标签 -->
{% load static %} <!-- 加载static标签库 -->
{% load i18n %} <!-- 加载国际化标签库 -->
{% load myapp_tags %} <!-- 加载自定义标签库 -->
<!-- 多个标签库 -->
{% load static i18n cache %}
3.4 URL标签
<!-- 基本URL -->
<a href="{% url 'home' %}">首页</a>
<!-- 带参数的URL -->
<a href="{% url 'article_detail' article.id %}">{{ article.title }}</a>
<!-- 命名参数 -->
<a href="{% url 'archive' year=2024 month=3 %}">2024年3月</a>
<!-- 命名空间URL -->
<a href="{% url 'blog:article_detail' article.id %}">文章详情</a>
<!-- as语法(存储URL) -->
{% url 'article_list' as article_url %}
<a href="{{ article_url }}">文章列表</a>
<!-- 条件URL -->
{% url 'article_detail' article.id as article_url %}
{% if article_url %}
<a href="{{ article_url }}">查看文章</a>
{% endif %}
3.5 其他常用标签
<!-- csrf_token -->
<form method="post">
{% csrf_token %}
<input type="text" name="title">
<button type="submit">提交</button>
</form>
<!-- static -->
<link rel="stylesheet" href="{% static 'css/style.css' %}">
<img src="{% static 'images/logo.png' %}" alt="Logo">
<!-- 带变量的static -->
{% static 'images/' as image_base %}
<img src="{{ image_base }}logo.png">
<!-- cache缓存 -->
{% load cache %}
{% cache 500 sidebar %}
<!-- 缓存500秒 -->
<div class="sidebar">
{% include "sidebar.html" %}
</div>
{% endcache %}
<!-- 带键名的缓存 -->
{% cache 500 sidebar user.id %}
<div class="user-sidebar">
{{ user.username }}
</div>
{% endcache %}
<!-- spaceless(去除空白) -->
{% spaceless %}
<div>
<p>内容</p>
</div>
{% endspaceless %}
<!-- 输出: <div><p>内容</p></div> -->
<!-- verbatim(不解析) -->
{% verbatim %}
{{ 这里的模板语法不会被解析 }}
{% endverbatim %}
<!-- autoescape -->
{% autoescape off %}
{{ html_content }} <!-- 不转义 -->
{% endautoescape %}
{% autoescape on %}
{{ html_content }} <!-- 转义 -->
{% endautoescape %}
四、内置过滤器详解
4.1 字符串过滤器
<!-- 大小写转换 -->
{{ name|upper }} <!-- 大写 -->
{{ name|lower }} <!-- 小写 -->
{{ name|title }} <!-- 标题格式 -->
{{ name|capfirst }} <!-- 首字母大写 -->
<!-- 字符串处理 -->
{{ text|truncatewords:10 }} <!-- 截取10个单词 -->
{{ text|truncatechars:50 }} <!-- 截取50个字符 -->
{{ text|truncatechars_html:50 }} <!-- 截取HTML保留标签 -->
{{ text|wordcount }} <!-- 单词数 -->
{{ text|linebreaks }} <!-- 换行转<br> -->
{{ text|linebreaksbr }} <!-- 换行转<br>(不转义) -->
{{ text|linenumbers }} <!-- 添加行号 -->
{{ text|striptags }} <!-- 移除HTML标签 -->
{{ text|removetags:"p div" }} <!-- 移除指定标签 -->
{{ text|escapejs }} <!-- JS转义 -->
{{ text|cut:" " }} <!-- 移除空格 -->
{{ text|slugify }} <!-- 转为slug格式 -->
{{ text|urlencode }} <!-- URL编码 -->
{{ text|urlize }} <!-- URL转链接 -->
{{ text|urlizetrunc:30 }} <!-- URL转链接并截断 -->
<!-- 字符串判断 -->
{{ text|startswith:"Hello" }} <!-- 是否以Hello开头 -->
{{ text|endswith:"world" }} <!-- 是否以world结尾 -->
{{ text|contains:"test" }} <!-- 是否包含test -->
4.2 数值过滤器
<!-- 数值格式化 -->
{{ value|add:5 }} <!-- 加5 -->
{{ value|add:"-3" }} <!-- 减3 -->
{{ price|floatformat }} <!-- 默认1位小数 -->
{{ price|floatformat:2 }} <!-- 2位小数 -->
{{ price|floatformat:"-2" }} <!-- 2位小数(负数不显示小数点后0) -->
{{ value|divisibleby:3 }} <!-- 是否能被3整除 -->
<!-- 获取绝对值 -->
{{ value|abs }}
<!-- 整数处理 -->
{{ items|length }} <!-- 长度 -->
{{ items|length_is:5 }} <!-- 长度是否为5 -->
4.3 日期时间过滤器
<!-- 日期格式化 -->
{{ date|date:"Y-m-d" }} <!-- 2024-03-15 -->
{{ date|date:"Y年m月d日" }} <!-- 2024年03月15日 -->
{{ date|date:"Y-m-d H:i:s" }} <!-- 2024-03-15 14:30:00 -->
<!-- 时间格式化 -->
{{ time|time:"H:i" }} <!-- 14:30 -->
<!-- 时间间隔 -->
{{ duration|timesince:start }} <!-- 距start多久 -->
{{ duration|timeuntil:end }} <!-- 距end还有多久 -->
<!-- 格式符号说明 -->
<!--
Y: 四位年份
y: 两位年份
m: 两位月份
n: 月份(不补零)
M: 月份缩写
F: 月份全称
d: 两位日期
j: 日期(不补零)
D: 星期缩写
l: 星期全称
H: 24小时制小时
h: 12小时制小时
i: 分钟
s: 秒
A/a: AM/PM
-->
4.4 列表过滤器
<!-- 列表操作 -->
{{ items|first }} <!-- 第一个元素 -->
{{ items|last }} <!-- 最后一个元素 -->
{{ items|random }} <!-- 随机元素 -->
{{ items|length }} <!-- 长度 -->
{{ items|join:", " }} <!-- 用逗号连接 -->
{{ items|slice:":5" }} <!-- 切片前5个 -->
{{ items|slice:"1:3" }} <!-- 切片索引1-3 -->
{{ items|dictsort:"name" }} <!-- 按name排序 -->
{{ items|dictsortreversed:"name" }} <!-- 按name降序 -->
{{ items|unordered_list }} <!-- 无序列表HTML -->
<!-- 列表判断 -->
{{ items|length_is:0 }} <!-- 长度是否为0 -->
{{ items|length_is:5 }} <!-- 长度是否为5 -->
4.5 默认值与逻辑过滤器
<!-- 默认值 -->
{{ value|default:"默认值" }} <!-- None/False时显示默认值 -->
{{ value|default_if_none:"值" }} <!-- 仅None时显示默认值 -->
<!-- 布尔值 -->
{{ value|yesno:"是,否,未知" }} <!-- True/False/None映射 -->
<!-- 逻辑判断 -->
{{ items|isempty }} <!-- 是否为空 -->
4.6 安全过滤器
<!-- 转义 -->
{{ html|escape }} <!-- HTML转义 -->
{{ html|safe }} <!-- 不转义(信任HTML) -->
{{ html|force_escape }} <!-- 强制转义 -->
<!-- 安全转义 -->
{{ js|escapejs }} <!-- JS转义 -->
{{ json|json_script:"data" }} <!-- 输出为script标签 -->
五、Context处理器详解
5.1 Context处理器机制
5.2 内置Context处理器
# 内置Context处理器说明
# django.template.context_processors.debug
"""
注入变量:
- DEBUG: 调试模式
- sql_queries: SQL查询列表(DEBUG=True时)
"""
# django.template.context_processors.request
"""
注入变量:
- request: 当前HttpRequest对象
"""
# django.contrib.auth.context_processors.auth
"""
注入变量:
- user: 当前用户对象
- perms: 用户权限对象
"""
# django.contrib.messages.context_processors.messages
"""
注入变量:
- messages: 消息列表
- DEFAULT_MESSAGE_LEVELS: 消息级别映射
"""
# django.template.context_processors.static
"""
注入变量:
- STATIC_URL: 静态文件URL
"""
# django.template.context_processors.media
"""
注入变量:
- MEDIA_URL: 媒体文件URL
"""
# django.template.context_processors.csrf
"""
注入变量:
- csrf_token: CSRF令牌
"""
# django.template.context_processors.tz
"""
注入变量:
- TIME_ZONE: 时区
"""
# django.template.context_processors.static
"""
注入变量:
- STATIC_URL: 静态文件URL
- STATIC_ROOT: 静态文件根目录
"""
5.3 自定义Context处理器
# myapp/context_processors.py
def site_settings(request):
"""
站点设置Context处理器
注入站点全局配置
"""
from django.conf import settings
return {
'SITE_NAME': getattr(settings, 'SITE_NAME', 'My Site'),
'SITE_VERSION': getattr(settings, 'SITE_VERSION', '1.0.0'),
'SITE_DESCRIPTION': getattr(settings, 'SITE_DESCRIPTION', ''),
}
def user_notifications(request):
"""
用户通知Context处理器
注入用户未读通知
"""
if request.user.is_authenticated:
from myapp.models import Notification
unread_count = Notification.objects.filter(
user=request.user,
is_read=False
).count()
return {
'unread_notifications': unread_count,
}
return {
'unread_notifications': 0,
}
def categories(request):
"""
分类列表Context处理器
注入全站分类
"""
from myapp.models import Category
from django.core.cache import cache
categories = cache.get('global_categories')
if categories is None:
categories = list(Category.objects.filter(is_active=True))
cache.set('global_categories', categories, 3600)
return {
'global_categories': categories,
}
def theme_settings(request):
"""
主题设置Context处理器
支持用户自定义主题
"""
theme = 'default'
if request.user.is_authenticated:
theme = getattr(request.user, 'theme', 'default')
elif 'theme' in request.COOKIES:
theme = request.COOKIES['theme']
return {
'current_theme': theme,
'theme_css': f'css/themes/{theme}.css',
}
# settings.py 配置
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',
# 自定义处理器
'myapp.context_processors.site_settings',
'myapp.context_processors.user_notifications',
'myapp.context_processors.categories',
'myapp.context_processors.theme_settings',
],
},
}]
六、自定义模板组件
6.1 自定义过滤器
# myapp/templatetags/myapp_filters.py
from django import template
from django.utils.safestring import mark_safe
import re
register = template.Library()
@register.filter(name='currency')
def currency(value, symbol='¥'):
"""
货币格式化过滤器
用法: {{ price|currency:"$" }}
"""
try:
value = float(value)
return f"{symbol}{value:,.2f}"
except (ValueError, TypeError):
return value
@register.filter
def percentage(value, decimals=2):
"""
百分比格式化
用法: {{ rate|percentage }} 或 {{ rate|percentage:1 }}
"""
try:
value = float(value)
return f"{value:.{decimals}f}%"
except (ValueError, TypeError):
return value
@register.filter
def truncate_middle(value, length=50):
"""
中间截断
用法: {{ long_text|truncate_middle:30 }}
"""
if len(str(value)) <= length:
return value
half = length // 2
return f"{value[:half]}...{value[-half:]}"
@register.filter
def highlight(text, query):
"""
高亮搜索词
用法: {{ content|highlight:query }}
"""
if not query:
return text
pattern = re.compile(f'({re.escape(query)})', re.IGNORECASE)
result = pattern.sub(r'<mark>\1</mark>', str(text))
return mark_safe(result)
@register.filter
def timesince_simple(value):
"""
简化的时间间隔
用法: {{ created_at|timesince_simple }}
"""
from django.utils import timezone
from datetime import timedelta
if not value:
return ''
now = timezone.now()
diff = now - value
if diff < timedelta(minutes=1):
return '刚刚'
elif diff < timedelta(hours=1):
return f'{int(diff.seconds / 60)}分钟前'
elif diff < timedelta(days=1):
return f'{int(diff.seconds / 3600)}小时前'
elif diff < timedelta(days=30):
return f'{diff.days}天前'
elif diff < timedelta(days=365):
return f'{int(diff.days / 30)}个月前'
else:
return f'{int(diff.days / 365)}年前'
@register.filter
def model_name(obj):
"""
获取模型名称
用法: {{ obj|model_name }}
"""
if hasattr(obj, '_meta'):
return obj._meta.verbose_name
return ''
@register.filter(is_safe=True)
def markdown(value):
"""
Markdown渲染
用法: {{ content|markdown }}
"""
import markdown as md
return mark_safe(md.markdown(value, extensions=['extra', 'codehilite']))
@register.filter
def get_item(dictionary, key):
"""
字典取值
用法: {{ mydict|get_item:key }}
"""
if dictionary is None:
return None
return dictionary.get(key)
# 在模板中加载
# {% load myapp_filters %}
6.2 自定义简单标签
# myapp/templatetags/myapp_tags.py
from django import template
from django.utils.safestring import mark_safe
import datetime
register = template.Library()
@register.simple_tag
def current_time(format_string='%Y-%m-%d %H:%M:%S'):
"""
当前时间标签
用法: {% current_time "%Y-%m-%d" %}
"""
return datetime.datetime.now().strftime(format_string)
@register.simple_tag
def query_transform(request, **kwargs):
"""
URL参数转换
用法: {% query_transform request page=2 %}
"""
updated = request.GET.copy()
for key, value in kwargs.items():
if value:
updated[key] = value
else:
updated.pop(key, None)
return updated.urlencode()
@register.simple_tag
def url_replace(request, field, value):
"""
URL参数替换
用法: {% url_replace request 'page' 2 %}
"""
query_dict = request.GET.copy()
query_dict[field] = value
return query_dict.urlencode()
@register.simple_tag
def active_class(request, pattern):
"""
导航激活状态
用法: {% active_class request '/blog/' %}
"""
import re
if re.search(pattern, request.path):
return 'active'
return ''
@register.simple_tag
def site_version():
"""
站点版本
用法: {% site_version %}
"""
from django.conf import settings
return getattr(settings, 'SITE_VERSION', '1.0.0')
@register.simple_tag(takes_context=True)
def current_user_attr(context, attr):
"""
获取当前用户属性
用法: {% current_user_attr 'username' %}
"""
request = context['request']
if request.user.is_authenticated:
return getattr(request.user, attr, '')
return ''
@register.simple_tag(takes_context=True)
def render_breadcrumb(context):
"""
渲染面包屑
用法: {% render_breadcrumb %}
"""
request = context.get('request')
if not request:
return ''
path_parts = request.path.strip('/').split('/')
if not path_parts or path_parts == ['']:
return mark_safe('<nav class="breadcrumb"><a href="/">首页</a></nav>')
breadcrumb = ['<nav class="breadcrumb"><a href="/">首页</a>']
current_path = ''
for part in path_parts:
current_path += f'/{part}'
breadcrumb.append(f' > <a href="{current_path}">{part.title()}</a>')
breadcrumb.append('</nav>')
return mark_safe(''.join(breadcrumb))
6.3 自定义包含标签
# myapp/templatetags/myapp_tags.py
from django import template
register = template.Library()
@register.inclusion_tag('tags/pagination.html')
def pagination(page_obj, request=None):
"""
分页标签
用法: {% pagination page_obj request %}
"""
return {
'page_obj': page_obj,
'request': request,
}
@register.inclusion_tag('tags/article_card.html')
def article_card(article, show_image=True, show_summary=True):
"""
文章卡片标签
用法: {% article_card article show_image=False %}
"""
return {
'article': article,
'show_image': show_image,
'show_summary': show_summary,
}
@register.inclusion_tag('tags/user_avatar.html')
def user_avatar(user, size=40, show_name=False):
"""
用户头像标签
用法: {% user_avatar user size=80 show_name=True %}
"""
return {
'user': user,
'size': size,
'show_name': show_name,
}
@register.inclusion_tag('tags/alert.html')
def alert(message, alert_type='info', dismissible=True):
"""
警告框标签
用法: {% alert "操作成功" "success" %}
"""
return {
'message': message,
'alert_type': alert_type,
'dismissible': dismissible,
}
@register.inclusion_tag('tags/social_share.html', takes_context=True)
def social_share(context, url=None, title=None):
"""
社交分享标签
用法: {% social_share url=title %}
"""
request = context['request']
if url is None:
url = request.build_absolute_uri()
if title is None:
title = context.get('title', '')
return {
'url': url,
'title': title,
}
# templates/tags/pagination.html
"""
<nav class="pagination">
{% if page_obj.has_previous %}
<a href="?page={{ page_obj.previous_page_number }}" class="prev">上一页</a>
{% endif %}
<span class="current">
第 {{ page_obj.number }} 页,共 {{ page_obj.paginator.num_pages }} 页
</span>
{% if page_obj.has_next %}
<a href="?page={{ page_obj.next_page_number }}" class="next">下一页</a>
{% endif %}
</nav>
"""
6.4 自定义模板标签Node类
# myapp/templatetags/advanced_tags.py
from django import template
from django.utils.safestring import mark_safe
register = template.Library()
class EchoNode(template.Node):
"""
Echo标签节点
"""
def __init__(self, values):
self.values = values
def render(self, context):
rendered = []
for value in self.values:
if isinstance(value, template.Variable):
rendered.append(str(value.resolve(context)))
else:
rendered.append(str(value))
return ' '.join(rendered)
@register.tag
def echo(parser, token):
"""
Echo标签
用法: {% echo "Hello" user.name "!" %}
"""
parts = token.split_contents()[1:]
values = []
for part in parts:
if part[0] in ('"', "'"):
values.append(part[1:-1])
else:
values.append(template.Variable(part))
return EchoNode(values)
class CaptureNode(template.Node):
"""
Capture标签节点
将内容存储到变量中
"""
def __init__(self, nodelist, variable_name):
self.nodelist = nodelist
self.variable_name = variable_name
def render(self, context):
output = self.nodelist.render(context)
context[self.variable_name] = output
return ''
@register.tag
def capture(parser, token):
"""
Capture标签
用法:
{% capture as captured_content %}
<div>复杂内容</div>
{% endcapture %}
{{ captured_content }}
"""
parts = token.split_contents()
if len(parts) != 3 or parts[1] != 'as':
raise template.TemplateSyntaxError(
"capture标签语法: {% capture as variable_name %}...{% endcapture %}"
)
variable_name = parts[2]
nodelist = parser.parse(('endcapture',))
parser.delete_first_token()
return CaptureNode(nodelist, variable_name)
class IfInNode(template.Node):
"""
Ifin标签节点
检查值是否在列表中
"""
def __init__(self, value, list_var, nodelist_true, nodelist_false):
self.value = template.Variable(value)
self.list_var = template.Variable(list_var)
self.nodelist_true = nodelist_true
self.nodelist_false = nodelist_false
def render(self, context):
try:
value = self.value.resolve(context)
list_value = self.list_var.resolve(context)
if value in list_value:
return self.nodelist_true.render(context)
return self.nodelist_false.render(context)
except Exception:
return self.nodelist_false.render(context)
@register.tag
def ifin(parser, token):
"""
Ifin标签
用法:
{% ifin user.role allowed_roles %}
有权限
{% else %}
无权限
{% endifin %}
"""
parts = token.split_contents()
if len(parts) != 3:
raise template.TemplateSyntaxError(
"ifin标签语法: {% ifin value list %}...{% endifin %}"
)
value = parts[1]
list_var = parts[2]
nodelist_true = parser.parse(('else', 'endifin'))
token = parser.next_token()
if token.contents == 'else':
nodelist_false = parser.parse(('endifin',))
parser.delete_first_token()
else:
nodelist_false = template.NodeList()
return IfInNode(value, list_var, nodelist_true, nodelist_false)
七、模板渲染机制
7.1 渲染流程
7.2 模板加载器
# Django内置模板加载器
# 1. filesystem.Loader - 文件系统加载器
"""
从TEMPLATES['DIRS']指定的目录加载模板
"""
# 2. app_directories.Loader - 应用目录加载器
"""
从各应用的templates子目录加载模板
需要设置'APP_DIRS': True
"""
# 3. cached.Loader - 缓存加载器
"""
缓存已解析的模板,提高性能
配置:
'loaders': [
('django.template.loaders.cached.Loader', [
'django.template.loaders.filesystem.Loader',
'django.template.loaders.app_directories.Loader',
]),
],
"""
# 自定义模板加载器
from django.template.loaders.base import Loader
from django.template import TemplateDoesNotExist
class DatabaseLoader(Loader):
"""
从数据库加载模板
"""
def get_template_sources(self, template_name):
from myapp.models import DatabaseTemplate
try:
template = DatabaseTemplate.objects.get(name=template_name)
yield template.content
except DatabaseTemplate.DoesNotExist:
raise TemplateDoesNotExist(template_name)
def load_template_source(self, template_name, template_dirs=None):
for source in self.get_template_sources(template_name):
return source, template_name
raise TemplateDoesNotExist(template_name)
# 配置自定义加载器
TEMPLATES = [{
# ...
'OPTIONS': {
'loaders': [
'myapp.loaders.DatabaseLoader',
'django.template.loaders.filesystem.Loader',
'django.template.loaders.app_directories.Loader',
],
},
}]
7.3 Context对象详解
from django.template import Context, RequestContext
# 基本Context
def context_basics():
"""Context基础用法"""
# 创建Context
context = Context({
'title': '文章标题',
'articles': ['文章1', '文章2'],
})
# 访问值
print(context['title'])
# 更新值
context['new_key'] = '新值'
context.update({'key1': 'value1', 'key2': 'value2'})
# 压平(合并所有字典)
flat = context.flatten()
# 迭代
for key, value in context.items():
print(f'{key}: {value}')
# RequestContext
def request_context_view(request):
"""RequestContext使用"""
# RequestContext会自动应用Context处理器
context = RequestContext(request, {
'title': '页面标题',
})
# 等价于
from django.template import engines
engine = engines['django']
context = engine.template_context_processors(request)
context.update({'title': '页面标题'})
return render(request, 'template.html', context)
# Context栈
def context_stack():
"""Context栈操作"""
context = Context({'a': 1})
# push新层
with context.push():
context['a'] = 2
print(context['a']) # 2
# pop后恢复
print(context['a']) # 1
# 使用update
with context.update({'b': 2}):
print(context['b']) # 2
八、性能优化与最佳实践
8.1 模板性能优化
# 1. 使用模板缓存
TEMPLATES = [{
# ...
'OPTIONS': {
'loaders': [
('django.template.loaders.cached.Loader', [
'django.template.loaders.filesystem.Loader',
'django.template.loaders.app_directories.Loader',
]),
],
},
}]
# 2. 减少模板继承层级
# 不推荐:多层继承
# base.html -> base_with_sidebar.html -> base_with_nav.html -> page.html
# 推荐:扁平继承
# base.html -> page.html
# 3. 使用with标签缓存复杂计算
{% with article.get_related_articles as related %}
{% for item in related %}
{{ item.title }}
{% endfor %}
{% endwith %}
# 4. 避免在模板中进行复杂计算
# 不推荐
{{ article.get_complex_score }}
# 推荐:在视图中计算
def article_detail(request, pk):
article = Article.objects.get(pk=pk)
article.complex_score = calculate_score(article)
return render(request, 'article.html', {'article': article})
# 5. 使用select_related/prefetch_related
def article_list(request):
articles = Article.objects.select_related('author').prefetch_related('tags')
return render(request, 'articles.html', {'articles': articles})
8.2 模板安全最佳实践
# 1. 自动转义(默认开启)
TEMPLATES = [{
# ...
'OPTIONS': {
'autoescape': True, # 默认True
},
}]
# 2. 谨慎使用safe过滤器
# 不推荐
{{ user_input|safe }}
# 推荐:先清理
{{ user_input|striptags|safe }}
# 3. 使用escapejs处理JS变量
<script>
var content = "{{ content|escapejs }}";
</script>
# 4. 使用json_script处理复杂数据
{{ data|json_script:"my-data" }}
<script>
var data = JSON.parse(document.getElementById('my-data').textContent);
</script>
# 5. CSRF保护
<form method="post">
{% csrf_token %}
...
</form>
8.3 模板组织最佳实践
# 推荐的模板目录结构
templates/
├── base.html # 基础模板
├── base_with_sidebar.html # 带侧边栏的基础模板
├── includes/ # 可复用组件
│ ├── header.html
│ ├── footer.html
│ ├── sidebar.html
│ ├── pagination.html
│ └── article_card.html
├── myapp/ # 应用模板
│ ├── article_list.html
│ ├── article_detail.html
│ └── article_form.html
└── errors/ # 错误页面
├── 400.html
├── 403.html
├── 404.html
└── 500.html
更多推荐

所有评论(0)