1-Django - 模板

before

Django2.2 + Python3.6.8

模板语法

Django模板中有两个特殊的模板语法:

  • {{}},两个花括号内为变量。在模板渲染中会替换成相应的变量值。
  • {%%},花括号内包含百分号,称为标签。表示逻辑相关的操作,比如说for循环,if判断。

变量名由字母数字和下划线组成。

.在模板语言中有特殊的含义,用来获取对象的属性。

模板中的语法一般用以下几种形式,比如我们在views.py中返回了如下内容:

# views.py
from django.shortcuts import render, HttpResponse
def index(request)
    l = ['zhang', 'kai']
    d = {'name': '张开'}
    class A:
        def __init__(self, name, age):
            self.name = name
            self.age = age
        def talk(self):
            return '扯淡'
    zhangkaitui = A('张开腿', 18)
    zhangkaizui = A('张开嘴', 18)
    obj = [zhangkaitui, zhangkaizui]
    return render(request, 'index.html', {'l': l, 'd': d, 'obj': obj})

再来看前端页面都能取哪些值:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="x-ua-compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Title</title>
</head>
<body>
<!-- 取对象l中的第一个元素 -->
<p>{{ l.0 }}</p>

<!-- 取字典中指定key的值 -->
<p>{{ d.name }}</p>

<!-- 取对象中的属性,因为对象被封装成列表,所以要先取0索引对应的元素对象 -->
<p>{{ obj.0.name }}</p>

<!-- 只能调用不带参数的方法 -->
<p>{{ obj.1.talk }}</p>
</body>
</html>

注意,当Django中的模板遇到.时,会按照如下顺序查找:

  1. 在字典中查询
  2. 属性或者方法
  3. 数字索引

模板文件查询规则

这一小结主要来说下模板文件,也就是render的html文件,django后台是如何查找的,它的查询顺序是怎样的。
PS:如果学完一个项目中多app的使用和路由相关的知识来看,更合适。
首先我们来看下settings.py的关于模板的配置:

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [os.path.join(BASE_DIR, 'templates')],   # 请每次创建完一个django项目之后,都检查下这个路径,确保这个路径是完备的
        'APP_DIRS': True,   # 允许django去app中寻找模板文件
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]

上面主要有两个参数是我们需要注意的:

  • DIRS:这个是我们模板文件存放的路径,一般在项目根目录下,不过每当你创建django项目之后,就来检查下这个路径,看看是否有问题。
  • APP_DIRS:当值为True时,允许django去app中寻找模板文件,也就是说,当你各个app都有自己单独的模板文件时,需要将这个参数设置为True

django在寻找模板文件时:

  1. 首先会寻找DIRS指定的模板文件,你也可以认为是公共的模板文件存放目录。
  2. 按照settings中INSTALLED_APPS中注册app的顺序去寻找模板文件,所以,并不是每个app会优先寻找自己app内部的模板文件。

如果建议,各个app内如果有自己的模板文件,那么建议在各自app内的templates目录内再创建一个目录,用来存放你的模板文件,防止出现被别的app中同名的模板文件截胡的情况出现。

过滤器

Django自带过滤器

过滤器(filter)一般用来修改变量展示的结果。

语法:

# 无参过滤器
{{ 变量|过滤器名字 }}

# 有参过滤器
{{ 变量|过滤器名字:"参数" }}

注意:过滤器中:的左右两侧没有空格!

过滤器中的常用方法

  • filesizeformat:将值对应的格式转化为人类可读的形式。
# views.py
value = 123456789
return render(request, 'index.html', {'value': value})
# index.html
<p>{{ value|filesizeformat }}</p>

展示结果是117.7 MB

  • safe:为了安全,Django会对HTML中的js等语法进行自动转义,但有些情况下我们不希望被转移,就要告诉Django这段代码是安全的,不需要转义。
# views.py
value = "<a href='#'>Go !</a>"
return render(request, 'index.html', {'value': value})
# index.html
<p>{{ value|safe }}</p>
<p>{{ value }}</p>

