Commit 0d4b5b9b authored by Russell Keith-Magee's avatar Russell Keith-Magee
Browse files

Fixed #1662 -- Added resolver for string-form model references for models that...

Fixed #1662 -- Added resolver for string-form model references for models that have already been loaded, with tests to validate both forward and backward referenced model names. Light refactoring of model loading to make regression tests behave more like normal model loading. Also clarifies the text of some validation errors.


git-svn-id: http://code.djangoproject.com/svn/django/trunk@3195 bcc190cf-cafb-0310-a4f2-bffc1f526a37
parent bc2d8cdb
Loading
Loading
Loading
Loading
+2 −2
Original line number Diff line number Diff line
@@ -855,7 +855,7 @@ def get_validation_errors(outfile, app=None):
            if f.rel:
                rel_opts = f.rel.to._meta
                if f.rel.to not in models.get_models():
                    e.add(opts, "'%s' has relation with uninstalled model %s" % (f.name, rel_opts.object_name))
                    e.add(opts, "'%s' has relation with model %s, which has not been installed" % (f.name, rel_opts.object_name))

                rel_name = RelatedObject(f.rel.to, cls, f).get_accessor_name()
                for r in rel_opts.fields:
@@ -876,7 +876,7 @@ def get_validation_errors(outfile, app=None):
            # existing fields, m2m fields, m2m related objects or related objects
            rel_opts = f.rel.to._meta
            if f.rel.to not in models.get_models():
                e.add(opts, "'%s' has m2m relation with uninstalled model %s" % (f.name, rel_opts.object_name))
                e.add(opts, "'%s' has m2m relation with model %s, which has not been installed" % (f.name, rel_opts.object_name))

            rel_name = RelatedObject(f.rel.to, cls, f).get_accessor_name()
            for r in rel_opts.fields:
+7 −2
Original line number Diff line number Diff line
from django.db import backend, connection, transaction
from django.db.models import signals
from django.db.models import signals, get_model
from django.db.models.fields import AutoField, Field, IntegerField, get_ul_class
from django.db.models.related import RelatedObject
from django.utils.translation import gettext_lazy, string_concat
@@ -23,6 +23,11 @@ def add_lookup(rel_cls, field):
    name = field.rel.to
    module = rel_cls.__module__
    key = (module, name)
    model = get_model(rel_cls._meta.app_label,field.rel.to)
    if model:
        field.rel.to = model
        field.do_related_class(model, rel_cls)
    else:
        pending_lookups.setdefault(key, []).append((rel_cls, field))

def do_pending_lookups(sender):
+22 −13
Original line number Diff line number Diff line
@@ -5,36 +5,45 @@ from django.core.exceptions import ImproperlyConfigured

__all__ = ('get_apps', 'get_app', 'get_models', 'get_model', 'register_models')

_app_list = None # Cache of installed apps.
_app_list = []   # Cache of installed apps.
                 # Entry is not placed in app_list cache until entire app is loaded.
_app_models = {} # Dictionary of models against app label
                 # Each value is a dictionary of model name: model class
                 # Applabel and Model entry exists in cache when individual model is loaded.
_loaded = False  # Has the contents of settings.INSTALLED_APPS been loaded? 
                 # i.e., has get_apps() been called?

def get_apps():
    "Returns a list of all installed modules that contain models."
    global _app_list
    if _app_list is not None:
        return _app_list
    _app_list = []
    global _loaded
    if not _loaded:
        _loaded = True
        for app_name in settings.INSTALLED_APPS:
            try:
            mod = __import__(app_name, '', '', ['models'])
                load_app(app_name)
            except ImportError:
                pass # Assume this app doesn't have a models.py in it.
                     # GOTCHA: It may have a models.py that raises ImportError.
        else:
            try:
                _app_list.append(mod.models)
            except AttributeError:
                pass # This app doesn't have a models.py in it.
    return _app_list

def get_app(app_label):
    "Returns the module containing the models for the given app_label."
    get_apps() # Run get_apps() to populate the _app_list cache. Slightly hackish.
    for app_name in settings.INSTALLED_APPS:
        if app_label == app_name.split('.')[-1]:
            return __import__(app_name, '', '', ['models']).models
            return load_app(app_name)
    raise ImproperlyConfigured, "App with label %s could not be found" % app_label

def load_app(app_name):
    "Loads the app with the provided fully qualified name, and returns the model module."
    mod = __import__(app_name, '', '', ['models'])
    if mod.models not in _app_list:
        _app_list.append(mod.models)
    return mod.models
    
def get_models(app_mod=None):
    """
    Given a module containing models, returns a list of the models. Otherwise
+0 −0

Empty file added.

+48 −0
Original line number Diff line number Diff line
from django.db import models

class Foo(models.Model):
    name = models.CharField(maxlength=50)

    def __str__(self):
        return "Foo %s" % self.name

class Bar(models.Model):
    name = models.CharField(maxlength=50)
    normal = models.ForeignKey(Foo, related_name='normal_foo')
    fwd = models.ForeignKey("Whiz")
    back = models.ForeignKey("Foo")

    def __str__(self):
        return "Bar %s" % self.place.name

class Whiz(models.Model):
    name = models.CharField(maxlength = 50)

    def __str__(self):
        return "Whiz %s" % self.name

API_TESTS = """
# Regression test for #1662: Check that string form referencing of models works, both as
# pre and post reference

>>> f1 = Foo(name="Foo1")
>>> f1.save()
>>> f2 = Foo(name="Foo1")
>>> f2.save()

>>> w1 = Whiz(name="Whiz1")
>>> w1.save()

>>> b1 = Bar(name="Bar1", normal=f1, fwd=w1, back=f2)
>>> b1.save()

>>> b1.normal
<Foo: Foo Foo1>

>>> b1.fwd
<Whiz: Whiz Whiz1>

>>> b1.back
<Foo: Foo Foo1>

"""
Loading