Skip to content

Latest commit

 

History

History
191 lines (139 loc) · 8.49 KB

File metadata and controls

191 lines (139 loc) · 8.49 KB

四、使用蓝图创建控制器

模型视图控制器MVC方程的最后一部分是控制器。我们已经在main.py文件中看到了视图函数的基本用法。现在,将引入更复杂、更强大的版本,我们将把不同的视图功能转换为内聚的整体。我们还将讨论 Flask 如何处理 HTTP 请求的生存期以及定义 Flask 视图的高级方法。

请求设置、拆卸和应用全局

在某些情况下,所有视图函数都需要一个特定于请求的变量,并且还需要从模板中访问该变量。为了实现这一点,我们可以使用 Flask 的 decorator 函数@app.before_request和对象g。每次发出新请求前都会执行功能@app.before_request。Flask对象g是一个线程安全存储库,用于存储每个特定请求所需的任何数据。在请求结束时,对象被销毁,并且在新请求开始时生成一个新对象。例如,以下代码检查 Flasksession变量是否包含登录用户的条目;如果存在,则将User对象添加到g

from flask import g, session, abort, render_template

@app.before_request
def before_request():
    ifuser_idin session:
        g.user = User.query.get(session[‘user_id’])

@app.route(‘/restricted’)
def admin():
    if g.user is None:
        abort(403)
    return render_template(‘admin.html’)

多个功能可以用@app.before_request修饰,在执行请求的查看功能之前,都会执行功能。还有一个 decorator@app.teardown_request,在每个请求结束后调用。请记住,这种处理用户登录的方法只是一个示例,并不安全。推荐方法见第 6 章保护您的应用中所述。

错误页面

向最终用户显示浏览器的默认错误页面是不和谐的,因为用户会丢失您应用的所有上下文,他们必须点击返回按钮才能返回您的站点。要在 Flaskabort()函数返回错误时显示您自己的模板,请使用errorhandlerdecorator 函数:

@app.errorhandler(404)
def page_not_found(error):
    return render_template('page_not_found.html'), 404

errorhandler还可用于将内部服务器错误和 HTTP 500 代码转换为用户友好的错误页面。app.errorhandler()函数可以使用一个或多个 HTTP 状态代码来定义它将操作的代码。返回元组而不仅仅是 HTML 字符串允许您定义Response对象的 HTTP 状态代码。默认设置为200

基于类的视图

在大多数Flask 应用中,视图由函数处理。然而,当许多视图共享公共功能或有代码片段可以分解为单独的函数时,将视图实现为类以利用继承是很有用的。

例如,如果我们有呈现模板的视图,我们可以创建一个通用视图类来保持代码干燥

from flask.views import View

class GenericView(View):
    def __init__(self, template):
        self.template = template
        super(GenericView, self).__init__()

    def dispatch_request(self):
        return render_template(self.template)

app.add_url_rule(
    '/', view_func=GenericView.as_view(
        'home', template='home.html'
    )
)

关于这段代码,首先要注意的是我们视图类中的dispatch_request()函数。这是我们视图中的函数,它充当普通视图函数并返回 HTML 字符串。app.add_url_rule()函数模仿app.route()函数,因为它将路由绑定到函数调用。第一个参数定义函数的路由,view_func参数定义处理路由的函数。View.as_view()方法被传递给view_func参数,因为它将View类转换为视图函数。第一个参数定义了 view 函数的名称,因此像url_for()这样的函数可以路由到它。其余参数将传递给View类的__init__函数。

与普通视图函数一样,View类必须明确允许使用GET以外的 HTTP 方法。要允许使用其他方法,必须添加包含命名方法列表的类变量:

class GenericView(View):
    methods = ['GET', 'POST']
    …
    def dispatch_request(self):
        if request.method ==GET’:
            return render_template(self.template)
        elif request.method ==POST’:
            …

方法类视图

通常,当函数处理多个 HTTP 方法时,由于if语句中嵌套了大量代码,因此代码可能变得难以读取:

