mirror of https://github.com/django/django.git
Split out a BaseAppCache, make AppCache borg again, add _meta.app_cache
This commit is contained in:
parent
941d23e548
commit
104ad0504b
|
@ -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 @@ 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
|
||||
|
@ -117,9 +116,9 @@ class DatabaseSchemaEditor(BaseDatabaseSchemaEditor):
|
|||
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)])
|
||||
|
||||
|
|
|
@ -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)
|
||||
# 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.
|
||||
|
|
|
@ -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():
|
||||
"""
|
||||
Returns a dictionary to be used as the initial value of the
|
||||
[shared] state of the app cache.
|
||||
"""
|
||||
return dict(
|
||||
# Keys of app_store are the model modules for each application.
|
||||
app_store = SortedDict(),
|
||||
|
||||
# Mapping of installed app_labels to model modules for that app.
|
||||
app_labels = {},
|
||||
|
||||
# Mapping of app_labels to a dictionary of model names to model code.
|
||||
# May contain apps that are not installed.
|
||||
app_models = SortedDict(),
|
||||
|
||||
# Mapping of app_labels to errors raised when trying to import the app.
|
||||
app_errors = {},
|
||||
|
||||
# -- Everything below here is only used when populating the 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):
|
||||
# Keys of app_store are the model modules for each application.
|
||||
self.app_store = SortedDict()
|
||||
# Mapping of installed app_labels to model modules for that app.
|
||||
self.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()
|
||||
# Mapping of app_labels to errors raised when trying to import the app.
|
||||
self.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 = {}
|
||||
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 @@ class AppCache(object):
|
|||
self.register_models(app_label, *models.values())
|
||||
|
||||
|
||||
class AppCacheWrapper(object):
|
||||
class AppCache(BaseAppCache):
|
||||
"""
|
||||
As AppCache can be changed at runtime, this class wraps it so any
|
||||
imported references to 'cache' are changed along with it.
|
||||
A cache that stores installed applications and their models. Used to
|
||||
provide reverse-relations and for app introspection (e.g. admin).
|
||||
|
||||
Borg version of the BaseAppCache class.
|
||||
"""
|
||||
|
||||
def __init__(self, cache):
|
||||
self._cache = cache
|
||||
__shared_state = _initialize()
|
||||
|
||||
def set_cache(self, cache):
|
||||
self._cache = cache
|
||||
def __init__(self):
|
||||
self.__dict__ = self.__shared_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
|
||||
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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"]
|
||||
|
|
|
@ -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 @@ class SchemaTests(TransactionTestCase):
|
|||
# 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"
|
||||
|
|
Loading…
Reference in New Issue