Commit 6ebf1152 authored by Tai Lee's avatar Tai Lee Committed by Anssi Kääriäinen
Browse files

Fixed #14694 -- Made ``defer()`` work with reverse relations

Reverse o2o fields are now usable with defer.
parent 2a0e4c24
Loading
Loading
Loading
Loading
+16 −4
Original line number Diff line number Diff line
@@ -599,16 +599,21 @@ class Query(object):
            for name in parts[:-1]:
                old_model = cur_model
                source = opts.get_field_by_name(name)[0]
                if is_reverse_o2o(source):
                    cur_model = source.model
                else:
                    cur_model = source.rel.to
                opts = cur_model._meta
                # Even if we're "just passing through" this model, we must add
                # both the current model's pk and the related reference field
                # to the things we select.
                # (if it's not a reverse relation) to the things we select.
                if not is_reverse_o2o(source):
                    must_include[old_model].add(source)
                add_to_dict(must_include, cur_model, opts.pk)
            field, model, _, _ = opts.get_field_by_name(parts[-1])
            if model is None:
                model = cur_model
            if not is_reverse_o2o(field):
                add_to_dict(seen, model, field)

        if defer:
@@ -1983,3 +1988,10 @@ def add_to_dict(data, key, value):
        data[key].add(value)
    else:
        data[key] = set([value])

def is_reverse_o2o(field):
    """
    A little helper to check if the given field is reverse-o2o. The field is
    expected to be some sort of relation field or related object.
    """
    return not hasattr(field, 'rel') and field.field.unique
+4 −0
Original line number Diff line number Diff line
@@ -55,6 +55,10 @@ class Feature(models.Model):
class SpecialFeature(models.Model):
    feature = models.ForeignKey(Feature)

class OneToOneItem(models.Model):
    item = models.OneToOneField(Item, related_name="one_to_one_item")
    name = models.CharField(max_length=15)

class ItemAndSimpleItem(models.Model):
    item = models.ForeignKey(Item)
    simple = models.ForeignKey(SimpleItem)
+30 −1
Original line number Diff line number Diff line
@@ -9,7 +9,7 @@ from django.db.models.loading import cache
from django.test import TestCase

from .models import (ResolveThis, Item, RelatedItem, Child, Leaf, Proxy,
    SimpleItem, Feature, ItemAndSimpleItem, SpecialFeature)
    SimpleItem, Feature, ItemAndSimpleItem, OneToOneItem, SpecialFeature)


class DeferRegressionTest(TestCase):
@@ -111,6 +111,7 @@ class DeferRegressionTest(TestCase):
                Item,
                ItemAndSimpleItem,
                Leaf,
                OneToOneItem,
                Proxy,
                RelatedItem,
                ResolveThis,
@@ -147,6 +148,7 @@ class DeferRegressionTest(TestCase):
                "Leaf_Deferred_name_value",
                "Leaf_Deferred_second_child_id_value",
                "Leaf_Deferred_value",
                "OneToOneItem",
                "Proxy",
                "RelatedItem",
                "RelatedItem_Deferred_",
@@ -182,6 +184,33 @@ class DeferRegressionTest(TestCase):
        self.assertEqual(1, qs.count())
        self.assertEqual('Foobar', qs[0].name)

    def test_reverse_one_to_one_relations(self):
        # Refs #14694. Test reverse relations which are known unique (reverse
        # side has o2ofield or unique FK) - the o2o case
        item = Item.objects.create(name="first", value=42)
        o2o = OneToOneItem.objects.create(item=item, name="second")
        self.assertEqual(len(Item.objects.defer('one_to_one_item__name')), 1)
        self.assertEqual(len(Item.objects.select_related('one_to_one_item')), 1)
        self.assertEqual(len(Item.objects.select_related(
            'one_to_one_item').defer('one_to_one_item__name')), 1)
        self.assertEqual(len(Item.objects.select_related('one_to_one_item').defer('value')), 1)
        # Make sure that `only()` doesn't break when we pass in a unique relation,
        # rather than a field on the relation.
        self.assertEqual(len(Item.objects.only('one_to_one_item')), 1)
        with self.assertNumQueries(1):
            i = Item.objects.select_related('one_to_one_item')[0]
            self.assertEquals(i.one_to_one_item.pk, o2o.pk)
            self.assertEquals(i.one_to_one_item.name, "second")
        with self.assertNumQueries(1):
            i = Item.objects.select_related('one_to_one_item').defer(
                'value', 'one_to_one_item__name')[0]
            self.assertEquals(i.one_to_one_item.pk, o2o.pk)
            self.assertEquals(i.name, "first")
        with self.assertNumQueries(1):
            self.assertEquals(i.one_to_one_item.name, "second")
        with self.assertNumQueries(1):
            self.assertEquals(i.value, 42)

    def test_defer_with_select_related(self):
        item1 = Item.objects.create(name="first", value=47)
        item2 = Item.objects.create(name="second", value=42)