模版层

模板层#

模板简介#

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

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

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

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

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

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

变量#

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

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

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

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

  • 字典查询(Dictionary lookup)

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

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

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

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

视图代码

Copy
# 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())

模板代码

Copy
<!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#

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

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

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

Copy
{% 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

Copy
# 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语句后还可以跟过滤器来进行判断

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

更复杂的表达式

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

and not or

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

csrf_token#

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

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

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

更多标签#

参考官方文档

自定义过滤器与标签#

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

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

通常有以下步骤

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

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

Copy
from django import template

register = template.Library()

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

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

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

自定义过滤器#

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

Copy
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个或不传参数, 如果需要传递多个参数, 可以传递一个字符串, 以,分隔多个参数.

Copy
@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函数来装饰我们的自定义函数.

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

Copy
@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代码片段..

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

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

模板导入与继承#

模板导入#

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

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

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

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

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

Copy
{% include 'base.html' %}

模板继承#

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

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

Copy
<!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 告诉模版引擎: 子模版可能会覆盖掉模版中的这些位置。

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

Copy
{% 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-10-20 21:05  suwanbin_thought  阅读(126)  评论(0编辑  收藏  举报