如果不使用safe转义,则会将a标签展示为普通的字符串。

  • date:日期格式化
# views.py
import datetime
value = datetime.datetime.now()
return render(request, 'index.html', {'value': value})
# index.html
<p>{{ value }}</p>   <!-- June 3, 2019, 5:22 p.m. -->
<p>{{ value|date:"Y-m-d H:i:s" }}</p>   <!-- 2019-06-03 17:22:31 -->
<p>{{ value|date:"Y/m/d H:i:s" }}</p>   <!-- 2019/06/03 17:22:31 -->

其他方法:

方法 描述 示例
default 如果变量后端没有传或者为空,可以给个默认值 {{value|default:"zhangkai"}}
lower 小写 {{ value|lower }}
upper 大写 {{ value|upper }}
title 标题 {{ value|title }}
ljust 左对齐 {{ value|ljust:"10" }}
rjust 右对齐 {{ value|rjust:"10" }}
center 居中 {{ value|center:"15" }}
length 返回value的长度 {{ value|length }}
slice 切片 {{ value|slice:"1:-1" }}
first 获取第一个元素 {{ value|first }}
last 获取最后一个元素 {{ value|last }}
join 字符串拼接 {{ value|join:"-" }}
truncatechars 如果字符串字符多于指定的字符数量,
那么会被截断。
截断的字符串将以可翻译的省略号序列(...)结尾。
{{ value|truncatechars:"3"}}
truncatewords 以空格做截断符号,截断单词 {{value|truncatewords:"3"}}
cut 删除指定字符,比如删除一段字符串中的空格 {{value|cut:" "}}

自定义过滤器

自定义过滤器在非前后端分离项目中有应用。

步骤:

  1. settings.py中的INSTALLED_APPS列表中,配置app,不然Django无法找到自定义过滤器。
  2. 在app中创建templatetags目录,注意,目录名只能叫做templatetags
  3. templatetags目录内,创建任意py文件,比如mytag.py,然后写你自定义的逻辑。
  4. 前端页面使用{{}}引入。

views.py

from django.shortcuts import render, HttpResponse, redirect

def books(request):
    name = '张开'
    return render(request, 'books.html', {"name": name})

mytag.py

from django import template

register = template.Library()   # 注册器,变量名必须叫做register

@register.filter    # 被装饰的foo就成了Django的过滤器
def foo(v1):
    return v1

@register.filter    # 被装饰的bar就成了Django的过滤器
def bar(v1, v2):  # 自定义过滤器最多只能有两个参数
    return v1 + v2

"""
在自定义过滤器中:
    - 第一个参数是使用过滤器时,管道符前面的值  {{ name|foo }}
    - 第二个参数是使用过滤器时,冒号后面的值  {{ name|bar:"v2" }}
"""

books.html

<!DOCTYPE html>
<html lang="en">
<head>
    {% load mytag %}   <!-- 需要load引入自定义过滤器所在的文件 -->
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<!-- 如果过滤器定义时只定义了一个形参,那么这里使用时,可以不传参,默认会将前面的值传进去,如下面的foo过滤器会自动的将name传递给形参v1 -->
{{ name|foo }}
<!-- 如果过滤器定义时定义了2个形参,那么这里使用时,可以只传一个值,如下面的bar过滤器会自动的将name传递给形参v1,bar后面的"v2"传递给形参v2-->
{{ name|bar:"v2" }}

</body>
</html>

include组件

组件是一个封装完整的功能模块,哪里需要直接include引入就可以了。

form.html

<form action="" method="post">
    <input type="text" name="username" placeholder="user">
    <input type="password" name="password" placeholder="pwd">
</form>

books.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<div>
    {% include 'form.html' %}   <!-- 引入组件 -->
</div>
</body>
</html>

tags

什么是模板标签?

