Django自定义模板过滤器和标签的实现方法


Posted in Python onAugust 21, 2019

现在我们已经很熟悉Django的MTV模式了。模板(template)负责如何去展示数据,而视图(view)负责筛选出正确的数据。因此通常来说逻辑都是放到视图中的,但模板也需要一些 和表示相关的逻辑 :比如循环展示(如 {% for ... %} )、或者以某种特定格式输出(如 {{ ...|date:'Y-m-d' }} )等,这些功能都是靠模板的 过滤器(filters) 和 标签(tags) 实现的。

Django的模板语言包含了很多内置的过滤器和标签,设计目的是满足应用需要占位逻辑需求。但有的时候这些通用的功能满足不了你的某些需求,这时候就需要自定义过滤器和标签来实现了。

前置条件

要在Django中使用模板过滤器或标签,就首先得 注册 它们。

注册方法如下:

  • 在APP中新建名为 templatetags 的目录(方便起见,教程选择了 article 这个APP)
  • 在此目录中新建名为 __init__.py 的空文件,使得此目录被视作一个Python的包
  • 在此目录中新建python文件(比如 my_filters_and_tags.py ),就可以在里面愉快的写代码啦

完成后的目录结构如下:

article/
  __init__.py
  views.py
  models.py
  # 新增目录
  templatetags/
    __init__.py # 空文件
    my_filters_and_tags.py # 即将写代码的地方
  ...

请注意:

  • 目录必须位于已注册的APP中,这是出于安全性的考虑
  • 新建目录后,必须手动重启服务器,里面的过滤器和标签才能生效

前置条件就完成了,接下来我们看看如何写一个模板过滤器。

模板过滤器

过滤器 filter 的表现形式为紧跟在上下文后面的管道符 | ,管道符后面是filter的名称: {{ ...|filter_name }} 。有的filter还可以带有参数: {{ ...|filter_name:var }}

注意过滤器名称的冒号后面不能有空格。

filter 这个名字可能会让你误认为它只是用来筛选某些特定数据的,但实际上它远不止这点功能。它可以改变上下文的最终展示效果,也可以将上下文通过运算输出为特定的值。

小试牛刀

要成为一个可用的 filter ,文件中必须包含一个名为 register 的模块级变量,它是一个 template.Library 实例,所有的 filters 均在其中注册。所以在 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()

filter可以通过装饰器进行注册。若注册装饰器中携带了 name 参数,则其值为此filter的名称;若未携带,则函数名就是filter的名称。

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自定义模板过滤器和标签的实现方法 

实际上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 %}

包含标签

包含标签可以让另一个模板为当前模板渲染数据。听起来比较拗口,还是通过例子来理解。

假设现在有一个需求,是要在文章详情页面中,显示所有相关评论的发布时间。因此在 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自定义模板过滤器和标签的实现方法,希望对大家有所帮助,如果大家有任何疑问欢迎给我留言,小编会及时回复大家的!

Python 相关文章推荐
Python单元测试框架unittest简明使用实例
Apr 13 Python
Python创建模块及模块导入的方法
May 27 Python
python读取csv文件并把文件放入一个list中的实例讲解
Apr 27 Python
Django实现表单验证
Sep 08 Python
python 检查是否为中文字符串的方法
Dec 28 Python
Python3使用TCP编写一个简易的文件下载器功能
May 08 Python
ipython和python区别详解
Jun 26 Python
pandas分批读取大数据集教程
Jun 06 Python
解决Keras使用GPU资源耗尽的问题
Jun 22 Python
Python+OpenCV图像处理——实现轮廓发现
Oct 23 Python
浅谈盘点5种基于Python生成的个性化语音方法
Feb 05 Python
Python 数据结构之十大经典排序算法一文通关
Oct 16 Python
扩展Django admin的list_filter()可使用范围方法
Aug 21 #Python
python机器学习包mlxtend的安装和配置详解
Aug 21 #Python
python 画出使用分类器得到的决策边界
Aug 21 #Python
Django url,从一个页面调到另个页面的方法
Aug 21 #Python
python requests更换代理适用于IP频率限制的方法
Aug 21 #Python
docker django无法访问redis容器的解决方法
Aug 21 #Python
django和vue实现数据交互的方法
Aug 21 #Python
You might like
PHP面向接口编程 耦合设计模式 简单范例
2011/03/23 PHP
php加速器eAccelerator的配置参数、API详解
2014/05/05 PHP
php实现压缩多个CSS与JS文件的方法
2014/11/11 PHP
Zend Framework基本页面布局分析
2016/03/19 PHP
Yii2下session跨域名共存的解决方案
2017/02/04 PHP
PHP的imageTtfText()函数深入详解
2021/03/03 PHP
一个选择最快的服务器转向代码
2009/04/27 Javascript
两种WEB下的模态对话框 (asp.net或js的分别实现)
2009/12/02 Javascript
基于JQuery的浮动DIV显示提示信息并自动隐藏
2011/02/11 Javascript
js中AppendChild与insertBefore的用法详细解析
2013/12/16 Javascript
解决Jquery向页面append新元素之后事件的绑定问题
2015/03/16 Javascript
jQuery文件上传控件 Uploadify 详解
2016/06/20 Javascript
详解windows下vue-cli及webpack 构建网站(二)导入bootstrap样式
2017/06/17 Javascript
解决Vue2.0自带浏览器里无法打开的原因(兼容处理)
2017/07/28 Javascript
详谈Node.js之操作文件系统
2017/08/29 Javascript
使用JS中的Replace()方法遇到的问题小结
2017/10/20 Javascript
解决layui表格内文本超出隐藏的问题
2019/09/12 Javascript
[10:07]2014DOTA2国际邀请赛 实拍选手现场观战DK对阵Titan
2014/07/12 DOTA
使用Python的Zato发送AMQP消息的教程
2015/04/16 Python
Python 功能和特点(新手必学)
2015/12/30 Python
python简单读取大文件的方法
2016/07/01 Python
Python3实现的画图及加载图片动画效果示例
2018/01/19 Python
Python如何使用ElementTree解析xml
2020/10/12 Python
德国富尔达运动鞋店:43einhalb
2020/12/25 全球购物
信息系统专业个人求职信范文
2013/12/07 职场文书
乔迁宴答谢词
2014/01/21 职场文书
火箭队口号
2014/06/18 职场文书
户籍证明模板
2014/09/28 职场文书
2014年话务员工作总结
2014/11/19 职场文书
首都博物馆观后感
2015/06/05 职场文书
大学入学感言
2015/08/01 职场文书
详解MySQL集群搭建
2021/05/26 MySQL
linux中nohup和后台运行进程查看及终止
2021/06/24 Python
SpringBoot使用ip2region获取地理位置信息的方法
2022/06/21 Java/Android
tree shaking对打包体积优化及作用
2022/07/07 Java/Android
html原生table实现合并单元格以及合并表头的示例代码
2023/05/07 HTML / CSS