Package: python-django
Severity: normal
Tags: patch

Upstream bug: https://code.djangoproject.com/ticket/28549

From the upstream bug report:

-----------------------------
 Using the models:

from django.db import models

class Base(models.Model):
    f1 = models.CharField(max_length=10)

class Sub(Base):
    f2 = models.CharField(max_length=10)

it seems that I can't defer() both f1 and f2 in a single query:

Sub.objects.defer('f1', 'f2').query.sql_with_params()[0]
u'SELECT "defer_base"."id", "defer_base"."f1", "defer_sub"."base_ptr_id" FROM "defer_sub" INNER JOIN "defer_base" ON ("defer_sub"."base_ptr_id" = "defer_base"."id")'

(we're seeing f1 in the SELECT value list).

I seem to be able to defer f1 or f2 separately though.

I'm no django hacker, but: it seems as though django.db.models.sql.query.Query.deferred_to_data() is iterating both models in the loop:

#640:
            for model, values in six.iteritems(seen):
                for field in model._meta.fields:
                    if field in values:
                        continue

- and so is discovering f1 twice: once as Base.f1 and again as Sub.f1. Since the field in values test only skips Base.f1, we're still left with Sub.f1 in the workset dict.
-----------------------------

This bug caused significant performance degradation when we upgraded a Django application to a new version that relied on model inheritance.

The attached patch is a backport of commit 84b7cb7df00192b2f804e2c6fd98b78b5bfd1ffa from upstream master.

This issue applies to both 1:1.10.7-2 and 1:1.11.5-1.

Patch supplied by Jeremy Kerr and tested/backported by Daniel Axtens.


Regards,
--
Andrew Donnellan              OzLabs, ADL Canberra
andrew.donnel...@au1.ibm.com  IBM Australia Limited
>From ac607f20c2dbcd2cfd88fcd8b78259d75abdb7d4 Mon Sep 17 00:00:00 2001
From: Jeremy Kerr <j...@ozlabs.org>
Date: Thu, 31 Aug 2017 08:59:45 +0800
Subject: [PATCH] Fixed #28549 -- Fixed QuerySet.defer() with super and
 subclass fields.

commit 84b7cb7df00192b2f804e2c6fd98b78b5bfd1ffa upstream.

Previously, deferring fields in different classes didn't omit the
superclass' deferred field.

Thanks Simon Charette for the suggested fix.

[ajd: backported on top of Debian tree]
Signed-off-by: Andrew Donnellan <andrew.donnel...@au1.ibm.com>
---
 django/db/models/sql/query.py | 2 +-
 tests/defer/tests.py          | 5 +++++
 2 files changed, 6 insertions(+), 1 deletion(-)

diff --git a/django/db/models/sql/query.py b/django/db/models/sql/query.py
index e51b1037c..8813dce57 100644
--- a/django/db/models/sql/query.py
+++ b/django/db/models/sql/query.py
@@ -669,7 +669,7 @@ class Query(object):
             # models.
             workset = {}
             for model, values in six.iteritems(seen):
-                for field in model._meta.fields:
+                for field in model._meta.local_fields:
                     if field in values:
                         continue
                     m = field.model._meta.concrete_model
diff --git a/tests/defer/tests.py b/tests/defer/tests.py
index 65f1f2bb1..ef7180a85 100644
--- a/tests/defer/tests.py
+++ b/tests/defer/tests.py
@@ -190,6 +190,11 @@ class BigChildDeferTests(AssertionMixin, TestCase):
         self.assertEqual(obj.value, "foo")
         self.assertEqual(obj.other, "bar")
 
+    def test_defer_subclass_both(self):
+        # Deferring fields from both superclass and subclass works.
+        obj = BigChild.objects.defer("other", "value").get(name="b1")
+        self.assert_delayed(obj, 2)
+
     def test_only_baseclass_when_subclass_has_added_field(self):
         # You can retrieve a single field on a baseclass
         obj = BigChild.objects.only("name").get(name="b1")
-- 
2.11.0

Reply via email to