diff --git a/django/db/models/query.py b/django/db/models/query.py index b2ffa32004..53ee999d0d 100644 --- a/django/db/models/query.py +++ b/django/db/models/query.py @@ -1558,8 +1558,13 @@ def prefetch_related_objects(result_cache, related_lookups): good_objects = False break else: - # We already did this list - break + # Since prefetching can re-use instances, it is possible to + # have the same instance multiple times in obj_list. So we + # can reach this branch either because we did all of + # obj_list already, or because we did 'obj' earlier in this + # iteration over obj_list. In the first case we could + # shortcut and exit the loop, but not in the second. + continue if not good_objects: break diff --git a/tests/prefetch_related/models.py b/tests/prefetch_related/models.py index 81c569844f..82bf85e401 100644 --- a/tests/prefetch_related/models.py +++ b/tests/prefetch_related/models.py @@ -195,3 +195,23 @@ class Employee(models.Model): class Meta: ordering = ['id'] + + +### Ticket 19607 + +@python_2_unicode_compatible +class LessonEntry(models.Model): + name1 = models.CharField(max_length=200) + name2 = models.CharField(max_length=200) + + def __str__(self): + return "%s %s" % (self.name1, self.name2) + + +@python_2_unicode_compatible +class WordEntry(models.Model): + lesson_entry = models.ForeignKey(LessonEntry) + name = models.CharField(max_length=200) + + def __str__(self): + return "%s (%s)" % (self.name, self.id) diff --git a/tests/prefetch_related/tests.py b/tests/prefetch_related/tests.py index e81560f01f..3921153246 100644 --- a/tests/prefetch_related/tests.py +++ b/tests/prefetch_related/tests.py @@ -8,7 +8,8 @@ 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) + BookWithYear, BookReview, Person, House, Room, Employee, Comment, + LessonEntry, WordEntry) class PrefetchRelatedTests(TestCase): @@ -618,3 +619,25 @@ class MultiDbTests(TestCase): ages = ", ".join(str(a.authorwithage.age) for a in A.prefetch_related('authorwithage')) self.assertEqual(ages, "50, 49") + + +class Ticket19607Tests(TestCase): + + def setUp(self): + + for id, name1, name2 in [ + (1, 'einfach', 'simple'), + (2, 'schwierig', 'difficult'), + ]: + LessonEntry.objects.create(id=id, name1=name1, name2=name2) + + for id, lesson_entry_id, name in [ + (1, 1, 'einfach'), + (2, 1, 'simple'), + (3, 2, 'schwierig'), + (4, 2, 'difficult'), + ]: + WordEntry.objects.create(id=id, lesson_entry_id=lesson_entry_id, name=name) + + def test_bug(self): + list(WordEntry.objects.prefetch_related('lesson_entry', 'lesson_entry__wordentry_set'))