【问题标题】:flask admin custom QueryAjaxModelLoader烧瓶管理员自定义 QueryAjaxModelLoader
【发布时间】:2015-09-17 08:22:23
【问题描述】:

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

我有一个Product 模型、一个Organisation 模型和一个将它们关联起来的连接表,定义如下:

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 Admin Model 视图中,该模型具有对 Product 模型的外键约束,我在表单创建视图中使用 form_ajax_refs,以允许选择动态加载的 Product项目。

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

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

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

第 1 次尝试

覆盖ModelView 类的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")
    )

不幸的是,这并不能解决问题,并返回与以下相同的行为:

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 类。

在我的第二次尝试中,我将QueryAjaxModelLoader 类子类化并尝试覆盖它的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

然后我使用新的AjaxModelLoader 代替以前的form_ajax_refs 方法,如下所示:

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

不幸的是,无论是用我的查询覆盖session 还是model 的值都不会从AJAX 加载器返回任何产品,而覆盖fields 仍然会返回所有产品;不仅仅是 org_id "Google" 的产品。

我希望不采取的措施

我希望能够不必为每个组织创建新模型的情况下实现这一目标,因为这将被证明是不可扩展的并且设计不佳。

欢迎提出任何建议。谢谢。

【问题讨论】:

标签: python flask sqlalchemy flask-admin


【解决方案1】:

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

像这样覆盖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()
    )

【讨论】:

  • 知道如何基于使用另一个字段而不是静态字符串(上面的“Google”)来执行此操作,即我在一个字段中选择组织,然后基于产品的下拉列表能够例如,根据 id 过滤。
  • 首先将所选字段存储在模型中会更可行,而不是让下拉列表动态填充相同表单中其他字段的内容。在这种情况下,我将创建一个单独的模型视图来存储组织。否则,您可能正在考虑做一些自定义 Javascript。
  • 我认为会的,我希望您可能需要并且已经完成了艰苦的工作:) 我将尝试制定一个通用解决方案,因为我的管理门户需要这个在几种不同的情况下。
  • 我认为我设法实现了这一点。从 QueryAjaxModelLoader 我修改了 init 以接受和附​​加过滤器 arg,我还将 get_list 覆盖为 and_ 过滤器。要生成表单,我必须修改 AjaxSelect2Widget call 以确保将我的过滤器字段添加到生成的 html 中。最后,我修改了 form-1.0.0.js processAjaxWidget 以将过滤器信息作为查询的一部分传回,
  • @nickjb 如果您能够分享一个非常有帮助的要点。基于另一个字段的值来约束字段值一定是一种普遍的需求。
【解决方案2】:

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

这是一个通用类,可以对外键表进行过滤。

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'])

希望对您有所帮助。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2021-01-02
    • 2018-04-15
    • 1970-01-01
    • 1970-01-01
    • 2015-12-18
    • 2020-08-11
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多