From b107421acfc4046ffaa799aceef2c3b4877207f2 Mon Sep 17 00:00:00 2001 From: Loic Bistuer Date: Wed, 13 Nov 2013 11:42:12 +0700 Subject: [PATCH] [1.6.x] Fixed #21410 -- prefetch_related() for ForeignKeys with related_name='+' Regression introduced by commit 9777442. Thanks to trac username troygrosfield for the report and test case. Backpatch of cb83448891f2920c926f61826d8eae4051a3d8f2 from master. Conflicts: tests/prefetch_related/models.py --- django/db/models/fields/related.py | 12 +++++++++++- tests/prefetch_related/models.py | 15 +++++++++++++++ tests/prefetch_related/tests.py | 28 +++++++++++++++++++++++++++- 3 files changed, 53 insertions(+), 2 deletions(-) diff --git a/django/db/models/fields/related.py b/django/db/models/fields/related.py index 2813a39567..d50c72d66c 100644 --- a/django/db/models/fields/related.py +++ b/django/db/models/fields/related.py @@ -273,7 +273,17 @@ class ReverseSingleRelatedObjectDescriptor(six.with_metaclass(RenameRelatedObjec rel_obj_attr = self.field.get_foreign_related_value instance_attr = self.field.get_local_related_value instances_dict = dict((instance_attr(inst), inst) for inst in instances) - query = {'%s__in' % self.field.related_query_name(): instances} + related_field = self.field.foreign_related_fields[0] + + # FIXME: This will need to be revisited when we introduce support for + # composite fields. In the meantime we take this practical approach to + # solve a regression on 1.6 when the reverse manager in hidden + # (related_name ends with a '+'). Refs #21410. + if self.field.rel.is_hidden(): + query = {'%s__in' % related_field.name: set(instance_attr(inst)[0] for inst in instances)} + else: + query = {'%s__in' % self.field.related_query_name(): instances} + qs = self.get_queryset(instance=instances[0]).filter(**query) # Since we're going to assign directly in the cache, # we must manage the reverse relation cache manually. diff --git a/tests/prefetch_related/models.py b/tests/prefetch_related/models.py index 82bf85e401..f94659113a 100644 --- a/tests/prefetch_related/models.py +++ b/tests/prefetch_related/models.py @@ -215,3 +215,18 @@ class WordEntry(models.Model): def __str__(self): return "%s (%s)" % (self.name, self.id) + + +## Ticket #21410: Regression when related_name="+" + +@python_2_unicode_compatible +class Author2(models.Model): + name = models.CharField(max_length=50, unique=True) + first_book = models.ForeignKey('Book', related_name='first_time_authors+') + favorite_books = models.ManyToManyField('Book', related_name='+') + + def __str__(self): + return self.name + + class Meta: + ordering = ['id'] diff --git a/tests/prefetch_related/tests.py b/tests/prefetch_related/tests.py index 2e3fee6be6..384457aba4 100644 --- a/tests/prefetch_related/tests.py +++ b/tests/prefetch_related/tests.py @@ -9,7 +9,7 @@ from django.utils import six from .models import (Author, Book, Reader, Qualification, Teacher, Department, TaggedItem, Bookmark, AuthorAddress, FavoriteAuthors, AuthorWithAge, BookWithYear, BookReview, Person, House, Room, Employee, Comment, - LessonEntry, WordEntry) + LessonEntry, WordEntry, Author2) class PrefetchRelatedTests(TestCase): @@ -651,3 +651,29 @@ class Ticket19607Tests(TestCase): def test_bug(self): list(WordEntry.objects.prefetch_related('lesson_entry', 'lesson_entry__wordentry_set')) + + +class Ticket21410Tests(TestCase): + + def setUp(self): + self.book1 = Book.objects.create(title="Poems") + self.book2 = Book.objects.create(title="Jane Eyre") + self.book3 = Book.objects.create(title="Wuthering Heights") + self.book4 = Book.objects.create(title="Sense and Sensibility") + + self.author1 = Author2.objects.create(name="Charlotte", + first_book=self.book1) + self.author2 = Author2.objects.create(name="Anne", + first_book=self.book1) + self.author3 = Author2.objects.create(name="Emily", + first_book=self.book1) + self.author4 = Author2.objects.create(name="Jane", + first_book=self.book4) + + self.author1.favorite_books.add(self.book1, self.book2, self.book3) + self.author2.favorite_books.add(self.book1) + self.author3.favorite_books.add(self.book2) + self.author4.favorite_books.add(self.book3) + + def test_bug(self): + list(Author2.objects.prefetch_related('first_book', 'favorite_books'))