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聚类算法之凝聚层次聚类实例分析
Nov 20 Python
老生常谈Python进阶之装饰器
May 11 Python
Python3 XML 获取雅虎天气的实现方法
Feb 01 Python
python 3.7.4 安装 opencv的教程
Oct 10 Python
TENSORFLOW变量作用域(VARIABLE SCOPE)
Jan 10 Python
Python编程快速上手——Excel表格创建乘法表案例分析
Feb 28 Python
在django admin详情表单显示中添加自定义控件的实现
Mar 11 Python
python爬虫数据保存到mongoDB的实例方法
Jul 28 Python
python批量合成bilibili的m4s缓存文件为MP4格式 ver2.5
Dec 01 Python
python 用pandas实现数据透视表功能
Dec 21 Python
Python函数中的不定长参数相关知识总结
Jun 24 Python
python小型的音频操作库mp3Play
Apr 24 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中cookies使用指南
2007/03/16 PHP
PHP MemCached高级缓存配置图文教程
2010/08/05 PHP
php中json_decode()和json_encode()的使用方法
2012/06/04 PHP
php Hex RGB颜色值互换的使用
2013/05/10 PHP
解析使用ThinkPHP应该掌握的调试手段
2013/06/20 PHP
php上传图片生成缩略图(GD库)
2016/01/06 PHP
PHP的Yii框架中移除组件所绑定的行为的方法
2016/03/18 PHP
解决thinkPHP 5 nginx 部署时,只跳转首页的问题
2019/10/16 PHP
javascript 动态添加表格行
2006/06/22 Javascript
用正则表达式 动态创建/增加css style script 兼容IE firefox
2009/03/10 Javascript
jQuery 三击事件实现代码
2013/09/11 Javascript
Jquery AJAX POST与GET之间的区别
2013/11/14 Javascript
JS模拟实现Select效果代码
2015/09/24 Javascript
微信小程序(应用号)简单实例应用及实例详解
2016/09/26 Javascript
jquery PrintArea 实现票据的套打功能(代码)
2017/03/17 Javascript
微信小程序中子页面向父页面传值实例详解
2017/03/20 Javascript
JS中的算法与数据结构之常见排序(Sort)算法详解
2019/08/16 Javascript
[02:16]2018年度CS GO最具人气选手-完美盛典
2018/12/16 DOTA
Python实现截屏的函数
2015/07/26 Python
python 对给定可迭代集合统计出现频率,并排序的方法
2018/10/18 Python
Django的models中on_delete参数详解
2019/07/16 Python
Python scrapy增量爬取实例及实现过程解析
2019/12/24 Python
python 图像的离散傅立叶变换实例
2020/01/02 Python
Python函数式编程实例详解
2020/01/17 Python
解决tensorflow/keras时出现数组维度不匹配问题
2020/06/29 Python
Office DEPOT法国官网:欧迪办公用品采购
2018/01/03 全球购物
大学生个人求职信范文
2013/09/21 职场文书
四下基层实施方案
2014/03/28 职场文书
节能减排倡议书
2014/04/15 职场文书
六查六看剖析材料
2014/10/06 职场文书
2015年国庆节慰问信
2015/03/23 职场文书
公司宣传语大全
2015/07/13 职场文书
2016年三八节红领巾广播稿
2015/12/17 职场文书
导游词之秦始皇兵马俑博物馆
2019/09/29 职场文书
如何使用Maxwell实时同步mysql数据
2021/04/08 MySQL
python爬取网页版QQ空间,生成各类图表
2021/06/02 Python