【问题标题】:Django filter related field using related model's custom managerDjango使用相关模型的自定义管理器过滤相关字段
【发布时间】:2017-11-17 09:32:06
【问题描述】:

通过相关字段进行过滤时,如何应用自定义管理器查询集中的注释和过滤器?这里有一些代码来说明我的意思。

经理和模型

from django.db.models import Value, BooleanField

class OtherModelManager(Manager):
    def get_queryset(self):
        return super(OtherModelManager, self).get_queryset().annotate(
            some_flag=Value(True, output_field=BooleanField())
        ).filter(
            disabled=False
        )

class MyModel(Model):
    other_model = ForeignKey(OtherModel)

class OtherModel(Model):
    disabled = BooleanField()

    objects = OtherModelManager()

正在尝试使用管理器过滤相关字段

# This should only give me MyModel objects with related 
# OtherModel objects that have the some_flag annotation 
# set to True and disabled=False
my_model = MyModel.objects.filter(some_flag=True)

如果你尝试上面的代码,你会得到以下错误:

TypeError: Related Field got invalid lookup: some_flag

为了进一步澄清,基本上相同的问题被报告为一个错误,没有回应如何实际实现这一点:https://code.djangoproject.com/ticket/26393

我知道这可以通过直接在 MyModel 过滤器中使用来自管理器的过滤器和注释来实现,但关键是要保持此 DRY 并确保此行为在任何地方重复 em> 访问此模型(除非明确指示不要访问)。

