Commit 104ad050 authored by Andrew Godwin's avatar Andrew Godwin
Browse files

Split out a BaseAppCache, make AppCache borg again, add _meta.app_cache

parent 941d23e5
Loading
Loading
Loading
Loading
+7 −8
Original line number Diff line number Diff line
from django.db.backends.schema import BaseDatabaseSchemaEditor
from django.db.models.loading import cache, default_cache, AppCache
from django.db.models.fields.related import ManyToManyField
from django.db.models.loading import BaseAppCache


class DatabaseSchemaEditor(BaseDatabaseSchemaEditor):
@@ -38,20 +38,19 @@ class DatabaseSchemaEditor(BaseDatabaseSchemaEditor):
        for field in delete_fields:
            del body[field.name]
            del mapping[field.column]
        # Work inside a new AppCache
        app_cache = BaseAppCache()
        # Construct a new model for the new state
        meta_contents = {
            'app_label': model._meta.app_label,
            'db_table': model._meta.db_table + "__new",
            'unique_together': model._meta.unique_together if override_uniques is None else override_uniques,
            'app_cache': app_cache,
        }
        meta = type("Meta", tuple(), meta_contents)
        body['Meta'] = meta
        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
+8 −12
Original line number Diff line number Diff line
@@ -19,7 +19,6 @@ from django.db.models.query_utils import DeferredAttribute, deferred_class_facto
from django.db.models.deletion import Collector
from django.db.models.options import Options
from django.db.models import signals
from django.db.models.loading import register_models, get_model
from django.utils.translation import ugettext_lazy as _
from django.utils.functional import curry
from django.utils.encoding import force_str, force_text
@@ -134,7 +133,7 @@ class ModelBase(type):
                new_class._base_manager = new_class._base_manager._copy_to_model(new_class)

        # Bail out early if we have already created this class.
        m = get_model(new_class._meta.app_label, name,
        m = new_class._meta.app_cache.get_model(new_class._meta.app_label, name,
                      seed_cache=False, only_installed=False)
        if m is not None:
            return m
@@ -242,16 +241,13 @@ class ModelBase(type):

        new_class._prepare()
        
        if new_class._meta.auto_register:
            register_models(new_class._meta.app_label, new_class)
        new_class._meta.app_cache.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,
        return new_class._meta.app_cache.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.
+79 −68
Original line number Diff line number Diff line
@@ -16,57 +16,52 @@ __all__ = ('get_apps', 'get_app', 'get_models', 'get_model', 'register_models',
        'load_app', 'app_cache_ready')


class AppCache(object):
def _initialize():
    """
    A cache that stores installed applications and their models. Used to
    provide reverse-relations and for app introspection (e.g. admin).
    Returns a dictionary to be used as the initial value of the
    [shared] state of the app cache.
    """

    def __init__(self):
    return dict(
        # Keys of app_store are the model modules for each application.
        self.app_store = SortedDict()
        app_store = SortedDict(),

        # Mapping of installed app_labels to model modules for that app.
        self.app_labels = {}
        app_labels = {},

        # Mapping of app_labels to a dictionary of model names to model code.
        # May contain apps that are not installed.
        self.app_models = SortedDict()
        app_models = SortedDict(),

        # Mapping of app_labels to errors raised when trying to import the app.
        self.app_errors = {}
        app_errors = {},

        # -- Everything below here is only used when populating the cache --
        self.loaded = False
        self.handled = {}
        self.postponed = []
        self.nesting_level = 0
        self._get_models_cache = {}
        loaded = False,
        handled = {},
        postponed = [],
        nesting_level = 0,
        _get_models_cache = {},
    )


class BaseAppCache(object):
    """
    A cache that stores installed applications and their models. Used to
    provide reverse-relations and for app introspection (e.g. admin).

    This provides the base (non-Borg) AppCache class - the AppCache
    subclass adds borg-like behaviour for the few cases where it's needed,
    and adds the code that auto-loads from INSTALLED_APPS.
    """

    def __init__(self):
        self.__dict__ = _initialize()

    def _populate(self):
        """
        Fill in all the cache information. This method is threadsafe, in the
        sense that every caller will see the same state upon return, and if the
        cache is already initialised, it does no work.
        Stub method - this base class does no auto-loading.
        """
        if self.loaded:
            return
        # Note that we want to use the import lock here - the app loading is
        # in many cases initiated implicitly by importing, and thus it is
        # possible to end up in deadlock when one thread initiates loading
        # without holding the importer lock and another thread then tries to
        # import something which also launches the app loading. For details of
        # this situation see #18251.
        imp.acquire_lock()
        try:
            if self.loaded:
                return
            for app_name in settings.INSTALLED_APPS:
                if app_name in self.handled:
                    continue
                self.load_app(app_name, True)
            if not self.nesting_level:
                for app_name in self.postponed:
                    self.load_app(app_name)
        self.loaded = True
        finally:
            imp.release_lock()

    def _label_for(self, app_mod):
        """
@@ -253,42 +248,58 @@ class AppCache(object):
            self.register_models(app_label, *models.values())


class AppCacheWrapper(object):
    """
    As AppCache can be changed at runtime, this class wraps it so any
    imported references to 'cache' are changed along with it.
class AppCache(BaseAppCache):
    """
    A cache that stores installed applications and their models. Used to
    provide reverse-relations and for app introspection (e.g. admin).

    def __init__(self, cache):
        self._cache = cache
    Borg version of the BaseAppCache class.
    """

    def set_cache(self, cache):
        self._cache = cache
    __shared_state = _initialize()

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

    def __setattr__(self, attr, value):
        if attr in ("_cache", "set_cache"):
            self.__dict__[attr] = value
    def _populate(self):
        """
        Fill in all the cache information. This method is threadsafe, in the
        sense that every caller will see the same state upon return, and if the
        cache is already initialised, it does no work.
        """
        if self.loaded:
            return
        return setattr(self._cache, attr, value)

        # Note that we want to use the import lock here - the app loading is
        # in many cases initiated implicitly by importing, and thus it is
        # possible to end up in deadlock when one thread initiates loading
        # without holding the importer lock and another thread then tries to
        # import something which also launches the app loading. For details of
        # this situation see #18251.
        imp.acquire_lock()
        try:
            if self.loaded:
                return
            for app_name in settings.INSTALLED_APPS:
                if app_name in self.handled:
                    continue
                self.load_app(app_name, True)
            if not self.nesting_level:
                for app_name in self.postponed:
                    self.load_app(app_name)
                self.loaded = True
        finally:
            imp.release_lock()

default_cache = AppCache()
cache = AppCacheWrapper(default_cache)
cache = AppCache()


# These methods were always module level, so are kept that way for backwards
# 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)
# 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
+4 −4
Original line number Diff line number Diff line
@@ -8,7 +8,7 @@ from django.conf import settings
from django.db.models.fields.related import ManyToManyRel
from django.db.models.fields import AutoField, FieldDoesNotExist
from django.db.models.fields.proxy import OrderWrt
from django.db.models.loading import get_models, app_cache_ready
from django.db.models.loading import get_models, app_cache_ready, cache
from django.utils import six
from django.utils.functional import cached_property
from django.utils.datastructures import SortedDict
@@ -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', 'swappable', 'auto_created', 'index_together', 'auto_register')
                 'abstract', 'managed', 'proxy', 'swappable', 'auto_created', 'index_together', 'app_cache')


