Python Falcon - Jinja2 模板


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

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

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

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

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

pip3 install jinja2

你好世界模板

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链接。

金贾2

模板变量

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。浏览器显示以下输出 -

金贾2 你好

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 图像

HTML 表单模板

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

<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属性中的表单数据,该属性只不过是表单元素及其值的字典。然后添加学生词典。

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路线。您会发现附加了一个新行。

Jinja2 示例

多部分表格

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

BodyPart类具有以下属性 -

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

  • 数据- 主体部分内容字节

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

  • text - 当前正文部分解码为文本字符串(仅当其类型为 text/plain 时,否则无)

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

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

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

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

<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"
   }
]