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