How to cross GenericForeignKey in Django?

I am using Django v1.9.4 with PostgreSQL 9.2.14. With the following models:

from django.db import models from django.contrib.contenttypes.fields import GenericRelation, GenericForeignKey from django.contrib.contenttypes.models import ContentType class Foo(models.Model): content_type = models.ForeignKey(ContentType) object_id = models.PositiveIntegerField() bar = GenericForeignKey('content_type', 'object_id') class Bar(models.Model): foos = GenericRelation(Foo, related_query_name='bars') class Meta: abstract = True class BarX(Bar): name = models.CharField(max_length=10, default='bar x') class BarY(Bar): name = models.CharField(max_length=10, default='bar y') 

Create some examples to demonstrate my problem:

 >>> bar_x = BarX.objects.create() >>> bar_y = BarY.objects.create() >>> foo1 = Foo.objects.create(bar=bar_x) >>> foo2 = Foo.objects.create(bar=bar_y) >>> foo1.bar.name u'bar x' >>> foo2.bar.name u'bar y' 

I cannot go through GFK in django, trying to filter, GenericRelation an exception with a message suggesting to add GenericRelation . But using a common relation through the bars request name associated with it does not work reliably. For instance:

 >>> [foo.bar.name for foo in Foo.objects.all()] [u'bar x', u'bar y'] # in a pure python loop, it working >>> Foo.objects.filter(bar__name='bar x') FieldError: Field 'bar' does not generate an automatic reverse relation and therefore cannot be used for reverse querying. If it is a GenericForeignKey, consider adding a GenericRelation. >>> Foo.objects.values_list('bars__name', flat=1) [None, u'bar y'] # but why None is returned in here? >>> Foo.objects.filter(bars__name='bar x') [] # why no result here? >>> Foo.objects.filter(bars__name='bar y') [<Foo: Foo object>] # but this one works? 

What am I doing wrong?


Caveat for future readers: related_query_name on GenericRelation do not work properly in Django 1.9.

Added in Django 1.10 was related_query_name now supports application label and class interpolation using the% (app_label) s' and '% (class) s' / a> lines, after fix for # 25354 was merged.

If you are in Django 1.10, you can go ahead and put the GenericRelation in an abstract base class and template, like related_query_name='%(app_label)s_%(class)s' , to ensure uniqueness in subclasses.

+7
source share
1 answer

In general, it is not possible to go through the GenericForeignKey in this direction the way you are trying. A GenericForeignKey can point to any model in your application in general, and not just Bar and its subclasses. For this reason, Foo.objects.filter(bar__somefield='some value') cannot know which target model you have in mind at the moment, and therefore it is not possible to determine which fields have target models. In fact, there is no way to choose which database table you can join with when performing such a query - it can be any table depending on the value of Foo.content_type .

If you want to use a generic relation in joins, you will need to define a GenericRelation at the other end of this relationship. That way, you can let Django know which model it should look for on the other side.

For example, you can create your BarX and BarY as follows:

 class BarX(Bar): name = models.CharField(max_length=10, default='bar x') foos = GenericRelation(Foo, related_query_name='bar_x') class BarY(Bar): name = models.CharField(max_length=10, default='bar y') foos = GenericRelation(Foo, related_query_name='bar_y') 

If you do this, you can perform queries such as:

 Foo.objects.filter(bar_x__name='bar x') Foo.objects.filter(bar_y__name='bar y') 

However, you need to select one target model. This is a limitation that you cannot overcome in any way; Each database connection must know in advance in which tables it works.

If you absolutely must allow both BarX and BarY as the target, you must specify them explicitly in your request filter using Q :

 Foo.objects.filter(Q(bar_x__name='bar x') | Q(bar_y__name='bar y')) 
+12
source

All Articles