Skip to content

Commit

Permalink
Split out a BaseAppCache, make AppCache borg again, add _meta.app_cache
Browse files Browse the repository at this point in the history
  • Loading branch information
andrewgodwin committed May 9, 2013
1 parent 941d23e commit 104ad05
Show file tree
Hide file tree
Showing 6 changed files with 112 additions and 115 deletions.
15 changes: 7 additions & 8 deletions django/db/backends/sqlite3/schema.py
Original file line number Original file line Diff line number Diff line change
@@ -1,6 +1,6 @@
from django.db.backends.schema import BaseDatabaseSchemaEditor 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.fields.related import ManyToManyField
from django.db.models.loading import BaseAppCache




class DatabaseSchemaEditor(BaseDatabaseSchemaEditor): class DatabaseSchemaEditor(BaseDatabaseSchemaEditor):
Expand Down Expand Up @@ -38,20 +38,19 @@ def _remake_table(self, model, create_fields=[], delete_fields=[], alter_fields=
for field in delete_fields: for field in delete_fields:
del body[field.name] del body[field.name]
del mapping[field.column] del mapping[field.column]
# Work inside a new AppCache
app_cache = BaseAppCache()
# Construct a new model for the new state # Construct a new model for the new state
meta_contents = { meta_contents = {
'app_label': model._meta.app_label, 'app_label': model._meta.app_label,
'db_table': model._meta.db_table + "__new", 'db_table': model._meta.db_table + "__new",
'unique_together': model._meta.unique_together if override_uniques is None else override_uniques, 'unique_together': model._meta.unique_together if override_uniques is None else override_uniques,
'app_cache': app_cache,
} }
meta = type("Meta", tuple(), meta_contents) meta = type("Meta", tuple(), meta_contents)
body['Meta'] = meta body['Meta'] = meta
body['__module__'] = model.__module__ 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) temp_model = type(model._meta.object_name, model.__bases__, body)
cache.set_cache(default_cache)
# Create a new table with that format # Create a new table with that format
self.create_model(temp_model) self.create_model(temp_model)
# Copy data from the old table # Copy data from the old table
Expand Down Expand Up @@ -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) return self._alter_many_to_many(model, old_field, new_field, strict)
elif old_type is None or new_type is None: 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)" % ( 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, old_field,
new_field, new_field,
)) ))
# Alter by remaking table # Alter by remaking table
self._remake_table(model, alter_fields=[(old_field, new_field)]) self._remake_table(model, alter_fields=[(old_field, new_field)])


Expand Down
20 changes: 8 additions & 12 deletions django/db/models/base.py
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@
from django.db.models.deletion import Collector from django.db.models.deletion import Collector
from django.db.models.options import Options from django.db.models.options import Options
from django.db.models import signals 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.translation import ugettext_lazy as _
from django.utils.functional import curry from django.utils.functional import curry
from django.utils.encoding import force_str, force_text from django.utils.encoding import force_str, force_text
Expand Down Expand Up @@ -134,7 +133,7 @@ def __new__(cls, name, bases, attrs):
new_class._base_manager = new_class._base_manager._copy_to_model(new_class) new_class._base_manager = new_class._base_manager._copy_to_model(new_class)


# Bail out early if we have already created this 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) seed_cache=False, only_installed=False)
if m is not None: if m is not None:
return m return m
Expand Down Expand Up @@ -242,16 +241,13 @@ def __new__(cls, name, bases, attrs):


new_class._prepare() new_class._prepare()


if new_class._meta.auto_register: new_class._meta.app_cache.register_models(new_class._meta.app_label, new_class)
register_models(new_class._meta.app_label, new_class) # Because of the way imports happen (recursively), we may or may not be
# 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
# 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
# should only be one class for each model, so we always return the # registered version.
# registered version. return new_class._meta.app_cache.get_model(new_class._meta.app_label, name,
return get_model(new_class._meta.app_label, name, seed_cache=False, only_installed=False)
seed_cache=False, only_installed=False)
else:
return new_class


def copy_managers(cls, base_managers): def copy_managers(cls, base_managers):
# This is in-place sorting of an Options attribute, but that's fine. # This is in-place sorting of an Options attribute, but that's fine.
Expand Down
147 changes: 79 additions & 68 deletions django/db/models/loading.py
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -16,57 +16,52 @@
'load_app', 'app_cache_ready') 'load_app', 'app_cache_ready')




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

