Django使用相关模型的自定义管理器过滤相关字段

时间:2022-10-04 14:59:28

How can I apply annotations and filters from a custom manager queryset when filtering via a related field? Here's some code to demonstrate what I mean.

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

Manager and models

经理和模特

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

Attempting to filter the related field using the manager

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

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

If you try the above code you will get the following error:

如果您尝试上面的代码,您将收到以下错误:

TypeError: Related Field got invalid lookup: some_flag

TypeError:相关字段无效查找:some_flag

To further clarify, essentially the same question was reported as a bug with no response on how to actually achieve this: https://code.djangoproject.com/ticket/26393.

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

I'm aware that this can be achieved by simply using the filter and annotation from the manager directly in the MyModel filter, however the point is to keep this DRY and ensure this behaviour is repeated everywhere this model is accessed (unless explicitly instructed not to).

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

3 个解决方案

#1


3  

How about running nested queries (or two queries, in case your backend is MySQL; performance).

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

The first to fetch the pk of the related OtherModel objects.

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

The second to filter the Model objects on the fetched pks.

第二个是在获取的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


2  

I don't think what you want is possible.

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

1) I think you are miss-understanding what annotations do.

1)我认为你错过了解注释的作用。

Generating aggregates for each item in a QuerySet

The second way to generate summary values is to generate an independent summary for each object in a QuerySet. For example, if you are retrieving a list of books, you may want to know how many authors contributed to each book. Each Book has a many-to-many relationship with the Author; we want to summarize this relationship for each book in the QuerySet.

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

Per-object summaries can be generated using the annotate() clause. When an annotate() clause is specified, each object in the QuerySet will be annotated with the specified values.

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

The syntax for these annotations is identical to that used for the aggregate() clause. Each argument to annotate() describes an aggregate that is to be calculated.

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

So when you say:

所以当你说:

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

You are not annotation some_flag over other_model.
i.e. you won't have: mymodel.other_model.some_flag

你不是注释some_flag而不是other_model。即你不会:mymodel.other_model.some_flag

You are annotating other_model__some_flag over mymodel.
i.e. you will have: mymodel.other_model__some_flag

你在mymodel上注释了other_model__some_flag。即你将拥有:mymodel.other_model__some_flag

2) I'm not sure how familiar SQL is for you, but in order to preserve MyModel.objects.filter(other_model__some_flag=True) possible, i.e. to keep the annotation when doing JOINS, the ORM would have to do a JOIN over subquery, something like:

2)我不确定SQL对你来说有多熟悉,但是为了保留MyModel.objects.filter(other_model__some_flag = True),即在进行JOINS时保留注释,ORM必须在子查询上进行JOIN , 就像是:

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

which would be super slow and I'm not surprised they are not doing it.

这将是超级慢,我并不惊讶他们没有这样做。

Possible solution

don't annotate your field, but add it as a regular field in your model.

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

#3


1  

The simplified answer is that models are authoritative on the field collection and Managers are authoritative on collections of models. In your efforts to make it DRY you made it WET, cause you alter the field collection in your manager.

简化的答案是模型对字段集合具有权威性,而管理者对模型集合具有权威性。在你努力让它干涸的时候你做了WET,因为你改变了经理中的场地收集。

In order to fix it, you would have to teach the model about the lookup and need to do that using the Lookup API.

为了解决这个问题,你必须教会模型有关查找的信息,并且需要使用Lookup API来完成。

Now I'm assuming that you're not actually annotating with a fixed value, so if that annotation is in fact reducible to fields, then you may just get it done, because in the end it needs to be mapped to database representation.

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

#1


3  

How about running nested queries (or two queries, in case your backend is MySQL; performance).

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

The first to fetch the pk of the related OtherModel objects.

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

The second to filter the Model objects on the fetched pks.

第二个是在获取的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


2  

I don't think what you want is possible.

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

1) I think you are miss-understanding what annotations do.

1)我认为你错过了解注释的作用。

Generating aggregates for each item in a QuerySet

The second way to generate summary values is to generate an independent summary for each object in a QuerySet. For example, if you are retrieving a list of books, you may want to know how many authors contributed to each book. Each Book has a many-to-many relationship with the Author; we want to summarize this relationship for each book in the QuerySet.

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

Per-object summaries can be generated using the annotate() clause. When an annotate() clause is specified, each object in the QuerySet will be annotated with the specified values.

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

The syntax for these annotations is identical to that used for the aggregate() clause. Each argument to annotate() describes an aggregate that is to be calculated.

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

So when you say:

所以当你说:

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

You are not annotation some_flag over other_model.
i.e. you won't have: mymodel.other_model.some_flag

你不是注释some_flag而不是other_model。即你不会:mymodel.other_model.some_flag

You are annotating other_model__some_flag over mymodel.
i.e. you will have: mymodel.other_model__some_flag

你在mymodel上注释了other_model__some_flag。即你将拥有:mymodel.other_model__some_flag

2) I'm not sure how familiar SQL is for you, but in order to preserve MyModel.objects.filter(other_model__some_flag=True) possible, i.e. to keep the annotation when doing JOINS, the ORM would have to do a JOIN over subquery, something like:

2)我不确定SQL对你来说有多熟悉,但是为了保留MyModel.objects.filter(other_model__some_flag = True),即在进行JOINS时保留注释,ORM必须在子查询上进行JOIN , 就像是:

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

which would be super slow and I'm not surprised they are not doing it.

这将是超级慢,我并不惊讶他们没有这样做。

Possible solution

don't annotate your field, but add it as a regular field in your model.

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

#3


1  

The simplified answer is that models are authoritative on the field collection and Managers are authoritative on collections of models. In your efforts to make it DRY you made it WET, cause you alter the field collection in your manager.

简化的答案是模型对字段集合具有权威性,而管理者对模型集合具有权威性。在你努力让它干涸的时候你做了WET,因为你改变了经理中的场地收集。

In order to fix it, you would have to teach the model about the lookup and need to do that using the Lookup API.

为了解决这个问题,你必须教会模型有关查找的信息,并且需要使用Lookup API来完成。

Now I'm assuming that you're not actually annotating with a fixed value, so if that annotation is in fact reducible to fields, then you may just get it done, because in the end it needs to be mapped to database representation.

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