Fixed #15866, #15850 -- Prevented get_model() and get_models() from returning not-installed models (by default). Thanks adsva for report.

git-svn-id: http://code.djangoproject.com/svn/django/trunk@16053 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
Carl Meyer 2011-04-20 17:58:37 +00:00
parent fda8a9a390
commit 6bdaef26ec
8 changed files with 67 additions and 12 deletions

View File

@ -90,7 +90,8 @@ class ContentType(models.Model):
def model_class(self): def model_class(self):
"Returns the Python model class for this type of content." "Returns the Python model class for this type of content."
from django.db import models from django.db import models
return models.get_model(self.app_label, self.model) return models.get_model(self.app_label, self.model,
only_installed=False)
def get_object_for_this_type(self, **kwargs): def get_object_for_this_type(self, **kwargs):
""" """

View File

@ -88,7 +88,8 @@ class ModelBase(type):
new_class._base_manager = new_class._base_manager._copy_to_model(new_class) new_class._base_manager = new_class._base_manager._copy_to_model(new_class)
# Bail out early if we have already created this class. # Bail out early if we have already created this class.
m = get_model(new_class._meta.app_label, name, False) m = get_model(new_class._meta.app_label, name,
seed_cache=False, only_installed=False)
if m is not None: if m is not None:
return m return m
@ -201,7 +202,8 @@ class ModelBase(type):
# the first time this model tries to register with the framework. There # the first time this model tries to register with the framework. There
# should only be one class for each model, so we always return the # should only be one class for each model, so we always return the
# registered version. # registered version.
return get_model(new_class._meta.app_label, name, False) return get_model(new_class._meta.app_label, name,
seed_cache=False, only_installed=False)
def copy_managers(cls, base_managers): def copy_managers(cls, base_managers):
# This is in-place sorting of an Options attribute, but that's fine. # This is in-place sorting of an Options attribute, but that's fine.

View File

@ -67,7 +67,8 @@ def add_lazy_relation(cls, field, relation, operation):
# string right away. If get_model returns None, it means that the related # 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 # model isn't loaded yet, so we need to pend the relation until the class
# is prepared. # is prepared.
model = get_model(app_label, model_name, False) model = get_model(app_label, model_name,
seed_cache=False, only_installed=False)
if model: if model:
operation(field, model, cls) operation(field, model, cls)
else: else:

View File

