Django自定义模板过滤器和标签实战指南
本文详细介绍如何在Django中自定义模板过滤器和标签。从创建templatetags目录、注册register实例开始,讲解filter装饰器的使用及参数处理,并演示timesince_zh相对时间过滤器的实现。此外,涵盖simple_tag和inclusion_tag的高级用法,包括上下文访问、参数传递及模板复用,帮助开发者灵活扩展Django模板功能。
Django模板过滤器与标签概述
熟悉Django的MTV架构后,我们知道模板负责数据展示,视图负责数据筛选。虽然逻辑通常置于视图中,但模板仍需处理部分展示逻辑,例如循环渲染(如 {% for ... %})或格式化输出(如 {{ ...|date:'Y-m-d' }} )。这些功能主要通过模板的过滤器(filters)和标签(tags)来实现。
Django内置了大量过滤器和标签,旨在满足通用的占位逻辑需求。然而,当内置功能无法满足特定业务场景时,开发者需要自定义过滤器和标签以扩展模板能力。
前置条件与目录结构
前置条件
在Django中启用模板过滤器或标签,首要步骤是完成注册流程。
具体的注册步骤如下:
- 在指定的APP目录下创建一个名为 templatetags 的文件夹(本教程以 article 应用为例)。
- 在该文件夹内创建一个名为 __init__.py 的空文件,确保该目录被Python识别为一个包。
- 在 templatetags 目录下创建具体的Python文件(例如 my_filters_and_tags.py),即可在此文件中编写自定义逻辑。
完成上述步骤后,目录结构应如下所示:
article/
__init__.py
views.py
models.py
# 新增目录
templatetags/
__init__.py # 空文件
my_filters_and_tags.py # 即将写代码的地方
...
注意事项:
- 该目录必须位于已注册的APP中,这是出于安全性的考量。
- 创建新目录后,必须手动重启Django服务器,自定义的过滤器和标签才能生效。
前置准备工作已完成,接下来我们将探讨如何编写模板过滤器。
模板过滤器实现详解
模板过滤器
过滤器(filter)在模板中的表现形式为紧跟在上下文变量后的管道符 |,其后跟随filter的名称: {{ ...|filter_name }}。部分过滤器支持接收参数: {{ ...|filter_name:var }} 。
需注意,过滤器名称后的冒号后面不能包含空格。
尽管名为“过滤器”,其功能远不止筛选数据。它能够改变上下文的最终展示效果,或通过运算将上下文转换为特定值。
小试牛刀
要使自定义代码成为可用的filter,文件中必须定义一个名为 register 的模块级变量,该变量必须是 template.Library 的实例,所有过滤器均在此处注册。因此,在 my_filter_and_tags.py 文件中应添加如下代码:
article/templatetags/my_filter_and_tags.py
from django import template
register = template.Library()
随后,你可以像编写普通Python函数一样定义过滤器逻辑:
article/templatetags/my_filter_and_tags.py
from django import template
register = template.Library()
@register.filter(name='transfer')
def transfer(value, arg):
"""将输出强制转换为字符串 arg """
return arg
@register.filter()
def lower(value):
"""将字符串转换为小写字符"""
return value.lower()
过滤器可通过装饰器进行注册。若装饰器中指定了 name 参数,则使用该值作为filter名称;若未指定,则默认使用函数名。
过滤器必须是接收一到两个参数的Python函数。第一个参数为上下文变量,第二个参数由filter提供。例如,在过滤器 {{ var|foo:"bar" }} 中,var 为第一个参数,bar 作为第二个参数传入。
在模板中调用这些filter时,需使用 {% load ... %} 语句加载filter文件,示例如下:
# 任意模板文件中
{% load my_filters_and_tags %}
{{ 'ABC'|transfer:'cool' }} # 输出:'cool'
{{ 'ABC'|lower }} # 输出: 'abc'
相对时间过滤器示例
更人性化的时间显示
掌握filter的基本用法后,我们来实现一个更具实用价值的功能。
对于人类用户而言,相对时间比绝对时间更易读。例如,“发表于 3天前”能直观反映文章时效,而“发表于 2019年8月10日”则需用户自行推算当前日期。
因此,我们编写一个显示相对日期的 time_since_zh 过滤器:
article/templatetags/my_filter_and_tags.py
...
from django.utils import timezone
import math
# 获取相对时间
@register.filter(name='timesince_zh')
def time_since_zh(value):
now = timezone.now()
diff = now - value
if diff.days == 0 and diff.seconds >= 0 and diff.seconds < 60:
return '刚刚'
if diff.days == 0 and diff.seconds >= 60 and diff.seconds < 3600:
return str(math.floor(diff.seconds / 60)) + "分钟前"
if diff.days == 0 and diff.seconds >= 3600 and diff.seconds < 86400:
return str(math.floor(diff.seconds / 3600)) + "小时前"
if diff.days >= 1 and diff.days < 30:
return str(diff.days) + "天前"
if diff.days >= 30 and diff.days < 365:
return str(math.floor(diff.days / 30)) + "个月前"
if diff.days >= 365:
return str(math.floor(diff.days / 365)) + "年前"
该代码逻辑简单,即比较文章发布时间与当前时间,并返回相应的中文时间字符串。
修改文章列表模板中与发布时间相关的代码,应用新写的filter:
templates/article/list.html
...
{% load my_filters_and_tags %}
...
<!-- 旧代码
{{ article.created|date:'Y-m-d' }}
-->
<!-- 新代码 -->
{{ article.created|timesince_zh }}
...
运行效果如下:
实际上,Django内置了 timesince 过滤器,但其输出为英文,不够友好。自定义过滤器解决了这一本地化问题。
模板标签的高级应用
模板标签
模板标签(tag)比过滤器更为复杂,功能也更为强大。
标签(tag)在模板中的表现形式为 {% tag_name ... %},例如常见的内置标签 {% url ... %} 和 {% static ... %}。若内置标签无法满足需求,Django提供了多种快捷方式,简化了大多数类型标签的开发过程。
简单标签与上下文访问
简单标签
simple_tag 是最常用的标签类型。其注册方法与过滤器类似:
@register.simple_tag
def change_http_to_https(url):
new_url = url.replace('http://', 'https://')
return new_url
调用时,同样需在模板文件中通过 {% load... %} 语句引入。用法显而易见: {% change_http_to_https ... %} ,该标签的作用是将HTTP链接转换为HTTPS链接。
在使用 Django-allauth 进行微博登录时,默认返回的用户头像链接为 http 格式(尽管微博提供https版本)。若站点已升级至https且不想深入研究微博接口,此标签将非常有用。
补充说明,Django-allauth 第三方登录的头像 URL 保存在 User.socialaccount_set.all.0.get_avatar_url 中。
以下示例展示了如何返回指定格式的时间字符串:
import datetime
@register.simple_tag
def current_time(format_string):
return datetime.datetime.now().strftime(format_string)
调用时,可将其保存为模板变量,以便在期望位置多次复用:
{% current_time "%Y-%m-%d %I:%M %p" as the_time %}
<p>The time is {{ the_time }}.</p>
<p>Again, the time is {{ the_time }}.</p>
模板标签也可访问当前上下文,只需在注册标签时传入 takes_context 参数:
@register.simple_tag(takes_context=True)
def current_time(context, format_string):
timezone = context['timezone']
return your_get_current_time_method(timezone, format_string)
需注意,函数的第一个参数必须为 context。
与过滤器不同,标签可以接受任意数量的位置参数或关键字参数。例如:
@register.simple_tag
def my_tag(a, b, *args, **kwargs):
warning = kwargs['warning']
profile = kwargs['profile']
...
return ...
在模板中调用时,任意数量以空格分隔的参数将传递给模板标签。与Python类似,关键字参数使用等号(" = ")赋值,且必须位于位置参数之后:
{% my_tag 123 "abcd" book.title warning=message profile=user.profile %}
包含标签与模板复用
包含标签
包含标签(inclusion tag)允许另一个模板为当前模板渲染数据。这听起来较为抽象,通过示例理解更为直观。
假设需求是在文章详情页面显示所有相关评论的发布时间。因此在 my_filter_and_tags.py 中编写如下代码:
my_filter_and_tags.py
...
@register.inclusion_tag('article/tag_list.html')
def show_comments_pub_time(article):
"""显示文章评论的发布时间"""
comments = article.comments.all()
return {'comments': comments}
函数传入的参数可以是模板中的上下文变量。函数体内部获取所有相关评论的查询集,并将结果 comments 返回。注意,返回结果将传入 tag_list.html 模板中,因此需新建该模板并写入:
templates/article/tag_list.html
<ul>
{% for comment in comments %}
<li> {{ comment.created }} </li>
{% endfor %}
</ul>
随后,在文章详情页面的模板中,选择合适位置插入:
templates/article/detail.html
...
{% load my_filters_and_tags %}
...
{% show_comments_pub_time article %}
刷新详情页面,若配置正确,即可看到所有评论的发表时间展示出来。
包含标签的另一应用场景是按钮组件。某些按钮外观相似,但根据页面不同功能各异,此时也可利用包含标签实现。
总之,包含标签可将常用模板代码打包为小组件,便于重复利用。
目前的博客项目中暂时无需使用包含标签,因此可安全删除上述代码。
总结
以上介绍了Django自定义模板过滤器和标签的实现方法,希望有所帮助。如有任何疑问,欢迎留言,小编会及时回复!
本站声明
本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。













