Skip to content

Commit 104ad05

Browse files
committed
Split out a BaseAppCache, make AppCache borg again, add _meta.app_cache
1 parent 941d23e commit 104ad05

File tree

6 files changed

+112
-115
lines changed

6 files changed

+112
-115
lines changed

django/db/backends/sqlite3/schema.py

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from django.db.backends.schema import BaseDatabaseSchemaEditor
2-
from django.db.models.loading import cache, default_cache, AppCache
32
from django.db.models.fields.related import ManyToManyField
3+
from django.db.models.loading import BaseAppCache
44

55

66
class DatabaseSchemaEditor(BaseDatabaseSchemaEditor):
@@ -38,20 +38,19 @@ def _remake_table(self, model, create_fields=[], delete_fields=[], alter_fields=
3838
for field in delete_fields:
3939
del body[field.name]
4040
del mapping[field.column]
41+
# Work inside a new AppCache
42+
app_cache = BaseAppCache()
4143
# Construct a new model for the new state
4244
meta_contents = {
4345
'app_label': model._meta.app_label,
4446
'db_table': model._meta.db_table + "__new",
4547
'unique_together': model._meta.unique_together if override_uniques is None else override_uniques,
48+
'app_cache': app_cache,
4649
}
4750
meta = type("Meta", tuple(), meta_contents)
4851
body['Meta'] = meta
4952
body['__module__'] = model.__module__
50-
self.app_cache = AppCache()
51-
cache.set_cache(self.app_cache)
52-
cache.copy_from(default_cache)
5353
temp_model = type(model._meta.object_name, model.__bases__, body)
54-
cache.set_cache(default_cache)
5554
# Create a new table with that format
5655
self.create_model(temp_model)
5756
# Copy data from the old table
@@ -117,9 +116,9 @@ def alter_field(self, model, old_field, new_field, strict=False):
117116
return self._alter_many_to_many(model, old_field, new_field, strict)
118117
elif old_type is None or new_type is None:
119118
raise ValueError("Cannot alter field %s into %s - they are not compatible types (probably means only one is an M2M with implicit through model)" % (
120-
old_field,
121-
new_field,
122-
))
119+
old_field,
120+
new_field,
121+
))
123122
# Alter by remaking table
124123
self._remake_table(model, alter_fields=[(old_field, new_field)])
125124

django/db/models/base.py

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919
from django.db.models.deletion import Collector
2020
from django.db.models.options import Options
2121
from django.db.models import signals
22-
from django.db.models.loading import register_models, get_model
2322
from django.utils.translation import ugettext_lazy as _
2423
from django.utils.functional import curry
2524
from django.utils.encoding import force_str, force_text
@@ -134,7 +133,7 @@ def __new__(cls, name, bases, attrs):
134133
new_class._base_manager = new_class._base_manager._copy_to_model(new_class)
135134

136135
# Bail out early if we have already created this class.
137-
m = get_model(new_class._meta.app_label, name,
136+
m = new_class._meta.app_cache.get_model(new_class._meta.app_label, name,
138137
seed_cache=False, only_installed=False)
139138
if m is not None:
140139
return m
@@ -242,16 +241,13 @@ def __new__(cls, name, bases, attrs):
242241

243242
new_class._prepare()
244243

245-
if new_class._meta.auto_register:
246-
register_models(new_class._meta.app_label, new_class)
247-
# Because of the way imports happen (recursively), we may or may not be
248-
# the first time this model tries to register with the framework. There
249-
# should only be one class for each model, so we always return the
250-
# registered version.
251-
return get_model(new_class._meta.app_label, name,
252-
seed_cache=False, only_installed=False)
253-
else:
254-
return new_class
244+
new_class._meta.app_cache.register_models(new_class._meta.app_label, new_class)
245+
# Because of the way imports happen (recursively), we may or may not be
246+
# the first time this model tries to register with the framework. There
247+
# should only be one class for each model, so we always return the
248+
# registered version.
249+
return new_class._meta.app_cache.get_model(new_class._meta.app_label, name,
250+
seed_cache=False, only_installed=False)
255251

256252
def copy_managers(cls, base_managers):
257253
# This is in-place sorting of an Options attribute, but that's fine.

django/db/models/loading.py

Lines changed: 79 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -16,57 +16,52 @@
1616
'load_app', 'app_cache_ready')
1717

1818

