Как связать разные псевдонимы базы данных Mongoengine для представлений Flask-Admin? - PullRequest
0 голосов
/ 05 февраля 2019

При разработке интерфейса веб-администратора для информационной системы с использованием Flask-Admin и Mongoengine мне нужны Flask-Admin ModelView s для всех моих сущностей.Система использует несколько баз данных MongoDB .Для ясности предположим, что их два.

Обычно люди управляют таким поведением, используя псевдонимы базы данных Mongoengine .Во время инициализации мы определяем несколько псевдонимов для нашего приложения Flask, используя конфигурацию Flask-Mongoengine:

    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 meta, которое связывает базу данных (представленную ее псевдонимом)для конкретной сущности:

    class Entity(Document):
        field = StringField()

        meta = {'db_alias': SECOND_DB_ALIAS}

К сожалению, здесь это не соответствует моим потребностям, поскольку одни и те же сущности (представленные одним и тем же классом Document) могут присутствовать в обеих базах данных .Я хочу установить базу данных, к которой запрашиваю, в зависимости от логики приложения.

Ну, как угодно.Мы по-прежнему можем динамически переключать базы данных, используя менеджеры контекста Mongoengine :

    with switch_db(Entity, SECOND_DB_ALIAS):
         Entity(field="value").save()

(Обратите внимание: к сожалению, не является поточно-ориентированным на момент написания этого вопроса)

Это то, что я делаю в остальной части приложения.Проблема в том, что Я не могу найти способ сделать то же самое в ModelView s моего Flask-Admin.Как установить псевдоним базы данных для запроса в этой ситуации?

    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())

1 Ответ

0 голосов
/ 05 февраля 2019

Решил это.

Потратил некоторое время на изучение ModelView исходного кода , и, действительно, ничего подобного не было реализовано.Ну что ж, пришлось закатать рукава.

Мы должны обернуть все запросы к базе данных с помощью switch_db менеджера контекста.Документация Flask-Admin содержит список методов, необходимых для реализации бэкэнда модели .Таким образом, если какой-либо запрос к базе данных имеет место, он есть.

Изучив реализацию этих методов в ModelView, мы можем выяснить, что запросы Mongoengine могут выполняться только в методах get_list, get_one, create_model, 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 на данный момент не является потокобезопасным.База данных включается на весь класс Entity при входе и выходе из контекста.Итак, я не уверен, как он будет себя вести при высокой нагрузке в многопоточном приложении Flask и будут ли проблемы с состоянием гонки.

Если кто-нибудь придумает лучший подход к проблеме или с какой-либоулучшения этого кода, я был бы рад услышать.

...