Django模板语法 - 后端数据如何“传给”前端渲染? | Python 主题月

4,242 阅读12分钟

本文正在参加「Python主题月」,详情查看 活动链接

微信公众号搜索【程序媛小庄】,Rest cannot be enjoyed by lazy people.~

前言

项目中产生的数据通常都是会变化的,因此全栈项目中前端页面向用户展示的数据也应当是后端的动态的产生的数据,因此后端产生的数据如何传给前端以及前端又如何接收后端传送的数据并且渲染就是本文的一部分主题了。上述的三个问题都可以通过Django的模板语言解决,一起来看看吧~

Tips

前端接收后端数据并进行处理即模板语法共有两种分别是{{}}和{% %},django模板语法的取值是固定格式,只能采用.进行取值,既可以.索引也可以点.key

{{}}   # 变量相关,用在接收后端传递过来的数据、过滤器、自定义过滤器
{% %}   # 逻辑相关,用在标签(if判断 for循环)、自定义标签simple_tag、inclusion_tag、模版继承extend和导入include
<p>{{ d.key }}</p>	# 字典使用点key方式
<p>{{ my_str.0 }}</p>  # 字符串使用点索引的方式
<p>{{ l.0 }}</p>			# 列表使用点索引的方式
<p>{{ obj.get_class }}</p>		# 对象点属性或方法的方式

后端数据如何传给前端

首先需要知道后端向前端传递数据的方式或者有哪些数据类型django后端可以传给前端?在变量传值的时候,既支持python的基本数据类型也支持python的函数、类和对象。

基本数据类型

Python中的基本数据类型传给前端,前端都可以进行渲染,基本数据类型包括str, int, float, bool, set, dict, list, tuple,django项目中后端向HTML页面中传送数据的方式非常简单,在视图函数中返回页面的同时返回前端需要的数据即可,有两种方式可以实现,老板们可以自由选择:

字典方式传值

采用这种方式向前端传值,只将前端需要的数据传给前端页面,缺点就是太麻烦了,前端获取后端传来的变量值时就是通过字典的key来获取。

# views.py
def index(request):
    my_str = 'string'
    my_int = 11
    my_list = [1, 2, 3]
    my_tup = (1, 2, 3)
    my_dic = {'name': 'python', 'age': 18}
    my_set = {1, 3, 4}
    my_bool = True
    my_float = 11.11
    return_dic = {
        'str': my_str,
        'int': my_int,
        'list': my_list,
        'dict': my_dic,
        'tuple': my_tup,
        'set': my_set,
        'bool': my_bool,
        'float': my_float,
    }
    return render(request, 'render_html.html', return_dic)

# render_html.html
<body>
    {{ str }}
    {{ int }}
    {{ list }}
    {{ dict }}
    {{ tuple }}
    {{ set }}
    {{ bool }}
    {{ float }}
</body>

locals()

locals()这一方式可以将后端视图函数中所有的变量都传给前端页面,好处就方便缺点就是可能有些数据前端并不需要。推荐使用这一方式,非常方便,使用此种方式获取后端传来的值,直接通过原变量名取值。

# views.py
def index(request):
    my_str = 'string'
    my_int = 11
    my_list = [1, 2, 3]
    my_tup = (1, 2, 3)
    my_dic = {'name': 'python', 'age': 18}
    my_set = {1, 3, 4}
    my_bool = True
    my_float = 11.11
    return render(request, 'render_html.html', locals())

# render_html.html
<body>
    {{ my_str }}
    {{ my_int }}
    {{ my_list }}
    {{ my_tup }}
    {{ my_dic }}
    {{ my_set }}
    {{ my_bool }}
    {{ my_float }}
</body>

对象传值

模板语法也支持后端向前端传递函数、类和对象;

如果传递的是函数名,前端会将后端对应的函数自动加括号调用,得到该函数的返回值,但是模板语法不支持给函数传参;

如果传递的是类名,前端会将后端对应的类型直接实例化为一个对象并在前端页面打印该对象,打印对象时,如果对象有__str__方法,则返回该方法的返回值;

如果传递的是已经实例化后的对象,如果该对象有__call__方法即对象是否可以直接加括号调用,则优先执行__call__里面的返回值;如果没有该方法,则再直接打印该对象。

# views.py
from django.shortcuts import render

