Skip to content

Commit

Permalink
[1.9.x] Fixed #25563 -- Cached deferred models in their proxied model…
Browse files Browse the repository at this point in the history
…'s _meta.apps.

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

Backport of 3db3ab7 from master
  • Loading branch information
charettes committed Oct 20, 2015
1 parent 094a608 commit 522b0bc
Show file tree
Hide file tree
Showing 3 changed files with 34 additions and 4 deletions.
9 changes: 5 additions & 4 deletions django/db/models/query_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
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
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down
9 changes: 9 additions & 0 deletions docs/releases/1.8.6.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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 changes: 20 additions & 0 deletions tests/defer_regress/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@
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,
Expand Down Expand Up @@ -263,6 +265,24 @@ def test_deferred_class_factory_no_attrs(self):
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):
Expand Down

0 comments on commit 522b0bc

Please sign in to comment.