Python Falcon - Jinja2 模板

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

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

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

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

首先,使用 PIP 实用程序在当前 Python 环境中安装 jinja2

pip3 install jinja2

Hello World 模板

jinja2 模块定义了一个 Template 类。 Template 对象是通过读取包含 HTML 脚本(扩展名为 .html 的文件)的文件内容获得的。 通过调用此 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 代码并在浏览器中访问 http://localhost:8000/hello 链接。

Jinja2

模板变量

jinja2 是一个服务器端模板库。 通过将 jinja2 模板语言的各种元素作为占位符放在 HTML 脚本中的适当分隔符内,将网页构建为模板。 模板引擎读取 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 代码并输入 http://localhost:8000/hello/Priya 作为 URL。 浏览器显示以下输出 −

Jinja2 Hello

在 jinja2 模板中循环

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

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

在下面的示例中,on_get() 响应程序将作为 dict 对象列表的 students 学生对象发送到模板 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 模板。 它接收学生对象作为字典对象列表,并将每个键的值放入表的 <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。 我们将使用它来获取模板对象并渲染它。

<html>
<body>
   <form method="POST" action="http://localhost: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 主机

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

Jinja2 示例

multipart 表单

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

BodyPart 类具有以下属性 −

  • stream − 仅针对当前主体部分的流包装器

  • data − 正文部分内容字节

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

  • text − 当前正文部分被解码为文本字符串(仅当它是 text/plain 类型时,否则为 None)

  • media − 由媒体处理程序以与 req.media 相同的方式自动解析

  • name, filename − Content-Disposition 标头中的相关部分

  • secure_filename − 可以在服务器文件系统上安全使用的经过清理的文件名。

以下 HTML 脚本 (index.html) 是一个 multipart 表单。

<html>
   <body>
      <form action="http://localhost: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)

运行上面的程序,访问http://localhost:8000/hello链接,呈现如下所示的表单 −

Jinja2 用户

填完数据提交表单时,浏览器中呈现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"
   }
]