# Create your views here.
def index(request):

    def func():
        print('我是func')
        return 'func'

    class Class():
        def get_self(self):
            return 'self'

        @staticmethod
        def static():
            return 'static'

        @classmethod
        def class_method(cls):
            return cls
        # 打印类/对象是自动触发,代替对象的内存地址
        def __str__(self):
            return '我是谁?'
    obj = Class()
    return render(request,'data.html',locals())

# data.html
<p>传递函数名,就会将后端的函数自动加括号调用,得到该函数的返回值,但是模板语法不支持给函数传参数</p>
<p>传类名的时候也会自动加括号调用,即实例化</p>
<p>模版语法内部能够自动判断出当前的变量名是否可以加括号调用,如果可以就会自动执行,针对的是函数名和类名</p>
{{ func }}
<p>{{ Class }}</p>
<p>{{ obj }}</p>
<p>{{ obj.get_self }}</p>
<p>{{ obj.static }}</p>
<p>{{ obj.class_method}}</p>

过滤器filters

在Django的模板语言中,可以通过过滤器改变变量的显示,过滤器就类似于是模版语法内置的内置方法,与后端数据类型的内置方法类似,基本语法为{{数据|过滤器:参数}},其中参数是可选的,两个参数分别在管道符|前后,但是过滤器最多只能有两个参数,过滤器支持链式操作,即一个过滤器的输出可以作为另一个过滤器的输入,django中内置了60多种过滤器,这里介绍一些常用的过滤器。

<!--常用过滤器-->
<p>统计长度{{ my_str|length }}</p>
<p>默认值,如果布尔值是True,默认值就是True,否则展示冒号后面的值{{ my_bool|default:'啥也不是' }}</p>
<p>文件大小:{{ file_size|filesizeformat }}</p>
<p>日期格式化:{{ current_time|date:'Y-m-d H:i:s' }}</p>
<p>切片操作,支持步长,顾头不顾尾{{ my_list|slice:'0:2:1' }}</p>
<p>摘要操作包含三个点(比如掘金文章的摘要){{ info|truncatechars:9 }}</p>
<p>切取单词,不包含三个点,只会按照空格切分单词{{ egn|truncatewords:'9' }}</p>
<p>移除特定的字符{{ msg|cut:' ' }}</p>
<p>拼接操作{{ my_str|join:'@' }}</p>
<p>加法运算(拼接操作){{ my_int|add:12 }}</p>
<p>加法运算(拼接操作){{ my_str|add:'12' }}</p>

另外在写全栈项目的时候,前端代码不一定非要在前端页面书写,也可以在后端写好,然后传递给前端,前端则需要将后端传来的变量进行转义,就需要用到safe过滤器,该过滤器的意思就是告诉HTML文档后端传过来的标签是安全的可以进行渲染。

<!--后端代码-->
def index(request):
	h = '<h1>我是html标签</h1>'
	return render(request, 'render_html.html', locals())

<!-- - 前端转义,过滤器的意思是告诉浏览器后端写的标签是安全的,可以进行渲染-->
{{h|safe}}

同时后端也可以直接将带有标签的字符串转为前端代码,然后前端直接通过模版语法拿来用即可。

<!--后端代码-->
from django.utils.safestring import mark_safe
def index(request):
    s = '<h1>后端转义</h1>'
    res = mark_safe(s)
	return(request, 'render_html.html', locals())

<!--前端代码-->
{{res}}

标签

此标签非彼标签,这里的标签指的并不是HTML文档中的标签,而是模板语法中的一堆逻辑,这些逻辑可以用来处理后端传给前端的数据。

前端页面for循环

在前端页面使用模板语言对数据进行for循环时,有两种用法,一种是像python一样直接循环输出被for循环对象中的每个元素,另一种是通过forloop得到每个元素的信息。for循环的模板语法如下:

{% for i in variable %}
	variable非空时执行的代码
	{% empty %}
		variable为空时执行的代码
{% endfor %}

如下述代码:

# views.py
from django.shortcuts import render


# Create your views here.
def index(request):
    my_str = 'string'
    my_dic = {'name': 'python', 'age': 18}
    my_list = []
    return render(request, 'render_html.html', locals())
<!--render_html.html-->
<body>
    <p>i就是每次循环得到的元素值,而每次循环得到的forloop是每个元素的详细信息</p>
    {% for i in my_str %}
        <p>{{ i }}, {{ forloop }}</p>
    {% endfor %}
    {% for i in my_list %}
    	<p>我不会被执行</p>
    	{% empty %}
    		<p>因为my_list是空的,所以我会被执行</p>
    {% endfor %}
    <!--for循环处理字典的方法与python后端非常类似-->
    {% for foo in my_dic.keys %}
        <p>{{ foo }}</p>
    {% endfor %}

    {% for foo in my_dic.values %}
        <p>{{ foo }}</p>
    {% endfor %}

    {% for foo in my_dic.items %}
        <p>{{ foo }}</p>
    {% endfor %}
    
