Browse Source

Fixed #26207 -- Replaced dynamic classes with non-data descriptors for deferred instance loading.

Anssi Kääriäinen 9 years ago
parent
commit
7f51876f99

+ 1 - 4
django/apps/config.py

@@ -165,8 +165,7 @@ class AppConfig(object):
             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 @@ class AppConfig(object):
         """
         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:

+ 2 - 4
django/apps/registry.py

@@ -156,8 +156,7 @@ class Apps(object):
 
     # 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 @@ class Apps(object):
 
         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):

+ 0 - 2
django/contrib/contenttypes/models.py

@@ -27,8 +27,6 @@ class ContentTypeManager(models.Manager):
     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):

+ 7 - 2
django/contrib/gis/db/models/proxy.py

@@ -5,10 +5,11 @@ objects corresponding to geographic model fields.
 
 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)
@@ -16,6 +17,7 @@ class SpatialProxy(object):
         """
         self._field = field
         self._klass = klass
+        super(SpatialProxy, self).__init__(field.attname, klass)
 
     def __get__(self, instance, cls=None):
         """
@@ -29,7 +31,10 @@ class SpatialProxy(object):
             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

+ 1 - 2
django/core/serializers/python.py

@@ -37,8 +37,7 @@ class Serializer(base.Serializer):
         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

+ 1 - 2
django/core/serializers/xml_serializer.py

@@ -52,8 +52,7 @@ class Serializer(base.Serializer):
             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:

+ 1 - 1
django/db/models/__init__.py

@@ -19,7 +19,7 @@ from django.db.models.query import (  # NOQA
 )
 
 # 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,

+ 41 - 55
django/db/models/base.py

@@ -27,12 +27,11 @@ from django.db.models.fields.related import (
 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
@@ -40,6 +39,17 @@ 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 @@ class ModelState(object):
 
 
 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 @@ class Model(six.with_metaclass(ModelBase)):
             # 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 @@ class Model(six.with_metaclass(ModelBase)):
 
         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 @@ class Model(six.with_metaclass(ModelBase)):
                 # 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 @@ class Model(six.with_metaclass(ModelBase)):
 
     @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 @@ class Model(six.with_metaclass(ModelBase)):
         """
         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 @@ class Model(six.with_metaclass(ModelBase)):
         """
         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 @@ class Model(six.with_metaclass(ModelBase)):
                     '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 @@ class Model(six.with_metaclass(ModelBase)):
         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 @@ class Model(six.with_metaclass(ModelBase)):
 
         # 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
 
 

+ 7 - 5
django/db/models/fields/__init__.py

@@ -20,7 +20,7 @@ from django.core import checks, exceptions, validators
 # 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 @@ class Field(RegisterLookupMixin):
             # 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 @@ class Field(RegisterLookupMixin):
             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))

+ 3 - 21
django/db/models/query.py

@@ -19,7 +19,7 @@ from django.db.models.deletion import Collector
 from django.db.models.expressions import Date, DateTime, F
 from django.db.models.fields import AutoField
 from django.db.models.query_utils import (
-    InvalidQuery, Q, check_rel_lookup_compatibility, deferred_class_factory,
+    InvalidQuery, Q, check_rel_lookup_compatibility,
 )
 from django.db.models.sql.constants import CURSOR
 from django.utils import six, timezone
@@ -60,11 +60,6 @@ class ModelIterable(BaseIterable):
         model_fields_start, model_fields_end = select_fields[0], select_fields[-1] + 1
         init_list = [f[0].target.attname
                      for f in select[model_fields_start:model_fields_end]]
-        if len(init_list) != len(model_cls._meta.concrete_fields):
-            init_set = set(init_list)
-            skip = [f.attname for f in model_cls._meta.concrete_fields
-                    if f.attname not in init_set]
-            model_cls = deferred_class_factory(model_cls, skip)
         related_populators = get_related_populators(klass_info, select, db)
         for row in compiler.results_iter(results):
             obj = model_cls.from_db(db, init_list, row[model_fields_start:model_fields_end])
@@ -1254,9 +1249,7 @@ class RawQuerySet(object):
             if skip:
                 if self.model._meta.pk.attname in skip:
                     raise InvalidQuery('Raw query must include the primary key')
-                model_cls = deferred_class_factory(self.model, skip)
-            else:
-                model_cls = self.model
+            model_cls = self.model
             fields = [self.model_fields.get(c) for c in self.columns]
             converters = compiler.get_converters([
                 f.get_col(f.model._meta.db_table) if f else None for f in fields
@@ -1718,7 +1711,7 @@ class RelatedPopulator(object):
                 return [row[row_pos] for row_pos in pos_list]
             self.reorder_for_init = reorder_for_init
 
-        self.model_cls = self.get_deferred_cls(klass_info, self.init_list)
+        self.model_cls = klass_info['model']
         self.pk_idx = self.init_list.index(self.model_cls._meta.pk.attname)
         self.related_populators = get_related_populators(klass_info, select, self.db)
         field = klass_info['field']
@@ -1732,17 +1725,6 @@ class RelatedPopulator(object):
             if field.unique:
                 self.reverse_cache_name = field.remote_field.get_cache_name()
 
-    def get_deferred_cls(self, klass_info, init_list):
-        model_cls = klass_info['model']
-        if len(init_list) != len(model_cls._meta.concrete_fields):
-            init_set = set(init_list)
-            skip = [
-                f.attname for f in model_cls._meta.concrete_fields
-                if f.attname not in init_set
-            ]
-            model_cls = deferred_class_factory(model_cls, skip)
-        return model_cls
-
     def populate(self, row, from_obj):
         if self.reorder_for_init:
             obj_data = self.reorder_for_init(row)

+ 3 - 54
django/db/models/query_utils.py

@@ -11,7 +11,6 @@ import inspect
 from collections import namedtuple
 
 from django.core.exceptions import FieldDoesNotExist
-from django.db.backends import utils
 from django.db.models.constants import LOOKUP_SEP
 from django.utils import tree
 
@@ -97,10 +96,9 @@ class DeferredAttribute(object):
         Retrieves and caches the value from the datastore on the first lookup.
         Returns the cached value.
         """
