59、Django模板层

Django框架 / 2021-02-22

一、模板层简述

前面的学习中,我们知道urls处理客户端的请求,将其转给对应的views来处理。如果views给客户端的响应是一个模板文件,那么就会去templates文件夹下面找HTML文件,这个templates目录就是Django的模板层。

在模板文件(HTML)中也是有语法的,比如最常见的两种特殊符号:{{}} {% %};变量相关用{{}},逻辑相关如for循环、if判断用{% %}。

二、模板语法传值

传值传值,它能传什么数据?

Python基本数据类型都支持,str int 列表 字典等等。也支持函数名和类名,但是二者会被自动加括号调用,且不支持参数,如有参数则不再执行,相当于摆设。因为模板语法内部能够自动判断出当前的变量名是否可以加括号调用,行则自动执行,这针对的是函数名和类名,连对象属性方法都能调用,就算是隐藏的方法也会调用,因为对象被展示到HTML页面上,就类似于打印操作也会触发__str__方法。

三、模板语法之取值

模板语法中变量名取值只能采用句点符,即只能用点号,万物皆可点。比如列表取值可以点加索引号,字典也可以点key,也可以混用。

views传递变量到前端模板文件

def index(request):
    sanxi = [111, 555, 777]
    return render(request, 'index.html', locals())

模板文件取值

