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

How to bind different Mongoengine database aliases for Flask-Admin views?

使用 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,
        },
    ]

现在我们可以使用 Documentmeta 字段,它将数据库(由其别名表示)绑定到特定实体:

    class Entity(Document):
        field = StringField()

        meta = {'db_alias': SECOND_DB_ALIAS}

不幸的是,它不适合我的需要,因为 相同的实体(由相同的 Document class 表示)可以存在于两个数据库中.我想根据应用程序的逻辑设置我查询的数据库。

好吧,随便吧。我们仍然可以使用 Mongoengine 的 context managers:

动态切换数据库
    with switch_db(Entity, SECOND_DB_ALIAS):
         Entity(field="value").save()

(注意:很遗憾,写这个问题的时候是not thread-safe

这就是我在应用程序的其余部分所做的。问题是 我无法在我的 Flask-Admin ModelViews 中找到执行相同操作的方法。在这种情况下如何设置要查询的数据库的别名?

    class EntityView(ModelView):
        can_delete = True
        can_edit = True
        can_view_details = True
        can_create = True

        can_export = True

        # No such or similar attribute!
        database_alias = SECOND_DB_ALIAS  

        def __init__(self):
            super().__init__(Entity, name="Entities")

    admin = Admin(app, name='Admin Panel', template_mode='bootstrap3')
    admin.add_view(EntityView())

解决了。

花了一些时间检查 ModelViewsource code,并且确实没有实现类似的东西。得撸起袖子了

我们必须使用 switch_db 上下文管理器将所有查询包装到数据库中。 Flask-Admin 文档包含 a list of methods needed to implement a model backend。因此,如果发生任何数据库查询,它就在那里。

通过检查这些方法在ModelView中的实现,我们可以发现Mongoengine查询只能在get_listget_onecreate_model、[=中执行18=] 和 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 目前不是线程安全的。数据库在进入和离开上下文时全开Entityclass。所以,我不确定它在多线程 Flask 应用程序的高负载下会如何表现,以及是否会出现竞争条件问题。

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