Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

Ticket #18343 -- Cleaned up deferred model implementation #80

Closed
wants to merge 2 commits into from

1 participant

@akaariai
Collaborator

Removed some dead-code, and some dump logic in deferred field loading
and deferred model reduce(). The biggest user visible change is
that primary keys do not need fetching from the DB in some inheritance
cases.

All tests pass on SQLite.

@akaariai akaariai [1/2] #18343 -- Cleaned up deferred model implementation
Generic cleanup and dead code removal in deferred model field loading
and model.__reduce__().
831c07b
@akaariai
Collaborator

I splitted the patches into two for easier review.

@akaariai akaariai [2/2] #18343 -- deferred model pk handling in inheritance case
Assuming an inherited model with parent_ptr_id -> id, doing a deferred
load and then fetching the id field would cause a database query, even
if the id field's value is already loaded in the parent_ptr_id field.
6474daf
@akaariai
Collaborator

Pushed in manually

@akaariai akaariai closed this
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on May 22, 2012
  1. @akaariai

    [1/2] #18343 -- Cleaned up deferred model implementation

    akaariai authored
    Generic cleanup and dead code removal in deferred model field loading
    and model.__reduce__().
  2. @akaariai

    [2/2] #18343 -- deferred model pk handling in inheritance case

    akaariai authored
    Assuming an inherited model with parent_ptr_id -> id, doing a deferred
    load and then fetching the id field would cause a database query, even
    if the id field's value is already loaded in the parent_ptr_id field.
This page is out of date. Refresh to see the latest.
View
8 django/db/models/base.py
@@ -404,7 +404,6 @@ def __reduce__(self):
# and as a result, the super call will cause an infinite recursion.
# See #10547 and #12121.
defers = []
- pk_val = None
if self._deferred:
from django.db.models.query_utils import deferred_class_factory
factory = deferred_class_factory
@@ -412,12 +411,7 @@ def __reduce__(self):
if isinstance(self.__class__.__dict__.get(field.attname),
DeferredAttribute):
defers.append(field.attname)
- if pk_val is None:
- # 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()
+ model = self._meta.proxy_for_model
else:
factory = simple_class_factory
return (model_unpickle, (model, defers, factory), data)
View
58 django/db/models/query_utils.py
@@ -6,8 +6,6 @@
circular import difficulties.
"""
-import weakref
-
from django.db.backends import util
from django.utils import tree
@@ -70,8 +68,6 @@ class DeferredAttribute(object):
"""
def __init__(self, field_name, model):
self.field_name = field_name
- self.model_ref = weakref.ref(model)
- self.loaded = False
def __get__(self, instance, owner):
"""
@@ -79,27 +75,33 @@ def __get__(self, instance, owner):
Returns the cached value.
"""
from django.db.models.fields import FieldDoesNotExist
+ # Fetch the non-deferred model
+ non_deferred_model = instance._meta.proxy_for_model
+ opts = non_deferred_model._meta
assert instance is not None
- cls = self.model_ref()
data = instance.__dict__
if data.get(self.field_name, self) is self:
# self.field_name is the attname of the field, but only() takes the
# actual name, so we need to translate it here.
try:
- cls._meta.get_field_by_name(self.field_name)
- name = self.field_name
+ f = opts.get_field_by_name(self.field_name)[0]
except FieldDoesNotExist:
- name = [f.name for f in cls._meta.fields
- if f.attname == self.field_name][0]
- # We use only() instead of values() here because we want the
- # various data coersion methods (to_python(), etc.) to be called
- # here.
- val = getattr(
- cls._base_manager.filter(pk=instance.pk).only(name).using(
- instance._state.db).get(),
- self.field_name
- )
+ f = [f for f in opts.fields
+ 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, opts, name)
+ if val is None:
+ # We use only() instead of values() here because we want the
+ # various data coersion methods (to_python(), etc.) to be
+ # called here.
+ val = getattr(
+ non_deferred_model._base_manager.only(name).using(
+ instance._state.db).get(pk=instance.pk),
+ self.field_name
+ )
data[self.field_name] = val
return data[self.field_name]
@@ -110,6 +112,28 @@ def __set__(self, instance, value):
"""
instance.__dict__[self.field_name] = value
+ def _check_parent_chain(self, instance, opts, name):
+ """
+ Check if the field is part of any parent chain in the model.
+ If so, return the child field's value. This is done so that
+ if we have parent_ptr_id already in the model, and we are
+ fetching the id field, we will not need to refetch the id
+ value which is guaranteed to be the same as the parent_ptr_id
+ value.
+ """
+ for parent, field in opts.parents.items():
+ # If fetching field 'id', and the 'id' value is found here, we
+ # will continue to fetch the instance's parent_ptr_id. It might
+ # be also a deferred field, and we end up here again, now fetching
+ # parent_ptr_id value instead.
+ if parent._meta.pk.attname == name:
+ return getattr(instance, field.attname)
+ ret = self._check_parent_chain(instance, parent._meta, name)
+ if ret:
+ return ret
+ return None
+
+
def select_related_descend(field, restricted, requested, reverse=False):
"""
Returns True if this field should be used to descend deeper for
View
15 tests/modeltests/defer/tests.py
@@ -158,3 +158,18 @@ def test_defer_proxy(self):
self.assert_delayed(child, 1)
self.assertEqual(child.name, 'p1')
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)
View
4 tests/modeltests/field_subclassing/tests.py
@@ -18,6 +18,10 @@ def test_defer(self):
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.save()
d = DataModel.objects.get(pk=d.pk)
Something went wrong with that request. Please try again.