19-
class AppCache(object):
19+
def _initialize():
2020
"""
21-
A cache that stores installed applications and their models. Used to
22-
provide reverse-relations and for app introspection (e.g. admin).
21+
Returns a dictionary to be used as the initial value of the
22+
[shared] state of the app cache.
2323
"""
24-
25-
def __init__(self):
24+
return dict(
2625
# Keys of app_store are the model modules for each application.
27-
self.app_store = SortedDict()
26+
app_store = SortedDict(),
27+
2828
# Mapping of installed app_labels to model modules for that app.
29-
self.app_labels = {}
29+
app_labels = {},
30+
3031
# Mapping of app_labels to a dictionary of model names to model code.
3132
# May contain apps that are not installed.
32-
self.app_models = SortedDict()
33+
app_models = SortedDict(),
34+
3335
# Mapping of app_labels to errors raised when trying to import the app.
34-
self.app_errors = {}
36+
app_errors = {},
37+
3538
# -- Everything below here is only used when populating the cache --
36-
self.loaded = False
37-
self.handled = {}
38-
self.postponed = []
39-
self.nesting_level = 0
40-
self._get_models_cache = {}
39+
loaded = False,
40+
handled = {},
41+
postponed = [],
42+
nesting_level = 0,
43+
_get_models_cache = {},
44+
)
45+
46+
47+
class BaseAppCache(object):
48+
"""
49+
A cache that stores installed applications and their models. Used to
50+
provide reverse-relations and for app introspection (e.g. admin).
51+
52+
This provides the base (non-Borg) AppCache class - the AppCache
53+
subclass adds borg-like behaviour for the few cases where it's needed,
54+
and adds the code that auto-loads from INSTALLED_APPS.
55+
"""
56+
57+
def __init__(self):
58+
self.__dict__ = _initialize()
4159

4260
def _populate(self):
4361
"""
44-
Fill in all the cache information. This method is threadsafe, in the
45-
sense that every caller will see the same state upon return, and if the
46-
cache is already initialised, it does no work.
62+
Stub method - this base class does no auto-loading.
4763
"""
48-
if self.loaded:
49-
return
50-
# Note that we want to use the import lock here - the app loading is
51-
# in many cases initiated implicitly by importing, and thus it is
52-
# possible to end up in deadlock when one thread initiates loading
53-
# without holding the importer lock and another thread then tries to
54-
# import something which also launches the app loading. For details of
55-
# this situation see #18251.
56-
imp.acquire_lock()
57-
try:
58-
if self.loaded:
59-
return
60-
for app_name in settings.INSTALLED_APPS:
61-
if app_name in self.handled:
62-
continue
63-
self.load_app(app_name, True)
64-
if not self.nesting_level:
65-
for app_name in self.postponed:
66-
self.load_app(app_name)
67-
self.loaded = True
68-
finally:
69-
imp.release_lock()
64+
self.loaded = True
7065

7166
def _label_for(self, app_mod):
7267
"""
@@ -253,42 +248,58 @@ def copy_from(self, other):
253248
self.register_models(app_label, *models.values())
254249

255250

256-
class AppCacheWrapper(object):
257-
"""
258-
As AppCache can be changed at runtime, this class wraps it so any
259-
imported references to 'cache' are changed along with it.
251+
class AppCache(BaseAppCache):
260252
"""
253+
A cache that stores installed applications and their models. Used to
254+
provide reverse-relations and for app introspection (e.g. admin).
261255
262-
def __init__(self, cache):
263-
self._cache = cache
256+
Borg version of the BaseAppCache class.
257+
"""
264258

265-
def set_cache(self, cache):
266-
self._cache = cache
259+
__shared_state = _initialize()
267260

268-
def __getattr__(self, attr):
269-
if attr in ("_cache", "set_cache"):
270-
return self.__dict__[attr]
271-
return getattr(self._cache, attr)
261+
def __init__(self):
262+
self.__dict__ = self.__shared_state
272263

273-
def __setattr__(self, attr, value):
274-
if attr in ("_cache", "set_cache"):
275-
self.__dict__[attr] = value
264+
def _populate(self):
265+
"""
266+
Fill in all the cache information. This method is threadsafe, in the
267+
sense that every caller will see the same state upon return, and if the
268+
cache is already initialised, it does no work.
269+
"""
270+
if self.loaded:
276271
return
277-
return setattr(self._cache, attr, value)
278-
272+
# Note that we want to use the import lock here - the app loading is
273+
# in many cases initiated implicitly by importing, and thus it is
274+
# possible to end up in deadlock when one thread initiates loading
275+
# without holding the importer lock and another thread then tries to
276+
# import something which also launches the app loading. For details of
277+
# this situation see #18251.
278+
imp.acquire_lock()
279+
try:
280+
if self.loaded:
281+
return
282+
for app_name in settings.INSTALLED_APPS:
283+
if app_name in self.handled:
284+
continue
285+
self.load_app(app_name, True)
286+
if not self.nesting_level:
287+
for app_name in self.postponed:
288+
self.load_app(app_name)
289+
self.loaded = True
290+
finally:
291+
imp.release_lock()
279292

