整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

Jinja2:使用Python进行模板渲染的艺术

Jinja2:使用Python进行模板渲染的艺术


lask 中的 Jinja2 是什么


前言

jinja2 是 Flask 作者开发的一个模板系统,起初是仿 Django 模板的一个模板引擎,为 Flask 提供模板支持,由于其灵活,快速和安全等优点被广泛使用。当我们开发 Web 应用程序时,通常需要将数据动态地渲染到 HTML 模板中,而 Python jinja2 模版技术正是为此而生的。

要了解jinja2,那么需要先理解模板的概念。模板在 Python 的web开发中广泛使用,它能够有效的将业务逻辑和页面逻辑分开,使代码可读性增强、并且更加容易理解和维护。

模板简单来说就是一个其中包含 占位变量 表示 动态 部分的文件,模板文件在经过动态赋值后,返回给用户。

模板渲染流程


Jinja2简介

Jinja2 是一个流行的 Python 模板引擎,它可以帮助我们将数据渲染到各种格式的文档中,如 HTML、XML、Markdown 等。Jinja2 具有以下几个特性:

  • 简单而灵活的语法
  • 支持模板继承和包含
  • 支持过滤器和控制结构
  • 可以自定义全局变量

安装

使用 pip 命令可以很容易地安装 Jinja2:

pip install Jinja2

基本语法

Jinja2 的基本语法与 Python 很相似,包括变量、表达式、控制结构等。下面是一些示例:

<!-- 变量 -->
<p>Hello, {{ name }}!</p>

<!-- 表达式 -->
{% if count > 10 %}
  <p>There are too many items.</p>
{% else %}
  <p>There are {{ count }} items.</p>
{% endif %}

<!-- 循环 -->
<ul>
{% for item in items %}
  <li>{{ item }}</li>
{% endfor %}
</ul>

在这个示例中,我们使用了变量、表达式和循环结构来渲染模板。变量使用 {{ ... }} 语法,表达式使用 {% ... %} 语法,循环结构使用 {% for ... in ... %} ... {% endfor %} 语法。

模板继承和包含

模板继承

Jinja2中最强大的部分就是模板继承。模板继承允许我们创建一个基本(骨架/Base)文件,其他模版文件从该骨架文件继承,然后针对需要的地方进行修改。

Jinja2的Base文件中,利用 block 关键字表示其包含的内容可以进行修改。可以帮助我们避免重复的代码和布局。

下面是一个示例:

<!-- base.html -->
<html>
<head>
  <title>{% block title %}{% endblock %}</title>
</head>
<body>
  {% block content %}
  {% endblock %}
</body>
</html>

<!-- page.html -->
{% extends "base.html" %}

{% block title %}My Page{% endblock %}

{% block content %}
  <h1>Welcome to my page!</h1>
{% endblock %}

在这个示例中,我们定义了一个基础模板 base.html,它包含了页面的基本结构和布局。我们还定义了一个子模板 page.html,它继承了 base.html,并覆盖了其中的标题和内容块。

模板包含

除了模板继承之外,Jinja2 还支持模板包含,可以将重复的代码抽象为一个单独的模板,然后在其他模板中引用它。

这可以帮助我们避免重复的代码和布局。下面是一个示例:

<!-- header.html -->
<header>
  <h1>My Website</h1>
  <nav>
    <ul>
      <li><a href="/">Home</a></li>
      <li><a href="/about">About</a></li>
      <li><a href="/contact">Contact</a></li>
    </ul>
  </nav>
</header>

<!-- page.html -->
{% include "header.html" %}

<main>
  <h1>Welcome to my page!</h1>
  <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</p>
</main>

{% include "footer.html" %}

在这个示例中,我们定义了一个头部模板 header.html,它包含了网站的标题和导航菜单。然后我们在页面模板 page.html 中引用了头部模板和尾部模板 footer.html,以构建完整的页面。

使用模板包含可以帮助我们更好地组织代码和布局,使代码更易于维护和扩展。

过滤器和控制结构

变量可以通过 过滤器 进行修改,过滤器可以理解为是jinja2里面的内置函数字符串处理函数

常用的过滤器有:

过滤器名称

说明

safe

渲染时值不转义

capitialize

把值的首字母转换成大写,其他子母转换为小写

lower

把值转换成小写形式

upper

把值转换成大写形式

title

把值中每个单词的首字母都转换成大写

trim

把值的首尾空格去掉

striptags

渲染之前把值中所有的HTML标签都删掉

join

拼接多个值为字符串

replace

替换字符串的值

round

默认对数字进行四舍五入,也可以用参数进行控制

int

把值转换成整型

除了上述特性,Jinja2 还有其他一些有用的功能,下面介绍其中几个:

