Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Django: select_related and GenericRelation

Does select_related work for GenericRelation relations, or is there a reasonable alternative? At the moment Django's doing individual sql calls for each item in my queryset, and I'd like to avoid that using something like select_related.

class Claim(models.Model):
    proof = generic.GenericRelation(Proof)


class Proof(models.Model):
    content_type = models.ForeignKey(ContentType)
    object_id = models.PositiveIntegerField()
    content_object = generic.GenericForeignKey('content_type', 'object_id')

I'm selecting a bunch of Claims, and I'd like the related Proofs to be pulled in instead of queried individually.

like image 201
Parand Avatar asked May 30 '10 17:05

Parand


People also ask

What's the difference between Select_related and Prefetch_related in Django ORM?

select_related() “follows” foreign-key relationships, selecting additional related-object data when it executes its query. prefetch_related() does a separate lookup for each relationship and does the “joining” in Python.

What is Select_related Django?

Django offers a QuerySet method called select_related() that allows you to retrieve related objects for one-to-many relationships. This translates to a single, more complex QuerySet, but you avoid additional queries when accessing the related objects. The select_related method is for ForeignKey and OneToOne fields.

What is QuerySet?

A QuerySet is a collection of data from a database. A QuerySet is built up as a list of objects. QuerySets makes it easier to get the data you actually need, by allowing you to filter and order the data.


1 Answers

There isn't a built-in way to do this. But I've posted a technique for simulating select_related on generic relations on my blog.


Blog content summarized:

We can use Django's _content_object_cache field to essentially create our own select_related for generic relations.

generics = {}
for item in queryset:
    generics.setdefault(item.content_type_id, set()).add(item.object_id)

content_types = ContentType.objects.in_bulk(generics.keys())

relations = {}
for ct, fk_list in generics.items():
    ct_model = content_types[ct].model_class()
    relations[ct] = ct_model.objects.in_bulk(list(fk_list))

for item in queryset:
    setattr(item, '_content_object_cache', 
            relations[item.content_type_id][item.object_id])

Here we get all the different content types used by the relationships in the queryset, and the set of distinct object IDs for each one, then use the built-in in_bulk manager method to get all the content types at once in a nice ready-to-use dictionary keyed by ID. Then, we do one query per content type, again using in_bulk, to get all the actual object.

Finally, we simply set the relevant object to the _content_object_cache field of the source item. The reason we do this is that this is the attribute that Django would check, and populate if necessary, if you called x.content_object directly. By pre-populating it, we're ensuring that Django will never need to call the individual lookup - in effect what we're doing is implementing a kind of select_related() for generic relations.

like image 167
Daniel Roseman Avatar answered Oct 26 '22 02:10

Daniel Roseman