【问题标题】:Nested Blueprints in Flask?Flask 中的嵌套蓝图?
【发布时间】:2015-10-07 22:03:25
【问题描述】:

我还是 Flask 的新手,所以可能有一种明显的方法可以做到这一点,但到目前为止我还无法从文档中弄清楚。我的应用程序分为几个几乎完全不同的部分,它们共享诸如用户/会话/安全和基本模板之类的东西,但大多数都没有太多交互,并且应该在不同的路径下路由,例如 /part1/...。我认为这几乎正是蓝图的用途。但是,如果我需要在蓝图下进一步对路由和逻辑进行分组怎么办?

例如,我有blueprint1url_prefix='/blueprint1',也许在此之下,我希望收集围绕着用户分享照片和其他用户评论它们的视图。我想不出比这更好的方法了:

# app/blueprints/blueprint1/__init__.py

blueprint1 = Blueprint('blueprint1', __name__, template_folder='blueprint1')

@blueprint1.route('/photos')
def photos_index():
    return render_template('photos/index.html')

@blueprint.route('/photos/<int:photo_id>')
def photos_show(photo_id):
    photo = get_a_photo_object(photo_id)
    return render_template('photos/show.html', photo=photo)

@blueprint.route('/photos', methods=['POST'])
def photos_post():
    ...

这里的问题是与blueprint1 的照片部分相关的所有视图都位于“顶层”,可能带有视频或音频或其他任何东西的蓝图(命名为videos_index()...)。有没有办法以更分层的方式对它们进行分组,例如模板如何放在'blueprint1/photos' 子目录下?当然,我可以将所有照片视图放在它们自己的模块中,以使它们分开组织,但是如果我想将父路径 'blueprint1/photos' 更改为其他路径怎么办?我确定我可以发明一个函数或装饰器,将相关路由分组在同一根路径下,但是我仍然必须使用 photos_ 前缀命名所有函数,并像 url_for('blueprint1.photos_show') 一样引用它们 看起来蓝图是当 Flask 应用程序变大并且您需要将相似的部分组合和划分在一起时回答,但是当蓝图本身变大时您不能做同样的事情。

作为参考,在 Laravel 中,您可以将相关的“视图”分组到 Controller 类下,其中视图是方法。控制器可以驻留在像app\Http\Controllers\Blueprint1\Photocontroller 这样的分层命名空间中,路由可以像这样分组在一起

Route::group(['prefix' => 'blueprint1'], function() {

    Route::group(['prefix' => 'photos'], function() {

        Route::get('/', ['as' => 'blueprint.photos.index', 'uses' => 'ModelApiController@index']);
        Route::post('/', ['as' => 'blueprint.photos.store', 'uses' => 'ModelApiController@store']);
        Route::get('/{id}', ['as' => 'blueprint.photos.get', 'uses' => 'ModelApiController@get'])
            ->where('id', '[0-9]+');

    });

});

并且可以像action('Blueprint1\PhotoController@index')一样获取路线。

如果我能做一张照片蓝图,那么就做blueprint1.register_blueprint(photos_blueprint, url_prefix='/photos')之类的,这些问题就基本解决了。不幸的是,Flask 似乎不支持这样的嵌套蓝图。有没有其他方法来处理这个问题?

