Flask QuickBoot

路由

客户端把请求发送给Web服务器,Web服务器再把请求发送给程序实例。程序实例需要知道对每个URL请求运行哪些代码,所以保存了一个 URL 到 Python 函数的映射关系。处理URL和函数之间关系的程序称路由

Flask中使用 app.route 装饰器,把装饰器的函数注册到路由。

@app.route('/')
def index():
    return '<h1> hello </h1>'

这个例子将 index() 函数注册为程序根 '/' 地址。
访问 http://localhost:5000/ 后,会触发服务器执行 index() 函数,这个函数的返回值称为响应,是客户端接收到的内容。

index() 这样的函数称为视图函数。视图函数返回的响应可以是包含 HTML 的简单字符串,也可以是复杂的表单。

动态路由

如 Github 中,登录到某人的主页, 这时URL为 https://github.com/<user-name>,用户名 user-name 是地址的一部分。

Flask 中,只需要在 route 装饰器中使用特殊的句法即可。

@app.route('/user/<name>')
def user(name):
    return '<h1> hello, %s </h1>' % name

尖括号中的内容就是动态部分,任何匹配静态部分的URL都会映射到这个路由上。调用视图函数时,Flask 会将动态部分作为参数传入函数中。

路由中的动态部分默认使用字符串,不过也可以使用类型定义:int float path 类型。
例如: /user/<int:id>

path 类型也是字符串,但不把斜线视作分隔符。

程序和请求上下文

flask 从客户端收到请求时, 要让视图函数能访问一些对象, 这样才能处理请求。 request 就是一个很好的例子, 它封装了客户端发送的 HTTP 请求。

要想让视图函数能够访问请求对象, 一个显而易见的方式是将其作为参数传入视图函数,不过这会导致程序中的每个视图函数都增加一个参数。除了访问请求对象, 如果视图函数在处理请求时还要访问其他对象,情况会变得更糟。

为了避免大量可有可无的参数把视图函数弄得一团糟, flask 使用 上下文 临时把某些对象变为全局可访问。

from flask import request

@app.route('/')
def index():
    user_agent = request.header.get('User-Agent')
    return '<p> Your browser is %s </p>' % user_agent

注意这个视图函数中我们如何把 request 当作全局变量使用。事实上, request 不可能是全局变量。在多线程服务器中,多个线程同时处理不同客户端发送的不同请求时,每个线程看到的 request 对象必然不同。flask 使用上下文让特定变量在一个线程中全局可访问, 与此同时却不会干扰其他线程。

flask 中有两种上下文: 程序上下文、请求上下文

变量名 上下文 说明
current_app 程序上下文 当前激活程序的程序实例
g 程序上下文 处理请求时用作临时存储的对象,每次请求都会重设这个变量
request 请求上下文 请求对象,封装了客户端发出的HTTP请求中的内容
session 请求上下文 用户会话,用于存储请求之间需要“记住”的值的词典

flask 在分发请求之前激活(或推送)程序和请求上下文,请求处理完成后再将其删除。程序上下文被推送后,就可以在线程中使用 current_appg 变量。类似的,请求上下文被推送后,就可以使用 requestsession 变量。如果使用这些变量时我们没有激活程序上下文或请求上下文,就会导致错误。

请求调度

程序收到客户端发来的请求时, 要找到处理该请求的视图函数。为了完成这个任务, flask 会在程序的 URL映射 中查找请求的 URL。 URL映射 是 URL 和 视图函数之间的对应关系。 flask 使用 app.route 装饰器或者非装饰器形式的 app.add_url_rule() 生成映射。

可以使用 app.url_map 查看 URL 映射。

>>> from hello import app
>>> app.url_map
Map([<Rule '/' (GET, HEAD, OPTIONS) -> index>,
 <Rule '/static/<filename>' (GET, HEAD, OPTIONS) -> static>,
 <Rule '/user/<name>' (GET, HEAD, OPTIONS) -> user>])

//user/<name> 路由是在程序中使用 app.route 装饰器定义。 /static/<filename> 路由是 flask 添加的特殊路由,用于访问静态文件。

URL 映射中 HEAD、Options、GET 是请求方法,由路由进行处理。flask 为每个路由都指定了请求方法,这样不同的请求方法发送到相同的 URL 上时,会使用不同的视图函数进行处理。 HEADOPTIONS 方法由 flask 自动处理。

请求钩子

有时在处理请求之前或之后执行代码会很有用。例如,在请求开始时,我们可能需要创建数据库连接或认证发起请求的用户。为了避免在每个视图函数中都使用重复的代码, flask 提供了注册通用函数的功能, 注册的函数可在请求被分发到视图函数之前或之后调用。

