如何为Flask-Admin视图绑定不同的Mongoengine数据库别名?

时间:2019-02-05 14:29:57

标签: python flask mongoengine flask-admin flask-mongoengine

使用Flask-Admin和Mongoengine为信息系统开发Web管理界面,我需要为所有实体使用Flask-Admin的ModelView。系统使用几个MongoDB数据库。为了清楚起见,我们假设其中有两个。

通常,人们使用Mongoengine的database aliases管理此类行为。在初始化期间,我们使用Flask-Mongoengine的配置为Flask应用定义了几个别名:

    from mongoengine import DEFAULT_CONNECTION_NAME
    # Local packages
    from config import CurrentConfig  

    SECOND_DB_ALIAS = "second_db"

    app.config['MONGODB_SETTINGS'] = [
        {
            "ALIAS": DEFAULT_CONNECTION_NAME,
            "DB": CurrentConfig.DATABASE_NAME,
        },
        {
            "ALIAS": SECOND_DB_ALIAS,
            "DB": CurrentConfig.SECOND_DATABASE_NAME,
        },
    ]

现在,我们可以使用Document的{​​{1}}字段,该字段将数据库(由别名表示)绑定到特定实体:

meta

不幸的是,这不符合我的需求,因为两个数据库中都可以存在相同的实体(由相同的 class Entity(Document): field = StringField() meta = {'db_alias': SECOND_DB_ALIAS} 类表示)。我想根据应用程序的逻辑设置要查询的数据库。

好吧,随便吧。我们仍然可以使用Mongoengine的context managers动态切换数据库:

Document

(注意:很遗憾,在撰写此问题时是not thread-safe

这就是我在其余应用程序中所做的。问题是在我的Flask-Admin的 with switch_db(Entity, SECOND_DB_ALIAS): Entity(field="value").save() 中找不到解决方法。在这种情况下如何设置要查询的数据库别名?

ModelView

1 个答案:

答案 0 :(得分:0)

解决了。

花了一些时间检查ModelView的{​​{3}},实际上,没有实现任何类似的东西。好吧,必须卷起袖子。

我们必须使用switch_db上下文管理器将所有查询包装到数据库。 Flask-Admin文档包含source code。因此,如果发生任何数据库查询,就在那里。

通过检查这些方法在ModelView中的实现,我们可以发现Mongoengine查询只能在get_listget_onecreate_model,{{1}中执行}和update_model方法。

现在,我们从delete_model派生并用所需的上下文管理器包装这些方法:

ModelView

然后我们可以像这样在视图中切换数据库:

class SwitchableModelView(ModelView):
    database_alias = DEFAULT_CONNECTION_NAME

    # Override query methods to add database switchers

    def get_list(self, *args, **kwargs):
        with switch_db(self.model, self.database_alias):
            # It's crucial that the query gets executed immediately, 
            # while in the switch_db context, 
            # so we need to override the `execute` argument.
            kwargs['execute'] = True
            return super().get_list(*args, **kwargs)

    def get_one(self, *args, **kwargs):
        with switch_db(self.model, self.database_alias):
            return super().get_one(*args, **kwargs)

    def create_model(self, *args, **kwargs):
        with switch_db(self.model, self.database_alias):
            return super().create_model(*args, **kwargs)

    def update_model(self, *args, **kwargs):
        with switch_db(self.model, self.database_alias):
            return super().update_model(*args, **kwargs)

    def delete_model(self, *args, **kwargs):
        with switch_db(self.model, self.database_alias):
            return super().delete_model(*args, **kwargs)

如果省略class EntityView(SwitchableModelView): can_delete = True can_edit = True can_view_details = True can_create = True can_export = True # Now it works! database_alias = SECOND_DB_ALIAS def __init__(self): super().__init__(Entity, name="Entities") ,仍将使用默认连接,从而导致原始database_alias的行为。

我测试了它。有效。

尽管如此,我对这段代码的效率和可靠性有些担忧。正如我提到的,ModelView目前不是线程安全的。进入和离开上下文时,数据库将在整个switch_db类上切换。因此,我不确定在多线程Flask应用程序中它在高负载下如何运行,以及是否存在竞争条件问题。

如果有人想出更好的方法来解决此问题或对该代码进行任何改进,我将很高兴听到。