Python Falcon 简明教程

Python Falcon - Jinja2 Template

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

使用变量数据呈现 HTML 内容非常繁琐。为此,需要使用网络模板库。许多 Python Web 框架都捆绑了特定的模板库。但 Falcon 是一个极简主义微框架,不会捆绑任何模版库。

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

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

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

pip3 install jinja2

Hello World Template

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>

Example

下面的资源类中的 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)

Output

运行以上 Python 代码并访问浏览器中的 http://localhost:8000/hello 链接。

jinja2

Template Variable

jinja2 是一个服务器端模板库。网页通过将 jinja2 模板语言的各种元素作为占位符放置到 HTML 脚本内的适当定界符中来构造为模板。模板引擎读取 HTML 脚本,在服务器上用上下文数据替换占位符,重新组装 HTML 并将其呈现给客户端。

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

Example

在以下示例中,路由 /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>

Output

运行 Python 代码并输入 http://localhost:8000/hello/Priya 作为 URL。浏览器将显示以下输出:

jinja2 hello

Loop in jinja2 Template

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

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

在以下示例中, on_get() 响应器向模板 list.html 发送 students 对象,该对象是 dict 对象的列表。它会遍历数据并将其呈现为 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 img

HTML Form Template

在此部分,我们将看到 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 路由。

要能够读取表单数据,必须将 auto_parse_form_urlencoded 类的 falcon.RequestOptions 属性设置为 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 表单。

jinja host

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

jinja2 example

Multipart Forms

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

BodyPart 类具有以下属性 −

  1. stream − 仅适用于当前主体部分的流包装器

  2. data − 主体部分内容字节

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

  4. text − 将当前主体部分解码为文本字符串(仅当它为类型 text/plain 时提供,否则不提供)

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

  6. name, filename − 来自 Content-Disposition 标头的相关部分

  7. 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>

此表格由下方代码中的 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 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"
   }
]