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.
This commit is contained in:
Loic Bistuer 2013-11-13 11:42:12 +07:00 committed by Anssi Kääriäinen
parent 0048ed77c7
commit cb83448891
3 changed files with 56 additions and 6 deletions

View File

@ -274,7 +274,17 @@ class ReverseSingleRelatedObjectDescriptor(six.with_metaclass(RenameRelatedObjec
rel_obj_attr = self.field.get_foreign_related_value rel_obj_attr = self.field.get_foreign_related_value
instance_attr = self.field.get_local_related_value instance_attr = self.field.get_local_related_value
instances_dict = dict((instance_attr(inst), inst) for inst in instances) 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) qs = self.get_queryset(instance=instances[0]).filter(**query)
# Since we're going to assign directly in the cache, # Since we're going to assign directly in the cache,
# we must manage the reverse relation cache manually. # we must manage the reverse relation cache manually.

View File

@ -3,8 +3,8 @@ from django.contrib.contenttypes.models import ContentType
from django.db import models from django.db import models
from django.utils.encoding import python_2_unicode_compatible from django.utils.encoding import python_2_unicode_compatible
## Basic tests
## Basic tests
@python_2_unicode_compatible @python_2_unicode_compatible
class Author(models.Model): class Author(models.Model):
@ -80,8 +80,8 @@ class BookReview(models.Model):
book = models.ForeignKey(BookWithYear) book = models.ForeignKey(BookWithYear)
notes = models.TextField(null=True, blank=True) notes = models.TextField(null=True, blank=True)
## Models for default manager tests
## Models for default manager tests
class Qualification(models.Model): class Qualification(models.Model):
name = models.CharField(max_length=10) name = models.CharField(max_length=10)
@ -167,7 +167,6 @@ class Comment(models.Model):
## Models for lookup ordering tests ## Models for lookup ordering tests
class House(models.Model): class House(models.Model):
address = models.CharField(max_length=255) address = models.CharField(max_length=255)
owner = models.ForeignKey('Person', null=True) owner = models.ForeignKey('Person', null=True)
@ -212,7 +211,7 @@ class Employee(models.Model):
ordering = ['id'] ordering = ['id']
### Ticket 19607 ## Ticket #19607
@python_2_unicode_compatible @python_2_unicode_compatible
class LessonEntry(models.Model): class LessonEntry(models.Model):
@ -230,3 +229,18 @@ class WordEntry(models.Model):
def __str__(self): def __str__(self):
return "%s (%s)" % (self.name, self.id) 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']

View File

@ -10,7 +10,7 @@ from django.utils import six
from .models import (Author, Book, Reader, Qualification, Teacher, Department, from .models import (Author, Book, Reader, Qualification, Teacher, Department,
TaggedItem, Bookmark, AuthorAddress, FavoriteAuthors, AuthorWithAge, TaggedItem, Bookmark, AuthorAddress, FavoriteAuthors, AuthorWithAge,
BookWithYear, BookReview, Person, House, Room, Employee, Comment, BookWithYear, BookReview, Person, House, Room, Employee, Comment,
LessonEntry, WordEntry) LessonEntry, WordEntry, Author2)
class PrefetchRelatedTests(TestCase): class PrefetchRelatedTests(TestCase):
@ -973,3 +973,29 @@ class Ticket19607Tests(TestCase):
def test_bug(self): def test_bug(self):
list(WordEntry.objects.prefetch_related('lesson_entry', 'lesson_entry__wordentry_set')) 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'))