flask 管理员自定义 QueryAjaxModelLoader

flask admin custom QueryAjaxModelLoader

据我了解,Flask Admin 支持 AJAX 用于加载外键模型。 Flask Admin - Model Documentation 涵盖标题 form_ajax_refs 下的基础知识。我已经在很多场合成功地使用了它,但是我对我希望达到的定制级别有疑问。让我详细说明。

我有一个 Product 模型,一个 Organisation 模型和一个连接 table 来关联它们,定义如下:

class Product(Base):
    __tablename__ = "products"

    product_uuid = Column(UUID(as_uuid=True), primary_key=True)
    title = Column(String, nullable=False)
    description = Column(String, nullable=False)
    last_seen = Column(DateTime(timezone=True), nullable=False, index=True)
    price = Column(Numeric(precision=7, scale=2), nullable=False, index=True)

class Organisation(Base):
    __tablename__ = "organisations"
    org_id = Column(String, primary_key=True)
    org_name = Column(String, nullable=False)
    products = relationship(
        Product,
        secondary="organisation_products",
        backref="organisations"
    )

organisation_products_table = Table(
    "organisation_products",
    Base.metadata,
    Column("org_id", String, ForeignKey("organisations.org_id"), nullable=False),
    Column("product_uuid", UUID(as_uuid=True), ForeignKey("products.product_uuid"), nullable=False),
    UniqueConstraint("org_id", "product_uuid"),
    )

在名为 CuratedList 的模型的 Flask 管理模型视图中,该模型对 Product 模型具有外键约束,我在创建视图的表单中使用 form_ajax_refs,以允许选择动态加载的 Product 项。

form_ajax_refs = {"products": {"fields": (Product.title,)}}

这很好地显示了 Product 模型的所有行。

不过,我目前的要求是 仅使用 AJAX 模型加载器来显示具有特定 org_id 的产品,例如 "Google"。

第一次尝试

覆盖 ModelView class 的 get_query 函数以加入 organisation_products_table 并按 org_id 过滤。这看起来像这样:

def get_query(self):
    return (
        self.session.query(CuratedList)
        .join(
            curated_list_items_table,
            curated_list_items_table.c.list_uuid == CuratedList.list_uuid
        )
        .join(
            Product,
            Product.product_uuid == curated_list_items_table.c.product_uuid
        )
        .join(
            organisation_products_table,
            organisation_products_table.c.product_uuid == Product.product_uuid
        )
        .filter(CuratedList.org_id == "Google")
        .filter(organisation_products_table.c.org_id == "Google")
    )

不幸的是,这并没有解决问题,returns 与以下行为相同:

def get_query(self):
    return (
        self.session.query(CuratedList)
        .filter(CuratedList.org_id == self._org_id)
    )

它不影响 form_ajax_refs 的行为。

第 2 次尝试

Flask Admin - Model Documentation 提到了另一种使用 form_ajax_refs 的方法,它涉及使用 QueryAjaxModelLoader class.

在我的第二次尝试中,我子class QueryAjaxModelLoader class 并尝试覆盖它的 modelsessionfields 变量。像这样:

class ProductAjaxModelLoader(QueryAjaxModelLoader):
    def __init__(self, name, session, model, **options):
        super(ProductAjaxModelLoader, self).__init__(name, session, model, **options)

        fields = (
            session.query(model.title)
            .join(organisation_products_table)
            .filter(organisation_products_table.c.org_id == "Google")
        ).all()

        self.fields = fields
        self.model = model
        self.session = session

然后我不再使用以前的 form_ajax_refs 方法,而是使用新的 AjaxModelLoader 方法,如下所示:

form_ajax_refs = {
    "products": ProductAjaxModelLoader(
        "products", db.session, Product, fields=['title']
    )
}

不幸的是,无论是用我的查询覆盖 session 还是 model 的值 returns 都没有来自 AJAX 加载器的产品,并且仍然覆盖 fields returns所有产品;不只是 org_id "Google".

的产品

我不希望诉诸的

我希望能够实现此 而不必 为每个组织创建一个新模型,因为这将被证明是 non-scalable 并且设计不佳.

欢迎提出任何建议。谢谢

感谢 Joes 对我最初问题的评论,我制定了一个可行的解决方案:

重写 AjaxModelLoader 函数 get_list 像这样:

def get_list(self, term, offset=0, limit=DEFAULT_PAGE_SIZE):
    filters = list(
        field.ilike(u'%%%s%%' % term) for field in self._cached_fields
    )
    filters.append(Organisation.org_id == "Google")
    return (
        db.session.query(Product)
        .join(organisation_products_table)
        .join(Organisation)
        .filter(*filters)
        .all()
    )

经过多次试验和错误,感谢上面的帖子,我提出了一种将过滤器传递给 Ajax 模型加载器的通用方法。

这是一个通用的 class,它可以过滤外键 table。

from flask_admin.contrib.sqla.ajax import QueryAjaxModelLoader, DEFAULT_PAGE_SIZE

class FilteredAjaxModelLoader(QueryAjaxModelLoader):
    additional_filters = []

    def get_list(self, term, offset=0, limit=DEFAULT_PAGE_SIZE):
        filters = list(
            field.ilike(u'%%%s%%' % term) for field in self._cached_fields
        )
        for f in self.additional_filters:
            filters.append(f)
        # filters.append(User.list_id == 2) # Now this is passed in the constructor
        # filters.append(User.is_active == 'Y')
        return (
            db.session.query(self.model)
                .filter(*filters)
                .all()
        )

    def __init__(self, name, session, model, **options):
        super(FilteredAjaxModelLoader, self).__init__(name, session, model, **options)
        self.additional_filters = options.get('filters')

用法:

QueryAjaxModelLoader

一样将其传递到 form_ajax_refs
FilteredAjaxModelLoader('component', db.session, User, fields=['list_value'],
                                filters=[User.list_id == 2, User.is_active == 'Y'])

希望对您有所帮助。