Предполагается ли, что django prefetch_relation будет работать с GenericRelation?Python

Программы на Python
Ответить
Anonymous
 Предполагается ли, что django prefetch_relation будет работать с GenericRelation?

Сообщение Anonymous »

ОБНОВЛЕНИЕ 2026:
Начиная с Django 5.0 появился новый класс GenericPrefetch, который следует использовать для предварительной выборки GenericForeignKey

Код: Выделить всё

TaggedItem.objects.prefetch_related(GenericPrefetch(
"content_object", [Bookmark.objects.all(), Animal.objects.all()]
)).all()
ОБНОВЛЕНИЕ 2022 ГОДА: Исходный номер с галочкой #24272, который я открыл 8 лет назад по поводу этой проблемы, теперь закрыт в пользу #33651, который после реализации даст нам новый синтаксис для выполнения этого типа предварительной выборки.
КОНЕЦ ОБНОВЛЕНИЯ
Что это такое?
В Django есть класс GenericRelation, который добавляет обратное» универсальное отношение для включения дополнительного API.
Оказывается, мы можем использовать это обратное обобщенное отношение для фильтрации или упорядочивания, но мы не можем использовать его внутри prefetch_related.
Мне интересно, ошибка ли это или это не должно работать, или это можно реализовать в этой функции.
Позвольте мне показать вам на нескольких примерах, что я имею в виду.
Предположим, у нас есть две основные модели: фильмы и книги. И мы хотим назначить теги нашим фильмам и книгам, но вместо использования моделей MovieTag и BookTag мы хотим использовать один класс TaggedItem с GFK для фильма или книги.
Вот структура модели:

Код: Выделить всё

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 через это обратное общее отношение, мы получим AttributeError.

Код: Выделить всё

>>> TaggedItem.objects.all().prefetch_related('books')
Traceback (most recent call last):
...
AttributeError: 'Book' object has no attribute 'object_id'
Некоторые из вас могут спросить, почему я просто не использую здесь content_object вместо книг? Причина в том, что это работает только тогда, когда мы этого хотим:
  • Код: Выделить всё

    prefetch
    только на один уровень глубже наборов запросов, содержащих разные типы content_object.

    Код: Выделить всё

    >>> TaggedItem.objects.all().prefetch_related('content_object')
    [, , , ]
    
  • Код: Выделить всё

    prefetch
    много уровней, но из наборов запросов, содержащих только один тип content_object.

    Код: Выделить всё

    >>> TaggedItem.objects.filter(books__author__name='E L James').prefetch_related('content_object__author')
    [, , ]
    
Но если мы хотим и 1), и 2) (предварительно получить множество уровней из набора запросов, содержащих разные типы content_objects), мы не можем использовать content_object.

Код: Выделить всё

>>> TaggedItem.objects.all().prefetch_related('content_object__author')
Traceback (most recent call last):
...
AttributeError: 'Movie' object has no attribute 'author_id'
считает, что все content_objects являются Книгами, и поэтому у них есть Автор.
Теперь представьте себе ситуацию, когда мы хотим предварительно загрузить не только книги с их автором, но и фильмы с их режиссером. Вот несколько попыток.
Глупый способ:

Код: Выделить всё

>>> TaggedItem.objects.all().prefetch_related(
...     'content_object__author',
...     'content_object__director',
... )
Traceback (most recent call last):
...
AttributeError: 'Movie' object has no attribute 'author_id'
Может быть, с помощью специального объекта Prefetch?

Код: Выделить всё

>>> 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'
Но, как вы можете видеть, мы всегда получаем эту AttributeError.
Я использую 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()
Но здесь все по-другому. Джанго на самом деле пытается разрешить эту связь... и терпит неудачу. Это ошибка, о которой следует сообщить? Я никогда ничего не сообщал Джанго, поэтому сначала спрашиваю здесь. Я не могу отследить ошибку и решить для себя, является ли это ошибкой или функцией, которую можно реализовать.
Ответить

Быстрый ответ

Изменение регистра текста: 
Смайлики
:) :( :oops: :roll: :wink: :muza: :clever: :sorry: :angel: :read: *x)
Ещё смайлики…
   
К этому ответу прикреплено по крайней мере одно вложение.

Если вы не хотите добавлять вложения, оставьте поля пустыми.

Максимально разрешённый размер вложения: 15 МБ.

Вернуться в «Python»