【问题讨论】:

    标签: python design-patterns flask


    【解决方案1】:

    不幸的是,嵌套蓝图不是 Flask 中的当前功能。您必须手动完成。您可能会编写适合您的特定情况的代码,但尚未将通用解决方案添加到 Flask。已经对问题跟踪器进行了一些讨论:


    将可嵌套的蓝图添加到 Flask 中并不像自动为路由添加前缀那么简单。嵌套时需要考虑蓝图的许多其他特性,这会使一般实现变得更加复杂。尚未实施的原因是,社区中没有人对它有足够大的需求,而这不是通过快速解决方法而不是提供通用实施来解决的。

    【讨论】:

      【解决方案2】:

      我创建了一个名为 NestedBlueprint 的类来破解它。

      class NestedBlueprint(object):
          def __init__(self, blueprint, prefix):
              super(NestedBlueprint, self).__init__()
              self.blueprint = blueprint
              self.prefix = '/' + prefix
      
          def route(self, rule, **options):
              rule = self.prefix + rule
              return self.blueprint.route(rule, **options)
      

      这是包含蓝图的基本文件:panel/__init__.py

      from flask import Blueprint
      
      panel_blueprint = Blueprint(PREFIX, __name__, url_prefix='/panel')
      
      from . import customize
      

      这是包含嵌套蓝图的特定/嵌套文件:panel/customize.py

      from rest.api.panel import panel_blueprint
      from rest.api.util.nested_blueprint import NestedBlueprint
      
      nested_blueprint = NestedBlueprint(panel_blueprint, 'customize')
      
      
      @nested_blueprint.route('/test', methods=['GET'])
      def test():
          return ':)'
      

      然后你可以这样调用:

      $ curl http://localhost:5000/panel/customize/test
      :)
      

      【讨论】:

      • 此方法无法与@blueprint_name.before_request 一起使用
      【解决方案3】:

      更新

      Flask 2 已发布,支持嵌套蓝图。

      [开始:文档的一部分]

      嵌套蓝图

      可以在另一个蓝图上注册一个蓝图。

      parent = Blueprint('parent', __name__, url_prefix='/parent')
      child = Blueprint('child', __name__, url_prefix='/child')
      parent.register_blueprint(child)
      app.register_blueprint(parent)
      

      子蓝图将获取父级名称作为其名称的前缀,子级 URL 将以父级 URL 前缀作为前缀。

      url_for('parent.child.create')
      /parent/child/create
      

      向父级注册的特定蓝图之前的请求函数等将为子级触发。如果子进程没有可以处理给定异常的错误处理程序,则会尝试父进程。

      [ END:部分来自文档]

      来源:https://flask.palletsprojects.com/en/2.0.x/blueprints/#nesting-blueprints


      旧答案

      我的 hacky 工作是我创建了一个名为 ParentBP 的类,该类具有以下代码

      from typing import List
      from flask import Blueprint
      
      class ParentBP(object):
         name: str
         url_prefix: str
         subdomain: str
         blueprints: List[Blueprint]
      
      def __init__(self, name="", url_prefix="", subdomain="") -> None:
          self.name = name
          self.url_prefix = url_prefix
          self.subdomain = subdomain
          self.blueprints = []
      
      def register_blueprint(self, bp: Blueprint) -> None:
          bp.name = self.name + "-" + bp.name
          bp.url_prefix = self.url_prefix + (bp.url_prefix or "")
          if self.subdomain:
              bp.subdomain = self.subdomain
          self.blueprints.append(bp)
      

      因此您可以将其称为类似于下面的蓝图

      blueprint1 = Blueprint("blueprint1", __name__)
      blueprint2 = Blueprint("blueprint2", __name__, url_prefix="/bp2")
      
      api_v1 = ParentBP("api-v1", url_prefix="/api/v1")
      api_v1.register_blueprint(blueprint1)
      api_v1.register_blueprint(blueprint)
      

      为了使界面类似于正常向flask应用注册蓝图的界面,我扩展了Flask类如下

      class ExtendedFlask(Flask):
      
      def register_blueprint(self, blueprint: Union[Blueprint, ParentBP], **options: Any) -> None:
          if isinstance(blueprint, ParentBP):
              for bp in blueprint.blueprints:
                  super().register_blueprint(bp, **options)
          else:
              return super().register_blueprint(blueprint, **options)
      

      现在您可以正常执行以下操作了

      app = ExtendedFlask(__name__)
      app.register_blueprint(api_v1)
      

      【讨论】:

        【解决方案4】:

        这是我的解决方法:

        在导入蓝图时,我定义了我的嵌套路由:

        app.register_blueprint(product_endpoints, url_prefix='/sites/<int:site_id>/menus/<int:menu_id>/categories/<int:category_id>/products/<int:product_id>')
        app.register_blueprint(category_endpoints, url_prefix='/sites/<int:site_id>/menus/<int:menu_id>/categories/<int:category_id>')
        app.register_blueprint(menu_endpoints, url_prefix='/sites/<int:site_id>/menus/<int:menu_id>')
        app.register_blueprint(site_endpoints, url_prefix='/sites/<int:site_id>')
        

        在蓝图中,我正在重用路由解析函数。例如,在product_endpoints 文件中:

        from category_endpoints import get_category_data
        
        product_endpoints = Blueprint('product_endpoints', __name__)
        
        @product_endpoints.url_value_preprocessor
        def get_product_data(endpoint, values):
            if 'category_id' in values:
                get_category_data(endpoint, values)
        
            product = Product.get_by_id(int(values.pop('product_id')))
        
            if not product:
                abort(404)
        
            g.product = product
        

        category_endpoints 文件中:

        from menu_endpoints import get_menu_data
        
        category_endpoints = Blueprint('category_endpoints', __name__)
        
        @category_endpoints.url_value_preprocessor
        def get_category_data(endpoint, values):
            if 'menu_id' in values:
                get_menu_data(endpoint, values)
            category = ProductCategory.get_by_id(int(values.pop('category_id')))
        
            if not category:
                abort(404)
        
            g.category = category
        

        等...通过这种方法,我的蓝图也可用于/products/&lt;int:product_id&gt; 等直接路由。

        这种方法对我很有效。希望对你也有帮助。

        【讨论】:

          猜你喜欢
          • 2021-10-04
          • 2012-09-14
          • 1970-01-01
          • 2017-05-11
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2014-08-16
          • 1970-01-01
          相关资源
          最近更新 更多