The field associated with the Django filter using the appropriate user model manager

How can I apply annotations and filters from a set of user manager queries when filtering through a linked field? Here is 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 a linked field using a 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 code above, you will get the following error:

TypeError: Related Field got invalid lookup: some_flag

For further clarification, in fact, the same question was reported as an unanswered error on how to actually achieve this: https://code.djangoproject.com/ticket/26393 .

I know that this can be achieved simply by using the filter and the annotation from the manager directly in the MyModel filter, however, you must save this DRY and ensure that this behavior repeats everywhere that this model refers to (unless explicitly specified).

+5
source share
3 answers

How to work with nested queries (or two queries, if your backend is MySQL, performance).

The first is to retrieve pk related OtherModel objects.

The second is to filter Model objects on selected pks.

 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. 
+3
source

I do not think you want.

1) I think you do not understand what annotations do.

Creating aggregates for each item in a QuerySet

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

Aggregate data for each object can be generated using the annotate () clause. When the annotate () condition is specified, each object in the QuerySet will be annotated with the specified values.

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

So when you say:

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

You are not some_flag annotation over other_model .
that is, you will not have: mymodel.other_model.some_flag

You annotate other_model__some_flag over mymodel .
that is, you will have: mymodel.other_model__some_flag

2) I'm not sure how familiar you are with SQL , but to keep MyModel.objects.filter(other_model__some_flag=True) possible, i.e. keep annotation when doing JOINS , ORM would have to do JOIN on subquery , something like:

 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 very slow, and I'm not surprised that they do not.

Possible Solution

do not comment on your field, but add it as a regular field in your model.

+2
source

The simplified answer is that models are authoritative in the field meeting, and managers are authoritative in the model collection. In your efforts to do this DRY you did it WET because you are modifying the collection of fields in your manager.

To fix this, you will need to teach the search model and do it using the search API .

Now I assume that you are not actually annotating a fixed value, so if this annotation actually comes down to fields, you can just do it because in the end it needs to be mapped to a database view.

+1
source

Source: https://habr.com/ru/post/1268857/


All Articles