return dict(
def __init__(self):
# Keys of app_store are the model modules for each application. # 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. # 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. # Mapping of app_labels to a dictionary of model names to model code.
# May contain apps that are not installed. # 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. # 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 -- # -- Everything below here is only used when populating the cache --
self.loaded = False loaded = False,
self.handled = {} handled = {},
self.postponed = [] postponed = [],
self.nesting_level = 0 nesting_level = 0,
self._get_models_cache = {} _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): def _populate(self):
""" """
Fill in all the cache information. This method is threadsafe, in the Stub method - this base class does no auto-loading.
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: self.loaded = True
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): def _label_for(self, app_mod):
""" """
Expand Down Expand Up @@ -253,42 +248,58 @@ def copy_from(self, other):
self.register_models(app_label, *models.values()) 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).
def __init__(self, cache): Borg version of the BaseAppCache class.
self._cache = cache """


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


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


def __setattr__(self, attr, value): def _populate(self):
if attr in ("_cache", "set_cache"): """
self.__dict__[attr] = value 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
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 = AppCache()
cache = AppCacheWrapper(default_cache)




# These methods were always module level, so are kept that way for backwards # These methods were always module level, so are kept that way for backwards
# compatibility. These are wrapped with lambdas to stop the attribute # compatibility.
# access resolving directly to a method on a single cache instance. get_apps = cache.get_apps
get_apps = lambda *x, **y: cache.get_apps(*x, **y) get_app = cache.get_app
get_app = lambda *x, **y: cache.get_app(*x, **y) get_app_errors = cache.get_app_errors
get_app_errors = lambda *x, **y: cache.get_app_errors(*x, **y) get_models = cache.get_models
get_models = lambda *x, **y: cache.get_models(*x, **y) get_model = cache.get_model
get_model = lambda *x, **y: cache.get_model(*x, **y) register_models = cache.register_models
register_models = lambda *x, **y: cache.register_models(*x, **y) load_app = cache.load_app
load_app = lambda *x, **y: cache.load_app(*x, **y) app_cache_ready = cache.app_cache_ready
app_cache_ready = lambda *x, **y: cache.app_cache_ready(*x, **y)
8 changes: 4 additions & 4 deletions django/db/models/options.py
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from django.db.models.fields.related import ManyToManyRel from django.db.models.fields.related import ManyToManyRel
from django.db.models.fields import AutoField, FieldDoesNotExist from django.db.models.fields import AutoField, FieldDoesNotExist
from django.db.models.fields.proxy import OrderWrt 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 import six
from django.utils.functional import cached_property from django.utils.functional import cached_property
from django.utils.datastructures import SortedDict from django.utils.datastructures import SortedDict
Expand All @@ -21,7 +21,7 @@
DEFAULT_NAMES = ('verbose_name', 'verbose_name_plural', 'db_table', 'ordering', DEFAULT_NAMES = ('verbose_name', 'verbose_name_plural', 'db_table', 'ordering',
'unique_together', 'permissions', 'get_latest_by', 'unique_together', 'permissions', 'get_latest_by',
'order_with_respect_to', 'app_label', 'db_tablespace', '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 @python_2_unicode_compatible
Expand Down Expand Up @@ -70,8 +70,8 @@ def __init__(self, meta, app_label=None):
# from *other* models. Needed for some admin checks. Internal use only. # from *other* models. Needed for some admin checks. Internal use only.
self.related_fkey_lookups = [] self.related_fkey_lookups = []


# If we should auto-register with the AppCache # A custom AppCache to use, if you're making a separate model set.
self.auto_register = True self.app_cache = cache


def contribute_to_class(self, cls, name): def contribute_to_class(self, cls, name):
from django.db import connection from django.db import connection
Expand Down
25 changes: 14 additions & 11 deletions tests/schema/models.py
Original file line number Original file line Diff line number Diff line change
@@ -1,33 +1,36 @@
from django.db import models 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, # 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 # these models are all inserted into a separate AppCache so the main test
# a schema test is running. # runner doesn't syncdb them.

new_app_cache = BaseAppCache()




class Author(models.Model): class Author(models.Model):
name = models.CharField(max_length=255) name = models.CharField(max_length=255)
height = models.PositiveIntegerField(null=True, blank=True) height = models.PositiveIntegerField(null=True, blank=True)


class Meta: class Meta:
auto_register = False app_cache = new_app_cache




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


class Meta: class Meta:
auto_register = False app_cache = new_app_cache




class Book(models.Model): class Book(models.Model):
author = models.ForeignKey(Author) author = models.ForeignKey(Author)
title = models.CharField(max_length=100, db_index=True) title = models.CharField(max_length=100, db_index=True)
pub_date = models.DateTimeField() pub_date = models.DateTimeField()
#tags = models.ManyToManyField("Tag", related_name="books") # tags = models.ManyToManyField("Tag", related_name="books")


class Meta: class Meta:
auto_register = False app_cache = new_app_cache




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


class Meta: class Meta:
auto_register = False app_cache = new_app_cache




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


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




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


class Meta: class Meta:
auto_register = False app_cache = new_app_cache




class TagUniqueRename(models.Model): class TagUniqueRename(models.Model):
title = models.CharField(max_length=255) title = models.CharField(max_length=255)
slug2 = models.SlugField(unique=True) slug2 = models.SlugField(unique=True)


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




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


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

0 comments on commit 104ad05

Please sign in to comment.