Django: select_related and GenericRelation

2020-02-03 08:56发布

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.

3条回答
家丑人穷心不美
2楼-- · 2020-02-03 09:17

Looks like select_related and GRs don't work together. I guess you could write some kind of accessor for Claim that gets them all via the same query. This post gives you some pointers on raw SQL to get generic objects, if you need them

查看更多
对你真心纯属浪费
3楼-- · 2020-02-03 09:18

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.

查看更多
一纸荒年 Trace。
4楼-- · 2020-02-03 09:42

you can use .extra() function to manually extract fields :

Claims.filter(proof__filteryouwant=valueyouwant).extra(select={'field_to_pull':'proof_proof.field_to_pull'})

The .filter() will do the join, the .extra() will pull a field. proof_proof is the SQL table name for Proof model. If you need more than one field, specify each of them in the dictionnary.

查看更多
登录 后发表回答