2013-03-13 17 views
11

Staramy się przyspieszyć naszą aplikację z prefetch_related. Może on podążać za relacjami GenericForeignKey i może pójść głębiej z __, ale niestety nie powiedzie się, jeśli powiązany model nie ma takiego pola.Wiele pól do tej samej kolumny DB

Oto przykład modelowej struktury

class ModelA(models.Model): 
    event_object = models.ForeignKey(SomeModelA) 

class ModelB(models.Model): 
    event = models.ForeignKey(SomeModelB) 

class ModelC(models.Model): 
    content_type = models.ForeignKey(ContentType) 
    object_id = models.PositiveIntegerField() 
    content_object = generic.GenericForeignKey() 

Więc ModelC instancja może wskazywać albo ModelA lub ModelB. I mogę użyć takiego queryset wstępne pobieranie oba modele A i B: ModelC.objects.all().prefetch_related('content_object') Niestety ja również wybrać obiekt zdarzenia (SomeModelA lub SomeModelB)

Jeśli próbuję uruchomić

ModelC.objects.all().prefetch_related('content_object', 'content_object__event_object') 

to będzie działać, jeśli Występują tylko instancje ModelC, które wskazują na ModelA, ale w innym przypadku zakończy się niepowodzeniem, ponieważ ModelB nie ma pola event_object i zamiast tego ma .

Te modele są używane w wielu miejscach kodu, więc zmiana nazwy pola nie jest dobrym pomysłem. Zastanawiam się, czy istnieje sposób na stworzenie aliasu dla pola/kolumny.

starałem się zrobić tak:

class ModelB(models.Model): 
    event = models.ForeignKey(SomeModelB) 
    event_object = models.ForeignKey(SomeModelB, db_column='event_id', related_name='+') 

zrobić dwa pola, które wskazują na tej samej kolumnie w tabeli DB. Jednak to nie działa, ponieważ łamie metodę save. Django tworzy zapytanie SQL UPDATE, w którym jedna kolumna jest umieszczona dwukrotnie i otrzymuje DatabaseError

Czy istnieje sposób na stworzenie takiego aliasu? A może jest jeszcze jedno rozwiązanie, które sprawi, że prefetch_related nie będzie wyrzucać wyjątku?

Aktualizacja W save metodzie istnieje parametr update_fields, które mogą być stosowane w celu wykluczenia tego pola. Jednak został wprowadzony w wersji 1.5 i używamy wersji 1.4. Dlatego nadal szukam odpowiedzi.

Aktualizacja # 2: @ shx2 poprosił mnie o podanie informacji zwrotnej. Istnieją dwa możliwe przejścia zwrotne. 1-ty - gdy atrybut brakuje pierwszego obiektu:

Traceback (most recent call last): 
    File "<console>", line 1, in <module> 
    File "/home/igor/workspace/projectname/eggs/Django-1.4.2-py2.7.egg/django/db/models/query.py", line 72, in __repr__ 
    data = list(self[:REPR_OUTPUT_SIZE + 1]) 
    File "/home/igor/workspace/projectname/eggs/Django-1.4.2-py2.7.egg/django/db/models/query.py", line 97, in __iter__ 
    len(self) 
    File "/home/igor/workspace/projectname/eggs/Django-1.4.2-py2.7.egg/django/db/models/query.py", line 89, in __len__ 
    self._prefetch_related_objects() 
    File "/home/igor/workspace/projectname/eggs/Django-1.4.2-py2.7.egg/django/db/models/query.py", line 570, in _prefetch_related_objects 
    prefetch_related_objects(self._result_cache, self._prefetch_related_lookups) 
    File "/home/igor/workspace/projectname/eggs/Django-1.4.2-py2.7.egg/django/db/models/query.py", line 1664, in prefetch_related_objects 
    (attr, first_obj.__class__.__name__, lookup)) 
AttributeError: Cannot find 'event_object' on ModelB object, 'content_object__event_object' is an invalid parameter to prefetch_related() 

