Rest of the _meta.app_cache stuff. Schema tests work now.

This commit is contained in:
Andrew Godwin 2013-05-09 15:59:26 +01:00
parent 104ad0504b
commit 75bf394d86
5 changed files with 115 additions and 43 deletions

View File

@ -2,7 +2,7 @@ from operator import attrgetter
from django.db import connection, connections, router
from django.db.backends import util
from django.db.models import signals, get_model
from django.db.models import signals
from django.db.models.fields import (AutoField, Field, IntegerField,
PositiveIntegerField, PositiveSmallIntegerField, FieldDoesNotExist)
from django.db.models.related import RelatedObject, PathInfo
@ -18,8 +18,6 @@ from django import forms
RECURSIVE_RELATIONSHIP_CONSTANT = 'self'
pending_lookups = {}
def add_lazy_relation(cls, field, relation, operation):
"""
@ -70,14 +68,14 @@ def add_lazy_relation(cls, field, relation, operation):
# string right away. If get_model returns None, it means that the related
# model isn't loaded yet, so we need to pend the relation until the class
# is prepared.
model = get_model(app_label, model_name,
model = cls._meta.app_cache.get_model(app_label, model_name,
seed_cache=False, only_installed=False)
if model:
operation(field, model, cls)
else:
key = (app_label, model_name)
value = (cls, field, operation)
pending_lookups.setdefault(key, []).append(value)
cls._meta.app_cache.pending_lookups.setdefault(key, []).append(value)
def do_pending_lookups(sender, **kwargs):
@ -85,7 +83,7 @@ def do_pending_lookups(sender, **kwargs):
Handle any pending relations to the sending model. Sent from class_prepared.
"""
key = (sender._meta.app_label, sender.__name__)
for cls, field, operation in pending_lookups.pop(key, []):
for cls, field, operation in sender._meta.app_cache.pending_lookups.pop(key, []):
operation(field, sender, cls)
signals.class_prepared.connect(do_pending_lookups)
@ -1330,6 +1328,7 @@ def create_many_to_many_intermediary_model(field, klass):
'unique_together': (from_, to),
'verbose_name': '%(from)s-%(to)s relationship' % {'from': from_, 'to': to},
'verbose_name_plural': '%(from)s-%(to)s relationships' % {'from': from_, 'to': to},
'app_cache': field.model._meta.app_cache,
})
# Construct and return the new class.
return type(str(name), (models.Model,), {

View File

@ -35,7 +35,11 @@ def _initialize():
# Mapping of app_labels to errors raised when trying to import the app.
app_errors = {},
# Pending lookups for lazy relations
pending_lookups = {},
# -- Everything below here is only used when populating the cache --
loads_installed = True,
loaded = False,
handled = {},
postponed = [],
@ -56,12 +60,44 @@ class BaseAppCache(object):
def __init__(self):
self.__dict__ = _initialize()
# This stops _populate loading from INSTALLED_APPS and ignores the
# only_installed arguments to get_model[s]
self.loads_installed = False
def _populate(self):
"""
Stub method - this base class does no auto-loading.
"""
self.loaded = True
"""
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
if not self.loads_installed:
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):
"""
@ -169,12 +205,15 @@ class BaseAppCache(object):
By default, models that aren't part of installed apps will *not*
be included in the list of models. However, if you specify
only_installed=False, they will be.
only_installed=False, they will be. If you're using a non-default
AppCache, this argument does nothing - all models will be included.
By default, models that have been swapped out will *not* be
included in the list of models. However, if you specify
include_swapped, they will be.
"""
if not self.loads_installed:
only_installed = False
cache_key = (app_mod, include_auto_created, include_deferred, only_installed, include_swapped)
try:
return self._get_models_cache[cache_key]
@ -212,6 +251,8 @@ class BaseAppCache(object):
Returns None if no model is found.
"""
if not self.loads_installed:
only_installed = False
if seed_cache:
self._populate()
if only_installed and app_label not in self.app_labels:
@ -241,12 +282,6 @@ class BaseAppCache(object):
model_dict[model_name] = model
self._get_models_cache.clear()
def copy_from(self, other):
"Registers all models from the other cache into this one"
cache._populate()
for app_label, models in other.app_models.items():
self.register_models(app_label, *models.values())
class AppCache(BaseAppCache):
"""
@ -261,35 +296,6 @@ class AppCache(BaseAppCache):
def __init__(self):
self.__dict__ = self.__shared_state
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
# 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()
cache = AppCache()

View File

17
tests/app_cache/models.py Normal file
View File

@ -0,0 +1,17 @@
from django.db import models
from django.db.models.loading import BaseAppCache
# We're testing app cache presence on load, so this is handy.
new_app_cache = BaseAppCache()
class TotallyNormal(models.Model):
name = models.CharField(max_length=255)
class SoAlternative(models.Model):
name = models.CharField(max_length=255)
class Meta:
app_cache = new_app_cache

50
tests/app_cache/tests.py Normal file
View File

@ -0,0 +1,50 @@
from __future__ import absolute_import
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, BaseAppCache
from django.db import models
from .models import TotallyNormal, SoAlternative, new_app_cache
class AppCacheTests(TransactionTestCase):
"""
Tests the AppCache borg and non-borg versions
"""
def test_models_py(self):
"""
Tests that the models in the models.py file were loaded correctly.
"""
self.assertEqual(cache.get_model("app_cache", "TotallyNormal"), TotallyNormal)
self.assertEqual(cache.get_model("app_cache", "SoAlternative"), None)
self.assertEqual(new_app_cache.get_model("app_cache", "TotallyNormal"), None)
self.assertEqual(new_app_cache.get_model("app_cache", "SoAlternative"), SoAlternative)
def test_dynamic_load(self):
"""
Makes a new model at runtime and ensures it goes into the right place.
"""
old_models = cache.get_models(cache.get_app("app_cache"))
# Construct a new model in a new app cache
body = {}
new_app_cache = BaseAppCache()
meta_contents = {
'app_label': "app_cache",
'app_cache': new_app_cache,
}
meta = type("Meta", tuple(), meta_contents)
body['Meta'] = meta
body['__module__'] = TotallyNormal.__module__
temp_model = type("SouthPonies", (models.Model,), body)
# Make sure it appeared in the right place!
self.assertEqual(
old_models,
cache.get_models(cache.get_app("app_cache")),
)
self.assertEqual(new_app_cache.get_model("app_cache", "SouthPonies"), temp_model)