@ -25,7 +25,11 @@ class AppCache(object):
# Keys of app_store are the model modules for each application. # Keys of app_store are the model modules for each application.
app_store = SortedDict(), app_store = SortedDict(),
# Mapping of installed app_labels to model modules for that app.
app_labels = {},
# Mapping of app_labels to a dictionary of model names to model code. # Mapping of app_labels to a dictionary of model names to model code.
# May contain apps that are not installed.
app_models = SortedDict(), app_models = SortedDict(),
# Mapping of app_labels to errors raised when trying to import the app. # Mapping of app_labels to errors raised when trying to import the app.
@ -66,6 +70,13 @@ class AppCache(object):
finally: finally:
self.write_lock.release() self.write_lock.release()
def _label_for(self, app_mod):
"""
Return app_label for given models module.
"""
return app_mod.__name__.split('.')[-2]
def load_app(self, app_name, can_postpone=False): def load_app(self, app_name, can_postpone=False):
""" """
Loads the app with the provided fully qualified name, and returns the Loads the app with the provided fully qualified name, and returns the
@ -99,6 +110,7 @@ class AppCache(object):
self.nesting_level -= 1 self.nesting_level -= 1
if models not in self.app_store: if models not in self.app_store:
self.app_store[models] = len(self.app_store) self.app_store[models] = len(self.app_store)
self.app_labels[self._label_for(models)] = models
return models return models
def app_cache_ready(self): def app_cache_ready(self):
@ -146,7 +158,8 @@ class AppCache(object):
self._populate() self._populate()
return self.app_errors return self.app_errors
def get_models(self, app_mod=None, include_auto_created=False, include_deferred=False): def get_models(self, app_mod=None,
include_auto_created=False, include_deferred=False):
""" """
Given a module containing models, returns a list of the models. Given a module containing models, returns a list of the models.
Otherwise returns a list of all installed models. Otherwise returns a list of all installed models.
@ -166,20 +179,26 @@ class AppCache(object):
pass pass
self._populate() self._populate()
if app_mod: if app_mod:
app_list = [self.app_models.get(app_mod.__name__.split('.')[-2], SortedDict())] if app_mod in self.app_store:
app_list = [self.app_models.get(self._label_for(app_mod),
SortedDict())]
else:
app_list = []
else: else:
app_list = self.app_models.itervalues() app_list = [self.app_models.get(app_label, SortedDict())
for app_label in self.app_labels.iterkeys()]
model_list = [] model_list = []
for app in app_list: for app in app_list:
model_list.extend( model_list.extend(
model for model in app.values() model for model in app.values()
if ((not model._deferred or include_deferred) if ((not model._deferred or include_deferred) and
and (not model._meta.auto_created or include_auto_created)) (not model._meta.auto_created or include_auto_created))
) )
self._get_models_cache[cache_key] = model_list self._get_models_cache[cache_key] = model_list
return model_list return model_list
def get_model(self, app_label, model_name, seed_cache=True): def get_model(self, app_label, model_name,
seed_cache=True, only_installed=True):
""" """
Returns the model matching the given app_label and case-insensitive Returns the model matching the given app_label and case-insensitive
model_name. model_name.
@ -188,6 +207,8 @@ class AppCache(object):
""" """
if seed_cache: if seed_cache:
self._populate() self._populate()
if only_installed and app_label not in self.app_labels:
return None
return self.app_models.get(app_label, SortedDict()).get(model_name.lower()) return self.app_models.get(app_label, SortedDict()).get(model_name.lower())
def register_models(self, app_label, *models): def register_models(self, app_label, *models):

View File

@ -1023,7 +1023,11 @@ class ManageValidate(AdminScriptTestCase):
def test_app_with_import(self): def test_app_with_import(self):
"manage.py validate does not raise errors when an app imports a base class that itself has an abstract base" "manage.py validate does not raise errors when an app imports a base class that itself has an abstract base"
self.write_settings('settings.py', self.write_settings('settings.py',
apps=['admin_scripts.app_with_import', 'django.contrib.comments'], apps=['admin_scripts.app_with_import',
'django.contrib.comments',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sites'],
sdict={'DEBUG': True}) sdict={'DEBUG': True})
args = ['validate'] args = ['validate']
out, err = self.run_manage(args) out, err = self.run_manage(args)

View File

@ -0,0 +1,5 @@
from django.db import models
class NotInstalledModel(models.Model):
pass

View File

@ -4,7 +4,7 @@ import sys
import time import time
from django.conf import Settings from django.conf import Settings
from django.db.models.loading import cache, load_app from django.db.models.loading import cache, load_app, get_model, get_models
from django.utils.unittest import TestCase from django.utils.unittest import TestCase
@ -81,3 +81,24 @@ class EggLoadingTest(TestCase):
# Make sure the message is indicating the actual # Make sure the message is indicating the actual
# problem in the broken app. # problem in the broken app.
self.assertTrue("modelz" in e.args[0]) self.assertTrue("modelz" in e.args[0])
class GetModelsTest(TestCase):
def setUp(self):
import not_installed.models
self.not_installed_module = not_installed.models
def test_get_model_only_returns_installed_models(self):
self.assertEqual(
get_model("not_installed", "NotInstalledModel"), None)
def test_get_models_only_returns_installed_models(self):
self.assertFalse(
"NotInstalledModel" in
[m.__name__ for m in get_models()])
def test_get_models_with_app_label_only_returns_installed_models(self):
self.assertEqual(get_models(self.not_installed_module), [])