<body>
    <h1 class="text-center">index页面</h1>
    {{ sanxi.1 }}
	{# 浏览器展示结果即555 #}
</body>

四、模板语法之过滤器

过滤器,就类似于模板语法的内置方法,用来把变量值加以修饰后再显示。Django内置六十多个方法,我们了解其中一些常用的即可。

4.1、基本语法

这管道符号熟悉吧,其实就是把这个变量传给后面的过滤器进行处理,如果需要参数就在过滤器后面加冒号,然后跟上参数即可。跟Python内置方法处理变量一模一样,比如{{sanxi|length}}len(sanxi)是一样的效果。

{{变量|过滤器:参数}}

4.2、常用过滤器

其实这些过滤器也是暗合Python变量名命名规范的,即见名知意,你看它名称就大概知道用处了。

sanxi = [111, 555, 777]
length

顾名思义,统计长度用。

{{ sanxi|length }}
default

变量值是False或者为空、None,则使用default后指定的默认值,否则,使用变量本身的值,如果value=’‘则输出“nothing”。

{{ sanxi|default:'[666,]' }}
filesizeformat

将数字格式化为KB MB GB TB等单位,依据是值的位数有多少位。

# 假设sanxi = 111144465454
{{ sanxi|filesizeformat }}
# HTML页面输出结果:103.5 GB
date

格式化输出日期时间

# 假设sanxi = datetime.datetime.now()
{{ sanxi|date:'Y-m-d H:m:s' }}
# 页面输出结果:2021-02-19 22:02:22
slice

切片,支持步长。

# sanxi = 'hello,world!'
{{ sanxi|slice:'0:6:2' }}
# 输出结果:hlo
truncatechars

切取字符,后三个永远是三个点号,不够三位则仅显示点号

# sanxi = 'hello,world!'
{{ sanxi|truncatechars:'6' }}
# 输出结果:hel...
trunkcatewords

按照空格来切取单词,还是会带三个点号,但是这三个点号不在切取数量范围。

# sanxi = 'hi chrystal how are you'
{{ sanxi|truncatewords:'3' }}
# 输出结果:hi chrystal how ...
cut

移除特定的字符,原生strip只能移除两侧空白字符。

# sanxi = 'hi chrystal how are you'
{{ sanxi|cut:' ' }}
# 输出结果:hichrystalhowareyou
join

依照指定字符拼接

# sanxi = ['andy', 'chrystal']
{{ sanxi|join:'-' }}
# 输出结果:andy-chrystal
add

拼接变量

# man = ['andy', ]
# woman = ['chrystal']
{{ man|add:woman }}
# 输出结果:['andy', 'chrystal']

拼接加法运算

# man = 6
# woman = 5
{{ man|add:woman }}
# 输出结果:11
safe(重点)
前端转义

取消转义,默认是关闭的,因为你的代码如果有漏洞是死循环之类,这一取消转义麻烦就大了;但同时,它也能带来无限的可能性。

# title = '<h1>其实我是标题1</h1>'
{{ title|safe }}  # 如果不取消转义,那么下面的结果就是普通字符,取消转义后识别到它是HTML语法所以变成显示标题1
# 其实我是标题1
后端转义

后端转义的意义在于以后开发项目时,前端代码不一定非得在前端页面书写;也可以类似这样,先在后端写好,然后取消转义再传递给前端页面。

from django.utils.safestring import mark_safe  # 需要先导入此模块后端才能使用取消转义
# Create your views here.


def index(request):
    title = mark_safe('<h1>其实我是标题1</h1>')
    return render(request, 'index.html', locals())

五、模板语法之标签

Django的模板系统对标签的解释是在页面渲染的过程中提供相应的逻辑,比如Python 语言中 if判断、with、以及for循环等,这些在 Django 的模板系统中都有对应的标签,就是写起来复杂些。

5.1、for循环

Django的for循环也是用的大括号

5.1.1、for循环用法

后端views先传递名称空间

from django.shortcuts import render, HttpResponse, redirect, reverse
# Create your views here.


def index(request):
    sanxi = ['sanxi', 'andy', 'chrystal', 'teac', 'hsuhsin', 'kristal']
    return render(request, 'index.html', locals())

前端开始渲染

{% for foo in sanxi %}
    <p>{{ forloop }}</p>
    <p>{{ foo }}</p>
    {{ empty }}
        <p>there is null</p>
{% endfor %}
5.1.2、参数说明
forloop

forloop参数收录了for循环的循环序号

forloop.counter当前循环的索引值(从1开始)
forloop.counter0当前循环的倒序索引值(从1开始)
forloop.revcounter当前循环的索引值(从0开始)
forloop.revcounter0当前循环的倒序索引值(从0开始)
forloop.first当前循环是第一次循环则返回True,否则返回False
forloop.last当前循环是最后一次循环则返回True,否则返回False
forloop.parentloop本层循环的外层循环
empty

即被循环的变量名为空或找不到时,则执行empty的子代码

5.2、if判断

同样是用大括号,同样支持:and 、or、==、>、<、!=、<=、>=、in、not in、is、is not等。

5.2.1、if用法

if语句没什么特别要讲的,用pycharm等IDE进行语法补全写起来就没那么麻烦了。

{% if sanxi.0 == 'sanxi' %}
    <p>sanxi666</p>
{% elif sanxi.2 == 'chrystal' %}
    <p>pretty</p>
{% else %}
    <p>imposible</p>
{% endif %}

5.3、with

with用来给变量名起别名的,尤其是该变量名特别长又或者它的值来自数据库,起别名后就不用每次都去数据库查询数据了

5.3.1、with用法

在with语法内就可以通过as后面的别名快速使用前面非常复杂获取数据的方式,当然你也可以使用with haha=sanxi.0.sanxi.1这种方式,不过我觉得还是as看起来更加直观。

# sanxi = [{'sanxi': [111, 222, 333]}, 'andy', 'chrystal', 'teac', 'hsuhsin', 'kristal']
{% with sanxi.0.sanxi.1 as haha %}  # 比如我要拿222,每次都点太麻烦。
    <p>{{ haha }}</p>
{% endwith %}

5.4、csrf_token

当用form表单提交POST请求时必须加上标签{% csrf_token %},该标签用于防止跨站伪造请求。

5.4.1、CSRF用法
<form action="" method="post" enctype="multipart/form-data">
    {% csrf_token %}
    用户名:<input type="text" name="username"  value="{{ edit_obj.username }}" class="form-control">
    密码:<input type="password" name="password" value="{{ edit_obj.password }}" class="form-control">
    <input type="submit" value="提交" class="btn btn-info btn-block">
    <input type="file" name="filename" id="">
</form>
5.4.2、标签说明
  1. 在GET请求到达form表单时,标签{% csrf_token %}会被渲染成一个隐藏的input标签,该标签包含了由服务端生成的一串随机字符串,如
  2. 在使用form表单提交POST请求时,会提交上述随机字符串,服务端在接收到该POST请求时会对比该随机字符串,对比成功则处理该POST请求,否则拒绝,以此来确定客户端的身份。

六、自定义过滤器标签

自定义过滤器标签以及inclusion_tag分三步走

  1. 必须在APP下创建名为templatetags文件夹。
  2. 在文件夹内创建任意名称的py文件,一般叫mytag.py或者XXXtag.py
  3. 在该py文件内必须先书写下面两句话
from django import template
register = template.library()

6.1、自定义过滤器

跟自定义函数一样,当Django内置方法无法满足需求时,得自己自定义过滤器,自定义过滤器其实就是自定义函数,只是自定义过滤器最多只能有两个参数。

6.1.1、有名过滤器

即给自定义的过滤器命名

from django import template

register = template.Library()

@register.filter(name='cut')
def cut(variable):
    # empty variable
    return variable.replace('')
6.1.2、无名过滤器

若不自定义过滤器的名字,则默认函数名为过滤器名,此时参数只有一个。

from django import template

register = template.Library()

@register.filter()
def lower(variable):
    return variable.lower()

6.2、自定义标签

不像自定义过滤器参数最多是两个,自定义标签的参数可以有多个,前端使用时参数之间彼此用空格隔开。

from django import template


register = template.Library()


@register.simple_tag
def my_simple_tag(var1, var2, var3):
    return var1 * var2 + var3

6.3、自定义inclusion_tag

我们知道页面中可以通过include引入其它模板,但是它内容是固定的,如果想动态控制被引入模板的内容,需要自定义inclusion标签。

先定义一个方法,接着在页面上调用该方法,并且可以传值;该方法会生成一些数据然后传递给一个HTML页面,之后将渲染好的结果放到调用的位置,一般这个页面是局部而不是完整的。

6.3.1、使用演示

先定义标签方法

from django import template


register = template.Library()


@register.inclusion_tag('login.html')  # 此处放的是要引入的模板文件
def get_info(user, age):
    info = {'user': user, 'age': age}
    return locals()

被导入的login代码

<form action="" method="post" enctype="multipart/form-data">
    {% csrf_token %}
    用户名:<input type="text" name="username"  value="{{ user }}" class="form-control">
    年龄:<input type="text" name="password" value="{{ age }}" class="form-control">
    <input type="submit" value="提交" class="btn btn-info btn-block">
    <input type="file" name="filename" id="">
</form>

主页面index代码

<body>
    <h1 class="text-center">index页面</h1>
    {% load study_tag %}
    <p>{% get_info 'sanxi' 18 %}</p>
</body>

这个功能在我们后面学到BBS时就会用到。

七、模板的继承

我们浏览网站时会发现,有些网站大格局是固定,只有部分内容是动态变化的;比如导航条和左侧栏不变,右侧内容一直在变。那么它极有可能是运用了模板继承的手段来实现,下面我们也来尝试一下。

7.1、演示示例

HTML代码

效果来自bootstrap框架的支持,由于我是在笔记本上演示,所以导航条宽度问题不能水平方向展示!

请注意看代码里面我写的注释

<body>
<nav class="navbar navbar-inverse">
  <div class="container-fluid">
    <!-- Brand and toggle get grouped for better mobile display -->
    <div class="navbar-header">
      <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1" aria-expanded="false">
        <span class="sr-only">Toggle navigation</span>
        <span class="icon-bar"></span>
        <span class="icon-bar"></span>
        <span class="icon-bar"></span>
      </button>
      <a class="navbar-brand" href="#">Brand</a>
    </div>

    <!-- Collect the nav links, forms, and other content for toggling -->
    <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
      <ul class="nav navbar-nav">
        <li class="active"><a href="#">Link <span class="sr-only">(current)</span></a></li>
        <li><a href="#">Link</a></li>
        <li class="dropdown">
          <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">Dropdown <span class="caret"></span></a>
          <ul class="dropdown-menu">
            <li><a href="#">Action</a></li>
            <li><a href="#">Another action</a></li>
            <li><a href="#">Something else here</a></li>
            <li role="separator" class="divider"></li>
            <li><a href="#">Separated link</a></li>
            <li role="separator" class="divider"></li>
            <li><a href="#">One more separated link</a></li>
          </ul>
        </li>
      </ul>
      <form class="navbar-form navbar-left">
        <div class="form-group">
          <input type="text" class="form-control" placeholder="Search">
        </div>
        <button type="submit" class="btn btn-default">Submit</button>
      </form>
      <ul class="nav navbar-nav navbar-right">
        <li><a href="#">Link</a></li>
        <li class="dropdown">
          <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">Dropdown <span class="caret"></span></a>
          <ul class="dropdown-menu">
            <li><a href="#">Action</a></li>
            <li><a href="#">Another action</a></li>
            <li><a href="#">Something else here</a></li>
            <li role="separator" class="divider"></li>
            <li><a href="#">Separated link</a></li>
          </ul>
        </li>
      </ul>
    </div><!-- /.navbar-collapse -->
  </div><!-- /.container-fluid -->
</nav>  {# 导航条 #}
<div class="container-fluid">
    <div class="row">
        <div class="col-lg-3">
            <div class="list-group">  {# 左侧栏 #}
              <a href="#" class="list-group-item active">
                游戏论坛
              </a>
              <a href="/study/taking/" class="list-group-item">游戏讨论</a>  {# 点击跳转至指定页面 #}
              <a href="/study/introduction/" class="list-group-item">游戏攻略</a>
              <a href="/study/help/" class="list-group-item">新人求助</a>
              <a href="#" class="list-group-item">灌水区</a>
            </div>
        </div>
        <div class="col-lg-9">
            <div class="panel panel-primary">  {# 面板区域 #}
              <div class="panel-heading">
                <h3 class="panel-title">Panel title</h3>
              </div>
              <div class="panel-body">  
                  {% block content %}  {# 这这块区域设置为模板给别的地方继承 #}
                      <div class="jumbotron">
                          <h1>Hello, world!</h1>
                          <p>...</p>
                          <p><a class="btn btn-primary btn-lg" href="#" role="button">Learn more</a></p>
                      </div>
                  {% endblock %}
              </div>
            </div>
        </div>
    </div>
</div>
</body>
urls代码
from django.conf.urls import url
from study import views


urlpatterns = [
    url(r'^index/', views.index),
    url(r'^taking/', views.taking),
    url(r'^introduction/', views.introduction),
    url(r'^help/', views.ask_for_help),
]
views代码
from django.shortcuts import render, HttpResponse, redirect, reverse
# Create your views here.


def index(request):
    return render(request, 'index.html', locals())


def taking(request):
    return render(request, 'taking.html')


def introduction(request):
    return render(request, 'introduction.html')


def ask_for_help(request):
    return render(request, 'help.html')
模板继承

给每一个需要继承的页面添加入如下一行代码即可,即taking, introduction, help页面只需要以下一行代码即可搞定。

{% extends 'index.html' %}  {# 此处导入的是整个页面 #}


{% block content %}  {# 依照 #}
    我是taking页面
{% endblock %}

7.2、报错解决

在访问站点时,后端可能会报错"GET /static/js/bootstrap.min.js.map HTTP/1.1" 404,此时只需要去bootstrap.min.js里面注释掉最后一行/*# sourceMappingURL=bootstrap.min.css.map */

bootstrap.min.css报错同上处理。

7.3、模板继承总结

提前规划

提前用block划定可以被继承的区域,子页面如果需要修改这块区域的内容,也是同样的操作。

{% block content %}
    我是taking页面
{% endblock %}
继承模板

假如规划好了可以继承的模板,那么只需要在指定页面加入以下代码即可继承该模板。

{% extends 'index.html' %}
继承数量

一般来说,模板页面至少应该有三块可被继承修改的区域;比如:css、js、HTML;能够继承的区域越多,代表扩展性越强,但如果划定得太多区域,还不如直接重写页面。

八、模板导入

将页面的局部当成模块的形式导入,跟继承也很像,就一行代码

{% include 'index.html' %}
世间微尘里 独爱茶酒中