@app.route('/user', methods=['GET', 'POST', 'PUT', 'DELETE'])
def users():
    if request.method == 'GET':
        …
    elif request.method == 'POST':
        …
    elif request.method == 'PUT':
        …
    elif request.method == 'DELETE':
        …

这个可以通过MethodView类来解决。MethodView允许每个方法由不同的类方法处理,以分离关注点:

from flask.views import MethodView

class UserView(MethodView):
    def get(self):
        …
    def post(self):
        …
    def put(self):
        …
    def delete(self):
        …

app.add_url_rule(
    '/user',
    view_func=UserView.as_view('user')
)

蓝图

在Flask中,蓝图是扩展现有Flask app 的方法。蓝图提供了一种将视图组与通用功能相结合的方式,允许开发人员将其应用分解为不同的组件。在我们的架构中,蓝图将充当我们的控制器

将视图注册到蓝图中;可以为它定义一个单独的模板和静态文件夹,当它包含所有需要的内容时,可以在主 Flask 应用上注册它以添加 blueprint 内容。blueprint 的行为很像 Flask 应用对象,但实际上并不是一个自包含的应用。这就是 Flask 扩展提供视图函数的方式。为了了解什么是蓝图,这里有一个非常简单的例子:

from flask import Blueprint
example = Blueprint(
    'example',
    __name__,
    template_folder='templates/example',
    static_folder='static/example',
    url_prefix="/example"
)

@example.route('/')
def home():
    return render_template('home.html')

蓝图采用两个必需参数:蓝图名称和Flask内部使用的包装名称;将__name__传递给它就足够了。

其他参数是可选的,用于定义蓝图将在何处查找文件。由于指定了templates_folder,蓝图将不会在默认模板文件夹中查找,路由将呈现templates/example/home.html而不是templates/home.htmlurl_prefix选项会自动将提供的 URI 添加到蓝图中每个路由的开始处。因此,主视图的 URL 实际上是/example/

url_for()功能现在必须被告知请求的路线在哪个蓝图中:

{{ url_for('example.home') }}

此外,url_for()功能现在必须被告知视图是否从同一蓝图中渲染:

{{ url_for('.home') }}

url_for()功能还将在指定的静态文件夹中查找静态文件。

要将蓝图添加到我们的应用,请执行以下操作:

app.register_blueprint(example)

让我们将当前的应用转换为使用蓝图的应用。我们首先需要在所有路线之前确定蓝图:

blog_blueprint = Blueprint(
    'blog',
    __name__,
    template_folder='templates/blog',
    url_prefix="/blog"
)

现在,由于templates 文件夹已定义,我们需要将所有模板移动到 templates 文件夹的子文件夹 blog 中。接下来,我们所有的路线都需要将@app.route更改为@blog_blueprint.route,并且任何类视图分配现在都需要注册到blog_blueprint。请记住,模板中的url_for()函数调用也必须更改为在前面加上一个句点,以指示路由在同一蓝图中。

在文件末尾的if __name__ == '__main__':语句前面添加以下内容:

app.register_blueprint(blog_blueprint)

现在,我们所有的内容都回到了应用上,该应用已在 blueprint 下注册。因为我们的基础应用不再有任何视图,所以让我们在基础 URL 上添加一个重定向:

@app.route('/')
def index():
    return redirect(url_for('blog.home'))

为什么写博客而不是blog_blueprint?因为 blog 是蓝图的名称,而 Flask 在内部用于路由的名称。blog_blueprint是 Python 文件中变量的名称。

总结

现在我们的应用在蓝图中运行,但这给了我们什么?比如说,我们想在我们的网站上增加一个照片共享功能;我们可以将所有视图功能组合到一个蓝图中,其中包含自己的模板、静态文件夹和 URL 前缀,而不必担心会破坏站点其余部分的功能。在下一章中,在升级我们的文件和代码结构后,通过将蓝图分成不同的文件,蓝图将变得更加强大。