Remove AppCache state handling, replace with swappable caches
This commit is contained in:
parent
dbc17d035b
commit
49d1e6b0e2
|
@ -1,5 +1,5 @@
|
|||
from django.db.backends.schema import BaseDatabaseSchemaEditor
|
||||
from django.db.models.loading import cache
|
||||
from django.db.models.loading import cache, default_cache, AppCache
|
||||
from django.db.models.fields.related import ManyToManyField
|
||||
|
||||
|
||||
|
@ -46,10 +46,12 @@ class DatabaseSchemaEditor(BaseDatabaseSchemaEditor):
|
|||
}
|
||||
meta = type("Meta", tuple(), meta_contents)
|
||||
body['Meta'] = meta
|
||||
body['__module__'] = "__fake__"
|
||||
with cache.temporary_state():
|
||||
del cache.app_models[model._meta.app_label][model._meta.object_name.lower()]
|
||||
temp_model = type(model._meta.object_name, model.__bases__, body)
|
||||
body['__module__'] = model.__module__
|
||||
self.app_cache = AppCache()
|
||||
cache.set_cache(self.app_cache)
|
||||
cache.copy_from(default_cache)
|
||||
temp_model = type(model._meta.object_name, model.__bases__, body)
|
||||
cache.set_cache(default_cache)
|
||||
# Create a new table with that format
|
||||
self.create_model(temp_model)
|
||||
# Copy data from the old table
|
||||
|
|
|
@ -228,14 +228,17 @@ class ModelBase(type):
|
|||
return new_class
|
||||
|
||||
new_class._prepare()
|
||||
register_models(new_class._meta.app_label, new_class)
|
||||
|
||||
# Because of the way imports happen (recursively), we may or may not be
|
||||
# the first time this model tries to register with the framework. There
|
||||
# should only be one class for each model, so we always return the
|
||||
# registered version.
|
||||
return get_model(new_class._meta.app_label, name,
|
||||
seed_cache=False, only_installed=False)
|
||||
|
||||
if new_class._meta.auto_register:
|
||||
register_models(new_class._meta.app_label, new_class)
|
||||
# Because of the way imports happen (recursively), we may or may not be
|
||||
# the first time this model tries to register with the framework. There
|
||||
# should only be one class for each model, so we always return the
|
||||
# registered version.
|
||||
return get_model(new_class._meta.app_label, name,
|
||||
seed_cache=False, only_installed=False)
|
||||
else:
|
||||
return new_class
|
||||
|
||||
def copy_managers(cls, base_managers):
|
||||
# This is in-place sorting of an Options attribute, but that's fine.
|
||||
|
|
|
@ -236,69 +236,49 @@ class AppCache(object):
|
|||
model_dict[model_name] = model
|
||||
self._get_models_cache.clear()
|
||||
|
||||
def save_state(self):
|
||||
"""
|
||||
Returns an object that contains the current AppCache state.
|
||||
Can be provided to restore_state to undo actions.
|
||||
"""
|
||||
return {
|
||||
"app_store": SortedDict(self.app_store.items()),
|
||||
"app_labels": dict(self.app_labels.items()),
|
||||
"app_models": SortedDict((k, SortedDict(v.items())) for k, v in self.app_models.items()),
|
||||
"app_errors": dict(self.app_errors.items()),
|
||||
}
|
||||
|
||||
def restore_state(self, state):
|
||||
"""
|
||||
Restores the AppCache to a previous state from save_state.
|
||||
Note that the state is used by reference, not copied in.
|
||||
"""
|
||||
self.app_store = state['app_store']
|
||||
self.app_labels = state['app_labels']
|
||||
self.app_models = state['app_models']
|
||||
self.app_errors = state['app_errors']
|
||||
self._get_models_cache.clear()
|
||||
|
||||
def temporary_state(self):
|
||||
"Returns a context manager that restores the state on exit"
|
||||
return StateContextManager(self)
|
||||
|
||||
def unregister_all(self):
|
||||
"""
|
||||
Wipes the AppCache clean of all registered models.
|
||||
Used for things like migration libraries' fake ORMs.
|
||||
"""
|
||||
self.app_store = SortedDict()
|
||||
self.app_labels = {}
|
||||
self.app_models = SortedDict()
|
||||
self.app_errors = {}
|
||||
def copy_from(self, other):
|
||||
"Registers all models from the other cache into this one"
|
||||
cache._populate()
|
||||
for app_label, models in other.app_models.items():
|
||||
self.register_models(app_label, *models.values())
|
||||
|
||||
|
||||
class StateContextManager(object):
|
||||
class AppCacheWrapper(object):
|
||||
"""
|
||||
Context manager for locking cache state.
|
||||
Useful for making temporary models you don't want to stay in the cache.
|
||||
As AppCache can be changed at runtime, this class wraps it so any
|
||||
imported references to 'cache' are changed along with it.
|
||||
"""
|
||||
|
||||
def __init__(self, cache):
|
||||
self.cache = cache
|
||||
self._cache = cache
|
||||
|
||||
def __enter__(self):
|
||||
self.state = self.cache.save_state()
|
||||
def set_cache(self, cache):
|
||||
self._cache = cache
|
||||
|
||||
def __exit__(self, type, value, traceback):
|
||||
self.cache.restore_state(self.state)
|
||||
def __getattr__(self, attr):
|
||||
if attr in ("_cache", "set_cache"):
|
||||
return self.__dict__[attr]
|
||||
return getattr(self._cache, attr)
|
||||
|
||||
def __setattr__(self, attr, value):
|
||||
if attr in ("_cache", "set_cache"):
|
||||
self.__dict__[attr] = value
|
||||
return
|
||||
return setattr(self._cache, attr, value)
|
||||
|
||||
|
||||
cache = AppCache()
|
||||
default_cache = AppCache()
|
||||
cache = AppCacheWrapper(default_cache)
|
||||
|
||||
|
||||
# These methods were always module level, so are kept that way for backwards
|
||||
# compatibility.
|
||||
get_apps = cache.get_apps
|
||||
get_app = cache.get_app
|
||||
get_app_errors = cache.get_app_errors
|
||||
get_models = cache.get_models
|
||||
get_model = cache.get_model
|
||||
register_models = cache.register_models
|
||||
load_app = cache.load_app
|
||||
app_cache_ready = cache.app_cache_ready
|
||||
# compatibility. These are wrapped with lambdas to stop the attribute
|
||||
# access resolving directly to a method on a single cache instance.
|
||||
get_apps = lambda *x, **y: cache.get_apps(*x, **y)
|
||||
get_app = lambda *x, **y: cache.get_app(*x, **y)
|
||||
get_app_errors = lambda *x, **y: cache.get_app_errors(*x, **y)
|
||||
get_models = lambda *x, **y: cache.get_models(*x, **y)
|
||||
get_model = lambda *x, **y: cache.get_model(*x, **y)
|
||||
register_models = lambda *x, **y: cache.register_models(*x, **y)
|
||||
load_app = lambda *x, **y: cache.load_app(*x, **y)
|
||||
app_cache_ready = lambda *x, **y: cache.app_cache_ready(*x, **y)
|
||||
|
|
|
@ -21,7 +21,7 @@ get_verbose_name = lambda class_name: re.sub('(((?<=[a-z])[A-Z])|([A-Z](?![A-Z]|
|
|||
DEFAULT_NAMES = ('verbose_name', 'verbose_name_plural', 'db_table', 'ordering',
|
||||
'unique_together', 'permissions', 'get_latest_by',
|
||||
'order_with_respect_to', 'app_label', 'db_tablespace',
|
||||
'abstract', 'managed', 'proxy', 'auto_created')
|
||||
'abstract', 'managed', 'proxy', 'auto_created', 'auto_register')
|
||||
|
||||
@python_2_unicode_compatible
|
||||
class Options(object):
|
||||
|
@ -68,6 +68,9 @@ class Options(object):
|
|||
# from *other* models. Needed for some admin checks. Internal use only.
|
||||
self.related_fkey_lookups = []
|
||||
|
||||
# If we should auto-register with the AppCache
|
||||
self.auto_register = True
|
||||
|
||||
def contribute_to_class(self, cls, name):
|
||||
from django.db import connection
|
||||
from django.db.backends.util import truncate_name
|
||||
|
|
|
@ -10,14 +10,14 @@ class Author(models.Model):
|
|||
height = models.PositiveIntegerField(null=True, blank=True)
|
||||
|
||||
class Meta:
|
||||
managed = False
|
||||
auto_register = False
|
||||
|
||||
|
||||
class AuthorWithM2M(models.Model):
|
||||
name = models.CharField(max_length=255)
|
||||
|
||||
class Meta:
|
||||
managed = False
|
||||
auto_register = False
|
||||
|
||||
|
||||
class Book(models.Model):
|
||||
|
@ -27,7 +27,7 @@ class Book(models.Model):
|
|||
#tags = models.ManyToManyField("Tag", related_name="books")
|
||||
|
||||
class Meta:
|
||||
managed = False
|
||||
auto_register = False
|
||||
|
||||
|
||||
class BookWithM2M(models.Model):
|
||||
|
@ -37,7 +37,7 @@ class BookWithM2M(models.Model):
|
|||
tags = models.ManyToManyField("Tag", related_name="books")
|
||||
|
||||
class Meta:
|
||||
managed = False
|
||||
auto_register = False
|
||||
|
||||
|
||||
class BookWithSlug(models.Model):
|
||||
|
@ -47,7 +47,7 @@ class BookWithSlug(models.Model):
|
|||
slug = models.CharField(max_length=20, unique=True)
|
||||
|
||||
class Meta:
|
||||
managed = False
|
||||
auto_register = False
|
||||
db_table = "schema_book"
|
||||
|
||||
|
||||
|
@ -56,7 +56,7 @@ class Tag(models.Model):
|
|||
slug = models.SlugField(unique=True)
|
||||
|
||||
class Meta:
|
||||
managed = False
|
||||
auto_register = False
|
||||
|
||||
|
||||
class TagUniqueRename(models.Model):
|
||||
|
@ -64,7 +64,7 @@ class TagUniqueRename(models.Model):
|
|||
slug2 = models.SlugField(unique=True)
|
||||
|
||||
class Meta:
|
||||
managed = False
|
||||
auto_register = False
|
||||
db_table = "schema_tag"
|
||||
|
||||
|
||||
|
@ -73,5 +73,5 @@ class UniqueTest(models.Model):
|
|||
slug = models.SlugField(unique=False)
|
||||
|
||||
class Meta:
|
||||
managed = False
|
||||
auto_register = False
|
||||
unique_together = ["year", "slug"]
|
||||
|
|
|
@ -6,7 +6,7 @@ from django.utils.unittest import skipUnless
|
|||
from django.db import connection, DatabaseError, IntegrityError
|
||||
from django.db.models.fields import IntegerField, TextField, CharField, SlugField
|
||||
from django.db.models.fields.related import ManyToManyField, ForeignKey
|
||||
from django.db.models.loading import cache
|
||||
from django.db.models.loading import cache, default_cache, AppCache
|
||||
from .models import Author, AuthorWithM2M, Book, BookWithSlug, BookWithM2M, Tag, TagUniqueRename, UniqueTest
|
||||
|
||||
|
||||
|
@ -30,9 +30,12 @@ class SchemaTests(TestCase):
|
|||
connection.managed(True)
|
||||
# The unmanaged models need to be removed after the test in order to
|
||||
# prevent bad interactions with the flush operation in other tests.
|
||||
self.cache_state = cache.save_state()
|
||||
self.app_cache = AppCache()
|
||||
cache.set_cache(self.app_cache)
|
||||
cache.copy_from(default_cache)
|
||||
for model in self.models:
|
||||
model._meta.managed = True
|
||||
cache.register_models("schema", model)
|
||||
model._prepare()
|
||||
|
||||
def tearDown(self):
|
||||
# Delete any tables made for our models
|
||||
|
@ -40,10 +43,8 @@ class SchemaTests(TestCase):
|
|||
# Rollback anything that may have happened
|
||||
connection.rollback()
|
||||
connection.leave_transaction_management()
|
||||
# Unhook our models
|
||||
for model in self.models:
|
||||
model._meta.managed = False
|
||||
cache.restore_state(self.cache_state)
|
||||
cache.set_cache(default_cache)
|
||||
cache.app_models['schema'] = {} # One M2M gets left in the old cache
|
||||
|
||||
def delete_tables(self):
|
||||
"Deletes all model tables for our models for a clean test environment"
|
||||
|
|
Loading…
Reference in New Issue