模板标签是在{% %}中实现的固定写法,常见的模板标签有{% load xxxx %}{% block xxxx %}{% if xxxx %}等,通常都有{% endxxxx %}作为结束,当然有的可能没有。

模板标签本质上是函数,标签名可以理解为函数名,模板标签的作用是包括载入代码、渲染模板、简单的逻辑判断或计数。

if语句

模板中的if语句用户简单的判断,有if/elseif/elif/else几种形式:

# views.py
l2 = [1, 0, 1, 2]

后端views.py返回了上述列表。前端展示结果如下:

{% if l2 %}
<p>{{ l2.0 }}</p>
{% elif l2.1 %}
<p>{{ l2.1 }}</p>
{% elif l2.2 %}
<p>{{ l2.2 }}</p>
{% else %}
<p>l2</p>
{% endif %}

if语句支持andor==><!=<=>=not inisis not判断,注意运算符两边都要有空格。

for语句

模板中的for循环主要用于循环展示后端传过来的可迭代对象,用法如下示例:

{% for foo in list_obj %}
    <p>{{ foo }}</p>
{% empty %}
	<p>当循环对象为空或者没有这个变量时,显式empty内容</p>
{% endfor %}

{% for k,v in dict_obj.items %}  <!--也可以 dict_obj.keys   dict_obj.values-->
    <p>{{ k }} {{ v }}</p>
{% empty %}
	<p>当循环对象为空或者没有这个变量时,显式empty内容</p>
{% endfor %}

for循环中可用的参数:

Variable Description
forloop.counter 当前循环的索引值(从1开始)
forloop.counter0 当前循环的索引值(从0开始)
forloop.revcounter 当前循环的倒序索引值(到1结束)
forloop.revcounter0 当前循环的倒序索引值(到0结束)
forloop.first 当前循环是否是第一次循环(布尔值)
forloop.last 当前循环是否是最后一次循环(布尔值)
forloop.parentloop 本层循环的外层循环

来看一些示例:

{% for foo in l %}
    <p>{{ forloop.counter }} - {{ foo }}</p>
{% endfor %}

上例是一个简单的for循环。

再来看一个相对复杂的:

# views.py
l1 = ['姓名', '性别', '年龄']
l2 = [
    ['王二', '男', 18],
    ['李二', '男', 29],
    ['小红', '女', 29],
    ['小明', '男', 29],
]

后端views.py返回了上述两个列表,前端展示结果如下:

<table border="1">
    <thead>
    <tr>
        {% for foo in l1 %}
            <td>{{ foo }}</td>
        {% endfor %}
    </tr>
    </thead>
    <tbody>
    {% for bar in l2 %}
        <tr>
            {% for item in bar %}
                {% if forloop.parentloop.counter|divisibleby:"2" %} 
                    <td style="color: red">{{ item }}</td>
                {% else %}
                    <td>{{ item }}</td>
                {% endif %}
            {% endfor %}
        </tr>
    {% endfor %}
    </tbody>
</table>

上例中,divisibleby参数的意思是如果forloop.parentloop.counter的值能被整除则返回True。也即是执行这个if语句。

with语句

如果后端有一个很深的嵌套结构。那么每次在使用其中的变量(变量名也很长)的时候,就比较麻烦。这时可以使用with来为这个别名指定一个别名。

# views.py
d = {'k1': {'k2': {'k3': {'name': '张开'}}}}

后端views.py返回了上述字典。前端展示结果如下:

<!-- 写法1 -->
{% with d.k1.k2.k3.name as name %}
    <p>{{ name }}</p>
{% endwith %}
    <p>{{ name }}</p>   <!-- 别名name只能用于with中 -->

<!-- 写法2 -->
{% with name=d.k1.k2.k3.name %}
    <p>{{ name }}</p>
{% endwith %}
    <p>{{ name }}</p>   <!-- 别名name只能用于with中 -->

csrf

我们很可能会面临跨站请求被拦截的情况,那么需要在页面(form表单)中添加上:

{% csrf_token %}

