Fixed #18343 -- Cleaned up deferred model implementation
Generic cleanup and dead code removal in deferred model field loading and model.__reduce__(). Also fixed an issue where if an inherited model with a parent field chain parent_ptr_id -> id would be deferred loaded, then accessing the id field caused caused a database query, even if the id field's value is already loaded in the parent_ptr_id field.
This commit is contained in:
parent
7a4233b69c
commit
a8a81aae20
@ -404,7 +404,6 @@ class Model(object):
|
|||||||
# and as a result, the super call will cause an infinite recursion.
|
# and as a result, the super call will cause an infinite recursion.
|
||||||
# See #10547 and #12121.
|
# See #10547 and #12121.
|
||||||
defers = []
|
defers = []
|
||||||
pk_val = None
|
|
||||||
if self._deferred:
|
if self._deferred:
|
||||||
from django.db.models.query_utils import deferred_class_factory
|
from django.db.models.query_utils import deferred_class_factory
|
||||||
factory = deferred_class_factory
|
factory = deferred_class_factory
|
||||||
@ -412,12 +411,7 @@ class Model(object):
|
|||||||
if isinstance(self.__class__.__dict__.get(field.attname),
|
if isinstance(self.__class__.__dict__.get(field.attname),
|
||||||
DeferredAttribute):
|
DeferredAttribute):
|
||||||
defers.append(field.attname)
|
defers.append(field.attname)
|
||||||
if pk_val is None:
|
model = self._meta.proxy_for_model
|
||||||
# The pk_val and model values are the same for all
|
|
||||||
# DeferredAttribute classes, so we only need to do this
|
|
||||||
# once.
|
|
||||||
obj = self.__class__.__dict__[field.attname]
|
|
||||||
model = obj.model_ref()
|
|
||||||
else:
|
else:
|
||||||
factory = simple_class_factory
|
factory = simple_class_factory
|
||||||
return (model_unpickle, (model, defers, factory), data)
|
return (model_unpickle, (model, defers, factory), data)
|
||||||
|
@ -6,8 +6,6 @@ large and/or so that they can be used by other modules without getting into
|
|||||||
circular import difficulties.
|
circular import difficulties.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import weakref
|
|
||||||
|
|
||||||
from django.db.backends import util
|
from django.db.backends import util
|
||||||
from django.utils import tree
|
from django.utils import tree
|
||||||
|
|
||||||
@ -70,8 +68,6 @@ class DeferredAttribute(object):
|
|||||||
"""
|
"""
|
||||||
def __init__(self, field_name, model):
|
def __init__(self, field_name, model):
|
||||||
self.field_name = field_name
|
self.field_name = field_name
|
||||||
self.model_ref = weakref.ref(model)
|
|
||||||
self.loaded = False
|
|
||||||
|
|
||||||
def __get__(self, instance, owner):
|
def __get__(self, instance, owner):
|
||||||
"""
|
"""
|
||||||
@ -79,25 +75,30 @@ class DeferredAttribute(object):
|
|||||||
Returns the cached value.
|
Returns the cached value.
|
||||||
"""
|
"""
|
||||||
from django.db.models.fields import FieldDoesNotExist
|
from django.db.models.fields import FieldDoesNotExist
|
||||||
|
non_deferred_model = instance._meta.proxy_for_model
|
||||||
|
opts = non_deferred_model._meta
|
||||||
|
|
||||||
assert instance is not None
|
assert instance is not None
|
||||||
cls = self.model_ref()
|
|
||||||
data = instance.__dict__
|
data = instance.__dict__
|
||||||
if data.get(self.field_name, self) is self:
|
if data.get(self.field_name, self) is self:
|
||||||
# self.field_name is the attname of the field, but only() takes the
|
# self.field_name is the attname of the field, but only() takes the
|
||||||
# actual name, so we need to translate it here.
|
# actual name, so we need to translate it here.
|
||||||
try:
|
try:
|
||||||
cls._meta.get_field_by_name(self.field_name)
|
f = opts.get_field_by_name(self.field_name)[0]
|
||||||
name = self.field_name
|
|
||||||
except FieldDoesNotExist:
|
except FieldDoesNotExist:
|
||||||
name = [f.name for f in cls._meta.fields
|
f = [f for f in opts.fields
|
||||||
if f.attname == self.field_name][0]
|
if f.attname == self.field_name][0]
|
||||||
|
name = f.name
|
||||||
|
# Lets see if the field is part of the parent chain. If so we
|
||||||
|
# might be able to reuse the already loaded value. Refs #18343.
|
||||||
|
val = self._check_parent_chain(instance, name)
|
||||||
|
if val is None:
|
||||||
# We use only() instead of values() here because we want the
|
# We use only() instead of values() here because we want the
|
||||||
# various data coersion methods (to_python(), etc.) to be called
|
# various data coersion methods (to_python(), etc.) to be
|
||||||
# here.
|
# called here.
|
||||||
val = getattr(
|
val = getattr(
|
||||||
cls._base_manager.filter(pk=instance.pk).only(name).using(
|
non_deferred_model._base_manager.only(name).using(
|
||||||
instance._state.db).get(),
|
instance._state.db).get(pk=instance.pk),
|
||||||
self.field_name
|
self.field_name
|
||||||
)
|
)
|
||||||
data[self.field_name] = val
|
data[self.field_name] = val
|
||||||
@ -110,6 +111,20 @@ class DeferredAttribute(object):
|
|||||||
"""
|
"""
|
||||||
instance.__dict__[self.field_name] = value
|
instance.__dict__[self.field_name] = value
|
||||||
|
|
||||||
|
def _check_parent_chain(self, instance, name):
|
||||||
|
"""
|
||||||
|
Check if the field value can be fetched from a parent field already
|
||||||
|
loaded in the instance. This can be done if the to-be fetched
|
||||||
|
field is a primary key field.
|
||||||
|
"""
|
||||||
|
opts = instance._meta
|
||||||
|
f = opts.get_field_by_name(name)[0]
|
||||||
|
link_field = opts.get_ancestor_link(f.model)
|
||||||
|
if f.primary_key and f != link_field:
|
||||||
|
return getattr(instance, link_field.attname)
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
def select_related_descend(field, restricted, requested, reverse=False):
|
def select_related_descend(field, restricted, requested, reverse=False):
|
||||||
"""
|
"""
|
||||||
Returns True if this field should be used to descend deeper for
|
Returns True if this field should be used to descend deeper for
|
||||||
|
@ -158,3 +158,18 @@ class DeferTests(TestCase):
|
|||||||
self.assert_delayed(child, 1)
|
self.assert_delayed(child, 1)
|
||||||
self.assertEqual(child.name, 'p1')
|
self.assertEqual(child.name, 'p1')
|
||||||
self.assertEqual(child.value, 'xx')
|
self.assertEqual(child.value, 'xx')
|
||||||
|
|
||||||
|
def test_defer_inheritance_pk_chaining(self):
|
||||||
|
"""
|
||||||
|
When an inherited model is fetched from the DB, its PK is also fetched.
|
||||||
|
When getting the PK of the parent model it is useful to use the already
|
||||||
|
fetched parent model PK if it happens to be available. Tests that this
|
||||||
|
is done.
|
||||||
|
"""
|
||||||
|
s1 = Secondary.objects.create(first="x1", second="y1")
|
||||||
|
bc = BigChild.objects.create(name="b1", value="foo", related=s1,
|
||||||
|
other="bar")
|
||||||
|
bc_deferred = BigChild.objects.only('name').get(pk=bc.pk)
|
||||||
|
with self.assertNumQueries(0):
|
||||||
|
bc_deferred.id
|
||||||
|
self.assertEqual(bc_deferred.pk, bc_deferred.id)
|
||||||
|
@ -17,6 +17,10 @@ class CustomField(TestCase):
|
|||||||
self.assertTrue(isinstance(d.data, list))
|
self.assertTrue(isinstance(d.data, list))
|
||||||
self.assertEqual(d.data, [1, 2, 3])
|
self.assertEqual(d.data, [1, 2, 3])
|
||||||
|
|
||||||
|
d = DataModel.objects.defer("data").get(pk=d.pk)
|
||||||
|
self.assertTrue(isinstance(d.data, list))
|
||||||
|
self.assertEqual(d.data, [1, 2, 3])
|
||||||
|
# Refetch for save
|
||||||
d = DataModel.objects.defer("data").get(pk=d.pk)
|
d = DataModel.objects.defer("data").get(pk=d.pk)
|
||||||
d.save()
|
d.save()
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user