Opened 8 years ago

Closed 8 years ago

Last modified 8 years ago

#26676 closed Bug (fixed)

Prefetch with to_attr shouldn't store its result in the referenced relation cache

Reported by: Arnaud B Owned by: nobody
Component: Database layer (models, ORM) Version: 1.10
Severity: Release blocker Keywords: Prefetch prefetch_related queryset
Cc: Triage Stage: Ready for checkin
Has patch: yes Needs documentation: no
Needs tests: no Patch needs improvement: no
Easy pickings: no UI/UX: no

Description

When using prefetch_related with a Prefetch whose to_attr is set, the related object returns a list instead of a queryset, making chaining queries impossible. This bug affects both version 1.9 and 1.10, but to a different extent.

This minimal example illustrates the issue, the test fails with AttributeError: 'list' object has no attribute 'filter'

model'

from django.db import models

class Book(models.Model):
    name = models.CharField(max_length=12)

class Page(models.Model):
    number = models.SmallIntegerField()
    text = models.TextField()
    book = models.ForeignKey(Book)

tests.py

from django.test import TestCase
from alpha_test.models import Book, Page
from django.db.models import Q, Prefetch


class SimpleCase(TestCase):

    def setUp(self):

        book = Book.objects.create(name="Django Textbook")
        page1 = Page.objects.create(number=1, text="lorem", book=book)
        page2 = Page.objects.create(number=2, text="ipsum", book=book)

    def test_prefech_related_with_Prefetch(self):

        queryset = Page.objects.filter(number=1)
        prefetch_no_attr = Prefetch('page_set', queryset=queryset)
        prefetch_with_attr = Prefetch('page_set', queryset=queryset,
                                      to_attr='first_page')
        
        # Selecting the book with the Prefetch without to_attr
        # it is possible to chain queries on the page_set
        book = Book.objects.prefetch_related(prefetch_no_attr).first()
        first_page = book.page_set.filter(number=1) # works fine

        # Now with Prefetch with to_attr set to some value,
        # the same query on page_set fails for 1.10 (but works for 1.9).
        book = Book.objects.prefetch_related(prefetch_with_attr).first()
        first_page = book.page_set.filter(number=1)
        # However, querying on the to_attr fails for both 1.9 and 1.10
        first_page = book.first_page.filter(text="lorem")

Change History (5)

comment:1 by Simon Charette, 8 years ago

Severity: NormalRelease blocker
Summary: prefetch_related incorrectly returns list instead of queryset when using Prefetch with to_attrPrefetch with to_attr shouldn't store its result in the referenced relation cache
Triage Stage: UnreviewedAccepted
Version: master1.10

Hi Ursidours,

Thanks for your very detailed report.

As documented, when using to_attr the prefetched result is stored in a list. If you'd like to request this to be changed I suggest you open a new ticket for this purpose.

Now, using to_attr shouldn't prefetch and store results for the referenced relation (unless the referenced relation equals to_attr). It should be left untouched and that's a regression in 1.10 introduced by bdbe50a491ca41e7d4ebace47bfe8abe50a58211.

Based on your model definition a simplified test case could look like:

first_pages = Page.objects.filter(number=1)
books = Book.objects.prefetch_related(
    Prefetch('page_set', queryset=first_pages, to_attr='first_pages')
)
book = book.first()
assert list(book.page_set.filter(number=1)) == book.first_pages

comment:2 by Simon Charette, 8 years ago

Has patch: set

comment:3 by Tim Graham, 8 years ago

Triage Stage: AcceptedReady for checkin

comment:4 by Simon Charette <charette.s@…>, 8 years ago

Resolution: fixed
Status: newclosed

In 53a5fb3c:

Fixed #26676 -- Prevented prefetching to_attr from caching its result in through attr.

Thanks Ursidours for the report.

comment:5 by Simon Charette <charette.s@…>, 8 years ago

In 58f0d40b:

[1.10.x] Fixed #26676 -- Prevented prefetching to_attr from caching its result in through attr.

Thanks Ursidours for the report.

Backport of 53a5fb3cc0137bebeebc0d4d321dbfe20397b065 from master

Note: See TracTickets for help on using tickets.
Back to Top