6

Sto usando Django v1.9.4 con PostgreSQL 9.2.14 dietro. Con i seguenti modelli:Come attraversare una GenericForeignKey in 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') 

Creare alcuni casi per dimostrare il mio problema:

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

Non posso attraversare la GFK in Django, cercando di filtro solleva un'eccezione con un messaggio che suggerisce di aggiungere il GenericRelation. Ma l'utilizzo della relazione generica, tramite il nome della query correlata bars, non funziona in modo affidabile. Per esempio:

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

Che cosa sto facendo di sbagliato?


cautelativa nota ai futuri lettori: Templating related_query_name su GenericRelation non funziona correttamente su Django 1.9.

Aggiunto in Django 1.10 era related_query_name now supports app label and class interpolation using the '%(app_label)s' and '%(class)s' strings, dopo che fix per #25354 è stato unito.

Se sei su Django 1.10, puoi andare avanti e mettere il GenericRelation sulla classe base astratta e modello come related_query_name='%(app_label)s_%(class)s' per garantire l'univocità tra le sottoclassi.

risposta

9

In generale, non è possibile attraversare un GenericForeignKey in questa direzione nel modo in cui si sta tentando. Un GenericForeignKey può puntare a qualsiasi modello nell'applicazione, non solo a Bar e alle sue sottoclassi. Per questo motivo, Foo.objects.filter(bar__somefield='some value') non può sapere quale modello di target hai in mente al momento, e quindi è impossibile dire quali campi ha quel modello di destinazione. Di fatto, non è possibile scegliere a quale tabella di database partecipare quando si esegue una query di questo tipo: potrebbe essere qualsiasi tabella, a seconda del valore di Foo.content_type.

Se si desidera utilizzare una relazione generica in join, sarà necessario definire uno GenericRelation all'altro capo della relazione. In questo modo puoi far sapere a Django quale modello deve cercare dall'altra parte.

Ad esempio, è possibile creare i modelli BarX e BarY come questo:

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

Se si fa questo, allora si può eseguire query come la seguente:

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

Tuttavia, è necessario scegliere un singolo modello di destinazione. Questa è una limitazione che non puoi veramente superare in alcun modo; ogni join di database ha bisogno di sapere in anticipo su quali tabelle opera.

Se è assolutamente necessario per consentire sia BarX e BarY come destinazione, si dovrebbe essere in grado di elencare ciascuno di essi in modo esplicito nel filtro query utilizzando un Q espressione:

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

OK così sembra essere un limitazione dovuta all'unione di sql. Ma per gli esempi nella mia domanda, perché django consente le domande a tutti? Non dovrebbe piuttosto sollevare un'eccezione, piuttosto che restituire risultati errati? – wim

+0

Sì, sembra un bug, non penso che dovrebbe permetterti di fare queste domande. Potresti forse aggiungere l'SQL generato da quelle query alla tua domanda? – koniiiik

+1

Ho giocato un po 'con la tua soluzione e ho trovato un'altra cosa interessante, potrebbe non essere necessario inserire il campo 'GenericRelation' su ogni sottoclasse con un unico' related_query_name'. Puoi effettivamente lasciarlo sulla classe base e metterlo a modello come 'related_query_name = '% (app_label) s _% (class) s'' per garantire l'unicità. – wim

Problemi correlati