Python Falcon - Jinja2 模板



Falcon 库主要用于构建 API 和微服务。因此,默认情况下,Falcon 响应器返回 JSON 响应。但是,如果内容类型更改为 **falcon.MEDIA_HTML**,则可以渲染 HTML 输出。

使用可变数据渲染 HTML 内容非常繁琐。为此,使用 Web 模板库。许多 Python Web 框架都捆绑了特定的模板库。但 Falcon 作为一个极简主义的微框架,并没有预捆绑任何一个。

**Jinja2** 是许多 Python 框架使用的最流行的模板库之一。在本节中,我们将了解如何在 Falcon 应用程序中使用 Jinja2。Jinja2 是一种快速且对设计人员友好的模板语言,易于配置和调试。它的沙箱环境使其易于防止执行不受信任的代码,禁止潜在的不安全数据,并防止跨站点脚本攻击(称为 **XSS 攻击**)。

**Jinja2** 的另一个非常强大的功能是 **模板继承**,您可以在其中定义一个具有通用设计特征的基本模板,子模板可以覆盖该模板。

首先,使用 PIP 工具在当前 Python 环境中安装 **Jinja2**。

pip3 install jinja2

Hello World 模板

**Jinja2** 模块定义了一个 Template 类。通过读取包含 HTML 脚本(带有 .html 扩展名)的文件内容来获得 Template 对象。通过调用此 Template 对象的 **render()** 方法,可以将 HTML 响应渲染到客户端浏览器。Response 对象的 **content_type** 属性必须设置为 **falcon.MEDIA_HTML**。

让我们将以下 HTML 脚本另存为应用程序文件夹中的 **hello.py**。

<html>
   <body>
      <h2>Hello World</h2>
   </body>
</html>

示例

下面资源类中的 **on_get()** 响应器读取此文件并将其渲染为 HTML 响应。

import uvicorn
import falcon
import falcon.asgi
from jinja2 import Template
class HelloResource:
   async def on_get(self, req, resp):
      resp.status = falcon.HTTP_200
      resp.content_type = 'text/html'
      fp=open("hello.html","r")
      tempobj=Template(fp.read())
      resp.body=tempobj.render()
app = falcon.asgi.App()
hello = HelloResource()
app.add_route('/hello', hello)
if __name__ == "__main__":
   uvicorn.run("hello:app", host="0.0.0.0", port=8000, reload=True)

输出

运行上面的 Python 代码,并在浏览器中访问 **https://127.0.0.1:8000/hello** 链接。

Jinja2

模板变量

**Jinja2** 是一个服务器端模板库。网页被构建为一个模板,通过在 HTML 脚本内的适当分隔符内将 Jinja2 模板语言的各种元素作为占位符。模板引擎读取 HTML 脚本,在服务器上用上下文数据替换占位符,重新组装 HTML,并将其渲染到客户端。

**Template.render()** 函数有一个可选的上下文字典参数。此字典的关键属性成为模板变量。这有助于在网页中渲染响应器传递的数据。

示例

在以下示例中,路由 ** /hello/nm** 与资源对象注册,其中 nm 是路径参数。**on_get()** 响应器将其作为上下文传递给从网页获得的模板对象。

import uvicorn
import falcon
import falcon.asgi
from jinja2 import Template
class HelloResource:
   async def on_get(self, req, resp, nm):
      resp.status = falcon.HTTP_200
      resp.content_type = 'text/html'
      fp=open("hello.html","r")
      tempobj=Template(fp.read())
      resp.body=tempobj.render({'name':nm})
app = falcon.asgi.App()
hello = HelloResource()
app.add_route('/hello/{nm}', hello)
if __name__ == "__main__":
   uvicorn.run("hello:app", host="0.0.0.0", port=8000, reload=True)

**hello.html** 在模板变量名称中读取路径参数。它充当 HTML 脚本中的占位符。它放在 **{{** 和 **}}** 符号中,以便其值显示为 HTML 响应。

<html>
   <body>
      <h2>Hello {{ name }}</h2>
   </body>
</html>

输出

