Permalink
Browse files

Fixed #26207 -- Replaced dynamic classes with non-data descriptors fo…

…r deferred instance loading.
  • Loading branch information...
akaariai authored and timgraham committed Feb 2, 2016
1 parent dac075e commit 7f51876f99851fdc3fef63aecdfbcffa199c26b9
View
@@ -165,8 +165,7 @@ def get_model(self, model_name):
raise LookupError(
"App '%s' doesn't have a '%s' model." % (self.label, model_name))
- def get_models(self, include_auto_created=False,
- include_deferred=False, include_swapped=False):
+ def get_models(self, include_auto_created=False, include_swapped=False):
"""
Returns an iterable of models.
@@ -182,8 +181,6 @@ def get_models(self, include_auto_created=False,
"""
self.check_models_ready()
for model in self.models.values():
- if model._deferred and not include_deferred:
- continue
if model._meta.auto_created and not include_auto_created:
continue
if model._meta.swapped and not include_swapped:
View
@@ -156,8 +156,7 @@ def get_app_config(self, app_label):
# This method is performance-critical at least for Django's test suite.
@lru_cache.lru_cache(maxsize=None)
- def get_models(self, include_auto_created=False,
- include_deferred=False, include_swapped=False):
+ def get_models(self, include_auto_created=False, include_swapped=False):
"""
Returns a list of all installed models.
@@ -174,8 +173,7 @@ def get_models(self, include_auto_created=False,
result = []
for app_config in self.app_configs.values():
- result.extend(list(app_config.get_models(
- include_auto_created, include_deferred, include_swapped)))
+ result.extend(list(app_config.get_models(include_auto_created, include_swapped)))
return result
def get_model(self, app_label, model_name=None):
@@ -27,8 +27,6 @@ def get_by_natural_key(self, app_label, model):
def _get_opts(self, model, for_concrete_model):
if for_concrete_model:
model = model._meta.concrete_model
- elif model._deferred:
- model = model._meta.proxy_for_model
return model._meta
def _get_from_cache(self, opts):
@@ -5,17 +5,19 @@
Thanks to Robert Coup for providing this functionality (see #4322).
"""
+from django.db.models.query_utils import DeferredAttribute
from django.utils import six
-class SpatialProxy(object):
+class SpatialProxy(DeferredAttribute):
def __init__(self, klass, field):
"""
Proxy initializes on the given Geometry or Raster class (not an instance)
and the corresponding field.
"""
self._field = field
self._klass = klass
+ super(SpatialProxy, self).__init__(field.attname, klass)
def __get__(self, instance, cls=None):
"""
@@ -29,7 +31,10 @@ def __get__(self, instance, cls=None):
return self
# Getting the value of the field.
- geo_value = instance.__dict__[self._field.attname]
+ try:
+ geo_value = instance.__dict__[self._field.attname]
+ except KeyError:
+ geo_value = super(SpatialProxy, self).__get__(instance, cls)
if isinstance(geo_value, self._klass):
geo_obj = geo_value
@@ -37,8 +37,7 @@ def end_object(self, obj):
self._current = None
def get_dump_object(self, obj):
- model = obj._meta.proxy_for_model if obj._deferred else obj.__class__
- data = OrderedDict([('model', force_text(model._meta))])
+ data = OrderedDict([('model', force_text(obj._meta))])
if not self.use_natural_primary_keys or not hasattr(obj, 'natural_key'):
data["pk"] = force_text(obj._get_pk_val(), strings_only=True)
data['fields'] = self._current
@@ -52,8 +52,7 @@ def start_object(self, obj):
raise base.SerializationError("Non-model object (%s) encountered during serialization" % type(obj))
self.indent(1)
- model = obj._meta.proxy_for_model if obj._deferred else obj.__class__
- attrs = OrderedDict([("model", smart_text(model._meta))])
+ attrs = OrderedDict([("model", smart_text(obj._meta))])
if not self.use_natural_primary_keys or not hasattr(obj, 'natural_key'):
obj_pk = obj._get_pk_val()
if obj_pk is not None:
@@ -19,7 +19,7 @@
)
# Imports that would create circular imports if sorted
-from django.db.models.base import Model # NOQA isort:skip
+from django.db.models.base import DEFERRED, Model # NOQA isort:skip
from django.db.models.fields.related import ( # NOQA isort:skip
ForeignKey, ForeignObject, OneToOneField, ManyToManyField,
ManyToOneRel, ManyToManyRel, OneToOneRel,
View
@@ -27,19 +27,29 @@
from django.db.models.manager import ensure_default_manager
from django.db.models.options import Options
from django.db.models.query import Q
-from django.db.models.query_utils import (
- DeferredAttribute, deferred_class_factory,
-)
from django.db.models.utils import make_model_tuple
from django.utils import six
-from django.utils.encoding import force_str, force_text
+from django.utils.encoding import (
+ force_str, force_text, python_2_unicode_compatible,
+)
from django.utils.functional import curry
from django.utils.six.moves import zip
from django.utils.text import capfirst, get_text_list
from django.utils.translation import ugettext_lazy as _
from django.utils.version import get_version
+@python_2_unicode_compatible
+class Deferred(object):
+ def __repr__(self):
+ return str('<Deferred field>')
+
+ def __str__(self):
+ return str('<Deferred field>')
+
+DEFERRED = Deferred()
+
+
def subclass_exception(name, parents, module, attached_to=None):
"""
Create exception subclass. Used by ModelBase below.
@@ -353,7 +363,6 @@ def __init__(self, db=None):
class Model(six.with_metaclass(ModelBase)):
- _deferred = False
def __init__(self, *args, **kwargs):
signals.pre_init.send(sender=self.__class__, args=args, kwargs=kwargs)
@@ -377,11 +386,15 @@ def __init__(self, *args, **kwargs):
# is *not* consumed. We rely on this, so don't change the order
# without changing the logic.
for val, field in zip(args, fields_iter):
+ if val is DEFERRED:
+ continue
setattr(self, field.attname, val)
else:
# Slower, kwargs-ready version.
fields_iter = iter(self._meta.fields)
for val, field in zip(args, fields_iter):
+ if val is DEFERRED:
+ continue
setattr(self, field.attname, val)
kwargs.pop(field.name, None)
# Maintain compatibility with existing calls.
@@ -393,13 +406,8 @@ def __init__(self, *args, **kwargs):
for field in fields_iter:
is_related_object = False
- # This slightly odd construct is so that we can access any
- # data-descriptor object (DeferredAttribute) without triggering its
- # __get__ method.
- if (field.attname not in kwargs and
- (isinstance(self.__class__.__dict__.get(field.attname), DeferredAttribute) or
- field.column is None)):
- # This field will be populated on request.
+ # Virtual field
+ if field.attname not in kwargs and field.column is None:
continue
if kwargs:
if isinstance(field.remote_field, ForeignObjectRel):
@@ -435,15 +443,18 @@ def __init__(self, *args, **kwargs):
# field.name instead of field.attname (e.g. "user" instead of
# "user_id") so that the object gets properly cached (and type
# checked) by the RelatedObjectDescriptor.
- setattr(self, field.name, rel_obj)
+ if rel_obj is not DEFERRED:
+ setattr(self, field.name, rel_obj)
else:
- setattr(self, field.attname, val)
+ if val is not DEFERRED:
+ setattr(self, field.attname, val)
if kwargs:
for prop in list(kwargs):
try:
if isinstance(getattr(self.__class__, prop), property):
- setattr(self, prop, kwargs[prop])
+ if kwargs[prop] is not DEFERRED:
+ setattr(self, prop, kwargs[prop])
del kwargs[prop]
except AttributeError:
pass
@@ -454,10 +465,11 @@ def __init__(self, *args, **kwargs):
@classmethod
def from_db(cls, db, field_names, values):
- if cls._deferred:
- new = cls(**dict(zip(field_names, values)))
- else:
- new = cls(*values)
+ if len(values) != len(cls._meta.concrete_fields):
+ values = list(values)
+ values.reverse()
+ values = [values.pop() if f.attname in field_names else DEFERRED for f in cls._meta.concrete_fields]
+ new = cls(*values)
new._state.adding = False
new._state.db = db
return new
@@ -501,17 +513,8 @@ def __reduce__(self):
"""
data = self.__dict__
data[DJANGO_VERSION_PICKLE_KEY] = get_version()
- if not self._deferred:
- class_id = self._meta.app_label, self._meta.object_name
- return model_unpickle, (class_id, [], simple_class_factory), data
- defers = []
- for field in self._meta.fields:
- if isinstance(self.__class__.__dict__.get(field.attname),
- DeferredAttribute):
- defers.append(field.attname)
- model = self._meta.proxy_for_model
- class_id = model._meta.app_label, model._meta.object_name
- return (model_unpickle, (class_id, defers, deferred_class_factory), data)
+ class_id = self._meta.app_label, self._meta.object_name
+ return model_unpickle, (class_id,), data
def __setstate__(self, state):
msg = None
@@ -547,7 +550,7 @@ def get_deferred_fields(self):
"""
return {
f.attname for f in self._meta.concrete_fields
- if isinstance(self.__class__.__dict__.get(f.attname), DeferredAttribute)
+ if f.attname not in self.__dict__
}
def refresh_from_db(self, using=None, fields=None, **kwargs):
@@ -574,18 +577,14 @@ def refresh_from_db(self, using=None, fields=None, **kwargs):
'are not allowed in fields.' % LOOKUP_SEP)
db = using if using is not None else self._state.db
- if self._deferred:
- non_deferred_model = self._meta.proxy_for_model
- else:
- non_deferred_model = self.__class__
- db_instance_qs = non_deferred_model._default_manager.using(db).filter(pk=self.pk)
+ db_instance_qs = self.__class__._default_manager.using(db).filter(pk=self.pk)
# Use provided fields, if not set then reload all non-deferred fields.
+ deferred_fields = self.get_deferred_fields()
if fields is not None:
fields = list(fields)
db_instance_qs = db_instance_qs.only(*fields)
- elif self._deferred:
- deferred_fields = self.get_deferred_fields()
+ elif deferred_fields:
fields = [f.attname for f in self._meta.concrete_fields
if f.attname not in deferred_fields]
db_instance_qs = db_instance_qs.only(*fields)
@@ -664,6 +663,7 @@ def save(self, force_insert=False, force_update=False, using=None,
if force_insert and (force_update or update_fields):
raise ValueError("Cannot force both insert and updating in model saving.")
+ deferred_fields = self.get_deferred_fields()
if update_fields is not None:
# If update_fields is empty, skip the save. We do also check for
# no-op saves later on for inheritance cases. This bailout is
@@ -690,17 +690,11 @@ def save(self, force_insert=False, force_update=False, using=None,
# If saving to the same database, and this model is deferred, then
# automatically do a "update_fields" save on the loaded fields.
- elif not force_insert and self._deferred and using == self._state.db:
+ elif not force_insert and deferred_fields and using == self._state.db:
field_names = set()
for field in self._meta.concrete_fields:
if not field.primary_key and not hasattr(field, 'through'):
field_names.add(field.attname)
- deferred_fields = [
- f.attname for f in self._meta.fields
- if (f.attname not in self.__dict__ and
- isinstance(self.__class__.__dict__[f.attname], DeferredAttribute))
- ]
-
loaded_fields = field_names.difference(deferred_fields)
if loaded_fields:
update_fields = frozenset(loaded_fields)
@@ -1662,14 +1656,7 @@ def make_foreign_order_accessors(model, related_model):
########
-def simple_class_factory(model, attrs):
- """
- Needed for dynamic classes.
- """
- return model
-
-
-def model_unpickle(model_id, attrs, factory):
+def model_unpickle(model_id):
"""
Used to unpickle Model subclasses with deferred fields.
"""
@@ -1678,8 +1665,7 @@ def model_unpickle(model_id, attrs, factory):
else:
# Backwards compat - the model was cached directly in earlier versions.
model = model_id
- cls = factory(model, attrs)
- return cls.__new__(cls)
+ return model.__new__(model)
model_unpickle.__safe_for_unpickle__ = True
@@ -20,7 +20,7 @@
# purposes.
from django.core.exceptions import FieldDoesNotExist # NOQA
from django.db import connection, connections, router
-from django.db.models.query_utils import RegisterLookupMixin
+from django.db.models.query_utils import DeferredAttribute, RegisterLookupMixin
from django.utils import six, timezone
from django.utils.datastructures import DictWrapper
from django.utils.dateparse import (
@@ -504,10 +504,6 @@ def __reduce__(self):
# class self.__class__, then update its dict with self.__dict__
# values - so, this is very close to normal pickle.
return _empty, (self.__class__,), self.__dict__
- if self.model._deferred:
- # Deferred model will not be found from the app registry. This
- # could be fixed by reconstructing the deferred model on unpickle.
- raise RuntimeError("Fields of deferred models can't be reduced")
return _load_field, (self.model._meta.app_label, self.model._meta.object_name,
self.name)
@@ -696,6 +692,12 @@ def contribute_to_class(self, cls, name, private_only=False, virtual_only=NOT_PR
cls._meta.add_field(self, private=True)
else:
cls._meta.add_field(self)
+ if self.column:
+ # Don't override classmethods with the descriptor. This means that
+ # if you have a classmethod and a field with the same name, then
+ # such fields can't be deferred (we don't have a check for this).
+ if not getattr(cls, self.attname, None):
+ setattr(cls, self.attname, DeferredAttribute(self.attname, cls))
if self.choices:
setattr(cls, 'get_%s_display' % self.name,
curry(cls._get_FIELD_display, field=self))
Oops, something went wrong.

0 comments on commit 7f51876

Please sign in to comment.