FastAPI
五、jinja2模板
jinja是python知名web框架Flask的作者开发的⼀个开源的模板系统,起初是仿django模板的⼀个模板引擎DjangoTPL,为Flask提供模板支持,由于其灵活,快速和安全等优点被⼴泛使用。
jinja2是jinja2这个模块的2.0版本。
所谓的模板在Python的web开发中广泛使用,它能够有效的将业务逻辑(负责处理数据的代码)和表现逻辑(负责展示给客户端查看数据的代码,例如:print, html/css/js)分开,使代码可读性增强、并且更加容易理解和维护。
所谓的模板引擎就是提供给开发者在服务端中加载静态资源文件的技术。学习jinja2模块,可以我们在FastAPI中直接加载HTML文档内容并把服务端的数据快速的展示到网页中。
我们使用jinja2的目的是为了让FastAPI能完成一些web开发的任务,例如使用html/css/js展示数据。
官方文档:https://palletsprojects.com/p/jinja/
中文文档:https://doc.yonyoucloud.com/doc/jinja2-docs-cn/index.html
我们学习jinja的内容主要分四部分:
- 数据基本输出:输出变量
- 复合类型数据的输出:字典、元组、对象、列表等数据的成员数据
- 语句结构:判断、循环、注释等等
- 过滤器:用于对数据进行限制输出,或者格式化输出
5.1 快速使用
想要在FastAPI中使用jinja2模板引擎,我们对它进行初始化:导包、初始化模板目录,在接口函数/接口类方法中加载模板。
main.py
,代码:
import uvicorn
from fastapi import FastAPI, Request
# FastAPI中支持各种模板引擎,jinja2仅仅是FastAPI内置并推荐的一款而已,如果觉得有更好的模板引擎,是可以替换的。
from fastapi.templating import Jinja2Templates# 初始化模板,需要通过directory参数设置模板存放的路径
templates = Jinja2Templates(directory="templates")app = FastAPI()"""使用jinja2展示HTML文档"""
@app.get("/books")
async def get_book_list(request: Request):return templates.TemplateResponse("index1.html", {"request": request})if __name__ == '__main__':uvicorn.run("main:app", reload=True)
templates/index1.html
,代码:
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title><style>table{margin: 0 auto;}table th{border: 1px solid red;width: 120px;height: 40px;line-height: 40px;}h1{text-align: center;}</style>
</head>
<body><h1>图书管理系统</h1><table><tr><th>编号</th><th>标题</th><th>作者</th><th>价格</th></tr></table>
</body>
</html>
通过浏览器访问http://127.0.0.1:8000/books,效果如下:
5.1.1 快速体验
main.py
,代码:
import uvicorn
from fastapi import FastAPI, Request
# FastAPI中支持各种模板引擎,jinja2仅仅是FastAPI内置并推荐的一款而已,如果觉得有更好的模板引擎,是可以替换的。
from fastapi.templating import Jinja2Templates# 初始化模板,需要通过directory参数设置模板存放的路径
templates = Jinja2Templates(directory="templates")app = FastAPI()# 模拟来自数据库/Excel/第三方存储设备/Saas平台中的数据
db_book_list = {1: {"id": 1, "title": "图书标题1", "price": 35.50, "sale": 55.00, "banner": "uploads/1.png", "author": "娄金松1号"},2: {"id": 2, "title": "图书标题2", "price": 17.50, "sale": 28.70, "banner": "uploads/2.png", "author": "娄金松2号"},3: {"id": 3, "title": "图书标题3", "price": 45.00, "sale": 75.00, "banner": "uploads/3.png", "author": "娄金松3号"},4: {"id": 4, "title": "图书标题4", "price": 50.00, "sale": 89.50, "banner": "uploads/4.png", "author": "娄金松4号"},5: {"id": 5, "title": "图书标题5", "price": 31.50, "sale": 52.50, "banner": "uploads/5.png", "author": "娄金松5号"},6: {"id": 6, "title": "图书标题6", "price": 19.90, "sale": 40.00, "banner": "uploads/6.png", "author": "娄金松6号"},
}"""使用jinja2展示HTML文档"""
@app.get("/books")
async def get_book_list(request: Request):"""展示图书列表:param request: http请求:return:"""title = "图书管理系统v1.0"book_list = list(db_book_list.values())# locals() 获取当前作用域内部所有的变量,并返回一个字典print(locals())# return templates.TemplateResponse("index1.html", {"request": request, "title": title, "book_list": book_list})"""jinja2提供的模板响应对象TemplateResponse,会自动把参数1对应的HTML文档中,符合jinja2语法的内容通过正则替换成真实的变量数据"""html = templates.TemplateResponse("index1.html", locals())print(html.body)return htmlif __name__ == '__main__':uvicorn.run("main:app", reload=True)
templates/index1.html
,代码:
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title><style>table{margin: 0 auto;}table th{border: 1px solid red;width: 120px;height: 40px;line-height: 40px;}h1{text-align: center;}</style>
</head>
<body><h1>{{title}}</h1><table><tr><th>编号</th><th>标题</th><th>作者</th><th>价格</th></tr>{% for book in book_list %}<tr><th>{{book.id}}</th><th>{{book.title}}</th><th>{{book.author}}</th><th>{{book.price}}</th></tr>{% endfor %}</table>
</body>
</html>
浏览器访问 http://127.0.0.1:8000/books,效果如下:
5.2 基本数据输出
main.py
,代码:
import random, time
import uvicorn
from fastapi import FastAPI, Request
# FastAPI中支持各种模板引擎,jinja2仅仅是FastAPI内置并推荐的一款而已,如果觉得有更好的模板引擎,是可以替换的。
from fastapi.templating import Jinja2Templates# 初始化模板,需要通过directory参数设置模板存放的路径
templates = Jinja2Templates(directory="templates")app = FastAPI()class User(object):def __init__(self, username: str, password: str):self.username = usernameself.password = passworddef func(a,b):return a+b@app.get("/")
async def main(request: Request):"""输出数据:param request::return:"""title = "图书管理系统v1.0"num1 = 31num2 = 300.50t1 = True# 符合类型数据book_list = ["金瓶梅", "聊斋", "剪灯新话", "国色天香"]s1 = {"A", "B", "C"}# 传递对象到模板中使用user = User("xiaoming", "123456")return templates.TemplateResponse("index2.html", {"func": func, # 传递函数到模板中使用"time": time, # # 传递一个模块或者模块的成员"randint": random.randint,**locals()})if __name__ == '__main__':uvicorn.run("main:app", reload=True)
templates/index2.html
,代码:
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>{{title}}</title>
</head>
<body><h1>{{title}}</h1><p>num1={{num1}}</p><p>num2={{num2}}</p><p>t1={{t1}}</p><p>book_list={{book_list}}</p><p>s1={{s1}}</p><p>user={{user}}</p><p>func2={{func(3,5)}}</p><p>{{randint(0,1000)}}</p><p>{{time.time()}}</p>
</body>
</html>
5.3 复杂数据输出
main.py
,代码:
import random, time
import uvicorn
from fastapi import FastAPI, Request
# FastAPI中支持各种模板引擎,jinja2仅仅是FastAPI内置并推荐的一款而已,如果觉得有更好的模板引擎,是可以替换的。
from fastapi.templating import Jinja2Templates# 初始化模板,需要通过directory参数设置模板存放的路径
templates = Jinja2Templates(directory="templates")app = FastAPI()class User(object):def __init__(self, username: str, password: str):self.username = usernameself.password = passworddef func(a,b):return a+b@app.get("/")
async def main(request: Request):"""输出数据:param request::return:"""# 复合类型数据[字典、列表、元组、集合]# 复合数据[复合类型数据、对象、类、管道]book_list = ["金瓶梅", "聊斋", "剪灯新话", "国色天香"]s1 = {"A", "B", "C"}book = {"id": 2, "title": "图书标题2", "price": 17.50, "sale": 28.70, "banner": "uploads/2.png", "author": "娄金松2号"}# 传递对象到模板中使用user = User("xiaoming", "123456")# 多维数据books = {1: {"id": 1, "title": "图书标题1", "price": 35.50, "sale": 55.00, "banner": "uploads/1.png", "author": "娄金松1号"},2: {"id": 2, "title": "图书标题2", "price": 17.50, "sale": 28.70, "banner": "uploads/2.png", "author": "娄金松2号"},3: {"id": 3, "title": "图书标题3", "price": 45.00, "sale": 75.00, "banner": "uploads/3.png", "author": "娄金松3号"},4: {"id": 4, "title": "图书标题4", "price": 50.00, "sale": 89.50, "banner": "uploads/4.png", "author": "娄金松4号"},5: {"id": 5, "title": "图书标题5", "price": 31.50, "sale": 52.50, "banner": "uploads/5.png", "author": "娄金松5号"},6: {"id": 6, "title": "图书标题6", "price": 19.90, "sale": 40.00, "banner": "uploads/6.png", "author": "娄金松6号"},}return templates.TemplateResponse("index3.html", {**locals()})if __name__ == '__main__':uvicorn.run("main:app", reload=True)
templates/index3.html
,代码:
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Document</title><style>body{background-color: beige;}</style>
</head>
<body><p>book_list={{book_list}}</p><h3>使用中括号可以提取列表、字典、元组</h3><p>第一个成员:{{book_list[0]}}</p><p>最后一个成员:{{book_list[-1]}}</p><h3>也可以使用点语法获取列表、字典、元组的成员,但是不能使用负数下标</h3><p>第一个成员:{{book_list.0}}</p><hr><h3>获取集合的成员,不能使用下标,使用python原生的pop()</h3><p>{{s1.pop()}}</p><hr><p>{{book}}</p><h3>获取字典的成员,使用python原生写法或者使用点语法</h3><p>{{book.keys()}}</p><p>{{book.values()}}</p><p>{{book.items()}}</p><p>{{book['id']}} >>> {{book.id}}</p><p>{{book['title']}} >>> {{book.title}}</p><hr><h3>复杂数据结构中获取成员</h3><p>{{books.6.title}}</p><p>{{books[6]['title']}}</p>
</body>
</html>
pycharm设置jinja2模板引擎的语法高亮:File->Setings->Languages & Frmaeworks 。
5.4 过滤器
jinja2提供了一系列的过滤器函数给开发者用于在模板语法中对输出的变量的数据进行格式化调整的。变量可以通过“过滤器”进行修改(格式或数值的修改),过滤器可以理解为是jinja2⾥⾯的内置函数和字符串处理函数。
内置过滤器文档:https://doc.yonyoucloud.com/doc/jinja2-docs-cn/templates.html#builtin-filters
常用的过滤器有:
过滤器名称 | 说明 |
---|---|
capitalize | 把值的首字母转换成大写,其他字母转换为小写 |
lower | 把值转换成小写形式 |
title | 把值中每个单词的首字母都转换成大写 |
trim | 把值的首尾空格(左右空格)去掉 |
striptags | 渲染之前把值中所有的HTML标签都删掉 |
join | 拼接多个值为字符串 |
round | 默认对数字进行四舍五入,也可以用参数进行控制 |
safe | 可以让模板在渲染HTML时代码内容不会被转义成实体字符 |
truncate | 裁剪文字,用省略号代替 |
那么如何使用这些过滤器呢?只需要在变量后⾯使用管道符|
分割,多个过滤器可以链式调用,前⼀个过滤器的输出会作为后⼀个过滤器的输入。main.py
,代码:
import uvicorn
from fastapi import FastAPI, Requestfrom fastapi.templating import Jinja2Templatestemplates = Jinja2Templates(directory="templates")app = FastAPI()@app.get("/")
async def main(request: Request):title = "welcome to beijing"num1 = 3.55555567link = '<a href="https://www.baidu.com">百度</a>'link2 = '<script>alert("弹窗!!!")</script>'addr = ["北京市", "海淀区", "上庄"]return templates.TemplateResponse("index4.html", locals())if __name__ == '__main__':uvicorn.run("main:app", reload=True)
templates/index4.html
,代码:
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body><h3>使用过滤的基本语法</h3><p>{{title}}</p><p>{{title | capitalize}}</p><p>{{title | title}}</p><p>{{title | upper}}</p><p>{{title | lower}}</p><p>{{ title | truncate(11) }}</p><hr><h3>可以连续使用多个过滤器,每一个过滤的结果会作为下一个过滤器的参数</h3><p>{{ num1 }}</p><p>{{ num1 | round }}</p><p>{{ num1 | round | int}}</p><h3>过滤器的本质是jinja2提供模板使用的内置函数,所以可以直接使用参数的,但是左边的数据永远是第一个参数</h3><p>{{ "welcome to beijing, beijing 欢迎你" | replace("beijing", "shanghai") | upper }}</p><p>+{{ " 123123 "}}+</p><p>+{{ " 123123 " | trim}}+</p><h3>striptags过滤器可能存在误伤</h3><p>{{ link | striptags }}</p><p>{{ "假设x=3,y=7,z是整数,z<y并且z>x,z的值可能是?" | striptags }}</p><h3>按指定符号合并列表成员拼接成字符串</h3><p>{{ addr | join("-") }}</p><h3>最重要的一个过滤器 safe,告诉模板引擎,不要对代码进行实体转义</h3><p>{{ link | safe }}</p><p>{{ link2 }}</p>
</body>
</html>
5.5 语句结构
jinja提供了一系列以{%
开头,以%}
的语句结构(也叫模板标签),提供开发者对模板内容进行批量操作。
5.5.1 注释与表达式
main.py
,代码:
import uvicorn
from fastapi import FastAPI, Requestfrom fastapi.templating import Jinja2Templatestemplates = Jinja2Templates(directory="templates")app = FastAPI()@app.get("/")
async def main(request: Request):return templates.TemplateResponse("index5.html", locals())if __name__ == '__main__':uvicorn.run("main:app", reload=True)
templates/index5.html
,代码:
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body><h3>注释语句</h3>{# 注释语句,特殊的语句 #}{# 这里的内容,主要提供给服务端开发人员看的注释,在客户端不会显示 #}<h3>jinja2支持使用python的表达式</h3><p>{{ 1 + 3 }}</p><p>{{ 1 / 3 }}</p><p>{{ 1 > 3 }}</p>
</body>
</html>
5.5.2 分支语句
jinja2中的if语句类似与Python的if语句,它也具有单分支,多分支等多种结构,不同的是,jinja2提供的条件语句不需要使用冒号结尾,而结束控制语句则需要使用endif关键字
{% if age > 18 %}<p>成年区</p>{% else %}<p>未成年区</p>{% endif %}
5.5.3 循环语句
jinja2中的for循环用于迭代Python的数据类型,包括列表,元组和字典。在jinja2中不存在while循环。
{% for book in books %}<p>{{ book }}</p>
{% endfor %}
在一个 for 循环块中,jinja2为了方便开发者更好在循环中对数据进行操作提供了loop默认变量,你可以访问这些特殊的变量:
变量 | 描述 |
---|---|
loop.index | 当前循环迭代的次数(从 1 开始) |
loop.index0 | 当前循环迭代的次数(从 0 开始) |
loop.revindex | 到循环结束需要迭代的次数(从 1 开始) |
loop.revindex0 | 到循环结束需要迭代的次数(从 0 开始) |
loop.first | 如果是第一次迭代,为 True 。 |
loop.last | 如果是最后一次迭代,为 True 。 |
loop.length | 序列中的项目数。 |
loop.cycle | 在一串序列间期取值的辅助函数。见下面的解释。 |
main.py
,代码:
import uvicorn, random
from fastapi import FastAPI, Requestfrom fastapi.templating import Jinja2Templatestemplates = Jinja2Templates(directory="templates")app = FastAPI()@app.get("/")
async def main(request: Request):num = random.randint(0, 100)book_list = {1: {"id": 1, "title": "图书标题1", "price": 35.50, "sale": 55.1300, "banner": "uploads/1.png", "author": "娄金松1号"},2: {"id": 2, "title": "图书标题2", "price": 17.50, "sale": 28.7550, "banner": "uploads/2.png", "author": "娄金松2号"},3: {"id": 3, "title": "图书标题3", "price": 45.00, "sale": 75.0220, "banner": "uploads/3.png", "author": "娄金松3号"},14: {"id": 14, "title": "图书标题4", "price": 50.00, "sale": 89.5560, "banner": "uploads/4.png", "author": "娄金松4号"},15: {"id": 15, "title": "图书标题5", "price": 31.50, "sale": 52.550, "banner": "uploads/5.png", "author": "娄金松5号"},16: {"id": 16, "title": "图书标题6", "price": 19.90, "sale": 40.500, "banner": "uploads/6.png", "author": "娄金松6号"},}return templates.TemplateResponse("index6.html", locals())if __name__ == '__main__':uvicorn.run("main:app", reload=True)
templates/index6.html
,代码:
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title><style>table{border-collapse: collapse;}th, td{border: 1px solid red;width: 160px;height: 32px;line-height: 32px;text-align: center;}</style>
</head>
<body><h3>分支语句</h3><div>成绩:{{ num }}</div>{% if num <60 %}<div>不及格</div>{% elif num < 80 %}<div>良好</div>{% else %}<div>优秀</div>{% endif %}<h3>循环语句</h3><table><tr><th>编号[从0开始]</th><th>编号[从1开始]</th><th>编号[倒数排列]</th><th>ID</th><th>标题</th><th>价格</th><th>作者</th></tr>{% for key, book in book_list.items() %}{% if loop.first %}<tr style="background-color: red; color: white">{% else %}<tr>{% endif %}<td>{{ loop.index0 }}</td><td>{{ loop.index }}</td><td>{{ loop.revindex }}</td><td>{{ book.id }}</td><td>{{ book.title }}</td><td>{{ "%.2f" | format(book.price) }}</td><td>{{ book.author }}</td></tr>{% else %}<tr><td colspan="4">没有数据</td></tr>{% endfor %}</table>
</body>
</html>
5.5.4 包含语句
在开发前后端不分离项目时,一个web项目往往存在很多的网页文档。当存在很多网页在同一个项目时,肯定也就出现多个网页文档的部分内容是重复的(外观样式一模一样),针对这种情况,开发者可以使用jinja2模板引擎提供的模板分离技术或者模板继承技术来解决重复的外观代码问题。
5.5.4.1 模板分离技术
jinja2提供了{% include "模板文件名.html" %}
,允许我们加载外部的其他模板到当前模板中。
main.py
,代码:
import uvicorn, random
from fastapi import FastAPI, Requestfrom fastapi.templating import Jinja2Templatestemplates = Jinja2Templates(directory="templates")app = FastAPI()@app.get("/")
async def main(request: Request):"""站点首页"""return templates.TemplateResponse("main1.html", locals())@app.get("/user")
async def user(request: Request):return templates.TemplateResponse("user1.html", locals())if __name__ == '__main__':uvicorn.run("main:app", reload=True)
templates/main1.html
,代码:
{% include "header.html" %}<h1>站点首页 - main.html</h1>
{% include "footer.html" %}
templates/header.html
,代码:
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>站点首页</title><style>.header{width: 100%;height: 100px;background-color: orange;}.footer{width: 100%;height: 60px;background-color: #666;color: #fff;}</style>
</head>
<body><div class="header">头部</div>
templates/footer.html
,代码:
<div class="footer">脚部</div>
</body>
</html>
5.6 请求静态文件
main.py
,代码:
from fastapi.staticfiles import StaticFiles
from fastapi import FastAPI
....
app = FastAPI()
# 访问路径,必须以斜杠开头
# app.mount("访问路径",StaticFiles(directory="目录名"))
app.mount("/static",StaticFiles(directory="static"))....