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.
This commit is contained in:
Simon Charette 2015-10-19 14:17:55 -04:00
parent aff4b75c34
commit 3db3ab71e9
3 changed files with 30 additions and 4 deletions

View File

@ -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

View File

@ -29,3 +29,8 @@ Bugfixes
* 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.

View File

@ -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):