280-
default_cache = AppCache()
281-
cache = AppCacheWrapper(default_cache)
293+
cache = AppCache()
282294

283295

284296
# These methods were always module level, so are kept that way for backwards
285-
# compatibility. These are wrapped with lambdas to stop the attribute
286-
# access resolving directly to a method on a single cache instance.
287-
get_apps = lambda *x, **y: cache.get_apps(*x, **y)
288-
get_app = lambda *x, **y: cache.get_app(*x, **y)
289-
get_app_errors = lambda *x, **y: cache.get_app_errors(*x, **y)
290-
get_models = lambda *x, **y: cache.get_models(*x, **y)
291-
get_model = lambda *x, **y: cache.get_model(*x, **y)
292-
register_models = lambda *x, **y: cache.register_models(*x, **y)
293-
load_app = lambda *x, **y: cache.load_app(*x, **y)
294-
app_cache_ready = lambda *x, **y: cache.app_cache_ready(*x, **y)
297+
# compatibility.
298+
get_apps = cache.get_apps
299+
get_app = cache.get_app
300+
get_app_errors = cache.get_app_errors
301+
get_models = cache.get_models
302+
get_model = cache.get_model
303+
register_models = cache.register_models
304+
load_app = cache.load_app
305+
app_cache_ready = cache.app_cache_ready

django/db/models/options.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
from django.db.models.fields.related import ManyToManyRel
99
from django.db.models.fields import AutoField, FieldDoesNotExist
1010
from django.db.models.fields.proxy import OrderWrt
11-
from django.db.models.loading import get_models, app_cache_ready
11+
from django.db.models.loading import get_models, app_cache_ready, cache
1212
from django.utils import six
1313
from django.utils.functional import cached_property
1414
from django.utils.datastructures import SortedDict
@@ -21,7 +21,7 @@
2121
DEFAULT_NAMES = ('verbose_name', 'verbose_name_plural', 'db_table', 'ordering',
2222
'unique_together', 'permissions', 'get_latest_by',
2323
'order_with_respect_to', 'app_label', 'db_tablespace',
24-
'abstract', 'managed', 'proxy', 'swappable', 'auto_created', 'index_together', 'auto_register')
24+
'abstract', 'managed', 'proxy', 'swappable', 'auto_created', 'index_together', 'app_cache')
2525

2626

2727
@python_2_unicode_compatible
@@ -70,8 +70,8 @@ def __init__(self, meta, app_label=None):
7070
# from *other* models. Needed for some admin checks. Internal use only.
7171
self.related_fkey_lookups = []
7272

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

7676
def contribute_to_class(self, cls, name):
7777
from django.db import connection

tests/schema/models.py

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,36 @@
11
from django.db import models
2+
from django.db.models.loading import BaseAppCache
23

34
# Because we want to test creation and deletion of these as separate things,
4-
# these models are all marked as unmanaged and only marked as managed while
5-
# a schema test is running.
5+
# these models are all inserted into a separate AppCache so the main test
6+
# runner doesn't syncdb them.
7+
8+
new_app_cache = BaseAppCache()
69

710

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

1215
class Meta:
13-
auto_register = False
16+
app_cache = new_app_cache
1417

1518

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

1922
class Meta:
20-
auto_register = False
23+
app_cache = new_app_cache
2124

2225

2326
class Book(models.Model):
2427
author = models.ForeignKey(Author)
2528
title = models.CharField(max_length=100, db_index=True)
2629
pub_date = models.DateTimeField()
27-
#tags = models.ManyToManyField("Tag", related_name="books")
30+
# tags = models.ManyToManyField("Tag", related_name="books")
2831

2932
class Meta:
30-
auto_register = False
33+
app_cache = new_app_cache
3134

3235

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

3942
class Meta:
40-
auto_register = False
43+
app_cache = new_app_cache
4144

4245

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

4952
class Meta:
50-
auto_register = False
53+
app_cache = new_app_cache
5154
db_table = "schema_book"
5255

5356

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

5861
class Meta:
59-
auto_register = False
62+
app_cache = new_app_cache
6063

6164

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

6669
class Meta:
67-
auto_register = False
70+
app_cache = new_app_cache
6871
db_table = "schema_tag"
6972

7073

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

7578
class Meta:
76-
auto_register = False
79+
app_cache = new_app_cache
7780
unique_together = ["year", "slug"]

0 commit comments

Comments
 (0)