这个标签同样适用于ajax的跨站请求中。

模板继承

通常,多个页面中,有些内容是一样的,比如页面底部的版本信息区。这些一般不变,但是每个页面都要有,为了避免开发中重复造轮子的,Django提供了模板继承机制,通常是定义一个基础页面,在这个页面中定义公共组件,别的页面如果有需要这些组件(块)的地方,直接继承这个页面即可,也可以针对某些组件进行修改。

模板继承语法:

  • 模板:可以直接定义各页面的公共部分。

  • 子页面:使用{% extends 'base.html' %}语法,继承指定的模板的公共部分。

首先我们在模版(base.html)中,定义好相关的组件,比如导航条和底部的版本信息区。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="x-ua-compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Title</title>
    {% block css %}  
    <style>
        .w{
            width: 100%;
            height: 30px;
            background-color: #ce8483;
            text-align: center;
        }
    </style>
    {% endblock %}
</head>
<body>
<!-- head start-->
{% block head %}
<div class="w"> 我是导行条</div>
{% endblock %}
<!-- head end-->
<!--content start-->
{% block content %}  <!-- 预留的钩子,方便子页面扩展该部分 -->
<div class="w" style="margin-top: 300px;">我是内容</div>
{% endblock %}
<!--content end-->
<!-- foot start-->
{% block foot %}
<div class="w" style="position: fixed;bottom: 0;left: 0">我是底部版本信息区</div>
{% endblock %}
<!-- foot end-->
</body>
    
    {% block js %}  
    {% endblock %}
</html>

上例为我们写好的模板组件文件base.html。每个组件都使用{% block content %}语法包裹公共组件代码。如果子页面不修改其中的内容,就意味着使用模板中的格式。除此之外,也通常预留css和js钩子。

其他页面直接继承即可,比如有一个sun.html页面:

{% extends 'base.html' %}
{% block content %}
    <h1>我继承了模版中的头部和足部组件,但重写了content组件</h1>
{% endblock %}

上例中的页面,因为头部和足部与模板一致,无需修改,但content部分需要自定制,所以,直接使用{% block content %}语法, 重新定义该部分内容。

同样的,有时候,我们除了需要继续使用模板的内容,还需要添加上自己的内容,就需要用block.super了:

{% extends 'base.html' %}
{% block content %}
	{{ bloc.super }}  
    <h1>我继承了模板中的头部和足部组件,但重写了content组件</h1>
{% endblock %}

另外,有时候可能会盒子套盒子,我们写了很多的block块,也对应的会写很多的endblock,那么会造成endblock对不上开头的block:

{% block content1 %}
	{% block content2 %}
		{% block content3 %}
		{% endblock %}
	{% endblock %}
{% endblock %}

正如上例所示,如果对不齐的话, 就会造成endblock混乱,所以,我们不仅可以在block中加上名字,也可以再endblock上加上名字:

{% block content1 %}
	{% block content2 %}
		{% block content3 %}
		{% endblock content3 %}
	{% endblock content2 %}
{% endblock content1 %}

这样,首尾呼应,比较好。

静态文件引入

参考:https://www.cnblogs.com/Neeo/articles/10835891.html

自定义标签

自定义标签常应用于非前后端分离项目中。

simple_tag

和自定义过滤器的套路一样,自定义标签的创建和使用步骤如下:

  1. settings.py中的INSTALLED_APPS列表中,配置app,不然Django无法找到自定义的simple_tag。
  2. 在app中创建templatetags目录,注意,目录名只能叫做templatetags
  3. templatetags目录内,创建任意py文件,比如mytag.py,然后写你自定义的逻辑。
  4. 前端页面引入。注意,与自定义过滤器不同的是,前端使用{%%}引入。

views.py

from django.shortcuts import render, HttpResponse, redirect

def books(request):
    name = '张开'
    return render(request, 'books.html', {"name": name})

mytag.py

from django import template

register = template.Library()  # 注册器,变量名必须叫做register

