From cb83448891f2920c926f61826d8eae4051a3d8f2 Mon Sep 17 00:00:00 2001 From: Loic Bistuer Date: Wed, 13 Nov 2013 11:42:12 +0700 Subject: [PATCH] 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. --- django/db/models/fields/related.py | 12 +++++++++++- tests/prefetch_related/models.py | 22 ++++++++++++++++++---- tests/prefetch_related/tests.py | 28 +++++++++++++++++++++++++++- 3 files changed, 56 insertions(+), 6 deletions(-) diff --git a/django/db/models/fields/related.py b/django/db/models/fields/related.py index 0f267f0d7c..ac48e30114 100644 --- a/django/db/models/fields/related.py +++ b/django/db/models/fields/related.py @@ -274,7 +274,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 076ca9d9d2..e1b844dde5 100644 --- a/tests/prefetch_related/models.py +++ b/tests/prefetch_related/models.py @@ -3,8 +3,8 @@ from django.contrib.contenttypes.models import ContentType from django.db import models from django.utils.encoding import python_2_unicode_compatible -## Basic tests +## Basic tests @python_2_unicode_compatible class Author(models.Model): @@ -80,8 +80,8 @@ class BookReview(models.Model): book = models.ForeignKey(BookWithYear) notes = models.TextField(null=True, blank=True) -## Models for default manager tests +## Models for default manager tests class Qualification(models.Model): name = models.CharField(max_length=10) @@ -167,7 +167,6 @@ class Comment(models.Model): ## Models for lookup ordering tests - class House(models.Model): address = models.CharField(max_length=255) owner = models.ForeignKey('Person', null=True) @@ -212,7 +211,7 @@ class Employee(models.Model): ordering = ['id'] -### Ticket 19607 +## Ticket #19607 @python_2_unicode_compatible class LessonEntry(models.Model): @@ -230,3 +229,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 599eb14f96..dc5793b487 100644 --- a/tests/prefetch_related/tests.py +++ b/tests/prefetch_related/tests.py @@ -10,7 +10,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): @@ -973,3 +973,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'))