@python_2_unicode_compatible
@@ -70,8 +70,8 @@ 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
        # A custom AppCache to use, if you're making a separate model set.
        self.app_cache = cache

    def contribute_to_class(self, cls, name):
        from django.db import connection
+14 −11
Original line number Diff line number Diff line
from django.db import models
from django.db.models.loading import BaseAppCache

# Because we want to test creation and deletion of these as separate things,
# these models are all marked as unmanaged and only marked as managed while
# a schema test is running.
# these models are all inserted into a separate AppCache so the main test
# runner doesn't syncdb them.

new_app_cache = BaseAppCache()


class Author(models.Model):
@@ -10,14 +13,14 @@ class Author(models.Model):
    height = models.PositiveIntegerField(null=True, blank=True)

    class Meta:
        auto_register = False
        app_cache = new_app_cache


class AuthorWithM2M(models.Model):
    name = models.CharField(max_length=255)

    class Meta:
        auto_register = False
        app_cache = new_app_cache


class Book(models.Model):
@@ -27,7 +30,7 @@ class Book(models.Model):
    # tags = models.ManyToManyField("Tag", related_name="books")

    class Meta:
        auto_register = False
        app_cache = new_app_cache


class BookWithM2M(models.Model):
@@ -37,7 +40,7 @@ class BookWithM2M(models.Model):
    tags = models.ManyToManyField("Tag", related_name="books")

    class Meta:
        auto_register = False
        app_cache = new_app_cache


class BookWithSlug(models.Model):
@@ -47,7 +50,7 @@ class BookWithSlug(models.Model):
    slug = models.CharField(max_length=20, unique=True)

    class Meta:
        auto_register = False
        app_cache = new_app_cache
        db_table = "schema_book"


@@ -56,7 +59,7 @@ class Tag(models.Model):
    slug = models.SlugField(unique=True)

    class Meta:
        auto_register = False
        app_cache = new_app_cache


class TagUniqueRename(models.Model):
@@ -64,7 +67,7 @@ class TagUniqueRename(models.Model):
    slug2 = models.SlugField(unique=True)

    class Meta:
        auto_register = False
        app_cache = new_app_cache
        db_table = "schema_tag"


@@ -73,5 +76,5 @@ class UniqueTest(models.Model):
    slug = models.SlugField(unique=False)

    class Meta:
        auto_register = False
        app_cache = new_app_cache
        unique_together = ["year", "slug"]
Loading