Skip to content

Commit

Permalink
Rest of the _meta.app_cache stuff. Schema tests work now.
Browse files Browse the repository at this point in the history
  • Loading branch information
andrewgodwin committed May 9, 2013
1 parent 104ad05 commit 75bf394
Show file tree
Hide file tree
Showing 5 changed files with 115 additions and 43 deletions.
11 changes: 5 additions & 6 deletions django/db/models/fields/related.py
Expand Up @@ -2,7 +2,7 @@

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
Expand All @@ -18,8 +18,6 @@

RECURSIVE_RELATIONSHIP_CONSTANT = 'self'

pending_lookups = {}


def add_lazy_relation(cls, field, relation, operation):
"""
Expand Down Expand Up @@ -70,22 +68,22 @@ class MyModel(Model):
# 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):
"""
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)
Expand Down Expand Up @@ -1330,6 +1328,7 @@ def set_managed(field, model, cls):
'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,), {
Expand Down
80 changes: 43 additions & 37 deletions django/db/models/loading.py
Expand Up @@ -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 = [],
Expand All @@ -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):
"""
Expand Down Expand Up @@ -169,12 +205,15 @@ def get_models(self, app_mod=None,
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]
Expand Down Expand Up @@ -212,6 +251,8 @@ def get_model(self, app_label, model_name,
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:
Expand Down Expand Up @@ -241,12 +282,6 @@ def register_models(self, app_label, *models):
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):
"""
Expand All @@ -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()


Expand Down
Empty file added tests/app_cache/__init__.py
Empty file.
17 changes: 17 additions & 0 deletions tests/app_cache/models.py
@@ -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 changes: 50 additions & 0 deletions tests/app_cache/tests.py
@@ -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)

0 comments on commit 75bf394

Please sign in to comment.