</body>

----------------------------------页面结果如下----------------------------------
s, {'parentloop': {}, 'counter0': 0, 'counter': 1, 'revcounter': 6, 'revcounter0': 5, 'first': True, 'last': False}

t, {'parentloop': {}, 'counter0': 1, 'counter': 2, 'revcounter': 5, 'revcounter0': 4, 'first': False, 'last': False}

r, {'parentloop': {}, 'counter0': 2, 'counter': 3, 'revcounter': 4, 'revcounter0': 3, 'first': False, 'last': False}

i, {'parentloop': {}, 'counter0': 3, 'counter': 4, 'revcounter': 3, 'revcounter0': 2, 'first': False, 'last': False}

n, {'parentloop': {}, 'counter0': 4, 'counter': 5, 'revcounter': 2, 'revcounter0': 1, 'first': False, 'last': False}

g, {'parentloop': {}, 'counter0': 5, 'counter': 6, 'revcounter': 1, 'revcounter0': 0, 'first': False, 'last': True}
......

----------------------------------结果分析-------------------------------------------
每行的第一个元素就是for循环后得到的i值;
每行后面的字典就是forloop,每个元素的详细信息,代表意思分别如下:
	counter0:从零开始计算索引,当前元素的索引值
	counter:从1开始计算索引,当前元素的索引值
	revcounter:从1开始计算索引,将索引值反过来后当前元素的索引值
	revcounter0:从0开始计算索引,将索引值反过来后当前元素的索引值
	first:表示当前元素是否是第一个元素
	last:表示当前元素是否是最后一个元素

if判断

模板语法中的if判断和python中的if判断差不多,并且支持and 、or、==、>、<、!=、<=、>=、in、not in、is、is not判断。

<body>
{% if my_bool %}
    my_bool
{% elif  False %}
    哈哈
{% else %}
    上面都不成立才执行到我
{% endif %}
</body>

with起别名

在with语法内可以通过as后面的别名快速的使用到前面非常复杂的获取数据的方式所得到的的数据,即给复杂的变量起别名,比如:

from django.shortcuts import render


# Create your views here.
def index(request):
    my_dic = {
        'name': 'python',
        'age': 18,
        'hobby': ['study', 'play']
    }

    return render(request, 'render_html.html', locals())
<body>
{% with my_dic.hobby.0 as myhobby %}
    with语法{{ myhobby }}
    直接获取{{ my_dic.hobby.0 }}
{% endwith %}
</body>

自定义过滤器&标签&inclusion_tag

自定义过滤器、自定义标签和自定义inclusion_tag在自定义的过程中有三个相同的步骤分别如下:

①在应用下创建一个名字必须叫templatetags的文件夹;

②在该文件夹内创建任意名称的py文件;

③在该py文件内必须先书写下面两行代码;

from django import template


register = template.Library()

自定义过滤器

自定义过滤器需要在templatetags目录下创建的py文件中定义过滤器,功能和内置的过滤器相同,都是接收并处理视图函数传过来的数据,如果内置过滤器不能满足需求即可使用自定义过滤器。需要注意的是自定义过滤器最多也只能接收两个参数,比如定义一个计算两个元素之和的过滤器:

# templatetags/my_filter.py
from django import template


register = template.Library()
@register.filter(name='sum')  # name=自定义过滤器的名字
# 过滤器的功能
def my_sum(v1, v2):  # 过滤器接收两个参数
    return v1 + v2

在HTML文档中使用自定义的过滤器,需要先进行加载,如下:

<body>
{% load sum %}  <!--先加载自定义过滤器-->
{{ my_int }}
{{ my_int|my_sum:666 }}  
</body>

自定义标签

同样需要在templatetags目录下创建py文件,可以和自定义过滤器使用同一个py文件,但是建议分开方便管理,前端页面调用自定义标签对数据进行处理,如果模板语法中现有的标签不满足对数据的逻辑处理就可以自定义标签对数据进行处理。比如下述自定义标签:

# templatetags/my_tag.py
from django import template

