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:
parent
aff4b75c34
commit
3db3ab71e9
|
@ -10,7 +10,6 @@ from __future__ import unicode_literals
|
||||||
import inspect
|
import inspect
|
||||||
from collections import namedtuple
|
from collections import namedtuple
|
||||||
|
|
||||||
from django.apps import apps
|
|
||||||
from django.core.exceptions import FieldDoesNotExist
|
from django.core.exceptions import FieldDoesNotExist
|
||||||
from django.db.backends import utils
|
from django.db.backends import utils
|
||||||
from django.db.models.constants import LOOKUP_SEP
|
from django.db.models.constants import LOOKUP_SEP
|
||||||
|
@ -272,12 +271,13 @@ def deferred_class_factory(model, attrs):
|
||||||
"""
|
"""
|
||||||
if not attrs:
|
if not attrs:
|
||||||
return model
|
return model
|
||||||
|
opts = model._meta
|
||||||
# Never create deferred models based on deferred model
|
# Never create deferred models based on deferred model
|
||||||
if model._deferred:
|
if model._deferred:
|
||||||
# Deferred models are proxies for the non-deferred model. We never
|
# Deferred models are proxies for the non-deferred model. We never
|
||||||
# create chains of defers => proxy_for_model is the non-deferred
|
# create chains of defers => proxy_for_model is the non-deferred
|
||||||
# model.
|
# 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
|
# 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
|
# 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
|
# 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)
|
name = utils.truncate_name(name, 80, 32)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
return apps.get_model(model._meta.app_label, name)
|
return opts.apps.get_model(model._meta.app_label, name)
|
||||||
|
|
||||||
except LookupError:
|
except LookupError:
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
proxy = True
|
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 = {attr: DeferredAttribute(attr, model) for attr in attrs}
|
||||||
overrides["Meta"] = Meta
|
overrides["Meta"] = Meta
|
||||||
|
|
|
@ -29,3 +29,8 @@ Bugfixes
|
||||||
* Avoided a confusing stack trace when starting :djadmin:`runserver` with an
|
* Avoided a confusing stack trace when starting :djadmin:`runserver` with an
|
||||||
invalid :setting:`INSTALLED_APPS` setting (:ticket:`25510`). This regression
|
invalid :setting:`INSTALLED_APPS` setting (:ticket:`25510`). This regression
|
||||||
appeared in 1.8.5 as a side effect of fixing :ticket:`24704`.
|
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.
|
||||||
|
|
|
@ -3,8 +3,10 @@ from __future__ import unicode_literals
|
||||||
from operator import attrgetter
|
from operator import attrgetter
|
||||||
|
|
||||||
from django.apps import apps
|
from django.apps import apps
|
||||||
|
from django.apps.registry import Apps
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django.contrib.sessions.backends.db import SessionStore
|
from django.contrib.sessions.backends.db import SessionStore
|
||||||
|
from django.db import models
|
||||||
from django.db.models import Count
|
from django.db.models import Count
|
||||||
from django.db.models.query_utils import (
|
from django.db.models.query_utils import (
|
||||||
DeferredAttribute, deferred_class_factory,
|
DeferredAttribute, deferred_class_factory,
|
||||||
|
@ -263,6 +265,24 @@ class DeferRegressionTest(TestCase):
|
||||||
deferred_cls = deferred_class_factory(Item, ())
|
deferred_cls = deferred_class_factory(Item, ())
|
||||||
self.assertFalse(deferred_cls._deferred)
|
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):
|
class DeferAnnotateSelectRelatedTest(TestCase):
|
||||||
def test_defer_annotate_select_related(self):
|
def test_defer_annotate_select_related(self):
|
||||||
|
|
Loading…
Reference in New Issue