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编程中的反模式实例分析
Dec 08 Python
Python中用于检查英文字母大写的isupper()方法
May 19 Python
Python利用正则表达式匹配并截取指定子串及去重的方法
Jul 30 Python
python实现手机通讯录搜索功能
Feb 22 Python
解决Python3中的中文字符编码的问题
Jul 18 Python
python虚拟环境迁移方法
Jan 03 Python
python远程邮件控制电脑升级版
May 23 Python
Python tkinter和exe打包的方法
Feb 05 Python
使用python绘制cdf的多种实现方法
Feb 25 Python
Python如何爬取b站热门视频并导入Excel
Aug 10 Python
详解Python中的文件操作
Jan 14 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实现的博客欢迎提示功能(很特别哦)
2014/06/05 PHP
PHP获取文件行数的方法
2015/06/10 PHP
PHP-FPM 设置多pool及配置文件重写操作示例
2019/10/02 PHP
使javascript也能包含文件
2006/10/26 Javascript
jQuery+jqmodal弹出窗口实现代码分明
2010/06/14 Javascript
jquery.validate分组验证代码
2011/03/17 Javascript
Jquery模仿Baidu、Google搜索时自动补充搜索结果提示
2013/12/26 Javascript
jQuery 删除或是清空某个HTML元素示例
2014/08/04 Javascript
《JavaScript DOM 编程艺术》读书笔记之DOM基础
2015/01/09 Javascript
基于Css3和JQuery实现打字机效果
2015/08/11 Javascript
jQuery满意度星级评价插件特效代码分享
2015/08/19 Javascript
JS基于ocanvas插件实现的简单画板效果代码(附demo源码下载)
2016/04/05 Javascript
jQuery实现iframe父窗体和子窗体的相互调用
2016/06/17 Javascript
纯JS单页面赛车游戏制作代码分享
2017/03/03 Javascript
jQuery:unbind方法的使用详解
2017/08/14 jQuery
从parcel.js打包出错到选择nvm的全部过程
2018/01/23 Javascript
JavaScript循环遍历你会用哪些之小结篇
2018/09/28 Javascript
Vue 框架之键盘事件、健值修饰符、双向数据绑定
2018/11/14 Javascript
[56:14]Fnatic vs OG 2018国际邀请赛小组赛BO2 第二场 8.18
2018/08/19 DOTA
python 多进程通信模块的简单实现
2014/02/20 Python
Python中分数的相关使用教程
2015/03/30 Python
浅析Python中将单词首字母大写的capitalize()方法
2015/05/18 Python
通过Python实现自动填写调查问卷
2017/09/06 Python
Python实现购物评论文本情感分析操作【基于中文文本挖掘库snownlp】
2018/08/07 Python
Python对象与引用的介绍
2019/01/24 Python
python3多线程知识点总结
2019/09/26 Python
纪伊国屋新加坡网上书店:Kinokuniya新加坡
2017/12/29 全球购物
意大利咖啡、浓缩咖啡和浓缩咖啡机:illy caffe
2019/03/20 全球购物
Ray-Ban雷朋瑞典官方网站:全球领先的太阳眼镜品牌
2019/08/22 全球购物
通信研究生自荐信
2014/02/01 职场文书
2014年扫黄打非工作总结
2014/12/03 职场文书
学校扫黄打非工作总结
2015/10/15 职场文书
redis配置文件中常用配置详解
2021/04/14 Redis
Python必备技巧之字符数据操作详解
2022/03/23 Python
基于Python编写简易版的天天跑酷游戏的示例代码
2022/03/23 Python
ubuntu安装jupyter并设置远程访问的实现
2022/03/31 Python