安全过滤器

在模板中渲染用户提供的数据时,安全性是一个非常重要的问题。Jinja2 提供了一个安全过滤器,可以帮助我们过滤掉一些危险的 HTML 标签和属性,以防止 XSS 攻击等安全问题。

安全过滤器示例:

{{ user_input|safe }}

循环控制结构

除了基本的循环结构之外,Jinja2 还提供了一些高级的循环控制结构,比如 break、continue 和 loop.index0 等。这些控制结构可以帮助我们更好地控制循环过程。

在for循环中,jinja2提供了一些特殊的变量,用以来获取当前的遍历状态:

变量

描述

loop.index

当前迭代的索引(从1开始)

loop.index0

当前迭代的索引(从0开始)

loop.first

是否是第一次迭代,返回bool

loop.last

是否是最后一次迭代,返回bool

loop.length

序列中的项目数量

loop.revindex

到循环结束的次数(从1开始)

loop.revindex0

到循环结束的次数(从0开始)

循环控制结构示例:

{% for item in items %}
  {% if item=='stop' %}
    {% break %}
  {% elif item=='skip' %}
    {% continue %}
  {% endif %}
  <li>{{ loop.index0 }}: {{ item }}</li>
{% endfor %}

自定义全局变量

除了在模板中定义变量之外,Jinja2 还支持在 Python 代码中定义全局变量,然后在模板中使用。这可以帮助我们更好地组织代码和数据。

自定义全局变量示例:

from jinja2 import Environment

# 定义全局变量
my_var='Hello, world!'

# 创建模板引擎
env=Environment()

# 添加全局变量
env.globals['my_var']=my_var

# 渲染模板
template=env.from_string('<p>{{ my_var }}</p>')
result=template.render()
print(result)

在这个示例中,我们定义了一个全局变量 my_var,然后将它添加到模板引擎的全局变量中。最后,我们使用模板引擎渲染了一个模板,并输出了结果。

使用Jinja2进行渲染

jinja2模块中有一个名为Enviroment的类,这个类的实例用于存储配置和全局对象,然后从文件系统或其他位置中加载模板。

基本使用方法

大多数应用都在初始化的时候创建一个Environment对象,并用它加载模板。Environment支持两种加载方式:

  • PackageLoader:包加载器
  • FileSystemLoader:文件系统加载器

PackageLoader

参数:

  • PackageLoader()的两个参数为:python包的名称,以及模板目录名称。
  • get_template():获取模板目录下的某个具体文件。
  • render():接受变量,对模板进行渲染

使用包加载器来加载文档的最简单的方式如下:

from jinja2 import PackageLoader,Environment
# 创建一个包加载器对象
env=Environment(loader=PackageLoader('项目xx包目录','templates'))
# 获取一个模板文件
template=env.get_template('bast.html')    
# 渲染
template.render(name='daxin',age=18)   

FileSystemLoader

文件系统加载器,不需要模板文件存在某个Python包下,可以直接访问系统中的文件

总结

Jinja2 是一个强大而灵活的 Python 模板引擎,它可以帮助我们将数据渲染到各种格式的文档中。它具有简单而灵活的语法、支持模板继承和包含、支持过滤器和控制结构等特性。如果你需要在 Python 中使用模板引擎,Jinja2 是一个不错的选择。

当然,除了可以在 Python Web 开发过程中使用Jinja2模版,如果大家在做自动化脚本(自动化用例)时也可以使用它,可以根据相关自动化信息(比如:用例编号、步骤、断言等)批量生成不同自动化测试用例(脚本)。总之,Jinja2 的使用场景可以用于多种需求下,今天的文章就介绍到这里了,感谢大家的阅读。

击右上方红色按钮关注“小郑搞码事”,每天都能学到知识,搞懂一个问题!

还是那句话,前端页面渲染就四种方式。只要愿意学,花点时间从理论到实践搞一遍,很容易就学会了。往后工作中天天用,进一步深入研究。

四种方式如下图所示:

前两篇我以将后端模板渲染和客户端渲染两种渲染方式的实现思路和优缺点都说过了。

今天来聊一下第三种渲染方式—node中间层。都是很好理解很容易搞懂的玩意,没接触过的同学,不要想太复杂。

三. Node中间层

上一篇我讲的客户端渲染,相对于上上篇讲的后端模板渲染来说,它的优点是前后端分离开发,后台只负责提供数据,前端能够尽量发挥自己端的构建生态优势。而缺点是由于首屏数据需要等到JS加载完成才能显示,服务端返回的是空DOM结构,所以首屏等待时间长,体验不好。

那么今天我要说的第三种页面渲染方式,即能够保留前后端分离开发的优势,又能够实现首屏数据的快速响应。

