Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

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

  • Loading branch information...
commit 104ad0504b4b123277b3f0e7c0be7fb9e84c2d72 1 parent 941d23e
@andrewgodwin andrewgodwin authored
View
15 django/db/backends/sqlite3/schema.py
@@ -1,6 +1,6 @@
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 @@ def _remake_table(self, model, create_fields=[], delete_fields=[], alter_fields=
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
@@ -117,9 +116,9 @@ def alter_field(self, model, old_field, new_field, strict=False):
return self._alter_many_to_many(model, old_field, new_field, strict)
elif old_type is None or new_type is None:
raise ValueError("Cannot alter field %s into %s - they are not compatible types (probably means only one is an M2M with implicit through model)" % (
- old_field,
- new_field,
- ))
+ old_field,
+ new_field,
+ ))
# Alter by remaking table
self._remake_table(model, alter_fields=[(old_field, new_field)])
View
20 django/db/models/base.py
@@ -19,7 +19,6 @@
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 @@ def __new__(cls, name, bases, attrs):
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 @@ def __new__(cls, name, bases, attrs):
new_class._prepare()
- 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
+ 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 new_class._meta.app_cache.get_model(new_class._meta.app_label, name,
+ seed_cache=False, only_installed=False)
def copy_managers(cls, base_managers):
# This is in-place sorting of an Options attribute, but that's fine.
View
147 django/db/models/loading.py
@@ -16,57 +16,52 @@
'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()
+ self.loaded = True
def _label_for(self, app_mod):
"""
@@ -253,42 +248,58 @@ def copy_from(self, other):
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
View
8 django/db/models/options.py
@@ -8,7 +8,7 @@
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 @@
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 @@ 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
+ # 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
View
25 tests/schema/models.py
@@ -1,8 +1,11 @@
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,24 +13,24 @@ 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):
author = models.ForeignKey(Author)
title = models.CharField(max_length=100, db_index=True)
pub_date = models.DateTimeField()
- #tags = models.ManyToManyField("Tag", related_name="books")
+ # 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"]
View
12 tests/schema/tests.py
@@ -1,12 +1,10 @@
from __future__ import absolute_import
-import copy
import datetime
from django.test import TransactionTestCase
from django.utils.unittest import skipUnless
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, default_cache, AppCache
from .models import Author, AuthorWithM2M, Book, BookWithSlug, BookWithM2M, Tag, TagUniqueRename, UniqueTest
@@ -27,14 +25,6 @@ class SchemaTests(TransactionTestCase):
def setUp(self):
# Make sure we're in manual transaction mode
connection.set_autocommit(False)
- # The unmanaged models need to be removed after the test in order to
- # prevent bad interactions with the flush operation in other tests.
- self.app_cache = AppCache()
- cache.set_cache(self.app_cache)
- cache.copy_from(default_cache)
- for model in self.models:
- cache.register_models("schema", model)
- model._prepare()
def tearDown(self):
# Delete any tables made for our models
@@ -43,8 +33,6 @@ def tearDown(self):
# Rollback anything that may have happened
connection.rollback()
connection.set_autocommit(True)
- 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.