register = template.Library()
# 自定义标签,参数可以有多个
@register.simple_tag(name='my_tag')  # name=自定义标签的名字
def index(a,b,c):
    return f'{a}{b}{c}'

在HTML文档中使用自定义标签首先还是需要加载自定义标签所在的文件,使用方法如下:

<body>
{% load my_tag %}
<!--自定义标签使用语法:{% 标签名 参数1 参数2 参数3....%},标签多个参数彼此之间空格隔开-->
{% my_tag 'a' 'b' 'c' 'd' %}
</body>

自定义inclusion_tag

自定义inclusion_tag的原理稍稍复杂一点点,具体的原理如下:

首先还是在templatetags目录下创建py文件,并定义方法,在定义方法时指定将该方法返回的数据传递给哪个HTML文档比如为a.html;

在HTML文档中比如是b.html,调用该方法,并且在调用时可以给该方法传值;

自定义的方法会接收b.html传来的数据并返回结果给定义方法时指定的HTML页面a.html;

最后将渲染好的a.html页面放到调用自定义方法的b.html的位置。

嗯....我也觉得有点绕,还是画个图再解释一下吧~

image-20210709161429629

**当HTML页面中某一个地方的页面需要传参数才能动态的渲染出来并且在多个页面上都需要使用到该局部页面就可以考虑使用inclusion_tag。**具体使用方式如下:

# templatetags/my_inclusion_tag.py
from django import template

register = template.Library()


@register.inclusion_tag(filename='local.html', name='my_inclusion')  # 指定将返回的数据传递给local.html文件,如果不指定name在页面中调用inclusion_tag时写函数名即可
def test(n):
    data = ['第{}项'.format(i) for i in range(n)]
    return locals()
<!--local.html,inclusion_tag指定的页面-->
{% for i in data %}
    <li>{{ i }}</li>
{% endfor %}
<!--render_html.html,调用inclusion_tag的页面-->
<body>
{% load my_inclusion_tag %}  <!--首先需要加载inclusion_tag-->
{% my_inclusion 5 %}  <!--调用inclusion_tag,语法为{% 自定义inclusion_tag的函数名 参数%}-->
</body>

页面模板继承

不知道有没有细心的小伙伴发现其实很多网站的页面变化并不是全部都在变,而是只有局部发生变化,为了实现页面只有部分发生变化的效果可以通过模板继承实现,比如掘金的导航栏不管访问哪个页面导航栏都不会变化,这个效果就可以使用页面模板继承实现。实现模板的继承可以分为以下几步:

第一步,需要选好一个模板页面,作为其他页面继承的页面。

第二步,在继承模板页面的其他页面的HTML文档中清空所欲内容,因为这个页面已经不是整体的页面了,而是局部的一块页面,称为子页面,在子页面上需要书写下述代码;

{% extends '模板页面的文件名' %}
<!--这行代码的意思就是继承了模板页面上的所有的内容-->

第三步,在模板页面上划定可以被修改的区域,划定方式如下述代码:

<!--模板页面-->
{% block content %}  // content是可以被修改部分的名字,可以是任意的
	模板页面上可以被修改的部分
{% endblock%}

第四步,在子页面上声明想要修改模板页面上的内容:

{% block content %}  // content是可以被修改部分的名字,可以是任意的
	子页面独有的内容
{% endblock%}

另外需要补充的是一般情况下模板页面应该至少有三块可以被修改的区域,分别是:

  • css区域 放在head标签内
  • html区域
  • js区域 放在页面最下部分

因此模板页面中就需要有三个被画出来可以修改的地方,这样每一个子页面就可以有自己独有的js和css代码,但是需要注意的是,一般情况下 模版的页面上划定的区域越多,那么该模版的扩展性就越高,但是也不见划定过多的区域:

<!--模板页面-->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    {% block css %}
    <style></style>
	{% endblock %}
</head>
<body>
    <p>我是不能修改的部分</p>   
    {% block content %}
        我是可以修改的地方
    {% endblock %}
</body>
    
{% block js %}
    <script></script>
{% endblock %}
    
</html>

模板导入

模板除了继承也可以将某一个页面(局部页面)当成模块的形式导入,哪个页面需要直接导入即可,如下述代码:

语法格式:{% include '被导入页面的名字 '%}
{% include 'mu.html' %}

结语

文章首发于微信公众号程序媛小庄,同步于掘金

码字不易,转载请说明出处,走过路过的小伙伴们伸出可爱的小指头点个赞再走吧(╹▽╹)