Commit 522b0bc9 authored by Simon Charette's avatar Simon Charette
Browse files

[1.9.x] Fixed #25563 -- Cached deferred models in their proxied model's _meta.apps.

Thanks to Andriy Sokolovskiy for the report and Tim Graham for the review.

Backport of 3db3ab71 from master
parent 094a6084
Loading
Loading
Loading
Loading
+5 −4
Original line number Diff line number Diff line
@@ -10,7 +10,6 @@ from __future__ import unicode_literals
import inspect
from collections import namedtuple

from django.apps import apps
from django.core.exceptions import FieldDoesNotExist
from django.db.backends import utils
from django.db.models.constants import LOOKUP_SEP
@@ -272,12 +271,13 @@ def deferred_class_factory(model, attrs):
    """
    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 = model._meta.proxy_for_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
@@ -286,13 +286,14 @@ def deferred_class_factory(model, attrs):
    name = utils.truncate_name(name, 80, 32)

    try:
        return apps.get_model(model._meta.app_label, name)
        return opts.apps.get_model(model._meta.app_label, name)

    except LookupError:

        class Meta:
            proxy = True
            app_label = model._meta.app_label
            apps = opts.apps
            app_label = opts.app_label

        overrides = {attr: DeferredAttribute(attr, model) for attr in attrs}
        overrides["Meta"] = Meta
+9 −0
Original line number Diff line number Diff line
@@ -25,3 +25,12 @@ Bugfixes
* Allowed filtering over a ``RawSQL`` annotation (:ticket:`25506`).

* Made the ``Concat`` database function idempotent on SQLite (:ticket:`25517`).

* Avoided a confusing stack trace when starting :djadmin:`runserver` with an
  invalid :setting:`INSTALLED_APPS` setting (:ticket:`25510`). This regression
  appeared in 1.8.5 as a side effect of fixing :ticket:`24704`.

* Made deferred models use their proxied model's ``_meta.apps`` for caching
  and retrieval (:ticket:`25563`). This prevents any models generated in data
  migrations using ``QuerySet.defer()`` from leaking to test and application
  code.
+20 −0
Original line number Diff line number Diff line
@@ -3,8 +3,10 @@ from __future__ import unicode_literals
from operator import attrgetter

from django.apps import apps
from django.apps.registry 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,
@@ -263,6 +265,24 @@ class DeferRegressionTest(TestCase):
        deferred_cls = deferred_class_factory(Item, ())
        self.assertFalse(deferred_cls._deferred)

    def test_deferred_class_factory_apps_reuse(self):
        """
        #25563 - model._meta.apps should be used for caching and
        retrieval of the created proxy class.
        """
        isolated_apps = Apps(['defer_regress'])

        class BaseModel(models.Model):
            field = models.BooleanField()

            class Meta:
                apps = isolated_apps
                app_label = 'defer_regress'

        deferred_model = deferred_class_factory(BaseModel, ['field'])
        self.assertIs(deferred_model._meta.apps, isolated_apps)
        self.assertIs(deferred_class_factory(BaseModel, ['field']), deferred_model)


class DeferAnnotateSelectRelatedTest(TestCase):
    def test_defer_annotate_select_related(self):