请求钩子使用装饰器实现,flask 支持以下4中钩子

  • before_first_request : 注册一个函数,在处理第一个请求之前运行。
  • before_request: 注册一个函数,在每次请求之前运行。
  • after_request: 注册一个函数,如果没有未处理的异常抛出,在每次请求之后运行。
  • teardown_request: 注册一个函数,即使有未处理的异常抛出,也在每次请求之后运行。

在请求钩子函数和视图函数之间共享数据一般使用上下文全局变量 g 。 例如, before_request 处理程序可以从数据库中加载已登录用户, 并将其保存到 g.user 中。随后调用视图函数时, 视图函数再使用 g.user 获取用户。

响应

flask 调用视图函数后,会将其返回值作为响应的内容。大多数情况下,响应就是一个简单的字符串,作为 HTML 页面回送客户端。

但 HTTP 协议还需要返回 状态码, flask 默认设为 200, 这个代码表明请求已经被成功处理。如果视图函数返回的响应需要使用不同的状态码, 那么可以把数字代码作为第二个返回值, 添加到响应文本之后:

@app.route('/')
def index():
    return '<h1>Bad Request</h1>', 400

flask 视图函数还可以返回Response对象。 make_response() 函数可以接收 1个、2个或3个参数,并返回一个 Response 对象。下例创建了一个响应对象,然后设置了 cookie:

from flask import make_response

@app.route('/')
def index():
    response = make_response('<h1>Content</h1>')
    response.set_cookie('answer', '42')
    return response

还有一种特殊响应类型:重定向。这种响应没有页面文档,只告诉浏览器一个新地址用以加载新页面。

flask 提供了 redirect() 辅助函数,用以生成这种响应:

from flask import redirect

@app.route('/')
def index():
    return redirect('http://www.example.com')

还有一种处理错误的特殊响应,由 abort 函数生成,在下面例子中,如果 URL 中的动态参数 id 对应的用户不存在,就返回状态码 404

from flask import abort

@app.route('/user/<id>')
def get_user(id):
    user = load_user(id)
    if not user:
        abort(404)
    return '<h1> Hello, %s</h1>' % user.name

abort 不会把控制权交还给调用它的函数,而是抛出异常把控制权交给 Web服务器。

模版

模版是一个包含响应文本的文件,其中包含用占位变量表示的动态部分,其具体指只在请求的上下文中才能知道。使用真实值替换变量,再返回最终得到的响应字符串,这一过程称为 渲染 。为了渲染模版, Flask 使用了一个名为 Jinja2 的强大模版引擎。

Jinja2 模版引擎

形式最简单的Jinja2模版就是一个包含响应文本的文件。

示例 1 templates/index.html

<h1>Hello World!</h1>

示例 2 templates/user.html

<h1>Hello, {{ name }}!</h1>

示例2 中包含一个使用变量表示的动态部分 {{ name }}

渲染模版

默认情况下, Flask 在程序文件夹中的 templates 子文件夹中寻找模版。我们将 示例1 和 示例2 分别命名为 index.htmluser.html 存放在 templates 文件夹下。

视图函数对应为:

from flask import Flask, render_template

#...

@app.route('/')
def index():
    return render_template('index.html')
    
@app.route('/user/<name>')
def user(name):
    return render_template('user.html', name=name)

Flask 提供的 render_template 函数把 Jinja2 模版引擎集成到程序中。 render_template 函数的第一个参数是模版的文件名。随后的参数都是键值对, 表示模版中变量对应的真实值。在这段代码中,第二个模版收到一个名为 name 的变量。

视图函数中 name=name 是关键字参数,左边的 name 表示参数名,就是模版中的占位符;右边的 name 是当前作用域中的变量,表示同名参数的值。

变量

示例2 在模版中使用的 {{ name }} 结构表示一个变量,它是一种特殊的占位符, 告诉模版引擎这个位置的值从渲染模版时使用的数据中获取。

Jinja2 能识别所有类型的变量,甚至 列表、字典和对象。在模版中使用变量的一些示例如下:

<p>A vakue from a dictionary: {{ mydict['key'] }}.</p>
<p>A vakue from a list: {{ mylist[3] }}.</p>
<p>A vakue from a list, with a variable index: {{ mylist[myintvar] }}.</p>
<p>A vakue from a object's method: {{ myobj.somemethod() }}.</p>

可以使用 过滤器 修改变量, 过滤器名添加在变量名之后, 中间使用竖线分隔。例如,下述模版以首字母大写形式显示变量 name 值:

Hello, {{ name|capitalize }}

常用过滤器:

过滤器名 说明
safe 渲染值时不转义
capitalize 把值的首字母转换成大写,其他字母转换成小写
lower 把值转换成小写形式
upper 把值转换成大写形式
title 把值中的每个单词的首字母都转成大写
trim 把值的首尾空格去掉
scriptags 渲染之前把值中所有的 HTML 标签都删掉

