Django模板语言故意限制Python代码执行能力,禁止直接写复杂if/for嵌套逻辑,因其设计原则是分离关注点:模板仅负责展示,业务逻辑应移至Python层的自定义simple_tag中实现可测试、可复用、可调试的封装。

为什么不能直接在模板里写 if/for 嵌套逻辑
Django 模板语言故意限制了 Python 代码的执行能力,比如不支持函数调用带参数、不能定义变量、无法使用 elif 链或复杂表达式。当你需要做“根据用户权限渲染不同按钮 + 同时检查对象状态 + 拼接 URL 参数”这类组合逻辑时,硬塞进模板只会让 {% if %} 套三层还漏掉 else 分支,最后连自己都看不懂。
自定义 template tag 不是炫技,而是把模板里本不该出现的业务判断提前收口到 Python 层——可测试、可复用、可 debug。
如何正确注册并加载 simple_tag(最常用场景)
simple_tag 是处理“输入参数 → 返回字符串/安全 HTML”的首选。它比 inclusion_tag 轻量,又比 assignment_tag(已弃用)更明确。
- 必须放在 app 目录下的
templatetags/子目录中(如myapp/templatetags/mytags.py),且该目录要有__init__.py - 在
mytags.py开头必须加from django import template和register = template.Library() - 模板中使用前必须先
{% load mytags %},名字必须和文件名一致 - 函数返回值默认被自动转义,若需输出原始 HTML,用
@register.simple_tag(mark_safe=True)或手动包裹mark_safe()
示例:生成带权限校验的编辑链接
立即学习“Python免费学习笔记(深入)”;
提供使用 django-unfold (v0.56+) 构建高级 Django 管理界面的专业指导。当用户提及 django-unfold、unfold admin 等内容时使用此技能。
from django import template
from django.urls import reverse
from django.utils.safestring import mark_safe
register = template.Library()
@register.simple_tag(takes_context=True)
def edit_link(context, obj):
request = context['request']
if not request.user.has_perm('myapp.change_obj'):
return ''
url = reverse('obj_edit', args=[obj.pk])
return mark_safe(f'<a href="https://www.php.cn/link/c7dfbbb2c4b908a2b8841c6ebbdfa8d6">编辑</a>')
模板中:{% edit_link object %}
什么时候必须用 takes_context=True?
当你的 tag 需要访问当前请求、用户、session 或其他上下文变量时,takes_context=True 是唯一方式。不加这个参数,context 就只是个空字典。
-
takes_context=True后,第一个参数固定是context,后续才是你声明的位置参数 - 不要试图从
context里取request然后调用request.user.is_authenticated—— Django 默认没传request,得确保TEMPLATE_CONTEXT_PROCESSORS包含'django.template.context_processors.request' - 如果只依赖传入的对象(如
obj、field_name),就别加takes_context=True,避免无谓性能开销
filter vs. simple_tag:选错会导致模板变脆弱
filter 只能接收一个主参数(可加额外参数),语法是 {{ value|my_filter:"arg" }};simple_tag 支持任意数量位置/关键字参数,语法是 {% my_tag arg1 arg2 key=val %}。混用会立刻报错。
- 想对单个变量做转换(如格式化时间、截断文本)→ 用
filter - 想组合多个变量+上下文生成 HTML 片段 → 用
simple_tag -
filter不能访问request或user,除非你传进去(但那就违背 filter 设计意图) - Django 4.2+ 已废弃
assignment_tag,要用变量赋值功能,改用simple_tag+ 模板变量捕获:{% my_tag as result %}
容易忽略的一点:所有自定义 tag/filter 的 Python 文件修改后,Django 开发服务器不会自动重载——必须手动重启 runserver,否则一直用旧逻辑。

















