1
0
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:
Andrew Godwin 2012-09-22 00:47:04 +01:00
parent dbc17d035b
commit 49d1e6b0e2
6 changed files with 72 additions and 83 deletions

View File

@ -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)
cache.copy_from(default_cache)
temp_model = type(model._meta.object_name, model.__bases__, body) 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

View File

@ -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)
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 # 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 # 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 # should only be one class for each model, so we always return the
# registered version. # registered version.
return get_model(new_class._meta.app_label, name, return get_model(new_class._meta.app_label, name,
seed_cache=False, only_installed=False) 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.

View File

@ -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)

View File

@ -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

View File

@ -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"]

View File

@ -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"