模板层

模板层

模板简介

每一个Web框架都需要一种很便利的方法用于动态生成HTML页面, 最常见的做法是使用模板。

模板包含所需HTML页面的静态部分,以及一些特殊的模版语法,用于将动态内容插入静态部分。

简而言之, 模板层就是往HTML文件中填入动态内容的系统, 这部分内容通常可能需要计算,查询数据库等方式才可以获得.

Django自带一个称为DTL(Django Template Language )的模板语言,以及另外一种流行的Jinja2语言(需要提前安装). Django为加载和渲染模板定义了一套标准的API,与具体的后台无关。加载指的是,根据给定的模版名称找到的模板然后预处理,通常会将它编译好放在内存中。渲染则表示,使用上下文(Context)数据对模板插值并返回生成的字符串.

模板语法中最重要的就是这两种特殊符号了

  • {{ }} 插值表达式, 里面放置的是后台传递过来的上下文变量. 变量后还可以跟过滤器
  • {% %} 与逻辑相关的处理, 如标签, 导入模板文件, 加载静态文件等...

变量

以下是来自官方文档的翻译.

在Django的模板语言中按此语法使用:{{ 变量名 }}

当模版引擎遇到一个变量,它将计算这个变量,然后用结果替换掉它本身. 变量的命名包括任何字母数字以及下划线 _的组合。 变量名称中不能有空格或标点符号.

.在模板语言中有特殊的含义。当模版系统遇到点.,它将以这样的顺序查询:

  • 字典查询(Dictionary lookup)

  • 属性或方法查询(Attribute or method lookup)

  • 数字索引查询(Numeric index lookup)

    当使用的变量不存在的时候, Django会自动用''来代替.

模板语法还支持循环判断等逻辑功能, 具体使用方式看下面的例子

视图代码

# views.py
def tmp(request):
    # 测试一些基础数据类型
    integer = 100
    string = 'hello world'
    lst = [1, 2, 3, 4, 4]
    dic = {'name': 'root', 'password': '123'}
    set_ = {'a', 'b', 'c', 'd'}

    # 测试特殊对象
    def func():
        return 'func无参数'

    def func3():
        pass

    class Foo:
        def __str__(self):
            return 'Foo object'
    # 这里一般需要我们自己主动创建一个字典, 然后把使用到的所有变量丢进去
    # 当做上下文传递给模板文件, 这里图省事, 直接使用locals变量了.
    return render(request, 'tmp.html', locals())

模板代码

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<p>整数: {{ integer }}</p>
<p>字符串: {{ string }}</p>
<p>字典: {{ dic }}</p>
<p>列表: {{ lst }}</p>
<p>集合: {{ set_ }}</p>
<p>无参函数有返回值: {{ func }}</p>
<p>无参函数无返回值: {{ func3 }}</p>
<p>自定义类的实例对象: {{ Foo }}</p>
<div>
    <p>for循环列表</p>
    <ul>
        {% for l in lst %}
            <li>{{ l }}</li>
        {% endfor %}
    </ul>
    <p>for循环字典</p>
    <ol>
        {% for k, v in dic.items %}
            <li>{{ k }}: {{ v }}</li>
        {% endfor %}
    </ol>
</div>
</body>
</html>

浏览器的结果

上面的结果证明了我们几乎可以传任何类型的变量到模板中渲染, 需要注意的是调用函数或对象方法时不支持在后面传参数. 直接渲染对象是调用__str__方法, 渲染方法/函数是直接加括号调用.

过滤器

过滤器是用来修改变量的显示.

语法结构是: {{ value|filter: [参数] }}, 过滤器支持传一个参数或不传参.

|的作用和linux的管道符功能一样, 将前面的结果当做参数传递给后面的过滤器使用.

过滤器的注意点:

  • 过滤器支持“链式”操作。即一个过滤器的输出作为另一个过滤器的输入. 如{{ text|escape|linebreaks }}
  • 过滤器可以接受参数,例如:{{ sss|truncatewords:30 }},这将显示sss的前30个词。
  • 过滤器参数包含空格的话,必须用引号包裹起来。比如使用逗号和空格去连接一个列表中的元素,如:{{ list|join:', ' }}
  • |左右没有空格没有空格没有空格

常用的过滤器

default

类似字典里的get方法. 如果一个变量是false或者为空,使用给定的默认值. 否则,使用变量的值.

{{ value|default:"nothing"}} 当value为空的时候, 显示nothing

length

类似于len内置方法. 返回列表或字符串的长度.

{{ value|length }} 如果value的值是['a', 'b', 'c', 'd'], 结果就是4

filesizeformat

当需要展示一个大文件的大小的时候, 传入字节数, 格式化成我们更容易看的单位. 例如vaule的值为123456789, 返回117.7MB.

date

类似于time.strftime的格式化. 使用语法和格式化参数稍有不同.