注意:千万别在不可信的值上使用 safe 过滤器,例如用户在表单中输入的文本。
默认情况下,出于安全考虑, Jinja2 会转移所有变量。例如, 如果一个变量的值为 '<h1>Hello</h1>'Jinja2 会将其渲染成 '&lt;h1&gt;Hello&lt;/h1&gt;',浏览器能显示 h1 元素,但不会进行解释。

控制结构

Jinja2 提供了多种控制结构,可用来改变模版的渲染流程。

条件控制语句

{% if user %}
    Hello, {{ user }}
{% else %}
    Hello, Stranger!
{% endif %}

for 循环

<ul>
    {% for comment in comments %}
        <li>{{ comment }}</li>
    {% endfor %}
</ul>

宏类似 python 中的函数

{% macro render_comment(comment) %}
    <li>{{ comment }}</li>
{% endmacro %}

<ul>
    {% for comment in comments %}
        {{ render_comment(comment) }}
    {% endfor %}
</ul>

包含

需要在多出重复使用的模版代码片段可以写入单独的文件,再包含在所有模块中,以避免重复:

{% include 'common.html' %}

继承

另一种重复使用代码的强大方式是模版集成,它类似与 Python 代码中的类继承。首先, 创建一个名为 base.html 的基模版:

<html>
<head>
    {% block head %}
    <title>{% block title%}{% endblock %} - My Application<title>
    {% endblock %}
</head>
<body>
    {% block body %}
    {% endblock %}
</body>
</head>

block 标签定义的元素可在衍生模版中修改。在 base.html 中,我们定义了名为 headtitlebody 的块。注意, title 包含在 head 中。 下面这个示例是基模版的衍生模版:

{% extends "base.html" %}
{% block title %}Index{% endblock %}
{% block head %}
    {{ supper() }}
    <style>
    </style>
{% endblock %}
{% block body %}
<h1>Hello, World!</h1>
{% endblock %}

extends 指令声明这个模版衍生自 base.html。在 extends 指令后, 基模版中的 3 个块被重新定义, 模版引擎会将其插入适当的位置。注意新定义的 head 块, 在基模版中其内容不是空的, 所以使用 super() 获取原来的内容。

链接

任何具有多个路由的程序都需要可以连接不同页面的链接,例如导航条。

在模板中直接编写简单路由的 URL 链接不难,但对于包含可变部分的动态路由,在模板
中构建正确的 URL 就很困难。而且,直接编写 URL 会对代码中定义的路由产生不必要的
依赖关系。如果重新定义路由,模板中的链接可能会失效。

为了避免这些问题,Flask 提供了 url_for() 辅助函数,它可以使用程序URL 映射中保存
的信息生成 URL

url_for() 函数最简单的用法是以视图函数名(或者app.add_url_route() 定义路由时使用
的端点名)作为参数,返回对应的URL。

例如:程序中调用 url_for('index') 得到的结果是 / 。调用 url_for('index', _external=True) 返回的则是绝对地址,比如 http://localhost:5000/

使用url_for() 生成动态地址时, 将动态部分作为关键字参数传入。例如,url_for('user', name='john', _external=True) 的返回结果是 http://localhost:5000/user/john。传入 url_for() 的关键字参数不仅限于动态路由中的参数。函数能将任何额外参数添加到查询字符串中。例如,url_for('index', page=2) 的返回结果是 /?page=2

静态文件

Web 程序不是仅由 Python 代码和模板组成。大多数程序还会使用静态文件,例如 HTML 代码中引用的图片、JavaScript 源码文件和 CSS

对静态文件的引用被当成一个特殊的路由,即 /static/<filename>。例如,调用 url_for('static', filename='css/styles.css', _external=True) 得到的结果是 http://localhost:5000/static/css/styles.css

默认设置下,Flask 在程序根目录中名为 static 的子目录中寻找静态文件。如果需要,可在 static 文件夹中使用子文件夹存放文件。服务器收到前面那个URL 后,会生成一个响应,包含文件系统中static/css/styles.css 文件的内容。

示例1 展示了如何在程序的基模板中放置 favicon.ico 图标。这个图标会显示在浏览器的地址栏中。

示例1 templates/base.html 定义收藏夹图标

{% block head %}
{{ super() }}
<link rel="shortcut icon" href="{{ url_for('static', filename = 'favicon.ico') }}"
	type="image/x-icon">
<link rel="icon" href="{{ url_for('static', filename = 'favicon.ico') }}"
	type="image/x-icon">
{% endblock %}

图标的声明会插入 head 块的末尾。注意如何使用 super() 保留基模板中定义的块的原始内容。

posted @ 2021-08-31 11:08  ASH975  阅读(65)  评论(0编辑  收藏  举报