Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Remove AppCache state handling, replace with swappable caches

  • Loading branch information...
commit 49d1e6b0e20a363cbf9b105e8e6d3fc5fc1cad2f 1 parent dbc17d0
@andrewgodwin andrewgodwin authored
View
12 django/db/backends/sqlite3/schema.py
@@ -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 @@ def _remake_table(self, model, create_fields=[], delete_fields=[], alter_fields=
}
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
View
19 django/db/models/base.py
@@ -228,14 +228,17 @@ def __new__(cls, name, bases, attrs):
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.
View
88 django/db/models/loading.py
@@ -236,69 +236,49 @@ def register_models(self, app_label, *models):
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 set_cache(self, cache):
+ self._cache = cache
- def __enter__(self):
- self.state = self.cache.save_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)
- def __exit__(self, type, value, traceback):
- self.cache.restore_state(self.state)
+default_cache = AppCache()
+cache = AppCacheWrapper(default_cache)
-cache = AppCache()
# 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)
View
5 django/db/models/options.py
@@ -21,7 +21,7 @@
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 @@ def __init__(self, meta, app_label=None):
# 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
View
16 tests/modeltests/schema/models.py
@@ -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"]
View
15 tests/modeltests/schema/tests.py
@@ -6,7 +6,7 @@
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 @@ def setUp(self):
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 @@ def tearDown(self):
# 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"
Please sign in to comment.
Something went wrong with that request. Please try again.