Django基础篇 06-Django模板过滤器Filter和标签Tags
一、Django模板的内置过滤器Filter
什么是模板(Template)? Django的Template是如何工作的
Django的模板是静态的html文件,它只决定了一个页面的样式或外观。它需要视图View传递过来的变量(Variable)或内容对象(Context object)才能被渲染成一个完整的页面。
我们先来看一个新闻博客的例子。当用户访问/blog/article/2/的时候,URL路由器会调用视图views.py的article_detail方法。article_detail所做的就是提取id=2的文章对象,通过render方法,将它传递到模板文件/blog/article_detail.html。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
# blog/urls.py from django.urls import path from . import views urlpatterns = [ path('blog/article/<int:id>/', views.article_detail, name='article_detail'), ] # blog/views.py from django.shortcuts import render, get_object_or_404 from .models import Article def article_detail(request, id): article = get_object_or_404(Article, pk=id) return render(request, 'blog/article_detail.html', {"article": article})
下图是模板文件blog/article_detail.html的代码。在模板文件里我们可以通过双括号{{ article }} 显示变量或内容对象,还可以通过点号(.)用来直接访问变量的属性。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
{% block content %}
{{ article.title }}
{{ article.pub_date }}
{{ article.body }}
{% endblock %}
模板(Template)文件的正确位置
对于html模板文件,我们建议放在app/templates/app/文件夹里,而不是简单放在app/templates/里。看似我们多加了一层文件夹使问题复杂化了,但这样做实际上更安全。这与Django查找模板文件的方法有关。因为我们多加了一层app,这样Django只会查找app文件夹里的模板文件。在views.py里我们也建议通过app/template_name.html调用template,这样会杜绝与其它同名template的冲突。
模板过滤器Filter
您可以通过使用过滤器来改变变量在模板中的显示。比如{{ article.title | lower }} 中lower过滤器可以让文章的标题转化为小写。Django的模板提供了许多内置过滤器,你可以直接使用,非常方便。
下面是一些Django模板常用过滤器。
过滤器 | 例子 |
lower, upper | {{ article.title | lower }} 大小写 |
length | {{ name | length }} 长度 |
default | {{ value | default: "0" }} 默认值 |
date | {{ picture.date | date:"Y-m-j" }} 日期格式 |
dicsort | {{ value | dicsort: "name" }} 字典排序 |
escape | {{ title | escape }} 转义 |
filesizeformat | {{ file | filesizeformat }} 文件大小 |
first, last | {{ list | first }} 首或尾 |
floatformat | {{ value | floatformat }} 浮点格式 |
get_digit | {{ value | get_digit }} 位数 |
join | {{ list | join: "," }} 字符连接 |
make_list | {{ value | make_list }} 转字符串 |
pluralize | {{ number | pluralize }} 复数 |
random | {{ list | random }} 随机 |
slice | {{ list | slice: ":2" }} 切 |
slugify | {{ title | slugify }} 转为slug |
striptags | {{ body | striptags }} 去除tags |
time | {{ value | time: "H:i" }} 时间格式 |
timesince | {{ pub_date | timesince: given_date }} |
truncatechars | {{ title | truncatechars: 10 }} |
truncatewords | {{ title | truncatewords: 2 }} |
truncatechars_html | {{ title | truncatechars_html: 2 }} |
urlencode | {{ path | urlencode }} URL转义 |
wordcount | {{ body | wordcount }} 单词字数 |
二、Django模板的自定义过滤器Filter
模板过滤器(filter)的作用
Django模板中的变量是用双括号如{{ variable }}显示的。通过使用模板过滤器(filter)来可以改变变量在模板中的显示形式,比如{{ article.title | lower }} 中lower过滤器可以让文章的标题转化为小写。Django的模板提供了许多内置过滤器,一些常用的过滤器如下。
过滤器 | 例子 |
lower | {{ article.title | lower }} 大小写 |
length | {{ name | length }} 长度 |
default | {{ value | default: "0" }} 默认值 |
date | {{ pub_date | date:"Y-m-d" }} 日期格式 |
过滤器(filter)的本质和工作原理
Django模板过滤器(filter)的本质是一个函数,过滤器的名字就是函数名。该函数接收竖线" | "前的变量值(value)和冒号" : " 后的参数(args),返回一个值, 其中args可选。比如lower过滤器接收一个字符串,将其全部转化为小写后返回。date过滤器调用strftime方法将DateTime格式的数据转化为指定格式的字符串返回。
我们自定义过滤器本质就是自定义一个名叫my_filter_name(value, args)的函数,用来处理模板中传递过来的变量和参数。在使用自定义过滤器时时我们需要遵循先载入再使用的原则。
如何自定义模板过滤器
首先你要在你的app目录下新建一个叫templatetags的文件夹(不能取其它名字), 里面必需包含__init__.py的空文件。在该目录下你还要新建一个python文件专门存放你自定义的过滤器函数,本例中为blog_extras.py,当然你也可以取其它名字。整个目录结构如下所示:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
blog/ __init__.py models.py templatetags/ __init__.py blog_extras.py views.py
在模板中使用自定义的过滤器(filter)时,需要先使用{% load blog_extras %}载入自定义的过滤器,然后通过使用{{ variable | my_filter_name }} 使用。
案例1 - 自定义过滤器显示中文格式日期
Django自带的过滤器date:"Y-m-d" 可以将日期类型的数据转化为”2018-09-01“。如果使用date:"Y年-m月-d日", 则日期将显示为"2018年-09年-01日", 这显然不是我们想要的。我们现在希望自定义一个名叫chinese_date_format的过滤器,在模板中使用{{ pub_date | chinese_date_format }}即可将博文发布日期转化为“2018年9月1日”显示。
此时我们只需要在blog_extras.py文件中添入如下代码。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
from django import template register = template.Library() @register.filter def chinese_date_format(value): return "{}年{}月{}日".format(value.year, value.month, value.day)
我们从先建立模板的register,然后使用@register.filter过滤器注册我们自定义的过滤器chinese_date_format。在模板中先使用{% load blog_extras %}载入自定义的过滤器,然后在模板中使用{{ article.pub_date | chinese_date_format }} 即可显示中文格式日期了。
但上述代码有个问题,该过滤器只有当value为datetime格式的数据时才会工作。如果用户把它用在一个字符串上则会出现AttributeError。
我们现在可以对上述代码进行改进,先增加对value格式的判断。当value格式不为datetime时,该过滤器直接返回value本身。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
from django import template import datetime register = template.Library() @register.filter def chinese_date_format(value): if isinstance(value, datetime.datetime): return "{}年{}月{}日".format(value.year, value.month, value.day) else: return value
案例2 - 自定义过滤器给标题添加"最新"和"推荐"描述
案例1中我们只使用了竖线"|"前的变量值作为参数,我们还可以通过冒号:给过滤器函数传递更多的参数。我们现在要自定义一个叫add_description的过滤器。当我们在模板中使用{{ article.title | add_description:"最新" }}时,标题后面会加上"最新"字样。当我们在模板中使用{{ article.title | add_description:"最热" }}时,标题后面会加上"最热"字样。
blog_extras.py中代码如下所示。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
from django import template register = template.Library() @register.filter def add_description(value, args): return "{} ({})".format(value, args)
三、Django模板的标签Tags
模板标签Tags
在Django模板里,变量是放在双括号{{ }}里的,而代码是放在{% tag_name %}标签里的。Django里有很多自带标签,可以满足绝大部分开发需求。
标签Tags | 例子 |
{% block %} |
{% block content %} 代码块 {% endblock %}====定义代码块 在继承的子模板中替换 |
{% csrf_token %} | {% csrf_token %} 表单专用 |
{% for %}
|
<ul> |
{% for .. empty %} |
<ul> |
{% if %} |
{% if title != "python" %} |
{% url %} |
{% url 'blog:article_detail' article.id %} |
{% now %} |
{% now "jS F Y H:i" %}====时间格式化
|
{% with %} |
{% with total=business.employees.count %} |
{% load %} |
# load tags and filters |
{% include %} |
{% include "header.html" %} ===导入模板
|
{% extends %} |
{% extends "base.html" %}===继承模板
|
模板的继承
Django支持模板的继承。你需要使用extends标签。在下面经典模板继承案例中,template.html中的content模块,会替换掉base.html中的content模块。同时template.html继承了base.html的sidebar和footer模块。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
# base.html {% block sidebar %} {% endblock %} {% block content %} {% endblock %} {% block footer %} {% endblock %} # template.html {% extends "base.html" %} {% block content %} {{ some code }} {% endblock }
模板文件base.html的位置
extends标签支持相对路径,这就意味着当文件夹结构如下所示时:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
dir1/ template.html base2.html my/ base3.html base1.html
模板template.html中以下继承方式都是可以的。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
{% extends "./base2.html" %} {% extends "../base1.html" %} {% extends "./my/base3.html" %}
模板文件加载静态文件
要在模板文件里加载静态文件如css文件和js文件,你一共有三步要走:
第一步: 先在你的app下新建一个static文件夹,然后把你需要静态文件放进去,推荐路径app/static/app/custom.css。
第二步: 在myproject/settings.py增加静态文件设置静态文件STATIC_URL。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
STATIC_URL = '/static/'
第三步:在你的模板里使用{% load static %},如下所示。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
{% load static %} <!DOCTYPE html> <html lang="en"> <head> <title>{% block title %} Django Web Applications {% endblock %} </title> <link rel="stylesheet" href="{% static 'app/custom.css' %}"> </head>
如果你需要使用的静态文件不属于某个app,而属于整个项目project,那么你还可以定义静态文件文件夹列表。假设属于整个项目的静态文件放在/var/www/static/里,那么myproject/settings.py可以加入下面一行代码。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
STATICFILES_DIRS = [ os.path.join(BASE_DIR, "static"), '/var/www/static/', ]
四、Django模板的自定义标签Tags
什么是模板标签(tags)
模板标签都是放在{% %}括号里的,常见的模板标签有{% load xxxx %}, {% block xxxx %}, {% if xxx %}, {% url 'xxxx' %}。这些模板标签的本质也是函数,标签名一般即为函数名。这些标签的主要作用包括载入代码渲染模板或对传递过来的参数进行一定的逻辑判断或计算后返回。
比如下例中的url标签接收两个参数,一是命名的url, 一个是文章id,将其进行反向解析,生成一个类似blog/article/4/的链接。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
<a href="{% url 'blog:article_detail' article.id %}">详情</a>
Django模板标签(tags)的分类
-
simple_tag (简单标签 : 处理数据,返回一个字符串或者给context设置或添加变量。
-
inclusion_tag (包含标签) : 处理数据,返回一个渲染过的模板。
熟悉Django的都知道,我们一般在视图view里设置context,然后通过它来传递数据给模板。 一个context是一系列变量和它们值的集合。通过使用simple_tag, 我们可以在视图外给context设置或添加变量。注: Django 1.9以后不再支持assignment_tag了,均使用simple_tag。
如何自定义模板标签
首先你要在你的app目录下新建一个叫templatetags的文件夹(不能取其它名字), 里面必需包含__init__.py的空文件。在该目录下你还要新建一个python文件专门存放你自定义的模板标签函数,本例中为blog_extras.py,当然你也可以取其它名字。整个目录结构如下所示:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
blog/ __init__.py models.py templatetags/ __init__.py blog_extras.py views.py
在模板中使用自定义的模板标签时,需要先使用{% load blog_extras %}载入自定义的过滤器,然后通过{% tag_name %} 使用它。
自定义模板标签的3个简单例子
我们将定义3个简单模板标签,一个返回string, 一个给模板context传递变量,一个显示渲染过的模板。我们在blog_extra.py里添加下面代码。
#blog_extra.py
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
from django import template import datetime from blog.models import Article register = template.Library() # use simple tag to show string @register.simple_tag def total_articles(): return Article.objects.filter(status='p').count() # use simple tag to set context variable @register.simple_tag def get_first_article(): return Article.objects.filter(status='p').order_by('-pub_date')[0] # show rendered template @register.inclusion_tag('blog/latest_article_list.html') def show_latest_articles(count=5): latest_articles = Article.objects.filter(status='p').order_by('-pub_date')[:count] return {'latest_articles': latest_articles, }
# latest_article_list.html
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
<ul> {% for article in latest_articles %} <li>{{ article.title }} </li> {% endfor %} </ul>
# index.html (使用我们自定义的模板标签)
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
{% extends "blog/base.html" %} {% load blog_extras %} {% block content %} <p>文章数: {% total_articles %}</p> {% show_latest_articles %} {% get_first_article as first_article %} <p>第一篇文章: </p> <p>{{ first_article.title }}</p> {% endblock %}
最后展示效果图如下所示:
一个复杂的例子: 从模板或context接收参数后返回结果
上述3个简单例子的信息传递都是单向的,更常见的情况是标签函数接收从模板或context传递过来的参数,处理后再返回字符串或渲染过的模板。
下例中show_results标签需要接收模板传递的poll这个参数,才能返回poll结果。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
{% show_results poll %}
这时我们可以这样写show_results函数。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
@register.inclusion_tag('results.html') def show_results(poll): choices = poll.choice_set.all() return {'choices': choices}
当然poll这个变量出现在模板中并不是必需的,很多时候一个对象或一个对象清单已经存在全局变量context里,我们可以使用takes_context=True来直接使用context里的变量。假设poll已经存在context里,我们上面代码可以改为:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
@register.inclusion_tag('results.html', takes_context=True) def show_results(context): choices = context['poll'].choice_set.all() return {'choices': choices}
此时模板可以简化为如下代码,不再需要poll这个参数,即可显示poll的结果。
{% show_results %}
如何处理从模板中传递过来的多个参数
{% show_results poll %}中我们自定义的标签函数只接收了从模板传递过来的一个poll参数。一个模板也可以传递多个参数,如果参数名字或数量已知,Django的tag函数是可以按位置处理传递过来的参数的。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
{% my_tag "abcd" book.title warning=message profile=user.profile %} @register.inclusion_tag('my_template.html') def my_tag(a, b, *args, **kwargs): warning = kwargs['warning'] profile = kwargs['profile'] ... return ...
但类似{% url "article_detail" article.id article.slug %}中的url标签显然要复杂得多。它可以接收未知数量的参数和未知名字的参数, 而且参数中有的带双引号,有的不带双引号。
对于这种情况Django的做法是先对标签所在的节点进行解析(parser), 把接收过来的字符串整体作为一个token,先对token进行split拆分,然后再分别处理。
我们现在来看看{% format_time %}这个标签是如何时间日期的格式化的。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
<p>Published at at {% format_time article.pub_date "%Y-%m-%d %I:%M %p" %}.</p>
自定义的format_time标签函数完整代码如下。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
from django import template register = template.Library() @register.tag(name="format_time") def do_format_time(parser, token): try: # split_contents() knows not to split quoted strings. tag_name, date_to_be_formatted, format_string = token.split_contents() except ValueError: raise template.TemplateSyntaxError( "%r tag requires exactly two arguments" % token.contents.split()[0] ) if not (format_string[0] == format_string[-1] and format_string[0] in ('"', "'")): raise template.TemplateSyntaxError( "%r tag's argument should be in quotes" % tag_name ) return FormatTimeNode(date_to_be_formatted, format_string[1:-1]) class FormatTimeNode(template.Node): def __init__(self, date_to_be_formatted, format_string): self.date_to_be_formatted = template.Variable(date_to_be_formatted) self.format_string = format_string def render(self, context): try: actual_date = self.date_to_be_formatted.resolve(context) return actual_date.strftime(self.format_string) except template.VariableDoesNotExist: return ''
我们现在来着重看下上面这段代码是如何工作的。
-
Django模板解析器扫描整个模板,碰到了format_time这个标签,把其当作一个新的节点Node,获取了format_time article.pub_date "%Y-%m-%d" 这一长串字符串作为token
-
get_format_time方法利用token自带的split_contents方法把上述字符串拆分成三部分: 标签名(tag_name), 需要格式化的日期(date)和指定格式(format), 并返回需要格式化的日期和格式交由FormatTimeNode处理。format_string[1:-1]的作用是去掉双引号。
-
FormatTimeNode这个节点类负责渲染节点,通过render方法渲染新的节点,还可以通过context给模板传递其它的变量(如下所示)。当render方法不返回一个具体的值的时候,需要返回一个空字符串。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
def render(self, context): actual_date = self.date_to_be_formatted.resolve(context) context['formatted_time'] = actual_date.strftime(self.format_string) return ''
利用parse方法连续解析
有时你还会碰到{% comment %}和{% endcomment %}这样的标签。这时你就需要利用parse方法解析真个nodelist了。这个非常复杂,以后分析Django源码时会再做讲解。
小结
本文讲解了Django模板标签(tags)的2大类(simple tag和inclusion tag)及如何自定义模板标签,重点介绍了模板如何向标签函数传递一个或多个参数的原理。希望本文对你有所帮助。
五、练习的例子:
5.1模板内置过滤器的例子:
templates/template_tag.html下:添加模板filte的使用
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>标签模板</title> </head> <body> {#截取字符串#} <p>{{ content | truncatechars:30}}</p> {#转换为小写#} <p>{{ title | lower }}</p> {#转换为大写#} <p>{{ title | upper }}</p> {#list取下标#} <p>{{ stus.0 }}</p> {#list取长度#} <p>{{ stus | length }}</p> {#list切片#} <p>{{ stus | slice:"0:2" }}</p> {#list元素拼接#} <p>{{ stus | join:"-" }}</p> {#字典取值#} <p>{{ info.money }}</p> {#字符串拼接#} <p>{{ name | add:",你好" }}</p> {#int类型相加#} <p>{{ age | add:1 }}</p> {#日期格式化#} <p>{{ cur_date | date:"Y-m-d H:i:s" }}</p> {#自定义变量#} {% with class='测试自定义变量' %} <p>{{ class }}</p> {% endwith %} </body> </html>
user/views.py下:添加视图函数
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
def test(request): import datetime content = "金三胖,sb,SB,三胖,contentcontentcontentcontentcontentcontentcontentcontent" title = "TiTle" stus = ['django','python','flask','java','jvm','redis','es','mongodb'] name = '自动化测试' info = {'money':1000000} cur_date = datetime.datetime.now() return render(request,'template_tag.html',locals())
sky/urls.py下:添加url路由
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
from django.contrib import admin from django.urls import path from user import views urlpatterns = [ path('admin/', admin.site.urls), path('index',views.index), path('',views.index), path('category/<int:id>',views.category), path('detail/<int:article_id>',views.detail), path('article/<int:article_id>',views.article), path('maomao',views.maomao), path('test',views.test) ]
效果图:
工程目录结构为:
5.2模板自定义过滤器的例子:
user应用下新增templatetags python package目录,
my_tags.py filter和tags函数:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
from django.template import Library register = Library() @register.filter def mingan(content:str): return content.replace('金三胖','进大盘') @register.filter def mingan2(content:str,s:str): return content.replace('金三胖',s) @register.simple_tag def mingan3(content:str,*args): for arg in args: content = content.replace(arg,'进大盘') return content
template_tag.html 下:
{% load my_tags %}
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
{% load my_tags %} <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>标签模板</title> </head> <body> {#截取字符串#} <p>{{ content | truncatechars:30}}</p> {#转换为小写#} <p>{{ title | lower }}</p> {#转换为大写#} <p>{{ title | upper }}</p> {#list取下标#} <p>{{ stus.0 }}</p> {#list取长度#} <p>{{ stus | length }}</p> {#list切片#} <p>{{ stus | slice:"0:2" }}</p> {#list元素拼接#} <p>{{ stus | join:"-" }}</p> {#字典取值#} <p>{{ info.money }}</p> {#字符串拼接#} <p>{{ name | add:",你好" }}</p> {#int类型相加#} <p>{{ age | add:1 }}</p> {#日期格式化#} <p>{{ cur_date | date:"Y-m-d H:i:s" }}</p> {#自定义变量#} {% with class='测试自定义变量' %} <p>{{ class }}</p> {% endwith %} {#自定义过滤器和模板tags#} <p>{{ content | mingan }}</p> <p>{{ content | mingan2:"大傻子" }}</p> <p>{% mingan3 content 'sb' 'SB' '金三胖' '三胖' %}</p> </body> </html>
效果图:
六、参照文档:
https://mp.weixin.qq.com/s?__biz=MjM5OTMyODA4Nw==&mid=2247483780&idx=1&sn=8d8e19c5d21efe986eaa62dce8f78e67&chksm=a73c61bc904be8aa62f8933ab711ce35fed85dd99be7cee5f4c622599dcdee48305c26f9b5b0&scene=21#wechat_redirect
https://mp.weixin.qq.com/s?__biz=MjM5OTMyODA4Nw==&mid=2247484039&idx=1&sn=b8eb3a88bed07320a24f3a1bd7c8f369&chksm=a73c62bf904beba97215cfd1a22891c412c715de3478980562226beeccddc0aa0e746315e1b8&scene=21#wechat_redirect
https://mp.weixin.qq.com/s?__biz=MjM5OTMyODA4Nw==&mid=2247484044&idx=1&sn=e200dca29baadfd55a80cbf10e5342d9&chksm=a73c62b4904beba2cf7290f9d5f7a72c6157c05aa2e7abaa554b7f620c7549963ecea9c04636&scene=21#wechat_redirect