运行 Python 代码并输入 **https://127.0.0.1:8000/hello/Priya** 作为 URL。浏览器显示以下输出 -

Jinja2 Hello

Jinja2 模板中的循环

如果响应器传递任何 Python 可迭代对象,例如列表、元组或字典,则可以使用其循环构造语法在 Jinja2 模板中遍历其元素。

{% for item in collection %}
HTML block
{% endfor %}

在以下示例中,**on_get()** 响应器将 students 对象(它是 **dict** 对象列表)发送到模板 **list.html**。它依次遍历数据并将其渲染为 HTML 表格。

import falcon
import json
from waitress import serve
from jinja2 import Template
students = [
   {"id": 1, "name": "Ravi", "percent": 75.50},
   {"id": 2, "name": "Mona", "percent": 80.00},
   {"id": 3, "name": "Mathews", "percent": 65.25},
]
class StudentResource:
   def on_get(self, req, resp):
      resp.status = falcon.HTTP_OK
      resp.content_type = falcon.MEDIA_HTML
      fp=open("list.html","r")
      tempobj=Template(fp.read())
      resp.body=tempobj.render({'students':students})

**list.html** 是一个 Jinja2 模板。它接收 students 对象作为字典对象的列表,并将每个键的值放在表格的 <td>..<.td> 元素内。

<html>
<body>
<table border=1>
   <thead> <tr>
      <th>Student ID</th> <th>Student Name</th>
      <th>percentage</th>
      <th>Actions</th>
   </tr> </thead>
   <tbody>
   {% for Student in students %}
   <tr> <td>{{ Student.id }}</td> <td>{{ Student.name }}</td>
      <td>{{ Student.percent }}</td>
      <td>
         <a href="#">Edit</a>
         <a href="#">Delete</a>
      </td> </tr>
   {% endfor %}
   </tbody>
</table>
</body>
</html>

在浏览器的地址栏中访问 ** /students** 路由。学生列表将在浏览器中呈现。

Jinja2 Image

HTML 表单模板

在本节中,我们将了解 Falcon 如何从 HTML 表单读取数据。让我们将以下 HTML 脚本另存为 myform.html。我们将使用它来获取 Template 对象并渲染它。

<html>
<body>
   <form method="POST" action="https://127.0.0.1:8000/students">
   <p>Student Id: <input type="text" name="id"/> </p>
   <p>student Name: <input type="text" name="name"/> </p>
   <p>Percentage: <input type="text" name="percent"/> </p>
   <p><input type="submit"> </p>
</body>
</html>

Falcon App 对象在 Hello.py 文件中声明,该文件还具有映射到 ** /adddnew** 路由的资源类。**on_get()** 响应器读取 **myform.html** 并渲染相同的内容。将显示 HTML 表单。表单通过 POST 方法提交到 ** /students** 路由。

为了能够读取表单数据,必须将 **falcon.RequestOptions** 类的 **auto_parse_form_urlencoded** 属性设置为 True。

app = falcon.App()
app.req_options.auto_parse_form_urlencoded = True

这里,我们还从 **student.py** 导入 **StudentResource** 类。**on_get()** 响应器呈现学生列表。

当用户填写并提交表单时,将调用 **on_post()** 响应器。此方法在 **req.params** 属性中收集表单数据,这只是一个表单元素及其值的字典。然后追加 **students** 字典。

def on_post(self, req, resp):
   student=req.params
   students.append(student)

**hello.py** 的完整代码如下:

import falcon
import json
from waitress import serve
from jinja2 import Template
from student import StudentResource
class MyResource:
   def on_get(self, req, resp):
      resp.status = falcon.HTTP_200
      resp.content_type = 'text/html'
      fp=open("myform.html","r")
      tempobj=Template(fp.read())
      resp.body=tempobj.render()
app = falcon.App()
app.req_options.auto_parse_form_urlencoded = True
form = MyResource()
app.add_route('/addnew', form)
app.add_route("/students", StudentResource())
if __name__ == '__main__':
   serve(app, host='0.0.0.0', port=8000)

