2015-01-24 20 views
24

GÜNCELLEME: 24272Django GenericRelation ile çalışmak gerekiyordu prefetch_related mi

tüm ilgili neler var: Bu konuda Bir Açık Çok keyifli?

Django ekleyen GenericRelation sınıfına sahiptir ek API etkinleştirmek için jenerik bir ilişki “ters”.

Biz filtering veya ordering için bu reverse-generic-relation kullanabilirsiniz çıkıyor, ama biz prefetch_related içine kullanamaz.

Bunun bir hata mı, yoksa çalışması gerekmiyor mu, yoksa özellikte uygulanabilecek bir şey mi olduğunu merak ediyordum.

Size ne demek istediğimi örneklerle göstereyim.

İki ana modelimiz olduğunu varsayalım: Movies ve Books.

  • Movies bir Director
  • Books bir Author

var olması Ve biz Movies ve Books bizim etiket atamak istediğiniz, ancak bunun yerine MovieTag ve BookTag modellerini kullanarak nedeniyle, tek kullanmak istiyorum sınıfı, GFK - Movie veya Book.

from django.db import models 
from django.contrib.contenttypes.fields import GenericForeignKey, GenericRelation 
from django.contrib.contenttypes.models import ContentType 


class TaggedItem(models.Model): 
    tag = models.SlugField() 
    content_type = models.ForeignKey(ContentType) 
    object_id = models.PositiveIntegerField() 
    content_object = GenericForeignKey('content_type', 'object_id') 

    def __unicode__(self): 
     return self.tag 


class Director(models.Model): 
    name = models.CharField(max_length=100) 

    def __unicode__(self): 
     return self.name 


class Movie(models.Model): 
    name = models.CharField(max_length=100) 
    director = models.ForeignKey(Director) 
    tags = GenericRelation(TaggedItem, related_query_name='movies') 

    def __unicode__(self): 
     return self.name 


class Author(models.Model): 
    name = models.CharField(max_length=100) 

    def __unicode__(self): 
     return self.name 


class Book(models.Model): 
    name = models.CharField(max_length=100) 
    author = models.ForeignKey(Author) 
    tags = GenericRelation(TaggedItem, related_query_name='books') 

    def __unicode__(self): 
     return self.name 

Ve bazı ilk veri:

>>> from tags.models import Book, Movie, Author, Director, TaggedItem 
>>> a = Author.objects.create(name='E L James') 
>>> b1 = Book.objects.create(name='Fifty Shades of Grey', author=a) 
>>> b2 = Book.objects.create(name='Fifty Shades Darker', author=a) 
>>> b3 = Book.objects.create(name='Fifty Shades Freed', author=a) 
>>> d = Director.objects.create(name='James Gunn') 
>>> m1 = Movie.objects.create(name='Guardians of the Galaxy', director=d) 
>>> t1 = TaggedItem.objects.create(content_object=b1, tag='roman') 
>>> t2 = TaggedItem.objects.create(content_object=b2, tag='roman') 
>>> t3 = TaggedItem.objects.create(content_object=b3, tag='roman') 
>>> t4 = TaggedItem.objects.create(content_object=m1, tag='action movie') 

Yani docs gösterisi olarak böyle şeyler yapabilirim İşte

modeli yapıdır. Bu reverse generic relation aracılığıyla prefetch TaggedItem bazı related data çalışırsanız

>>> b1.tags.all() 
[<TaggedItem: roman>] 
>>> m1.tags.all() 
[<TaggedItem: action movie>] 
>>> TaggedItem.objects.filter(books__author__name='E L James') 
[<TaggedItem: roman>, <TaggedItem: roman>, <TaggedItem: roman>] 
>>> TaggedItem.objects.filter(movies__director__name='James Gunn') 
[<TaggedItem: action movie>] 
>>> Book.objects.all().prefetch_related('tags') 
[<Book: Fifty Shades of Grey>, <Book: Fifty Shades Darker>, <Book: Fifty Shades Freed>] 
>>> Book.objects.filter(tags__tag='roman') 
[<Book: Fifty Shades of Grey>, <Book: Fifty Shades Darker>, <Book: Fifty Shades Freed>] 

