Commit 49d1e6b0 authored by Andrew Godwin's avatar Andrew Godwin
Browse files

Remove AppCache state handling, replace with swappable caches

parent dbc17d03
Loading
Loading
Loading
Loading
+7 −5
Original line number Diff line number Diff line
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()]
        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
+11 −8
Original line number Diff line number Diff line
@@ -228,14 +228,17 @@ class ModelBase(type):
            return new_class

        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
            # 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.
+34 −54
Original line number Diff line number Diff line
@@ -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 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())

    def temporary_state(self):
        "Returns a context manager that restores the state on exit"
        return StateContextManager(self)

    def unregister_all(self):
class AppCacheWrapper(object):
    """
        Wipes the AppCache clean of all registered models.
        Used for things like migration libraries' fake ORMs.
    As AppCache can be changed at runtime, this class wraps it so any
    imported references to 'cache' are changed along with it.
    """
        self.app_store = SortedDict()
        self.app_labels = {}
        self.app_models = SortedDict()
        self.app_errors = {}

    def __init__(self, cache):
        self._cache = cache

class StateContextManager(object):
    """
    Context manager for locking cache state.
    Useful for making temporary models you don't want to stay in the cache.
    """
    def set_cache(self, cache):
        self._cache = cache

    def __init__(self, cache):
        self.cache = cache
    def __getattr__(self, attr):
        if attr in ("_cache", "set_cache"):
            return self.__dict__[attr]
        return getattr(self._cache, attr)

    def __enter__(self):
        self.state = self.cache.save_state()
    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)
+4 −1
Original line number Diff line number Diff line
@@ -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
+8 −8
Original line number Diff line number Diff line
@@ -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"]
Loading