Начиная с Django 5.0 появился новый класс GenericPrefetch, который следует использовать для предварительной выборки GenericForeignKey
Код: Выделить всё
TaggedItem.objects.prefetch_related(GenericPrefetch(
"content_object", [Bookmark.objects.all(), Animal.objects.all()]
)).all()
КОНЕЦ ОБНОВЛЕНИЯ
Что это такое?
В Django есть класс GenericRelation, который добавляет обратное» универсальное отношение для включения дополнительного API.
Оказывается, мы можем использовать это обратное обобщенное отношение для фильтрации или упорядочивания, но мы не можем использовать его внутри prefetch_related.
Мне интересно, ошибка ли это или это не должно работать, или это можно реализовать в этой функции.
Позвольте мне показать вам на нескольких примерах, что я имею в виду.
Предположим, у нас есть две основные модели: фильмы и книги.
- есть директор
Код: Выделить всё
Movies - имеет автора
Код: Выделить всё
Books
Вот структура модели:
Код: Выделить всё
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
Код: Выделить всё
>>> 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')
Код: Выделить всё
>>> b1.tags.all()
[]
>>> m1.tags.all()
[]
>>> TaggedItem.objects.filter(books__author__name='E L James')
[, , ]
>>> TaggedItem.objects.filter(movies__director__name='James Gunn')
[]
>>> Book.objects.all().prefetch_related('tags')
[, , ]
>>> Book.objects.filter(tags__tag='roman')
[, , ]
Код: Выделить всё
>>> TaggedItem.objects.all().prefetch_related('books')
Traceback (most recent call last):
...
AttributeError: 'Book' object has no attribute 'object_id'
- только на один уровень глубже наборов запросов, содержащих разные типы content_object.
Код: Выделить всё
prefetchКод: Выделить всё
>>> TaggedItem.objects.all().prefetch_related('content_object') [, , , ] - много уровней, но из наборов запросов, содержащих только один тип content_object.
Код: Выделить всё
prefetchКод: Выделить всё
>>> TaggedItem.objects.filter(books__author__name='E L James').prefetch_related('content_object__author') [, , ]
Код: Выделить всё
>>> TaggedItem.objects.all().prefetch_related('content_object__author')
Traceback (most recent call last):
...
AttributeError: 'Movie' object has no attribute 'author_id'
Код: Выделить всё
DjangoТеперь представьте себе ситуацию, когда мы хотим предварительно загрузить не только книги с их автором, но и фильмы с их режиссером. Вот несколько попыток.
Глупый способ:
Код: Выделить всё
>>> TaggedItem.objects.all().prefetch_related(
... 'content_object__author',
... 'content_object__director',
... )
Traceback (most recent call last):
...
AttributeError: 'Movie' object has no attribute 'author_id'
Код: Выделить всё
>>> 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.
Мне очень нравится API, основанный на обратных общих отношениях, было бы очень здорово иметь возможность выполнять предварительную выборку вот так:
Код: Выделить всё
>>> TaggedItem.objects.all().prefetch_related(
... 'books__author',
... 'movies__director',
... )
Traceback (most recent call last):
...
AttributeError: 'Book' object has no attribute 'object_id'
Код: Выделить всё
>>> 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'
Я использую Django 1.7.3 и Python 2.7.6. И мне любопытно, почему Django выдает эту ошибку? Почему Django ищет object_id в модели Book?
Почему я думаю, что это может быть ошибка?
Обычно, когда мы просим prefetch_related разрешить что-то, что он не может, мы видим:
Код: Выделить всё
>>> 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()
Мобильная версия