例如 {{ now|date:'Y-m-d H:i:s' }} --> 2019-09-24 23:28:09 使用格式化的时候不需要再传递%, 分和秒的显示也稍有不同, 更详细的参数见官网date过滤器.

safe

当我们传递给模板的是HTML代码的时候, 需要指定safe过滤器才能正确的按照html标签格式解析出来, 毫无疑问这样做是为了安全, 如果我们确保渲染的的代码不包含恶意script脚本的时候, 可以指定这个过滤器.

更多过滤器

参考官方文档

标签

模版语言中的标签类似Python中的函数,功能多样,使用灵活; 可以输出内容、控制结构,甚至可以访问其他的模板标签.

标签的通用语法是 {% tag %}, 下面是常用的内置标签.

for

<ol>
    {% for k, v in dic.items %}
    <li>{{ k }}: {{ v }}</li>
    {% endfor %}
</ol>

上面是通过for循环输出字典k, v键值对. for标签是一个特殊的标签, 中间的内容是包裹在 {% for %} 和{% endfor %}中.

特殊的, 在对可迭代对象进行遍历的过程中, 我们还可能经常需要访问索引, 这就需要用到模板内置的forloop对象.

{% for l in lst %}
	<p>{{ forloop }}</p>
{% endfor %}
# 浏览器输出结果
{'parentloop': {}, 'counter0': 0, 'counter': 1, 'revcounter': 5, 'revcounter0': 4, 'first': True, 'last': False}
{'parentloop': {}, 'counter0': 1, 'counter': 2, 'revcounter': 4, 'revcounter0': 3, 'first': False, 'last': False}
...
变量 描述
forloop.counter 当前循环的索引(从1开始)
forloop.counter0 当前循环的所用(从0开始)
forloop.revcounter 当前循环的倒序索引值(从1开始)
forloop.revcounter0 当前循环的倒序索引值(从0开始)
forloop.first 当前循环是不是第一次循环(布尔值)
forloop.last 当前循环是不是最后一次循环(布尔值)
forloop.parentloop 本层循环的外层循环

for ... empty

这是一个可选参数, 当循环为空的时候, 输出里面的内容.

if ... elif ... else ... endif

if的语法与Python的if语法一模一样, 支持的布尔型操作也几乎一致, 下面的几种方式都支持.

逻辑运算符

==, !=, <, >, <=, >=, in, not in, is, is not

# in运算符
{% if "bc" in "abcdef" %}
  This appears since "bc" is a substring of "abcdef"
{% endif %}
# is运算符
{% if somevar is None %}
  This appears if somevar is None, or if somevar is not found in the context.
{% endif %}
# ==
{% if somevar == "x" %}
  This appears if variable somevar equals the string "x"
{% endif %}
# if elif else
{% if athlete_list %}
    Number of athletes: {{ athlete_list|length }}
{% elif athlete_in_locker_room_list %}
    Athletes should be out of the locker room soon!
{% else %}
    No athletes.
{% endif %}

过滤器

if语句后还可以跟过滤器来进行判断

{% if now|date:'Y-m-d' > '2018-02-03' %}
    <p>if可以添加过滤器进行判断</p>
{% endif %}

更复杂的表达式

if语句还支持更复杂的逻辑表达式, 与或非操作

and not or

{% if a == b or c == d and e %}

csrf_token

这是一个跨站请求伪造保护的标签. 在页面的任何地方写上这个标签, 就会生成一个隐藏的input标签, 如果在form表单里面, 它会一起提交到后端, post请求不提交它的话, 就会报403禁止访问错误.

语法结构是{% csrf_token %}最后在页面生成下面这个隐藏input标签.

<input type='hidden' name='csrfmiddlewaretoken' value='3ZxGWK8bsJkpXGV9WEikOP0VwjhyEoZ6qMVwaizOdFFMDoXrtJTWJUdCWBoPjeyk' />

更多标签

参考官方文档

自定义过滤器与标签

自定义过滤器与标签需要有以下的目录结构

# 目录的层级结构
app/
    __init__.py
    models.py
    templatetags/
        __init__.py
        my_tag.py
    views.py

通常有以下步骤

  1. 在app目录下创建一个templatetags包或文件夹, 文件名必须是这个
  2. 在该包下可以创建任意的py文件, 这个就是我们自定义的标签或过滤器文件.
  3. 在该文件下就可以写过滤器函数和标签函数了

自定义标签与过滤器还需要注意, 需要需要在文件开头写下面两行代码

from django import template

register = template.Library()

这样我们就可以通过register对象的filter, simpletag方法来装饰注册我们的自定义标签过滤器.

最后不要忘记需要在模板中导入我们的自定义过滤器或标签文件 {% load mytag %}

导入自定义标签过滤器之后, 我们就可以正常像使用内置标签一样使用自定义的了.

自定义过滤器

使用过滤器需要使用register的filter函数来装饰我们的自定义函数

from django import template
register = template.Library()