通过一张图来看一下整体思路。

为了大家更好的理解,下面,我对着这张图将整个过程也解释一遍。

1. 前端发出一个请求URL

2. node层接收到这个请求,然后根据请求信息,向后端服务器发起请求,获取数据

3. 后端服务器接收到请求,然后根据请求信息,从数据库或者其他地方获取相应的数据,返回给node层

4. node层根据这些数据渲染好首屏HTML

5. node层将HTML文本返回给前端

传统的B/S架构是:浏览器-后端服务器-浏览器

加入node层的架构是:浏览器-node-后端服务器-node-浏览器

其中node层也是由前端开发来掌控的。所以后端依然只负责向前端提供数据。

由此可见,这种方式的优点就是:

第一. 保留了后端模板渲染(node层实现),首屏快速响应,SEO友好

第二. 保留前后端分离开发模式

同时,不足的地方是:

第一. 多了一个中间层,应用的性能有所降低

第二. 增加了架构的复杂度,不稳定性,降低应用的安全性

第三. 对开发人员要求会高很多

不过不用过于担心,这些缺点,在第四种渲染方式上会得到缓解。等我下篇更新。

题:输入 URL 到页面渲染的整个流程

  1. DNS解析
  2. TCP握手
  3. TLS握手
  4. 浏览器开始解析文件
  5. 构建 DOM 树、构建 CSSOM 树、解析JS
  6. 生成 Render 树
  7. 调用 GPU 绘制,合成图层,将内容显示在屏幕上了

DNS 的作用就是通过域名查询到具体的 IP

因为 IP 存在数字和英文的组合(IPv6),很不利于人类记忆,所以就出现了域名。你可以把域名看成是某个 IP 的别名,DNS 就是去查询这个别名的真正名称是什么。

在 TCP 握手之前就已经进行了 DNS 查询,这个查询是操作系统自己做的。当你在浏览器中想访问www.google.com时,会进行一下操作:

  1. 操作系统会首先在本地缓存中查询 IP
  2. 没有的话会去系统配置的 DNS 服务器中查询
  3. 如果这时候还没有的话,会直接去 DNS 根服务器查询,这一步查询会找出负责com这个一级域名的服务器
  4. 然后去该服务器查询google这个二级域名
  5. 接下来三级域名的查询其实是我们配置的,你可以给www这个域名配置一个 IP,然后还可以给别的三级域名配置一个 IP

以上介绍的是 DNS 迭代查询,还有种是递归查询,区别就是前者是由客户端去做请求,后者是由系统配置的 DNS 服务器做请求,得到结果后将数据返回给客户端。

TCP握手

接下来是 TCP 握手,应用层会下发数据给传输层,这里 TCP 协议会指明两端的端口号,然后下发给网络层。网络层中的 IP 协议会确定 IP 地址,并且指示了数据传输中如何跳转路由器。然后包会再被封装到数据链路层的数据帧结构中,最后就是物理层面的传输了。在这一部分中,可以详细说下 TCP 的握手情况以及 TCP 的一些特性。

当 TCP 握手结束后就会进行 TLS 握手,然后就开始正式的传输数据。

TLS握手

数据在进入服务端之前,可能还会先经过负责负载均衡的服务器,它的作用就是将请求合理的分发到多台服务器上,这时假设服务端会响应一个 HTML 文件。

首先浏览器会判断状态码是什么,如果是 200 那就继续解析,如果 400 或 500 的话就会报错,如果 300 的话会进行重定向,这里会有个重定向计数器,避免过多次的重定向,超过次数也会报错。

浏览器开始解析文件

浏览器开始解析文件,如果是 gzip 格式的话会先解压一下,然后通过文件的编码格式知道该如何去解码文件。

构建 DOM 树、构建 CSSOM 树、解析JS

文件解码成功后会正式开始渲染流程,先会根据 HTML 构建 DOM 树,有 CSS 的话会去构建 CSSOM 树。如果遇到 script 标签的话,会判断是否存在 async 或者 defer ,前者会并行进行下载并执行 JS,后者会先下载文件,然后等待 HTML 解析完成后顺序执行

如果以上都没有,就会阻塞住渲染流程直到 JS 执行完毕。遇到文件下载的会去下载文件,这里如果使用 HTTP/2 协议的话会极大的提高多图的下载效率。

生成 Render 树

CSSOM 树和 DOM 树构建完成后会开始生成 Render 树,这一步就是确定页面元素的布局、样式等等诸多方面的东西

调用 GPU 绘制,合成图层,将内容显示在屏幕上了

在生成 Render 树的过程中,浏览器就开始调用 GPU 绘制,合成图层,将内容显示在屏幕上了。