A jeśli prefetch_related parametry są ważne dla pierwszego obiektu następnie uzyskać 2nd traceback:

Traceback (most recent call last): 
    File "<console>", line 1, in <module> 
    File "/home/igor/workspace/projectname/eggs/Django-1.4.2-py2.7.egg/django/db/models/query.py", line 72, in __repr__ 
    data = list(self[:REPR_OUTPUT_SIZE + 1]) 
    File "/home/igor/workspace/projectname/eggs/Django-1.4.2-py2.7.egg/django/db/models/query.py", line 97, in __iter__ 
    len(self) 
    File "/home/igor/workspace/projectname/eggs/Django-1.4.2-py2.7.egg/django/db/models/query.py", line 89, in __len__ 
    self._prefetch_related_objects() 
    File "/home/igor/workspace/projectname/eggs/Django-1.4.2-py2.7.egg/django/db/models/query.py", line 570, in _prefetch_related_objects 
    prefetch_related_objects(self._result_cache, self._prefetch_related_lookups) 
    File "/home/igor/workspace/projectname/eggs/Django-1.4.2-py2.7.egg/django/db/models/query.py", line 1680, in prefetch_related_objects 
    obj_list, additional_prl = prefetch_one_level(obj_list, prefetcher, attr) 
    File "/home/igor/workspace/projectname/eggs/Django-1.4.2-py2.7.egg/django/db/models/query.py", line 1803, in prefetch_one_level 
    qs = getattr(obj, attname).all() 
AttributeError: 'ModelB' object has no attribute 'event_object' 
+0

Czy możesz dodać ślad zwrotny błędu 'prefetch_related'? – shx2

+0

Dlaczego potrzebujesz zestawu zapytań ModelC? Nie możesz po prostu zrobić 2 różnych zapytań i traktować je osobno? Wiem, że moje pytanie jest trochę naiwne, ale czasami problemem jest to, jak stajemy w obliczu tych trudności. – marianobianchi

+0

@marianobianchi nie, staramy się zoptymalizować jakąś część django admin, więc potrzebujemy pojedynczego zapytania. Również powyższe modele są uproszczone, mamy głębsze relacje w rzeczywistym projekcie – Igor

Odpowiedz

2

Wygląda jak błąd lub nadzoru w django. W celu obejścia tego problemu można spróbować zdefiniować niestandardowego menedżera, który wykonuje 2-etapowe wstępne pobieranie.

from django.db import models 
from django.db.models import Q 
from django.contrib.contenttypes.models import ContentType 

class PrefetchWorkaroundManager(models.Manager): 
    def get_queryset(self): 
     q = super(PrefetchWorkaroundManager, self).get_queryset() 
     content_typeA = ContentType.objects.get_for_model(ModelA) 
     content_typeB = ContentType.objects.get_for_model(ModelB) 
     return q.filter(content_type__pk = content_typeA.id).prefetch_related('content_object', 'content_object__event_object') | \ 
       q.filter(content_type__pk = content_typeB.id).prefetch_related('content_object', 'content_object__event') 

class ModelC(models.Model): 
    ... 

    objects_prefetched = PrefetchWorkaroundManager() 

Każdy rozmówca, który chce prefetching nastąpić powinno dostęp ModelC.objects_prefetched zamiast ModelC.objects:

ModelC.objects_prefetched.filter(...) 

Przyznaję, że nie testowałem go, więc to chyba nie działa jak jest. Uważam jednak, że takie podejście jest rozsądne.

+0

Dzięki za odpowiedź, ale niestety to nie działa. Kiedy używasz '|' w zestawach zapytań, buduje nowy zestaw zapytań. A "prefetch_related" jest oceniany, gdy sam zestaw zapytań jest oceniany. Nie jestem też pewien, czy jest to błąd w Django, czy nie, ale gdy używasz '|', używa on wyszukiwań prefetchowych tylko z pierwszego zapytania – Igor

Powiązane problemy