6

Я использую Django v1.9.4 с PostgreSQL 9.2.14 позади. С помощью следующих моделей:Как проехать GenericForeignKey в Django?

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') 

Создайте несколько экземпляров, чтобы продемонстрировать мою проблему:

>>> 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' 

Я не могу пересечь GFK в Джанго, пытаясь фильтр вызывает исключение с сообщением предлагая добавить GenericRelation. Но использование общего отношения, связанное с запросом имя bars, не работает надежно. Например:

>>> [foo.bar.name for foo in Foo.objects.all()] 
[u'bar x', u'bar y'] # in a pure python loop, it's 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? 

Что я делаю неправильно?


Cautionary примечание к будущим читателям: Шаблонирования related_query_name на GenericRelation не работают должным образом на Django 1.9.

Добавлено в Django 1.10 было related_query_name now supports app label and class interpolation using the '%(app_label)s' and '%(class)s' strings, после того как fix для #25354 было слито.

Если вы находитесь на Django 1.10, вы можете пойти и поставить GenericRelation на абстрактный базовый класс и шаблон, как related_query_name='%(app_label)s_%(class)s', чтобы обеспечить уникальность в подклассах.

ответ

9

В общем, невозможно пройти через GenericForeignKey в этом направлении так, как вы пытаетесь. A GenericForeignKey может указывать на любую модель вашего приложения, а не только на Bar и его подклассы. По этой причине Foo.objects.filter(bar__somefield='some value') не может знать, какую целевую модель вы имеете в виду на данный момент, и поэтому невозможно определить, какие поля имеют целевые модели. На самом деле, нет способа выбрать, с какой таблицей базы данных можно присоединиться при выполнении такого запроса - это может быть любая таблица, в зависимости от значения Foo.content_type.

Если вы хотите использовать обобщенное отношение в объединениях, вам нужно будет определить GenericRelation на другом конце этого отношения. Таким образом, вы можете позволить Django узнать, какую модель он должен искать на другой стороне.

Например, вы можете создать свой BarX и BarY модели, как это:

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') 

Если вы сделаете это, то вы можете выполнять запросы, как в следующем:

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

Однако, у вас есть выбрать одну целевую модель. Это ограничение, которое вы никак не можете преодолеть; каждому соединению базы данных необходимо заранее знать, на каких таблицах он работает.

Если вы абсолютно необходимо, чтобы оба BarX и BarY в качестве цели, вы должны быть в состоянии перечислить их обоих явно в фильтре запроса с использованием Q выражение:

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

ОК, так что, как представляется, ограничение из-за соединения sql. Но для примеров в моем вопросе, почему django разрешает запросы вообще? Разве это не должно скорее вызвать исключение, чем вернуть неверные результаты? – wim

+0

Да, это похоже на ошибку, я не думаю, что это должно позволить вам делать такие запросы. Не могли бы вы добавить SQL-запрос, сгенерированный этими запросами, на ваш вопрос? – koniiiik

+1

Я немного поиграл с вашим решением и нашел еще одну интересную вещь, возможно, нет необходимости помещать поле «GenericRelation» в каждом подклассе с уникальным 'related_query_name'. Вы можете оставить его в базовом классе и создать его как 'related_query_name = '% (app_label) s _% (класс) s'' для обеспечения уникальности. – wim

Смежные вопросы