Fixed #17439 -- Prevented spurious queries for missing objects after prefetch_related has run.

That affects nullable foreign key, nullable one-to-one, and reverse one-to-one relations.


git-svn-id: http://code.djangoproject.com/svn/django/trunk@17899 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
Aymeric Augustin 2012-04-11 21:11:22 +00:00
parent e2548ec2a9
commit 632b6a1a73
2 changed files with 23 additions and 8 deletions

View File

@ -6,6 +6,7 @@ import copy
import itertools import itertools
import sys import sys
from django.core import exceptions
from django.db import connections, router, transaction, IntegrityError from django.db import connections, router, transaction, IntegrityError
from django.db.models.fields import AutoField from django.db.models.fields import AutoField
from django.db.models.query_utils import (Q, select_related_descend, from django.db.models.query_utils import (Q, select_related_descend,
@ -1677,12 +1678,19 @@ def prefetch_related_objects(result_cache, related_lookups):
# (e.g. via select_related), or hopefully some other property # (e.g. via select_related), or hopefully some other property
# that doesn't support prefetching but needs to be traversed. # that doesn't support prefetching but needs to be traversed.
# We replace the current list of parent objects with that list. # We replace the current list of parent objects with the list
obj_list = [getattr(obj, attr) for obj in obj_list] # of related objects, filtering out empty or missing values so
# that we can continue with nullable or reverse relations.
# Filter out 'None' so that we can continue with nullable new_obj_list = []
# relations. for obj in obj_list:
obj_list = [obj for obj in obj_list if obj is not None] try:
new_obj = getattr(obj, attr)
except exceptions.ObjectDoesNotExist:
continue
if new_obj is None:
continue
new_obj_list.append(new_obj)
obj_list = new_obj_list
def get_prefetcher(instance, attr): def get_prefetcher(instance, attr):
@ -1778,8 +1786,7 @@ def prefetch_one_level(instances, prefetcher, attname):
vals = rel_obj_cache.get(instance_attr_val, []) vals = rel_obj_cache.get(instance_attr_val, [])
if single: if single:
# Need to assign to single cache on instance # Need to assign to single cache on instance
if vals: setattr(obj, cache_name, vals[0] if vals else None)
setattr(obj, cache_name, vals[0])
else: else:
# Multi, attribute represents a manager with an .all() method that # Multi, attribute represents a manager with an .all() method that
# returns a QuerySet # returns a QuerySet

View File

@ -68,6 +68,14 @@ class PrefetchRelatedTests(TestCase):
self.assertQuerysetEqual(self.book2.authors.all(), [u"<Author: Charlotte>"]) self.assertQuerysetEqual(self.book2.authors.all(), [u"<Author: Charlotte>"])
def test_onetoone_reverse_no_match(self):
# Regression for #17439
with self.assertNumQueries(2):
book = Book.objects.prefetch_related('bookwithyear').all()[0]
with self.assertNumQueries(0):
with self.assertRaises(BookWithYear.DoesNotExist):
book.bookwithyear
def test_survives_clone(self): def test_survives_clone(self):
with self.assertNumQueries(2): with self.assertNumQueries(2):
lists = [list(b.first_time_authors.all()) lists = [list(b.first_time_authors.all())