包含 **StudentResource** 类以及 **on_get()** 和 **on_post()** 响应器的 student.py 如下:

import falcon
import json
from waitress import serve
from jinja2 import Template
students = [
   {"id": 1, "name": "Ravi", "percent": 75.50},
   {"id": 2, "name": "Mona", "percent": 80.00},
   {"id": 3, "name": "Mathews", "percent": 65.25},
]
class StudentResource:
   def on_get(self, req, resp):
      resp.status = falcon.HTTP_OK
      resp.content_type = falcon.MEDIA_HTML
      fp=open("list.html","r")
      tempobj=Template(fp.read())
      resp.body=tempobj.render({'students':students})

   def on_post(self, req, resp):
      student = req.params
      students.append(student)
      resp.text = "Student added successfully."
      resp.status = falcon.HTTP_OK
      resp.content_type = falcon.MEDIA_JSON

从命令行运行 **hello.py**。通过输入 **http://locLhost:8000/addnew** 在浏览器中打开 HTML 表单。

Jinja2 Host

**students** 数据库字典将被追加。访问 ** /students** 路由。您会发现添加了一行新数据。

Jinja2 Example

多部分表单

为了让用户从本地文件系统中选择文件,HTML 表单的 **enctype** 属性必须设置为 multipart/form-data。Falcon 使用 **MultipartFormHandler** 处理 multipart/form-data 媒体类型,允许它遍历表单中的主体部分。

**BodyPart** 类具有以下属性:

  • **stream** - 仅适用于当前主体部分的流包装器

  • **data** - 主体部分内容字节

  • **content_type** 如果未指定,则默认为 text/plain,根据 RFC

  • **text** - 当前主体部分解码为文本字符串(仅在类型为 text/plain 时提供,否则为 None)

  • **media** - 通过媒体处理程序自动解析,方式与 req.media 相同

  • **name, filename** - 来自 Content-Disposition 标头的相关部分

  • **secure_filename** - 可以安全地用于服务器文件系统的经过清理的文件名。

以下 HTML 脚本(**index.html**)是一个多部分表单。

<html>
   <body>
      <form action="https://127.0.0.1:8000/hello" method="POST" enctype="multipart/form-data">
         <h3>Enter User name</h3>
         <p><input type='text' name='name'/></p>
         <h3>Enter address</h3>
         <p><input type='text' name='addr'/></p>
         <p><input type="file" name="file" /></p>
         <p><input type='submit' value='submit'/></p>
      </form>
   </body>
</html>

此表单由下面代码中 **HelloResource** 类的 **on_get()** 响应器呈现。表单数据提交到 **on_post()** 方法,该方法遍历各部分并发送表单数据的 JSON 响应。

import waitress
import falcon
import json
from jinja2 import Template
class HelloResource:
   def on_get(self, req, resp):
      resp.status = falcon.HTTP_200
      resp.content_type = 'text/html'
      fp=open("index.html","r")
      tempobj=Template(fp.read())
      resp.body=tempobj.render()

   def on_post(self, req, resp):
      result=[]
      for part in req.media:
         data={"name" :part.name,
            "content type":part.content_type,
            "value":part.text, "file":part.filename}
         result.append(data)
         resp.text = json.dumps(result)
         resp.status = falcon.HTTP_OK
         resp.content_type = falcon.MEDIA_JSON
app = falcon.App()
hello = HelloResource()
app.add_route('/hello', hello)
if __name__ == '__main__':
   waitress.serve(app, host='0.0.0.0', port=8000)

运行上述程序并访问 **https://127.0.0.1:8000/hello** 链接以渲染表单,如下所示:

Jinja2 User

填写数据并提交表单后,JSON 响应将在浏览器中呈现,如下所示:

[
   {
      "name": "name",
      "content type": "text/plain",
      "value": "SuyashKumar Khanna",
      "file": null
   },
   {
      "name": "addr",
      "content type": "text/plain",
      "value": "New Delhi",
      "file": null
   },
   {
      "name": "file",
      "content type": "image/png",
      "value": null,
      "file": "hello.png"
   }
]
广告