@register.filter(name='time_format')
def time_format(date, fmt='%Y-%m-%d'):
    # date: 一个datetime对象
    return date.strftime(fmt)

# 模板中导入就可以正常使用了
{% load mytag %}
<p>{{ t|time_format }}</p>
<p>{{ t|time_format:'%Y-%m-%d %X' }}</p>

过滤器支持传递1个或不传参数, 如果需要传递多个参数, 可以传递一个字符串, 以,分隔多个参数.

@register.filter(name='my_add')
def add(value, args):
    """过滤器不支持传递多个参数, 如果我们需要传递多个参数
    可以传递以,或其他分隔符区分的一个字符串参数
    """
    try:
        num_list = [int(n) for n in args.split(',')]
    except ValueError:
        return ''
    else:
        return value + sum(num_list)
    
# 模板
<p>{{ 10|my_add:'1,2,3,4' }}</p>

自定义标签

自定义标签需要使用register的simple_tag函数来装饰我们的自定义函数.

同样以上面的显示时间来举例子

@register.simple_tag(name='current_time')
def current_time(fmt='%Y-%m-%d'):
    return datetime.now().strftime(fmt)

# <p>{% current_time %}</p>

使用标签就需要使用使用{% %}来加载了, 此外标签内还支持传入任意的参数.

inclusion_tag

我们还可以注册inclusion_tag标签, 通过引入这个标签, 可以动态导入一个HTML代码片段..

@register.inclusion_tag('temp_list.html', name='temp_list')
def temp_list(n):
    """动态生成一个小页面, 是由n个li选项组成的列表组"""
    lst = list(range(1, n + 1))
    return {'lst': lst}
<!-- temp_list.html -->
<ul>
    {% for l in lst %}
        <li>第{{ l }}项</li>
    {% endfor %}
</ul>
{% load mytag %}
<p>inclusion tag 片段</p>
{% temp_list 5 %}

最后在模板里面导入标签, 再传入参数就可以动态的渲染出来我们需要的页面.

模板导入与继承

模板导入

模板导入的形式与inclusion_tag标签类似, 都是导入一个html页面片段. 只不过模板导入的是一个静态页面, 而inclusion_tag导入的是根据参数动态渲染的动态页面.

使用模板的导入的语法是: { % include '模板页面' %}

模板的导入语法非常简单, 先定义一个temp页面

<p>这里是temp页面</p>

然后在需要使用到这个组件的页面里直接导入即可

{% include 'base.html' %}

模板继承

模板的继承概念与Python的继承概念类似, 都是为了减少代码的重复与冗余. 当我们有许多页面的部分地方相同, 只需要修改一部分内容, 这时候就需要用到模板的继承. 下面官方文档的说明:

Django模版引擎中最强大也是最复杂的部分就是模版继承了。模版继承可以让您创建一个基本的“骨架”模版,它包含您站点中的全部元素,并且可以定义能够被子模版覆盖的 blocks 。

<!DOCTYPE html>
<html lang="en">
<head>
    <link rel="stylesheet" href="style.css">
    <title>{% block title %}My amazing site{% endblock %}</title>
</head>

<body>
    <div id="sidebar">
        {% block sidebar %}
        <ul>
            <li><a href="/">Home</a></li>
            <li><a href="/blog/">Blog</a></li>
        </ul>
        {% endblock %}
    </div>

    <div id="content">
        {% block content %}{% endblock %}
    </div>
</body>
</html>

这个模版,我们把它叫作 base.html, 它定义了一个可以用于两列排版页面的简单HTML骨架。“子模版”的工作是用它们的内容填充空的blocks。

在这个例子中, block 标签定义了三个可以被子模版内容填充的block。 block 告诉模版引擎: 子模版可能会覆盖掉模版中的这些位置。

子模版可能看起来是这样的:

{% extends "base.html" %}

{% block title %}My amazing blog{% endblock %}

{% block content %}
{% for entry in blog_entries %}
    <h2>{{ entry.title }}</h2>
    <p>{{ entry.body }}</p>
{% endfor %}
{% endblock %}

extends 标签是这里的关键。它告诉模版引擎,这个模版“继承”了另一个模版。当模版系统处理这个模版时,首先,它将定位父模版——在此例中,就是“base.html”。

那时,模版引擎将注意到 base.html 中的三个 block 标签,并用子模版中的内容来替换这些block

请注意,子模版并没有定义 sidebar block,所以系统使用了父模版中的值。父模版的 {% block %} 标签中的内容总是被用作备选内容(fallback)。

这种方式使代码得到最大程度的复用,并且使得添加内容到共享的内容区域更加简单,例如,部分范围内的导航。

总结一下, 在模板的继承中, 通常块分的越多越好, 而且一般一个页面至少有3块, css块, js块, 和content块. 这样分以后扩展性也越好.

posted @ 2019-09-25 22:20  yscl  阅读(314)  评论(0编辑  收藏  举报