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
This commit is contained in:
parent
bc2d8cdbc6
commit
0d4b5b9b4a
|
@ -855,7 +855,7 @@ def get_validation_errors(outfile, app=None):
|
||||||
if f.rel:
|
if f.rel:
|
||||||
rel_opts = f.rel.to._meta
|
rel_opts = f.rel.to._meta
|
||||||
if f.rel.to not in models.get_models():
|
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()
|
rel_name = RelatedObject(f.rel.to, cls, f).get_accessor_name()
|
||||||
for r in rel_opts.fields:
|
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
|
# existing fields, m2m fields, m2m related objects or related objects
|
||||||
rel_opts = f.rel.to._meta
|
rel_opts = f.rel.to._meta
|
||||||
if f.rel.to not in models.get_models():
|
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()
|
rel_name = RelatedObject(f.rel.to, cls, f).get_accessor_name()
|
||||||
for r in rel_opts.fields:
|
for r in rel_opts.fields:
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
from django.db import backend, connection, transaction
|
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.fields import AutoField, Field, IntegerField, get_ul_class
|
||||||
from django.db.models.related import RelatedObject
|
from django.db.models.related import RelatedObject
|
||||||
from django.utils.translation import gettext_lazy, string_concat
|
from django.utils.translation import gettext_lazy, string_concat
|
||||||
|
@ -23,7 +23,12 @@ def add_lookup(rel_cls, field):
|
||||||
name = field.rel.to
|
name = field.rel.to
|
||||||
module = rel_cls.__module__
|
module = rel_cls.__module__
|
||||||
key = (module, name)
|
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):
|
def do_pending_lookups(sender):
|
||||||
other_cls = sender
|
other_cls = sender
|
||||||
|
|
|
@ -5,36 +5,45 @@ from django.core.exceptions import ImproperlyConfigured
|
||||||
|
|
||||||
__all__ = ('get_apps', 'get_app', 'get_models', 'get_model', 'register_models')
|
__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
|
_app_models = {} # Dictionary of models against app label
|
||||||
# Each value is a dictionary of model name: model class
|
# 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():
|
def get_apps():
|
||||||
"Returns a list of all installed modules that contain models."
|
"Returns a list of all installed modules that contain models."
|
||||||
global _app_list
|
global _app_list
|
||||||
if _app_list is not None:
|
global _loaded
|
||||||
return _app_list
|
if not _loaded:
|
||||||
_app_list = []
|
_loaded = True
|
||||||
for app_name in settings.INSTALLED_APPS:
|
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:
|
|
||||||
try:
|
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:
|
except AttributeError:
|
||||||
pass # This app doesn't have a models.py in it.
|
pass # This app doesn't have a models.py in it.
|
||||||
return _app_list
|
return _app_list
|
||||||
|
|
||||||
def get_app(app_label):
|
def get_app(app_label):
|
||||||
"Returns the module containing the models for the given 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:
|
for app_name in settings.INSTALLED_APPS:
|
||||||
if app_label == app_name.split('.')[-1]:
|
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
|
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):
|
def get_models(app_mod=None):
|
||||||
"""
|
"""
|
||||||
Given a module containing models, returns a list of the models. Otherwise
|
Given a module containing models, returns a list of the models. Otherwise
|
||||||
|
|
|
@ -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
|
||||||
|
<Foo: Foo Foo1>
|
||||||
|
|
||||||
|
>>> b1.fwd
|
||||||
|
<Whiz: Whiz Whiz1>
|
||||||
|
|
||||||
|
>>> b1.back
|
||||||
|
<Foo: Foo Foo1>
|
||||||
|
|
||||||
|
"""
|
|
@ -162,10 +162,11 @@ class TestRunner:
|
||||||
# Initialize the test database.
|
# Initialize the test database.
|
||||||
cursor = connection.cursor()
|
cursor = connection.cursor()
|
||||||
|
|
||||||
|
from django.db.models.loading import load_app
|
||||||
# Install the core always installed apps
|
# Install the core always installed apps
|
||||||
for app in ALWAYS_INSTALLED_APPS:
|
for app in ALWAYS_INSTALLED_APPS:
|
||||||
self.output(1, "Installing contrib app %s" % app)
|
self.output(1, "Installing contrib app %s" % app)
|
||||||
mod = __import__(app + ".models", '', '', [''])
|
mod = load_app(app)
|
||||||
management.install(mod)
|
management.install(mod)
|
||||||
|
|
||||||
# Run the tests for each test model.
|
# Run the tests for each test model.
|
||||||
|
@ -173,8 +174,7 @@ class TestRunner:
|
||||||
for model_dir, model_name in test_models:
|
for model_dir, model_name in test_models:
|
||||||
self.output(1, "%s model: Importing" % model_name)
|
self.output(1, "%s model: Importing" % model_name)
|
||||||
try:
|
try:
|
||||||
# TODO: Abstract this into a meta.get_app() replacement?
|
mod = load_app(model_dir + '.' + model_name)
|
||||||
mod = __import__(model_dir + '.' + model_name + '.models', '', '', [''])
|
|
||||||
except Exception, e:
|
except Exception, e:
|
||||||
log_error(model_name, "Error while importing", ''.join(traceback.format_exception(*sys.exc_info())[1:]))
|
log_error(model_name, "Error while importing", ''.join(traceback.format_exception(*sys.exc_info())[1:]))
|
||||||
continue
|
continue
|
||||||
|
|
Loading…
Reference in New Issue