6

Używam Django v1.9.4 z PostgreSQL 9.2.14 tyłu. Z następującymi modelami:Jak przejść przez GenericForeignKey w 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') 

Tworzenie kilka wystąpień do wykazania mój 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' 

nie mogę przechodzić przez GFK w Django, próbując filtra podnosi wyjątek z komunikatem sugerującym, aby dodać GenericRelation. Ale używanie relacji generycznej przez powiązaną nazwę zapytania bars nie działa niezawodnie. Na przykład:

>>> [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? 

Co robię źle?


Cautionary uwaga do przyszłych czytelników: Templating related_query_name na GenericRelation nie działa poprawnie w Django 1.9.

Dodano w Django 1.10 po related_query_name now supports app label and class interpolation using the '%(app_label)s' and '%(class)s' strings, po fix dla #25354 zostało scalone.

Jeśli używasz Django 1.10, możesz śmiało umieścić GenericRelation na abstrakcyjnej klasie bazowej i nadać mu szablon podobny do related_query_name='%(app_label)s_%(class)s', aby zapewnić unikalność w obrębie podklas.

Odpowiedz

9

Ogólnie rzecz biorąc, nie jest możliwe przechodzenie przez GenericForeignKey w tym kierunku w sposób, w jaki próbujesz. A GenericForeignKey może wskazywać na dowolny model w twojej aplikacji, nie tylko Bar i jej podklasach. Z tego powodu Foo.objects.filter(bar__somefield='some value') nie może wiedzieć, jaki model docelowy masz na myśli w danej chwili, a zatem nie można określić, jakie pola ma model docelowy. W rzeczywistości nie ma sposobu, aby wybrać tabelę bazy danych, z którą można się połączyć podczas wykonywania takiego zapytania - może to być dowolna tabela, w zależności od wartości Foo.content_type.

Jeśli chcesz użyć ogólnej relacji w złączeniach, musisz zdefiniować GenericRelation na drugim końcu tej relacji. W ten sposób możesz dać Django znać, którego modelu ma szukać po drugiej stronie.

Na przykład, można utworzyć BarX i BarY modele tak:

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

Jeśli to zrobisz, to można wykonać kwerendy jak poniżej:

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

Jednak trzeba wybrać jeden model docelowy. Jest to ograniczenie, którego w żaden sposób nie można pokonać; każde połączenie z bazą danych musi wcześniej wiedzieć, z których tabel korzysta.

Jeśli absolutnie konieczne, aby umożliwić zarówno BarX i BarY jako cel, należy być w stanie wymienić ich oboje wyraźnie w filtrze zapytania używając Q wyrażenie:

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

OK więc wydaje się być ograniczenie z powodu sprzężenia sql. Ale dla przykładów w moim pytaniu, dlaczego django w ogóle pozwala na zapytania? Czy nie powinien on raczej podnosić wyjątek, niż zwracać nieprawidłowe wyniki? – wim

+0

Tak, to wygląda na błąd, nie sądzę, że powinno pozwolić ci na takie zapytania. Czy mógłbyś dodać do zapytania SQL wygenerowany przez te zapytania? – koniiiik

+1

Grałem trochę z twoim rozwiązaniem i znalazłem inną interesującą rzecz, nie może być konieczne umieszczenie pola 'GenericRelation' na każdej podklasie z unikalną' related_query_name'. Możesz go zostawić na klasie bazowej i ustawić szablon jako 'related_query_name = '% (app_label) s _% (class) s'', aby zapewnić wyjątkowość. – wim

Powiązane problemy