-        non_deferred_model = instance._meta.proxy_for_model
-        opts = non_deferred_model._meta
-
-        assert instance is not None
+        if instance is None:
+            return self
+        opts = instance._meta
         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
@@ -119,13 +117,6 @@ class DeferredAttribute(object):
             data[self.field_name] = val
         return data[self.field_name]
 
-    def __set__(self, instance, value):
-        """
-        Deferred loading attributes can be set normally (which means there will
-        never be a database lookup involved.
-        """
-        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
@@ -230,48 +221,6 @@ def select_related_descend(field, restricted, requested, load_fields, reverse=Fa
     return True
 
 
-# This function is needed because data descriptors must be defined on a class
-# object, not an instance, to have any effect.
-
-def deferred_class_factory(model, attrs):
-    """
-    Returns a class object that is a copy of "model" with the specified "attrs"
-    being replaced with DeferredAttribute objects. The "pk_value" ties the
-    deferred attributes to a particular instance of the model.
-    """
-    if not attrs:
-        return model
-    opts = model._meta
-    # Never create deferred models based on deferred model
-    if model._deferred:
-        # Deferred models are proxies for the non-deferred model. We never
-        # create chains of defers => proxy_for_model is the non-deferred
-        # model.
-        model = opts.proxy_for_model
-    # The app registry wants a unique name for each model, otherwise the new
-    # class won't be created (we get an exception). Therefore, we generate
-    # the name using the passed in attrs. It's OK to reuse an existing class
-    # object if the attrs are identical.
-    name = "%s_Deferred_%s" % (model.__name__, '_'.join(sorted(attrs)))
-    name = utils.truncate_name(name, 80, 32)
-
-    try:
-        return opts.apps.get_model(model._meta.app_label, name)
-
-    except LookupError:
-
-        class Meta:
-            proxy = True
-            apps = opts.apps
-            app_label = opts.app_label
-
-        overrides = {attr: DeferredAttribute(attr, model) for attr in attrs}
-        overrides["Meta"] = Meta
-        overrides["__module__"] = model.__module__
-        overrides["_deferred"] = True
-        return type(str(name), (model,), overrides)
-
-
 def refs_expression(lookup_parts, annotations):
     """
     A helper method to check if the lookup_parts contains references

+ 0 - 4
django/views/generic/detail.py

@@ -89,8 +89,6 @@ class SingleObjectMixin(ContextMixin):
         if self.context_object_name:
             return self.context_object_name
         elif isinstance(obj, models.Model):
-            if obj._deferred:
-                obj = obj._meta.proxy_for_model
             return obj._meta.model_name
         else:
             return None
@@ -152,8 +150,6 @@ class SingleObjectTemplateResponseMixin(TemplateResponseMixin):
             # only use this if the object in question is a model.
             if isinstance(self.object, models.Model):
                 object_meta = self.object._meta
-                if self.object._deferred:
-                    object_meta = self.object._meta.proxy_for_model._meta
                 names.append("%s/%s%s.html" % (
                     object_meta.app_label,
                     object_meta.model_name,

+ 24 - 13
docs/ref/models/instances.txt

@@ -73,13 +73,12 @@ when loading from the database.
 The ``db`` argument contains the database alias for the database the model
 is loaded from, ``field_names`` contains the names of all loaded fields, and
 ``values`` contains the loaded values for each field in ``field_names``. The
-``field_names`` are in the same order as the ``values``, so it is possible to
-use ``cls(**(zip(field_names, values)))`` to instantiate the object. If all
-of the model's fields are present, then ``values`` are guaranteed to be in
-the order ``__init__()`` expects them. That is, the instance can be created
-by ``cls(*values)``. It is possible to check if all fields are present by
-consulting ``cls._deferred`` - if ``False``, then all fields have been loaded
-from the database.
+``field_names`` are in the same order as the ``values``. If all of the model's
+fields are present, then ``values`` are guaranteed to be in the order
+``__init__()`` expects them. That is, the instance can be created by
+``cls(*values)``. If any fields are deferred, they won't appear in
+``field_names``. In that case, assign a value of ``django.db.models.DEFERRED``
+to each of the missing fields.
 
 In addition to creating the new model, the ``from_db()`` method must set the
 ``adding`` and ``db`` flags in the new instance's ``_state`` attribute.
@@ -87,14 +86,20 @@ In addition to creating the new model, the ``from_db()`` method must set the
 Below is an example showing how to record the initial values of fields that
 are loaded from the database::
 
+    from django.db.models import DEFERRED
+
     @classmethod
     def from_db(cls, db, field_names, values):
-        # default implementation of from_db() (could be replaced
-        # with super())
-        if cls._deferred:
-            instance = cls(**zip(field_names, values))
-        else:
-            instance = cls(*values)
+        # Default implementation of from_db() (subject to change and could
+        # be replaced with super()).
+        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)
         instance._state.adding = False
         instance._state.db = db
         # customization to store the original field values on the instance
@@ -114,6 +119,12 @@ The example above shows a full ``from_db()`` implementation to clarify how that
 is done. In this case it would of course be possible to just use ``super()`` call
 in the ``from_db()`` method.
 
+.. versionchanged:: 1.10
+
+    In older versions, you could check if all fields were loaded by consulting
+    ``cls._deferred``. This attribute is removed and
+    ``django.db.models.DEFERRED`` is new.
+
 Refreshing objects from database
 ================================
 

+ 6 - 0
docs/releases/1.10.txt

@@ -783,6 +783,12 @@ Miscellaneous
   Web servers already implement this behavior. Responses retrieved using the
   Django test client continue to have these "response fixes" applied.
 
+* ``Model.__init__()`` now receives ``django.db.models.DEFERRED`` as the value
+  of deferred fields.
+
+* The ``Model._deferred`` attribute is removed as dynamic model classes when
+  using ``QuerySet.defer()`` and ``only()`` is removed.
+
 .. _deprecated-features-1.10:
 
 Features deprecated in 1.10

+ 2 - 2
tests/admin_views/tests.py

@@ -3650,7 +3650,7 @@ class AdminCustomQuerysetTest(TestCase):
         self.assertContains(
             response,
             '<li class="success">The short message "<a href="%s">'
-            'ShortMessage_Deferred_timestamp object</a>" was changed successfully.</li>' %
+            'ShortMessage object</a>" was changed successfully.</li>' %
             reverse('admin:admin_views_shortmessage_change', args=(sm.pk,)), html=True
         )
 
@@ -3697,7 +3697,7 @@ class AdminCustomQuerysetTest(TestCase):
         self.assertContains(
             response,
             '<li class="success">The paper "<a href="%s">'
-            'Paper_Deferred_author object</a>" was changed successfully.</li>' %
+            'Paper object</a>" was changed successfully.</li>' %
             reverse('admin:admin_views_paper_change', args=(p.pk,)), html=True
         )
 

+ 5 - 6
tests/defer/tests.py

@@ -1,6 +1,6 @@
 from __future__ import unicode_literals
 
-from django.db.models.query_utils import DeferredAttribute, InvalidQuery
+from django.db.models.query_utils import InvalidQuery
 from django.test import TestCase
 
 from .models import (
@@ -15,10 +15,7 @@ class AssertionMixin(object):
         we examine attribute values. Therefore, this method returns the number
         of deferred fields on returned instances.
         """
-        count = 0
-        for field in obj._meta.fields:
-            if isinstance(obj.__class__.__dict__.get(field.attname), DeferredAttribute):
-                count += 1
+        count = len(obj.get_deferred_fields())
         self.assertEqual(count, num)
 
 
@@ -45,7 +42,9 @@ class DeferTests(AssertionMixin, TestCase):
         # of them except the model's primary key see #15494
         self.assert_delayed(qs.only("pk")[0], 3)
         # You can use 'pk' with reverse foreign key lookups.
-        self.assert_delayed(self.s1.primary_set.all().only('pk')[0], 3)
+        # The related_id is alawys set even if it's not fetched from the DB,
+        # so pk and related_id are not deferred.
+        self.assert_delayed(self.s1.primary_set.all().only('pk')[0], 2)
 
     def test_defer_only_chaining(self):
         qs = Primary.objects.all()

+ 0 - 63
tests/defer_regress/tests.py

@@ -2,16 +2,10 @@ from __future__ import unicode_literals
 
 from operator import attrgetter
 
-from django.apps import apps
 from django.contrib.contenttypes.models import ContentType
 from django.contrib.sessions.backends.db import SessionStore
-from django.db import models
 from django.db.models import Count
-from django.db.models.query_utils import (
-    DeferredAttribute, deferred_class_factory,
-)
 from django.test import TestCase, override_settings
-from django.test.utils import isolate_apps
 
 from .models import (
     Base, Child, Derived, Feature, Item, ItemAndSimpleItem, Leaf, Location,
@@ -98,28 +92,6 @@ class DeferRegressionTest(TestCase):
             list(SimpleItem.objects.annotate(Count('feature')).only('name')),
             list)
 
-    def test_ticket_11936(self):
-        app_config = apps.get_app_config("defer_regress")
-        # Regression for #11936 - get_models should not return deferred models
-        # by default. Run a couple of defer queries so that app registry must
-        # contain some deferred classes. It might contain a lot more classes
-        # depending on the order the tests are ran.
-        list(Item.objects.defer("name"))
-        list(Child.objects.defer("value"))
-        klasses = {model.__name__ for model in app_config.get_models()}
-        self.assertIn("Child", klasses)
-        self.assertIn("Item", klasses)
-        self.assertNotIn("Child_Deferred_value", klasses)
-        self.assertNotIn("Item_Deferred_name", klasses)
-        self.assertFalse(any(k._deferred for k in app_config.get_models()))
-
-        klasses_with_deferred = {model.__name__ for model in app_config.get_models(include_deferred=True)}
-        self.assertIn("Child", klasses_with_deferred)
-        self.assertIn("Item", klasses_with_deferred)
-        self.assertIn("Child_Deferred_value", klasses_with_deferred)
-        self.assertIn("Item_Deferred_name", klasses_with_deferred)
-        self.assertTrue(any(k._deferred for k in app_config.get_models(include_deferred=True)))
-
     @override_settings(SESSION_SERIALIZER='django.contrib.sessions.serializers.PickleSerializer')
     def test_ticket_12163(self):
         # Test for #12163 - Pickling error saving session with unsaved model
@@ -246,41 +218,6 @@ class DeferRegressionTest(TestCase):
         qs = SpecialFeature.objects.only('feature__item__name').select_related('feature__item')
         self.assertEqual(len(qs), 1)
 
-    def test_deferred_class_factory(self):
-        new_class = deferred_class_factory(
-            Item,
-            ('this_is_some_very_long_attribute_name_so_modelname_truncation_is_triggered',))
-        self.assertEqual(
-            new_class.__name__,
-            'Item_Deferred_this_is_some_very_long_attribute_nac34b1f495507dad6b02e2cb235c875e')
-
-    def test_deferred_class_factory_already_deferred(self):
-        deferred_item1 = deferred_class_factory(Item, ('name',))
-        deferred_item2 = deferred_class_factory(deferred_item1, ('value',))
-        self.assertIs(deferred_item2._meta.proxy_for_model, Item)
-        self.assertNotIsInstance(deferred_item2.__dict__.get('name'), DeferredAttribute)
-        self.assertIsInstance(deferred_item2.__dict__.get('value'), DeferredAttribute)
-
-    def test_deferred_class_factory_no_attrs(self):
-        deferred_cls = deferred_class_factory(Item, ())
-        self.assertFalse(deferred_cls._deferred)
-
-    @isolate_apps('defer_regress', kwarg_name='apps')
-    def test_deferred_class_factory_apps_reuse(self, apps):
-        """
-        #25563 - model._meta.apps should be used for caching and
-        retrieval of the created proxy class.
-        """
-        class BaseModel(models.Model):
-            field = models.BooleanField()
-
-            class Meta:
-                app_label = 'defer_regress'
-
-        deferred_model = deferred_class_factory(BaseModel, ['field'])
-        self.assertIs(deferred_model._meta.apps, apps)
-        self.assertIs(deferred_class_factory(BaseModel, ['field']), deferred_model)
-
 
 class DeferAnnotateSelectRelatedTest(TestCase):
     def test_defer_annotate_select_related(self):