From 632b6a1a73bc47226e40dc0a9891c67138a2161c Mon Sep 17 00:00:00 2001 From: Aymeric Augustin Date: Wed, 11 Apr 2012 21:11:22 +0000 Subject: [PATCH] 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 --- django/db/models/query.py | 23 ++++++++++++++-------- tests/modeltests/prefetch_related/tests.py | 8 ++++++++ 2 files changed, 23 insertions(+), 8 deletions(-) diff --git a/django/db/models/query.py b/django/db/models/query.py index 44acadf037..efa3e45e9f 100644 --- a/django/db/models/query.py +++ b/django/db/models/query.py @@ -6,6 +6,7 @@ import copy import itertools import sys +from django.core import exceptions from django.db import connections, router, transaction, IntegrityError from django.db.models.fields import AutoField 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 # that doesn't support prefetching but needs to be traversed. - # We replace the current list of parent objects with that list. - obj_list = [getattr(obj, attr) for obj in obj_list] - - # Filter out 'None' so that we can continue with nullable - # relations. - obj_list = [obj for obj in obj_list if obj is not None] + # We replace the current list of parent objects with the list + # of related objects, filtering out empty or missing values so + # that we can continue with nullable or reverse relations. + new_obj_list = [] + for obj in obj_list: + 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): @@ -1778,8 +1786,7 @@ def prefetch_one_level(instances, prefetcher, attname): vals = rel_obj_cache.get(instance_attr_val, []) if single: # Need to assign to single cache on instance - if vals: - setattr(obj, cache_name, vals[0]) + setattr(obj, cache_name, vals[0] if vals else None) else: # Multi, attribute represents a manager with an .all() method that # returns a QuerySet diff --git a/tests/modeltests/prefetch_related/tests.py b/tests/modeltests/prefetch_related/tests.py index 035ae4aa10..c15294af1d 100644 --- a/tests/modeltests/prefetch_related/tests.py +++ b/tests/modeltests/prefetch_related/tests.py @@ -68,6 +68,14 @@ class PrefetchRelatedTests(TestCase): self.assertQuerysetEqual(self.book2.authors.all(), [u""]) + 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): with self.assertNumQueries(2): lists = [list(b.first_time_authors.all())