Ama, biz bir AttributeError almak için gidiyoruz.

>>> TaggedItem.objects.all().prefetch_related('books') 
Traceback (most recent call last): 
    ... 
AttributeError: 'Book' object has no attribute 'object_id' 

Ben sadece yerine burada books ait content_object kullanmayın neden Bazılarınız sorabilir miyim acaba? Nedeni, çünkü bu yalnızca aşağıdakileri yapmak istediğimizde çalışır: 1) querysets'dan yalnızca bir düzey, farklı türde content_object içeren.

>>> TaggedItem.objects.all().prefetch_related('content_object') 
[<TaggedItem: roman>, <TaggedItem: roman>, <TaggedItem: roman>, <TaggedItem: action movie>] 

2) prefetch pek çok düzeyde fakat querysets den content_object yalnızca bir türünü içeren.ikimiz de 1 istiyorum) ve content_objects farklı içeren queryset 2) (için prefetch birçok seviyeleri ise

Ama biz kullanamaz content_object.
>>> TaggedItem.objects.all().prefetch_related('content_object__author') 
Traceback (most recent call last): 
    ... 
AttributeError: 'Movie' object has no attribute 'author_id' 

Django

tüm content_objects düşünüyor Books olup, bu nedenle Author.

Şimdiiçin istediğimiz durumu hayal edin.sadece booksauthor, aynı zamanda onların director ile movies onların ile. İşte birkaç deneme.

saçma yolu:

>>> TaggedItem.objects.all().prefetch_related(
...  'content_object__author', 
...  'content_object__director', 
...) 
Traceback (most recent call last): 
    ... 
AttributeError: 'Movie' object has no attribute 'author_id' 

Belki özel ile Prefetch nesne?

>>> 
>>> TaggedItem.objects.all().prefetch_related(
...  Prefetch('content_object', queryset=Book.objects.all().select_related('author')), 
...  Prefetch('content_object', queryset=Movie.objects.all().select_related('director')), 
...) 
Traceback (most recent call last): 
    ... 
ValueError: Custom queryset can't be used for this lookup. 

bu sorunun bazı çözümler here gösterilmiştir. Ama kaçınmak istediğim veriler üzerinde çok fazla masaj var. Gerçekten reversed generic relations gelen API gibi, böyle prefetchs yapabilmek için çok güzel olurdu:

>>> TaggedItem.objects.all().prefetch_related(
...  'books__author', 
...  'movies__director', 
...) 
Traceback (most recent call last): 
    ... 
AttributeError: 'Book' object has no attribute 'object_id' 

Ya böyle

:

>>> TaggedItem.objects.all().prefetch_related(
...  Prefetch('books', queryset=Book.objects.all().select_related('author')), 
...  Prefetch('movies', queryset=Movie.objects.all().select_related('director')), 
...) 
Traceback (most recent call last): 
    ... 
AttributeError: 'Book' object has no attribute 'object_id' 

Ama gördüğünüz gibi, biz uzağa o AttributeError olsun. Django 1.7.3 ve Python 2.7.6 kullanıyorum. Ve Django'nun bu hatayı neden attığını merak ediyorum? Neden Django, Book modelinde object_id modelini arıyor? Neden bu bir hata olabilir sence?

>>> TaggedItem.objects.all().prefetch_related('some_field') 
Traceback (most recent call last): 
    ... 
AttributeError: Cannot find 'some_field' on TaggedItem object, 'some_field' is an invalid parameter to prefetch_related() 

Ama burada durum farklı: biz olamaz şey çözmek için prefetch_related sorduğunuzda Genellikle görürüz. Django aslında ilişkiyi çözmeye çalışır ve başarısız olur. Bu bildirilmesi gereken bir hata mı? Django'ya hiç bir şey bildirmedim, işte bu yüzden önce burada soruyorum. Hatayı izleyemiyorum ve bunun bir hata mı, yoksa uygulanabilecek bir özellik mi olduğuna kendim karar veremiyorum. Eğer Book örneklerini almak ve ilgili etiketler Book.objects.prefetch_related('tags') kullanmak ön getirmek istiyorsanız

+2

Tamam Django Kaynak bakarak Söyleyebilirm kullanmak gerekir kitapları yazarları almak istiyorsanız bu bir hata, ama sadece ... desteklenmez 'select_related()' Bu bir 'ForeignKey' ilişki olduğu gibi. Bunu “prefetch_related” ile birlikte kullanmak için, şu anda [desteklenmeyen] özel bir queryset kullanmanız gerekir (https://github.com/django/django/blob/84b6c768301096849f5589d7612b129c5445abd3/django/contrib/contenttypes/fields.py# L170) Django tarafından jenerik ilişkiler için. –

+0

Tamam, teşekkürler. Bu konuda [bir bilet açtım] (https://code.djangoproject.com/ticket/24272). Bu özellik ORM aracılığıyla yapacaktır birgün Umut:) – Todor

cevap

20

. Burada ters ilişkiyi kullanmaya gerek yok.

Ayrıca Django source code ilgili testlerin bir göz olabilir.

prefetch_related , on the other hand, does a separate lookup for each relationship, and does the ‘joining’ in Python. This allows it to prefetch many-to-many and many-to-one objects, which cannot be done using select_related, in addition to the foreign key and one-to-one relationships that are supported by select_related. It also supports prefetching of GenericRelation and GenericForeignKey .

GÜNCELLEME: Eğer sonucu sınırlamak isterseniz, TaggedItem.objects.all().prefetch_related('content_object') kullanabilirsiniz bir TaggedItem için content_object önceden getirmek için

Ayrıca Django documentationprefetch_related()GenericForeignKey ve GenericRelation çalışmak gerekiyordu belirtiyor ek olarak ContentType için filtreleyebileceğiniz Book nesnelerini etiketleyebilirsiniz (prefetch_related'un related_query_name ile çalışıp çalışmadığından emin değil). Ayrıca kitap ile birlikte Author almak istiyorsanız bu bir ForeignKey ilişki olarak select_related() değil prefetch_related() kullanmaya gerek, bir custom prefetch_related() query bu birleştirebilirsiniz:

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

book_ct = ContentType.objects.get_for_model(Book) 
TaggedItem.objects.filter(content_type=book_ct).prefetch_related(
    Prefetch(
     'content_object', 
     queryset=Book.objects.all().select_related('author') 
    ) 
) 
+0

Yanıtınız @Bernhard için teşekkür ederiz. Sorun ne ben gerçekten ihtiyaç ** içeren 'TaggetItem'' queryset' ** sadece 'books' onların' author' ile 'prefetch'' books' için olmasıdır. Bu, şöyle bir şeye ihtiyacım var: TaggedItem.objects.all(). Prefetch_related ('books__author'). Ama bu garip bir hata veriyor: ** AttributeError: 'Book' nesnesi 'object_id' ** özelliğine sahip değil. Bu konuda fikrinizi isteyebilir miyim? Size bildirilmesi gereken bir böcek gibi görünüyor mu? – Todor

+0

Bu sorunun bazı geçici çözümleri sunulmaktadır [burada] (http://stackoverflow.com/questions/12466945/django-prefetch-related-objects-of-a-genericforeignkey). Ama bu kaçınmak istediğim verilerin çok özel bir masajı. API'nin "tersine çevrilmiş genel ilişkiler" den hoşlandığını ve bunun böyle bir işlevsellik elde etmenin yolu olduğuna inanıyorum. – Todor

+0

@Todor Cevabımı güncelledim, şu anda deneyemiyorum ama umarım size doğru yönde işaret eder ... Bunu "related_query_name" ile denemek isteyebilirsiniz. content_type ... –

İlgili konular