Commit 75bf394d authored by Andrew Godwin's avatar Andrew Godwin
Browse files

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

parent 104ad050
Loading
Loading
Loading
Loading
+5 −6
Original line number Diff line number Diff line
@@ -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,), {
+43 −37
Original line number Diff line number Diff line
@@ -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.
        """
        """
        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()


+0 −0

Empty file added.

+17 −0
Original line number Diff line number Diff line
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 −0
Original line number Diff line number Diff line
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)