zoukankan      html  css  js  c++  java
  • Flask从入门到精通之大型程序的结构二

    一.程序包

      程序包用来保存程序的所有代码、模板和静态文件。我们可以把这个包直接称为app(应用),如果有需求,也可使用一个程序专用名字。templates 和static 文件夹是程序包的一部分,因此这两个文件夹被移到了app 中。数据库模型和电子邮件支持函数也被移到了这个包中,分别保存为app/models.py 和app/email.py。

    使用程序工厂函数

      在单个文件中开发程序很方便,但却有个很大的缺点,因为程序在全局作用域中创建,所以无法动态修改配置。运行脚本时,程序实例已经创建,再修改配置为时已晚。这一点对单元测试尤其重要,因为有时为了提高测试覆盖度,必须在不同的配置环境中运行程序。

      这个问题的解决方法是延迟创建程序实例,把创建过程移到可显式调用的工厂函数中。这种方法不仅可以给脚本留出配置程序的时间,还能够创建多个程序实例,这些实例有时在测试中非常有用。程序的工厂函数在app 包的构造文件中定义,如下所示:

    from flask import Flask
    from flask_bootstrap import Bootstrap
    from flask_mail import Mail
    from flask_moment import Moment
    from flask_sqlalchemy import SQLAlchemy
    from flask_login import LoginManager
    from flask_pagedown import PageDown
    from config import config
    
    bootstrap = Bootstrap()
    mail = Mail()
    moment = Moment()
    db = SQLAlchemy()
    pagedown = PageDown()
    
    login_manager = LoginManager()
    login_manager.login_view = 'auth.login'
    
    
    def create_app(config_name):
        app = Flask(__name__)
        app.config.from_object(config[config_name])
        config[config_name].init_app(app)
    
        bootstrap.init_app(app)
        mail.init_app(app)
        moment.init_app(app)
        db.init_app(app)
        login_manager.init_app(app)
        pagedown.init_app(app)
        ...
        return app

      构造文件导入了大多数正在使用的Flask 扩展。由于尚未初始化所需的程序实例,所以没有初始化扩展,创建扩展类时没有向构造函数传入参数。create_app() 函数就是程序的工厂函数,接受一个参数,是程序使用的配置名。配置类在config.py 文件中定义,其中保存
    的配置可以使用Flask app.config 配置对象提供的from_object() 方法直接导入程序。至于配置对象,则可以通过名字从config 字典中选择。程序创建并配置好后,就能初始化扩展了。在之前创建的扩展对象上调用init_app() 可以完成初始化过程。

    在蓝本中实现程序功能

      转换成程序工厂函数的操作让定义路由变复杂了。在单脚本程序中,程序实例存在于全局作用域中,路由可以直接使用app.route 修饰器定义。但现在程序在运行时创建,只有调用create_app() 之后才能使用app.route 修饰器,这时定义路由就太晚了。和路由一样,自定义的错误页面处理程序也面临相同的困难,因为错误页面处理程序使用app.errorhandler 修饰器定义。

      幸好Flask 使用蓝本提供了更好的解决方法。蓝本和程序类似,也可以定义路由。不同的是,在蓝本中定义的路由处于休眠状态,直到蓝本注册到程序上后,路由才真正成为程序的一部分。使用位于全局作用域中的蓝本时,定义路由的方法几乎和单脚本程序一样。和程序一样,蓝本可以在单个文件中定义,也可使用更结构化的方式在包中的多个模块中创建。为了获得最大的灵活性,程序包中创建了一个子包,用于保存蓝本。下面的例子是这个子包的构造文件,蓝本就创建于此。

    app/main/__init__.py:创建蓝本
    from flask import Blueprint
    main = Blueprint('main', __name__)
    from . import views, errors

      通过实例化一个Blueprint 类对象可以创建蓝本。这个构造函数有两个必须指定的参数:蓝本的名字和蓝本所在的包或模块。和程序一样,大多数情况下第二个参数使用Python 的__name__ 变量即可。程序的路由保存在包里的app/main/views.py 模块中,而错误处理程序保存在app/main/errors.py 模块中。导入这两个模块就能把路由和错误处理程序与蓝本关联起来。注意,这些模块在app/main/__init__.py 脚本的末尾导入,这是为了避免循环导入依赖,因为在views.py 和errors.py 中还要导入蓝本main。

      蓝本在工厂函数create_app() 中注册到程序上:

    def create_app(config_name):
        from .main import main as main_blueprint
        app.register_blueprint(main_blueprint)
    
        from .auth import auth as auth_blueprint
        app.register_blueprint(auth_blueprint, url_prefix='/auth')
    
        from .api import api as api_blueprint
        app.register_blueprint(api_blueprint, url_prefix='/api/v1')
    
        return app

      在蓝本中编写错误处理程序稍有不同,如果使用errorhandler 修饰器,那么只有蓝本中的错误才能触发处理程序。要想注册程序全局的错误处理程序,必须使用app_errorhandler 

    from flask import render_template, request, jsonify
    from . import main
    
    
    @main.app_errorhandler(403)
    def forbidden(e):
        if request.accept_mimetypes.accept_json and 
                not request.accept_mimetypes.accept_html:
            response = jsonify({'error': 'forbidden'})
            response.status_code = 403
            return response
        return render_template('403.html'), 403

      在蓝本中编写视图函数主要有两点不同:第一,和前面的错误处理程序一样,路由修饰器由蓝本提供;第二,url_for() 函数的用法不同。你可能还记得,url_for() 函数的第一个参数是路由的端点名,在程序的路由中,默认为视图函数的名字。例如,在单脚本程序
    中,index() 视图函数的URL 可使用url_for('index') 获取。在蓝本中就不一样了,Flask 会为蓝本中的全部端点加上一个命名空间,这样就可以在不同的蓝本中使用相同的端点名定义视图函数,而不会产生冲突。命名空间就是蓝本的名字(Blueprint 构造函数的第一个参数),所以视图函数index() 注册的端点名是main.index,其URL 使用url_for('main.index') 获取。

      url_for() 函数还支持一种简写的端点形式,在蓝本中可以省略蓝本名,例如url_for('.index')。在这种写法中,命名空间是当前请求所在的蓝本。这意味着同一蓝本中的重定向可以使用简写形式,但跨蓝本的重定向必须使用带有命名空间的端点名。

    @main.route('/', methods=['GET', 'POST'])
    def index():
        form = PostForm()
        if current_user.can(Permission.WRITE) and form.validate_on_submit():
            post = Post(body=form.body.data,
                        author=current_user._get_current_object())
            db.session.add(post)
            db.session.commit()
            return redirect(url_for('.index'))

        

  • 相关阅读:
    如何提高网站在Google的排名(2)
    Key Words in my 2006
    UML: 关系
    What's SOAP
    "你试图打开的项目是Web项目,请指定URL路径"问题及解决方法
    WinXP下装SQL2000企业版
    多表联合查询的问题。。。。。
    实习技术员的基本功(十一)
    实习技术员的基本功(五)
    实习技术员的基本功(六)
  • 原文地址:https://www.cnblogs.com/senlinyang/p/8391189.html
Copyright © 2011-2022 走看看