Deborgified the app cache.
Improved Andrew's hack to create temporary app caches to handle migrations. Now the main app cache has a "master" flag set to True (which is a non-default keyword argument, thus unlikely to be used by mistake). Other app cache instances have "master" set to False. The only sanctioned way to access the app cache is by importing django.core.apps.app_cache. If you were instanciating an app cache and relying on the Borg pattern, you'll have to refactor your code.
This commit is contained in:
parent
6e895f9e06
commit
0242c56fd8
|
@ -23,47 +23,41 @@ class UnavailableApp(Exception):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
def _initialize():
|
class AppCache(object):
|
||||||
"""
|
"""
|
||||||
Returns a dictionary to be used as the initial value of the
|
A cache that stores installed applications and their models. Used to
|
||||||
[shared] state of the app cache.
|
provide reverse-relations and for app introspection.
|
||||||
"""
|
"""
|
||||||
return dict(
|
|
||||||
|
def __init__(self, master=False):
|
||||||
|
# Only one master of the app-cache may exist at a given time, and it
|
||||||
|
# shall be the app_cache variable defined at the end of this module.
|
||||||
|
if master and hasattr(sys.modules[__name__], 'app_cache'):
|
||||||
|
raise RuntimeError("You may create only one master app cache.")
|
||||||
|
|
||||||
|
# When master is set to False, the app cache isn't populated from
|
||||||
|
# INSTALLED_APPS and ignores the only_installed arguments to
|
||||||
|
# get_model[s].
|
||||||
|
self.master = master
|
||||||
|
|
||||||
# Mapping of labels to AppConfig instances for installed apps.
|
# Mapping of labels to AppConfig instances for installed apps.
|
||||||
app_configs=OrderedDict(),
|
self.app_configs=OrderedDict()
|
||||||
|
|
||||||
# Pending lookups for lazy relations
|
# Pending lookups for lazy relations
|
||||||
pending_lookups={},
|
self.pending_lookups={}
|
||||||
|
|
||||||
# Set of app names. Allows restricting the set of installed apps.
|
# Set of app names. Allows restricting the set of installed apps.
|
||||||
# Used by TransactionTestCase.available_apps for performance reasons.
|
# Used by TransactionTestCase.available_apps for performance reasons.
|
||||||
available_apps=None,
|
self.available_apps=None
|
||||||
|
|
||||||
# -- Everything below here is only used when populating the cache --
|
# -- Everything below here is only used when populating the cache --
|
||||||
loads_installed=True,
|
self.loaded=False
|
||||||
loaded=False,
|
self.handled=set()
|
||||||
handled=set(),
|
self.postponed=[]
|
||||||
postponed=[],
|
self.nesting_level=0
|
||||||
nesting_level=0,
|
self._get_models_cache={}
|
||||||
_get_models_cache={},
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class BaseAppCache(object):
|
|
||||||
"""
|
|
||||||
A cache that stores installed applications and their models. Used to
|
|
||||||
provide reverse-relations and for app introspection (e.g. admin).
|
|
||||||
|
|
||||||
This provides the base (non-Borg) AppCache class - the AppCache
|
|
||||||
subclass adds borg-like behaviour for the few cases where it's needed.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
self.__dict__ = _initialize()
|
|
||||||
# This stops populate loading from INSTALLED_APPS and ignores the
|
|
||||||
# only_installed arguments to get_model[s]
|
|
||||||
self.loads_installed = False
|
|
||||||
|
|
||||||
def populate(self):
|
def populate(self):
|
||||||
"""
|
"""
|
||||||
Fill in all the cache information. This method is threadsafe, in the
|
Fill in all the cache information. This method is threadsafe, in the
|
||||||
|
@ -72,7 +66,7 @@ class BaseAppCache(object):
|
||||||
"""
|
"""
|
||||||
if self.loaded:
|
if self.loaded:
|
||||||
return
|
return
|
||||||
if not self.loads_installed:
|
if not self.master:
|
||||||
self.loaded = True
|
self.loaded = True
|
||||||
return
|
return
|
||||||
# Note that we want to use the import lock here - the app loading is
|
# Note that we want to use the import lock here - the app loading is
|
||||||
|
@ -217,7 +211,7 @@ class BaseAppCache(object):
|
||||||
included in the list of models. However, if you specify
|
included in the list of models. However, if you specify
|
||||||
include_swapped, they will be.
|
include_swapped, they will be.
|
||||||
"""
|
"""
|
||||||
if not self.loads_installed:
|
if not self.master:
|
||||||
only_installed = False
|
only_installed = False
|
||||||
cache_key = (app_mod, include_auto_created, include_deferred, only_installed, include_swapped)
|
cache_key = (app_mod, include_auto_created, include_deferred, only_installed, include_swapped)
|
||||||
model_list = None
|
model_list = None
|
||||||
|
@ -271,7 +265,7 @@ class BaseAppCache(object):
|
||||||
Raises UnavailableApp when set_available_apps() in in effect and
|
Raises UnavailableApp when set_available_apps() in in effect and
|
||||||
doesn't include app_label.
|
doesn't include app_label.
|
||||||
"""
|
"""
|
||||||
if not self.loads_installed:
|
if not self.master:
|
||||||
only_installed = False
|
only_installed = False
|
||||||
if seed_cache:
|
if seed_cache:
|
||||||
self.populate()
|
self.populate()
|
||||||
|
@ -399,18 +393,4 @@ class BaseAppCache(object):
|
||||||
self.register_model(app_label, model)
|
self.register_model(app_label, model)
|
||||||
|
|
||||||
|
|
||||||
class AppCache(BaseAppCache):
|
app_cache = AppCache(master=True)
|
||||||
"""
|
|
||||||
A cache that stores installed applications and their models. Used to
|
|
||||||
provide reverse-relations and for app introspection (e.g. admin).
|
|
||||||
|
|
||||||
Borg version of the BaseAppCache class.
|
|
||||||
"""
|
|
||||||
|
|
||||||
__shared_state = _initialize()
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
self.__dict__ = self.__shared_state
|
|
||||||
|
|
||||||
|
|
||||||
app_cache = AppCache()
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
from django.core.apps.cache import BaseAppCache
|
from django.core.apps.cache import AppCache
|
||||||
from django.db.backends.schema import BaseDatabaseSchemaEditor
|
from django.db.backends.schema import BaseDatabaseSchemaEditor
|
||||||
from django.db.models.fields.related import ManyToManyField
|
from django.db.models.fields.related import ManyToManyField
|
||||||
|
|
||||||
|
@ -39,7 +39,7 @@ class DatabaseSchemaEditor(BaseDatabaseSchemaEditor):
|
||||||
del body[field.name]
|
del body[field.name]
|
||||||
del mapping[field.column]
|
del mapping[field.column]
|
||||||
# Work inside a new AppCache
|
# Work inside a new AppCache
|
||||||
app_cache = BaseAppCache()
|
app_cache = AppCache()
|
||||||
# Construct a new model for the new state
|
# Construct a new model for the new state
|
||||||
meta_contents = {
|
meta_contents = {
|
||||||
'app_label': model._meta.app_label,
|
'app_label': model._meta.app_label,
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
from django.core.apps.cache import BaseAppCache
|
from django.core.apps.cache import AppCache
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.utils.timezone import now
|
from django.utils.timezone import now
|
||||||
|
|
||||||
|
@ -22,7 +22,7 @@ class MigrationRecorder(object):
|
||||||
applied = models.DateTimeField(default=now)
|
applied = models.DateTimeField(default=now)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
app_cache = BaseAppCache()
|
app_cache = AppCache()
|
||||||
app_label = "migrations"
|
app_label = "migrations"
|
||||||
db_table = "django_migrations"
|
db_table = "django_migrations"
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
from django.core.apps.cache import BaseAppCache
|
from django.core.apps.cache import AppCache
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.db.models.options import DEFAULT_NAMES, normalize_unique_together
|
from django.db.models.options import DEFAULT_NAMES, normalize_unique_together
|
||||||
from django.utils import six
|
from django.utils import six
|
||||||
|
@ -32,7 +32,7 @@ class ProjectState(object):
|
||||||
def render(self):
|
def render(self):
|
||||||
"Turns the project state into actual models in a new AppCache"
|
"Turns the project state into actual models in a new AppCache"
|
||||||
if self.app_cache is None:
|
if self.app_cache is None:
|
||||||
self.app_cache = BaseAppCache()
|
self.app_cache = AppCache()
|
||||||
# We keep trying to render the models in a loop, ignoring invalid
|
# We keep trying to render the models in a loop, ignoring invalid
|
||||||
# base errors, until the size of the unrendered models doesn't
|
# base errors, until the size of the unrendered models doesn't
|
||||||
# decrease by at least one, meaning there's a base dependency loop/
|
# decrease by at least one, meaning there's a base dependency loop/
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
from django.core.apps.cache import BaseAppCache
|
from django.core.apps.cache import AppCache
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
|
||||||
# We're testing app cache presence on load, so this is handy.
|
# We're testing app cache presence on load, so this is handy.
|
||||||
|
|
||||||
new_app_cache = BaseAppCache()
|
new_app_cache = AppCache()
|
||||||
|
|
||||||
|
|
||||||
class TotallyNormal(models.Model):
|
class TotallyNormal(models.Model):
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
from __future__ import absolute_import
|
from __future__ import absolute_import
|
||||||
|
|
||||||
from django.core.apps import app_cache
|
from django.core.apps import app_cache
|
||||||
from django.core.apps.cache import BaseAppCache
|
from django.core.apps.cache import AppCache
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
|
|
||||||
|
@ -30,7 +30,7 @@ class AppCacheTests(TestCase):
|
||||||
old_models = app_cache.get_models(app_cache.get_app_config("app_cache").models_module)
|
old_models = app_cache.get_models(app_cache.get_app_config("app_cache").models_module)
|
||||||
# Construct a new model in a new app cache
|
# Construct a new model in a new app cache
|
||||||
body = {}
|
body = {}
|
||||||
new_app_cache = BaseAppCache()
|
new_app_cache = AppCache()
|
||||||
meta_contents = {
|
meta_contents = {
|
||||||
'app_label': "app_cache",
|
'app_label': "app_cache",
|
||||||
'app_cache': new_app_cache,
|
'app_cache': new_app_cache,
|
||||||
|
@ -45,3 +45,10 @@ class AppCacheTests(TestCase):
|
||||||
app_cache.get_models(app_cache.get_app_config("app_cache").models_module),
|
app_cache.get_models(app_cache.get_app_config("app_cache").models_module),
|
||||||
)
|
)
|
||||||
self.assertEqual(new_app_cache.get_model("app_cache", "SouthPonies"), temp_model)
|
self.assertEqual(new_app_cache.get_model("app_cache", "SouthPonies"), temp_model)
|
||||||
|
|
||||||
|
def test_singleton_master(self):
|
||||||
|
"""
|
||||||
|
Ensures that only one master app cache can exist.
|
||||||
|
"""
|
||||||
|
with self.assertRaises(RuntimeError):
|
||||||
|
AppCache(master=True)
|
||||||
|
|
|
@ -75,17 +75,14 @@ class EggLoadingTest(TestCase):
|
||||||
Test that repeated app loading doesn't succeed in case there is an
|
Test that repeated app loading doesn't succeed in case there is an
|
||||||
error. Refs #17667.
|
error. Refs #17667.
|
||||||
"""
|
"""
|
||||||
# AppCache is a Borg, so we can instantiate one and change its
|
app_cache = AppCache()
|
||||||
# loaded to False to force the following code to actually try to
|
# Pretend we're the master app cache to test populate().
|
||||||
# populate the cache.
|
app_cache.master = True
|
||||||
a = AppCache()
|
with override_settings(INSTALLED_APPS=('notexists',)):
|
||||||
a.loaded = False
|
with self.assertRaises(ImportError):
|
||||||
try:
|
app_cache.get_model('notexists', 'nomodel', seed_cache=True)
|
||||||
with override_settings(INSTALLED_APPS=('notexists',)):
|
with self.assertRaises(ImportError):
|
||||||
self.assertRaises(ImportError, app_cache.get_model, 'notexists', 'nomodel', seed_cache=True)
|
app_cache.get_model('notexists', 'nomodel', seed_cache=True)
|
||||||
self.assertRaises(ImportError, app_cache.get_model, 'notexists', 'nomodel', seed_cache=True)
|
|
||||||
finally:
|
|
||||||
a.loaded = True
|
|
||||||
|
|
||||||
|
|
||||||
class GetModelsTest(TestCase):
|
class GetModelsTest(TestCase):
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django.core.apps.cache import BaseAppCache
|
from django.core.apps.cache import AppCache
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
|
|
||||||
|
@ -61,7 +61,7 @@ class ContentTypesViewsTests(TestCase):
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = 'a model created on the fly'
|
verbose_name = 'a model created on the fly'
|
||||||
app_label = 'my_great_app'
|
app_label = 'my_great_app'
|
||||||
app_cache = BaseAppCache()
|
app_cache = AppCache()
|
||||||
|
|
||||||
ct = ContentType.objects.get_for_model(ModelCreatedOnTheFly)
|
ct = ContentType.objects.get_for_model(ModelCreatedOnTheFly)
|
||||||
self.assertEqual(ct.app_label, 'my_great_app')
|
self.assertEqual(ct.app_label, 'my_great_app')
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from django.core.apps.cache import BaseAppCache
|
from django.core.apps.cache import AppCache
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.utils.encoding import python_2_unicode_compatible
|
from django.utils.encoding import python_2_unicode_compatible
|
||||||
|
|
||||||
|
@ -12,7 +12,7 @@ class UnicodeModel(models.Model):
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
# Disable auto loading of this model as we load it on our own
|
# Disable auto loading of this model as we load it on our own
|
||||||
app_cache = BaseAppCache()
|
app_cache = AppCache()
|
||||||
verbose_name = 'úñí©óðé µóðéø'
|
verbose_name = 'úñí©óðé µóðéø'
|
||||||
verbose_name_plural = 'úñí©óðé µóðéøß'
|
verbose_name_plural = 'úñí©óðé µóðéøß'
|
||||||
|
|
||||||
|
@ -32,4 +32,4 @@ class UnserializableModel(models.Model):
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
# Disable auto loading of this model as we load it on our own
|
# Disable auto loading of this model as we load it on our own
|
||||||
app_cache = BaseAppCache()
|
app_cache = AppCache()
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
from django.core.apps.cache import BaseAppCache
|
from django.core.apps.cache import AppCache
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.db.migrations.state import ProjectState, ModelState, InvalidBasesError
|
from django.db.migrations.state import ProjectState, ModelState, InvalidBasesError
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
|
@ -14,7 +14,7 @@ class StateTests(TestCase):
|
||||||
Tests making a ProjectState from an AppCache
|
Tests making a ProjectState from an AppCache
|
||||||
"""
|
"""
|
||||||
|
|
||||||
new_app_cache = BaseAppCache()
|
new_app_cache = AppCache()
|
||||||
|
|
||||||
class Author(models.Model):
|
class Author(models.Model):
|
||||||
name = models.CharField(max_length=255)
|
name = models.CharField(max_length=255)
|
||||||
|
@ -100,15 +100,15 @@ class StateTests(TestCase):
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
app_label = "migrations"
|
app_label = "migrations"
|
||||||
app_cache = BaseAppCache()
|
app_cache = AppCache()
|
||||||
|
|
||||||
class Novel(Book):
|
class Novel(Book):
|
||||||
class Meta:
|
class Meta:
|
||||||
app_label = "migrations"
|
app_label = "migrations"
|
||||||
app_cache = BaseAppCache()
|
app_cache = AppCache()
|
||||||
|
|
||||||
# First, test rendering individually
|
# First, test rendering individually
|
||||||
app_cache = BaseAppCache()
|
app_cache = AppCache()
|
||||||
|
|
||||||
# We shouldn't be able to render yet
|
# We shouldn't be able to render yet
|
||||||
ms = ModelState.from_model(Novel)
|
ms = ModelState.from_model(Novel)
|
||||||
|
@ -123,19 +123,19 @@ class StateTests(TestCase):
|
||||||
class Foo(models.Model):
|
class Foo(models.Model):
|
||||||
class Meta:
|
class Meta:
|
||||||
app_label = "migrations"
|
app_label = "migrations"
|
||||||
app_cache = BaseAppCache()
|
app_cache = AppCache()
|
||||||
|
|
||||||
class Bar(models.Model):
|
class Bar(models.Model):
|
||||||
class Meta:
|
class Meta:
|
||||||
app_label = "migrations"
|
app_label = "migrations"
|
||||||
app_cache = BaseAppCache()
|
app_cache = AppCache()
|
||||||
|
|
||||||
class FooBar(Foo, Bar):
|
class FooBar(Foo, Bar):
|
||||||
class Meta:
|
class Meta:
|
||||||
app_label = "migrations"
|
app_label = "migrations"
|
||||||
app_cache = BaseAppCache()
|
app_cache = AppCache()
|
||||||
|
|
||||||
app_cache = BaseAppCache()
|
app_cache = AppCache()
|
||||||
|
|
||||||
# We shouldn't be able to render yet
|
# We shouldn't be able to render yet
|
||||||
ms = ModelState.from_model(FooBar)
|
ms = ModelState.from_model(FooBar)
|
||||||
|
@ -152,7 +152,7 @@ class StateTests(TestCase):
|
||||||
Tests that the ProjectState render method correctly renders models
|
Tests that the ProjectState render method correctly renders models
|
||||||
to account for inter-model base dependencies.
|
to account for inter-model base dependencies.
|
||||||
"""
|
"""
|
||||||
new_app_cache = BaseAppCache()
|
new_app_cache = AppCache()
|
||||||
|
|
||||||
class A(models.Model):
|
class A(models.Model):
|
||||||
class Meta:
|
class Meta:
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
from django.core.apps.cache import BaseAppCache
|
from django.core.apps.cache import AppCache
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
|
||||||
# Because we want to test creation and deletion of these as separate things,
|
# Because we want to test creation and deletion of these as separate things,
|
||||||
# these models are all inserted into a separate AppCache so the main test
|
# these models are all inserted into a separate AppCache so the main test
|
||||||
# runner doesn't migrate them.
|
# runner doesn't migrate them.
|
||||||
|
|
||||||
new_app_cache = BaseAppCache()
|
new_app_cache = AppCache()
|
||||||
|
|
||||||
|
|
||||||
class Author(models.Model):
|
class Author(models.Model):
|
||||||
|
|
|
@ -3,7 +3,7 @@ from __future__ import unicode_literals
|
||||||
import datetime
|
import datetime
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
from django.core.apps.cache import BaseAppCache
|
from django.core.apps.cache import AppCache
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
|
@ -58,7 +58,7 @@ class GetUniqueCheckTests(unittest.TestCase):
|
||||||
|
|
||||||
Meta = type(str('Meta'), (), {
|
Meta = type(str('Meta'), (), {
|
||||||
'unique_together': unique_together,
|
'unique_together': unique_together,
|
||||||
'app_cache': BaseAppCache()
|
'app_cache': AppCache()
|
||||||
})
|
})
|
||||||
|
|
||||||
checks, _ = M()._get_unique_checks()
|
checks, _ = M()._get_unique_checks()
|
||||||
|
|
Loading…
Reference in New Issue