diff --git a/django/core/management.py b/django/core/management.py index a163c83c5d..ea572729d9 100644 --- a/django/core/management.py +++ b/django/core/management.py @@ -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: diff --git a/django/db/models/fields/related.py b/django/db/models/fields/related.py index f9750217a2..3863e75993 100644 --- a/django/db/models/fields/related.py +++ b/django/db/models/fields/related.py @@ -1,5 +1,5 @@ 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,7 +23,12 @@ def add_lookup(rel_cls, field): name = field.rel.to module = rel_cls.__module__ key = (module, name) - pending_lookups.setdefault(key, []).append((rel_cls, field)) + 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): other_cls = sender diff --git a/django/db/models/loading.py b/django/db/models/loading.py index 7f5c97924b..4eafc22fb4 100644 --- a/django/db/models/loading.py +++ b/django/db/models/loading.py @@ -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 = [] - for app_name in settings.INSTALLED_APPS: - try: - mod = __import__(app_name, '', '', ['models']) - 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: + global _loaded + if not _loaded: + _loaded = True + for app_name in settings.INSTALLED_APPS: try: - _app_list.append(mod.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. 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 diff --git a/tests/regressiontests/string_lookup/__init__.py b/tests/regressiontests/string_lookup/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/regressiontests/string_lookup/models.py b/tests/regressiontests/string_lookup/models.py new file mode 100644 index 0000000000..7dd31f572d --- /dev/null +++ b/tests/regressiontests/string_lookup/models.py @@ -0,0 +1,48 @@ +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 + + +>>> b1.fwd + + +>>> b1.back + + +""" diff --git a/tests/runtests.py b/tests/runtests.py index 291ee15f7d..e5e9c18fca 100755 --- a/tests/runtests.py +++ b/tests/runtests.py @@ -162,10 +162,11 @@ class TestRunner: # Initialize the test database. cursor = connection.cursor() + from django.db.models.loading import load_app # Install the core always installed apps for app in ALWAYS_INSTALLED_APPS: self.output(1, "Installing contrib app %s" % app) - mod = __import__(app + ".models", '', '', ['']) + mod = load_app(app) management.install(mod) # Run the tests for each test model. @@ -173,8 +174,7 @@ class TestRunner: for model_dir, model_name in test_models: self.output(1, "%s model: Importing" % model_name) try: - # TODO: Abstract this into a meta.get_app() replacement? - mod = __import__(model_dir + '.' + model_name + '.models', '', '', ['']) + mod = load_app(model_dir + '.' + model_name) except Exception, e: log_error(model_name, "Error while importing", ''.join(traceback.format_exception(*sys.exc_info())[1:])) continue