【问题讨论】:

  • =True 在任意名称“some_flag”之后是什么意思?写一个示例如何手动获得正确的结果,因为我在最新的 Django 中得到了错误AttributeError: 'bool' object has no attribute 'resolve_expression',原因是简单的值True。不清楚你想要什么? (顺便说一句,似乎在上面的链接中它与您无关,并且原始发布者接受它不是错误报告的错误,而只是误解。)
  • 这是一个人为的例子来演示这个问题;不管我是否已更新注释以使其现在可以正常工作。您要求的示例很明显,只需将代码从管理器移至查询MyModel.objects.annotate(other_model__some_flag=Value(True, output_field=BooleanField())).filter(other_model__some_flag=True, other_model__disabled=False)。该链接是完全相同的问题,它被报告为一个错误,因为作者认为相关的 Django 功能可以解决它,但它没有。没有发布关于如何解决问题的回复。
  • 不,我想看看你声称的代码对你来说是正确的,但它还不够干,我希望我能干。您的“明显”示例肯定是不正确的,因为它在内部编译为SELECT app_mymodel.id,... , True AS "other_model__some_flag" FROM ... SQL 可以通过any_queryset.query.get_compiler('default').as_sql() 轻松验证。也许解释清楚,例如通过 SQL 你想得到什么。是的,简化的示例很有用,但经过验证的示例通过简化没有其他不相关的错误,例如在def get_queryset 之后丢失的return
  • 很高兴您解释了您的努力,但您还应该使用您确信自己知道的术语来解释问题,以便有可能获得相关答案。 (它似乎还不是 Django ORM 术语。) 1)注释通常是某些组的某个字段(或逐个字段的表达式)的计数或求和、最小值、最大值或平均值。与常量 True 无关。 2)如果顺序不相关,通常在注释之前过滤比在注释之后更好。没有更多信息就无法决定。 V
  • @hynekcer 很抱歉让您失望,但您混淆了注释和聚合。注解的常见工作负载是在行级别添加计算字段(例如amount=F('price')+F('quantity')(简化)。正如您所说的聚合,适用于一组行。

标签: python django django-models django-orm


【解决方案1】:

如何运行嵌套查询(或两个查询,如果您的后端是 MySQL;性能)。

第一个获取相关OtherModel对象的pk。

第二个过滤获取的pks上的Model对象。

other_model_pks = OtherModel.objects.filter(some_flag=...).values_list('pk', flat=True)
my_model = MyModel.objects.filter(other_model__in=other_model_pks)
# use (...__in=list(other_model_pks)) for MySQL to avoid a nested query.

【讨论】:

    【解决方案2】:

    我不认为你想要什么是可能的。

    1) 我认为您误解了注释的作用。

    为 QuerySet 中的每个项目生成聚合

    生成汇总值的第二种方法是生成一个 QuerySet 中每个对象的独立摘要。例如,如果您 正在检索书籍列表,您可能想知道有多少作者 为每本书做出贡献。每本书都有多对多的关系 与作者;我们想为每本书总结这种关系 在查询集中。

    可以使用 annotate() 子句生成每个对象的摘要。 当指定 annotate() 子句时,QuerySet 中的每个对象 将使用指定的值进行注释。

    这些注释的语法与用于 aggregate() 子句。 annotate() 的每个参数都描述了一个聚合 这是要计算的。

    所以当你说:

    MyModel.objects.annotate(other_model__some_flag=Value(True, output_field=BooleanField()))
    

    你不是在other_model上注释some_flag
    即你不会有:mymodel.other_model.some_flag

    您在 mymodel 上注释 other_model__some_flag
    即您将拥有:mymodel.other_model__some_flag

    2) 我不确定SQL 对您来说有多熟悉,但为了尽可能保留MyModel.objects.filter(other_model__some_flag=True),即在执行JOINS 时保留注释,ORM 必须执行@ 987654335@ 超过 subquery,类似于:

    INNER JOIN 
    (
        SELECT other_model.id, /* more fields,*/ 1 as some_flag
        FROM other_model
    ) as sub on mymodel.other_model_id = sub.id
    

    这会非常慢,我并不奇怪他们不这样做。

    可能的解决方案

    不要注释您的字段,而是将其作为常规字段添加到您的模型中。

    【讨论】:

    • 其实我认为我们应该在注解之后有mymodel.some_flag 而不是mymodel.other_model__some_flag,我在示例中犯了这个错误。此外,您假设的 SQL 将取决于被注释的内容 - 例如,如果它是通过 F("some_field") 进行的字段引用,则不需要子查询;聚合相同。连接和子查询也不是很慢,这只是教条;无论哪种方式,它都与 Django 开发人员无关——ORM 可以做我需要它做的事情,包括如果需要的话,可以在脚上开枪 :)
    • 无论如何,您的解决方案显然适用于删除任何注释,但这并不能解决 disabled 字段上的隐式过滤。我担心你对“不可能”的看法可能是对的,至少没有破解 ORM 或做一些可怕的.extra()
    • @Ben 我没有解决隐式过滤,因为它会跨关系保持,只有注释不会。
    • 我认为情况并非如此。查看管理器上的文档here:Manager’s aren’t used when querying on related models. For example, if the Question model from the tutorial had a deleted field and a base manager that filters out instances with deleted=True, a queryset like Choice.objects.filter(question__name__startswith='What') would include choices related to deleted questions.
    • 啊,你是对的,这似乎是来自1.10 的新行为,我仍然使用 1.8,所以我错过了那个。我做了一些测试,似乎没有办法恢复旧的行为。毕竟,explicit is better than implicit ;)
    【解决方案3】:

    简化的答案是模型对字段集合具有权威性,而经理对模型集合具有权威性。在你努力让它变干的过程中,你把它弄湿了,因为你改变了你的经理中的字段集合。

    为了修复它,您必须向模型教授有关查找的知识,并且需要使用 Lookup API 进行此操作。

    现在我假设您实际上并没有使用固定值进行注释,所以如果该注释实际上可以简化为字段,那么您可能会完成它,因为最终它需要映射到数据库表示。

    【讨论】:

      猜你喜欢
      • 2018-05-01
      • 1970-01-01
      • 1970-01-01
      • 2017-06-22
      • 1970-01-01
      • 2020-08-15
      • 1970-01-01
      • 1970-01-01
      • 2020-01-30
      相关资源
      最近更新 更多