烧瓶中的嵌套蓝图?

时间:2015-10-07 22:03:25

标签: python design-patterns flask

我还是Flask的新手,所以可能有一种明显的方法可以实现这一目标,但我还没有能够从文档中找到它。我的应用程序分为几个主要不同的部分,分享用户/会话/安全性和基本模板之类的东西,但大部分内容都没有多少交互,应该在/part1/...之类的不同路径下进行路由。我认为这正是蓝图的用武之地。但是如果我需要在蓝图下进一步分组路由和逻辑呢?

例如,我blueprint1 url_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似乎不支持这样的嵌套蓝图。有没有其他方法来处理这个问题?

4 个答案:

答案 0 :(得分:7)

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

答案 1 :(得分:4)

我创建了一个名为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
:)

答案 2 :(得分:2)

编辑 Flask 2 发布,支持嵌套蓝图 https://palletsprojects.com/blog/flask-2-0-released/

我的 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)

答案 3 :(得分:0)

以下是我的解决方法:

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

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/<int:product_id>等直接路线。

这种方法对我很有用。我希望它也可以帮助你。

相关问题