Как пройти GenericForeignKey в Джанго?

Я использую Django v1.9.4 с PostgreSQL 9.2.14 позади. Со следующими моделями:

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 в django, попытка фильтрации вызывает исключение с сообщением, предлагающим добавить 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?

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


Предостережение для будущих читателей: шаблоны related_query_name на GenericRelation не работает должным образом на Django 1.9.

Добавлено в Django 1.10, теперь теперь related_query_name поддерживает интерполяцию меток и классов приложений с использованием строк "%(app_label)s" и "%(class)s" после объединения исправления для #25354.

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

1 ответ

Решение

В общем, невозможно пройти GenericForeignKey в этом направлении, как вы пытаетесь. 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'))
Другие вопросы по тегам