4

I have the following example:

class Profile(models.Model):
    ...

class Person(models.Model):
    profile = models.ForeignKey(Profile, ...)

I have complex Model manager for Profile class and I built a view to list a big amount of Person. I try to compute everything in database so I would like to call the Profile Manager from Person QuerySet.

To do that, I need to do something like:

Person.objects.filter(...).select_related('profile', queryset=Profile.objects.with_computed_revenue().all())

And then I should be able to get person.profile.computed_revenue retrieved from SQL, with the function "with_computed_revenue" being a function of the ProfileManager that annotate computed_revenue.

The final goal is to add in person queryset :

.values('profile__computed_revenue')

It seems possible with Prefetch for prefetch_related, but I cannot find an equivalent with select_related.

  • Do you have ```related_name='profile'``` in your ```Person``` model? – Ayush Gupta Aug 06 '21 at 10:04
  • 1
    Can you share the "with_computed_revenue" method and relevant fields on the Profile model? Using select_related this way I'm not sure is possible – Iain Shelvington Aug 06 '21 at 10:09
  • 1
    If you can live without values() working the way you want this is possible with Prefetch and a custom queryset – Iain Shelvington Aug 06 '21 at 10:23
  • I agree with you lain, I think the way I want it is not possible. with_computed_revenue is an annotation with complex Case / When and various condition (it's like 400 lines). Do you know to make Prefetch works with a ForeignKey relation ? I get this error: 'Profile' object has no attribute '_iterable_class' – Quentin Mortier Aug 06 '21 at 10:45
  • @QuentinMortier added an answer with an example – Iain Shelvington Aug 06 '21 at 11:00

2 Answers2

1

If i have got correctly what you mean, As Django docs says in https://docs.djangoproject.com/en/3.2/ref/models/querysets/#prefetch-related :

select_related works by creating an SQL join and including the fields of the related object in the SELECT statement. For this reason, select_related gets the related objects in the same database query. However, to avoid the much larger result set that would result from joining across a ‘many’ relationship, select_related is limited to single-valued relationships - foreign key and one-to-one. prefetch_related, on the other hand, does a separate lookup for each relationship, and does the ‘joining’ in Python. This allows it to prefetch many-to-many and many-to-one objects, which cannot be done using select_related, in addition to the foreign key and one-to-one relationships that are supported by select_related.

You should use select_related for FK relations and prefetch_related for Many-To-One relation

In your case, Person model has many-to-one relation to Profile, thus you have to use prefetch_related

Amin
  • 2,605
  • 2
  • 7
  • 15
  • I agree with that indeed, it should be prefetch_related. But when using it you get an error: .prefetch_related( Prefetch( 'profile', queryset=Profile.objects.with_computed_revenue().all().first(), to_attr='computed_profile' ) ) this will give: 'Profile' object has no attribute '_iterable_class' – Quentin Mortier Aug 06 '21 at 10:26
  • And prefetch related won't allow me to use values('profile__anything') right ? – Quentin Mortier Aug 06 '21 at 10:29
1

To use a custom queryset with select_related you can use prefetch_related and a Prefetch object

Person.objects.prefetch_related(
    Prefetch('profile', queryset=Profile.objects.with_computed_revenue())
)

This will not make the annotations available in values() however

Iain Shelvington
  • 31,030
  • 3
  • 31
  • 50