如今咱们已经很熟悉Django的MTV模式了。模板(template)负责如何去展现数据,而视图(view)负责筛选出正确的数据。所以一般来讲逻辑都是放到视图中的,但模板也须要一些和表示相关的逻辑:好比循环展现(如{% for ... %}
)、或者以某种特定格式输出(如{{ ...|date:'Y-m-d' }}
)等,这些功能都是靠模板的**过滤器(filters)和标签(tags)**实现的。html
Django的模板语言包含了不少内置的过滤器和标签,设计目的是知足应用须要占位逻辑需求。但有的时候这些通用的功能知足不了你的某些需求,这时候就须要自定义过滤器和标签来实现了。python
要在Django中使用模板过滤器或标签,就首先得注册它们。git
注册方法以下:github
templatetags
的目录(方便起见,教程选择了article
这个APP)__init__.py
的空文件,使得此目录被视做一个Python的包my_filters_and_tags.py
),就能够在里面愉快的写代码啦完成后的目录结构以下:django
article/
__init__.py
views.py
models.py
# 新增目录
templatetags/
__init__.py # 空文件
my_filters_and_tags.py # 即将写代码的地方
...
复制代码
请注意:安全
前置条件就完成了,接下来咱们看看如何写一个模板过滤器。服务器
过滤器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()
复制代码
name
参数,则其值为此filter的名称;若未携带,则函数名就是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 %}
复制代码
包含标签可让另外一个模板为当前模板渲染数据。听起来比较拗口,仍是经过例子来理解。
假设如今有一个需求,是要在文章详情页面中,显示全部相关评论的发布时间。所以在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官方文档。