@register.simple_tag  # 被装饰的foo就成了Django的自定义标签了
def foo(v1):
    return v1

@register.simple_tag  # 被装饰的bar就成了Django的自定义标签了
def bar(v1, v2, v3):  # 自定义标签参数个数不限
    # 写你的逻辑
    return v1 + v2 + v3

books.html

<!DOCTYPE html>
<html lang="en">
<head>
    {% load mytag %}   <!-- 需要load引入自定义标签所在的文件 -->
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<div>{% foo 'v1' %}</div>
<div>{% bar 'v1' 'v2' 'v3' %}</div>   <!-- 多个参数以空格分割 -->
</body>
</html>

inclusion_tag

inclusion_tag的应用场景:通常适用于根据不同的身份,在同一套模板中,渲染不同的数据,也就是适用于动态的组件中。

和simpe tag的套路差不多,自定义标签的创建和使用步骤如下:

  1. settings.py中的INSTALLED_APPS列表中,配置app,不然Django无法找到自定义的simple_tag。
  2. 在app中创建templatetags目录,注意,目录名只能叫做templatetags
  3. templatetags目录内,创建任意py文件,比如mytag.py,然后写你自定义的逻辑。
  4. 前端页面引入。注意,前端使用{%%}引入。

templates/menu.html

<div>
    <ul>
        {% for foo in menu %}
            <li>{{ foo }}</li>
        {% endfor %}

    </ul>
</div>

mytag.py

from django import template

register = template.Library()  # 注册器,变量名必须叫做register

@register.inclusion_tag("menu.html")   # 你要将哪个组件设置为动态组件
def dynamic_inclusion_tag(v1):   # 自定义标签参数个数不限
    return {"menu": v1}   # 必须返回字典

views.py

from django.shortcuts import render, HttpResponse, redirect

def books(request):
    # menu菜单中的数据应该是从数据库中取出来的,根据各种条件动态返回
    # menu = ['后台管理', '主机管理', '数据统计']
    menu = ['主机管理', '数据统计']
    return render(request, 'books.html', {"menu": menu})

books.html

<!DOCTYPE html>
<html lang="en">
<head>
    {% load mytag %}  <!-- 需要load引入动态标签所在的文件 -->
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
{% dynamic_inclusion_tag menu %}  <!-- 多个参数以空格分割 -->
</body>
</html>

流程:

  1. 浏览器访问books路由,Django的books视图函数中render了books.html页面,并且传递menu参数。
  2. books.html中声明({% load mytag %})了动态标签的文件,然后引用了该文件中的dynamic_inclusion_tag函数,该函数接受一个参数,这个参数是上一步中传递的menu列表。
  3. dynamic_inclusion_tag函数通过装饰器声明menu.html为组件,并且传递menu列表到组件中。
  4. menu.html组件根据传过来的参数进行循环生成标签。
  5. books.html中再(隐式的)引入menu.html组件,最终返回给前端,进行展示。

注意事项

  1. Django模板语言不支持连续判断:
{% if a > b > c %}
...
{% endif %}
  1. Django模板语言中属性的优先级大于方法
# views.py
from django.shortcuts import render, HttpResponse, redirect

def books(request):
    dict_obj = {"items": ['a', 'b']}   # 会因为items导致前端报错
    return render(request, 'books.html', {"dict_obj": dict_obj})
# books.html
"""
dict_obj.items原本是字典的方法,但由于后端dict_obj中有key叫做items,
所以模板语法会优先使用这个属性,所以前端报错: Need 2 values to unpack in for loop; got 1. 
"""
{% for k, v in dict_obj.items %}  
    <p>{{ k }}----{{ v }}</p>
{% endfor %}

that's all,see also:

Django基础(16): 模板标签(tags)的介绍及如何自定义模板标签 | Django之模板 | Django官网

posted @ 2019-06-03 18:16  听雨危楼  阅读(865)  评论(0编辑  收藏  举报