diff --git a/django/contrib/admin/templatetags/adminapplist.py b/django/contrib/admin/templatetags/adminapplist.py index ff06b42e0c..c328ddf203 100644 --- a/django/contrib/admin/templatetags/adminapplist.py +++ b/django/contrib/admin/templatetags/adminapplist.py @@ -1,4 +1,5 @@ from django import template +from django.db.models import get_models register = template.Library() @@ -12,13 +13,18 @@ class AdminApplistNode(template.Node): app_list = [] user = context['user'] - for app in models.get_installed_model_modules(): - app_label = app.__name__.split('.')[-2] # TODO: Abstract this logic + for app in models.get_apps(): + # Determine the app_label. + app_models = get_models(app) + if not app_models: + continue + app_label = app_models[0]._meta.app_label + has_module_perms = user.has_module_perms(app_label) if has_module_perms: model_list = [] - for m in app._MODELS: + for m in app_models: if m._meta.admin: perms = { 'add': user.has_perm("%s.%s" % (app_label, m._meta.get_add_permission())), diff --git a/django/contrib/admin/views/doc.py b/django/contrib/admin/views/doc.py index 92833d5023..7ae8b76dde 100644 --- a/django/contrib/admin/views/doc.py +++ b/django/contrib/admin/views/doc.py @@ -137,10 +137,7 @@ def model_index(request): if not utils.docutils_is_available: return missing_docutils_page(request) - m_list = [] - for app in models.get_installed_model_modules(): - for model in app._MODELS: - m_list.append(model._meta) + m_list = [m._meta for m in models.get_models()] return render_to_response('admin_doc/model_index', {'models': m_list}, context_instance=RequestContext(request)) model_index = staff_member_required(model_index) diff --git a/django/contrib/admin/views/main.py b/django/contrib/admin/views/main.py index 7448be5818..e0a3b13111 100644 --- a/django/contrib/admin/views/main.py +++ b/django/contrib/admin/views/main.py @@ -41,14 +41,6 @@ use_raw_id_admin = lambda field: isinstance(field.rel, (models.ManyToOne, models class IncorrectLookupParameters(Exception): pass -def get_model(app_label, model_name): - for module in models.get_installed_models(): - if module.__name__.split('.')[-2] == app_label: # TODO: Refactor this logic. - for model in getattr(module, '_MODELS', ()): - if model._meta.object_name.lower() == model_name: - return model - raise Http404, "App %r, model %r, not found" % (app_label, model_name) - def get_javascript_imports(opts, auto_populated_fields, field_sets): # Put in any necessary JavaScript imports. js = ['js/core.js', 'js/admin/RelatedObjectLookups.js'] @@ -192,7 +184,9 @@ def index(request): index = staff_member_required(index) def add_stage(request, app_label, model_name, show_delete=False, form_url='', post_url='../', post_url_continue='../%s/', object_id_override=None): - model = get_model(app_label, model_name) + model = models.get_model(app_label, model_name) + if model is None: + raise Http404, "App %r, model %r, not found" % (app_label, model_name) opts = model._meta if not request.user.has_perm(app_label + '.' + opts.get_add_permission()): @@ -255,7 +249,9 @@ def add_stage(request, app_label, model_name, show_delete=False, form_url='', po add_stage = staff_member_required(add_stage) def change_stage(request, app_label, model_name, object_id): - model = get_model(app_label, model_name) + model = models.get_model(app_label, model_name) + if model is None: + raise Http404, "App %r, model %r, not found" % (app_label, model_name) opts = model._meta if not request.user.has_perm(app_label + '.' + opts.get_change_permission()): @@ -436,7 +432,9 @@ def _get_deleted_objects(deleted_objects, perms_needed, user, obj, opts, current def delete_stage(request, app_label, model_name, object_id): import sets - model = get_model(app_label, model_name) + model = models.get_model(app_label, model_name) + if model is None: + raise Http404, "App %r, model %r, not found" % (app_label, model_name) opts = model._meta if not request.user.has_perm(app_label + '.' + opts.get_delete_permission()): raise PermissionDenied @@ -466,7 +464,9 @@ def delete_stage(request, app_label, model_name, object_id): delete_stage = staff_member_required(delete_stage) def history(request, app_label, model_name, object_id): - model = get_model(app_label, model_name) + model = models.get_model(app_label, model_name) + if model is None: + raise Http404, "App %r, model %r, not found" % (app_label, model_name) action_list = LogEntry.objects.get_list(object_id__exact=object_id, content_type__id__exact=model._meta.get_content_type_id(), order_by=("action_time",), select_related=True) # If no history was found, see whether this object even exists. @@ -651,7 +651,9 @@ class ChangeList(object): self.lookup_params = lookup_params def change_list(request, app_label, model_name): - model = get_model(app_label, model_name) + model = models.get_model(app_label, model_name) + if model is None: + raise Http404, "App %r, model %r, not found" % (app_label, model_name) if not request.user.has_perm(app_label + '.' + model._meta.get_change_permission()): raise PermissionDenied try: diff --git a/django/core/management.py b/django/core/management.py index 1cef6d39d5..3072759cf2 100644 --- a/django/core/management.py +++ b/django/core/management.py @@ -767,101 +767,99 @@ def get_validation_errors(outfile): "Validates all installed models. Writes errors, if any, to outfile. Returns number of errors." from django.db import models e = ModelErrorCollection(outfile) - module_list = models.get_installed_model_modules() - for module in module_list: - for cls in module._MODELS: - opts = cls._meta + for cls in models.get_models(): + opts = cls._meta - # Do field-specific validation. - for f in opts.fields: - # Check for deprecated args - dep_args = getattr(f, 'deprecated_args', None) - if dep_args: - e.add(opts, "'%s' field: Initialised with deprecated args:%s" % (f.name, ",".join(dep_args))) - if isinstance(f, models.CharField) and f.maxlength in (None, 0): - e.add(opts, '"%s" field: CharFields require a "maxlength" attribute.' % f.name) - if isinstance(f, models.FloatField): - if f.decimal_places is None: - e.add(opts, '"%s" field: FloatFields require a "decimal_places" attribute.' % f.name) - if f.max_digits is None: - e.add(opts, '"%s" field: FloatFields require a "max_digits" attribute.' % f.name) - if isinstance(f, models.FileField) and not f.upload_to: - e.add(opts, '"%s" field: FileFields require an "upload_to" attribute.' % f.name) - if isinstance(f, models.ImageField): - try: - from PIL import Image - except ImportError: - e.add(opts, '"%s" field: To use ImageFields, you need to install the Python Imaging Library. Get it at http://www.pythonware.com/products/pil/ .' % f.name) - if f.prepopulate_from is not None and type(f.prepopulate_from) not in (list, tuple): - e.add(opts, '"%s" field: prepopulate_from should be a list or tuple.' % f.name) - if f.choices: - if not type(f.choices) in (tuple, list): - e.add(opts, '"%s" field: "choices" should be either a tuple or list.' % f.name) - else: - for c in f.choices: - if not type(c) in (tuple, list) or len(c) != 2: - e.add(opts, '"%s" field: "choices" should be a sequence of two-tuples.' % f.name) - if f.db_index not in (None, True, False): - e.add(opts, '"%s" field: "db_index" should be either None, True or False.' % f.name) - - # Check for multiple ManyToManyFields to the same object, and - # verify "singular" is set in that case. - for i, f in enumerate(opts.many_to_many): - for previous_f in opts.many_to_many[:i]: - if f.rel.to._meta == previous_f.rel.to._meta and f.rel.singular == previous_f.rel.singular: - e.add(opts, 'The "%s" field requires a "singular" parameter, because the %s model has more than one ManyToManyField to the same model (%s).' % (f.name, opts.object_name, previous_f.rel.to._meta.object_name)) - - # Check admin attribute. - if opts.admin is not None: - if not isinstance(opts.admin, models.AdminOptions): - e.add(opts, '"admin" attribute, if given, must be set to a models.AdminOptions() instance.') + # Do field-specific validation. + for f in opts.fields: + # Check for deprecated args + dep_args = getattr(f, 'deprecated_args', None) + if dep_args: + e.add(opts, "'%s' field: Initialised with deprecated args:%s" % (f.name, ",".join(dep_args))) + if isinstance(f, models.CharField) and f.maxlength in (None, 0): + e.add(opts, '"%s" field: CharFields require a "maxlength" attribute.' % f.name) + if isinstance(f, models.FloatField): + if f.decimal_places is None: + e.add(opts, '"%s" field: FloatFields require a "decimal_places" attribute.' % f.name) + if f.max_digits is None: + e.add(opts, '"%s" field: FloatFields require a "max_digits" attribute.' % f.name) + if isinstance(f, models.FileField) and not f.upload_to: + e.add(opts, '"%s" field: FileFields require an "upload_to" attribute.' % f.name) + if isinstance(f, models.ImageField): + try: + from PIL import Image + except ImportError: + e.add(opts, '"%s" field: To use ImageFields, you need to install the Python Imaging Library. Get it at http://www.pythonware.com/products/pil/ .' % f.name) + if f.prepopulate_from is not None and type(f.prepopulate_from) not in (list, tuple): + e.add(opts, '"%s" field: prepopulate_from should be a list or tuple.' % f.name) + if f.choices: + if not type(f.choices) in (tuple, list): + e.add(opts, '"%s" field: "choices" should be either a tuple or list.' % f.name) else: - # list_display - if not isinstance(opts.admin.list_display, (list, tuple)): - e.add(opts, '"admin.list_display", if given, must be set to a list or tuple.') - else: - for fn in opts.admin.list_display: - try: - f = opts.get_field(fn) - except models.FieldDoesNotExist: - if not hasattr(cls, fn) or not callable(getattr(cls, fn)): - e.add(opts, '"admin.list_display" refers to %r, which isn\'t a field or method.' % fn) - else: - if isinstance(f, models.ManyToManyField): - e.add(opts, '"admin.list_display" doesn\'t support ManyToManyFields (%r).' % fn) - # list_filter - if not isinstance(opts.admin.list_filter, (list, tuple)): - e.add(opts, '"admin.list_filter", if given, must be set to a list or tuple.') - else: - for fn in opts.admin.list_filter: - try: - f = opts.get_field(fn) - except models.FieldDoesNotExist: - e.add(opts, '"admin.list_filter" refers to %r, which isn\'t a field.' % fn) + for c in f.choices: + if not type(c) in (tuple, list) or len(c) != 2: + e.add(opts, '"%s" field: "choices" should be a sequence of two-tuples.' % f.name) + if f.db_index not in (None, True, False): + e.add(opts, '"%s" field: "db_index" should be either None, True or False.' % f.name) - # Check ordering attribute. - if opts.ordering: - for field_name in opts.ordering: - if field_name == '?': continue - if field_name.startswith('-'): - field_name = field_name[1:] - if opts.order_with_respect_to and field_name == '_order': - continue - try: - opts.get_field(field_name, many_to_many=False) - except models.FieldDoesNotExist: - e.add(opts, '"ordering" refers to "%s", a field that doesn\'t exist.' % field_name) + # Check for multiple ManyToManyFields to the same object, and + # verify "singular" is set in that case. + for i, f in enumerate(opts.many_to_many): + for previous_f in opts.many_to_many[:i]: + if f.rel.to._meta == previous_f.rel.to._meta and f.rel.singular == previous_f.rel.singular: + e.add(opts, 'The "%s" field requires a "singular" parameter, because the %s model has more than one ManyToManyField to the same model (%s).' % (f.name, opts.object_name, previous_f.rel.to._meta.object_name)) - # Check unique_together. - for ut in opts.unique_together: - for field_name in ut: - try: - f = opts.get_field(field_name, many_to_many=True) - except models.FieldDoesNotExist: - e.add(opts, '"unique_together" refers to %s, a field that doesn\'t exist. Check your syntax.' % field_name) - else: - if isinstance(f.rel, models.ManyToMany): - e.add(opts, '"unique_together" refers to %s. ManyToManyFields are not supported in unique_together.' % f.name) + # Check admin attribute. + if opts.admin is not None: + if not isinstance(opts.admin, models.AdminOptions): + e.add(opts, '"admin" attribute, if given, must be set to a models.AdminOptions() instance.') + else: + # list_display + if not isinstance(opts.admin.list_display, (list, tuple)): + e.add(opts, '"admin.list_display", if given, must be set to a list or tuple.') + else: + for fn in opts.admin.list_display: + try: + f = opts.get_field(fn) + except models.FieldDoesNotExist: + if not hasattr(cls, fn) or not callable(getattr(cls, fn)): + e.add(opts, '"admin.list_display" refers to %r, which isn\'t a field or method.' % fn) + else: + if isinstance(f, models.ManyToManyField): + e.add(opts, '"admin.list_display" doesn\'t support ManyToManyFields (%r).' % fn) + # list_filter + if not isinstance(opts.admin.list_filter, (list, tuple)): + e.add(opts, '"admin.list_filter", if given, must be set to a list or tuple.') + else: + for fn in opts.admin.list_filter: + try: + f = opts.get_field(fn) + except models.FieldDoesNotExist: + e.add(opts, '"admin.list_filter" refers to %r, which isn\'t a field.' % fn) + + # Check ordering attribute. + if opts.ordering: + for field_name in opts.ordering: + if field_name == '?': continue + if field_name.startswith('-'): + field_name = field_name[1:] + if opts.order_with_respect_to and field_name == '_order': + continue + try: + opts.get_field(field_name, many_to_many=False) + except models.FieldDoesNotExist: + e.add(opts, '"ordering" refers to "%s", a field that doesn\'t exist.' % field_name) + + # Check unique_together. + for ut in opts.unique_together: + for field_name in ut: + try: + f = opts.get_field(field_name, many_to_many=True) + except models.FieldDoesNotExist: + e.add(opts, '"unique_together" refers to %s, a field that doesn\'t exist. Check your syntax.' % field_name) + else: + if isinstance(f.rel, models.ManyToMany): + e.add(opts, '"unique_together" refers to %s. ManyToManyFields are not supported in unique_together.' % f.name) return len(e.errors) diff --git a/django/db/models/__init__.py b/django/db/models/__init__.py index 8abda5cdfa..68bf5f5c21 100644 --- a/django/db/models/__init__.py +++ b/django/db/models/__init__.py @@ -2,7 +2,7 @@ from django.conf import settings from django.core.exceptions import ObjectDoesNotExist, ImproperlyConfigured from django.core import validators from django.db import backend, connection -from django.db.models.loading import get_installed_models, get_installed_model_modules +from django.db.models.loading import * from django.db.models.query import Q from django.db.models.manager import Manager from django.db.models.base import Model, AdminOptions @@ -16,27 +16,6 @@ from django.utils.text import capfirst # Admin stages. ADD, CHANGE, BOTH = 1, 2, 3 -def get_models(app): - models = [] - get_models_helper(app, models) - return models - -def get_models_helper(mod, seen_models): - if hasattr(mod, '_MODELS'): - seen_models.extend(mod._MODELS) - if hasattr(mod, '__all__'): - for name in mod.__all__: - sub_mod = __import__("%s.%s" % (mod.__name__, name), '', '', ['']) - get_models_helper(sub_mod, seen_models) - -def get_app(app_label): - for app_name in settings.INSTALLED_APPS: - comps = app_name.split('.') - if app_label == comps[-1]: - app_models = __import__('%s.models' % app_name, '', '', ['']) - return app_models - raise ImproperlyConfigured, "App with label %s could not be found" % app_label - class LazyDate: """ Use in limit_choices_to to compare the field to dates calculated at run time diff --git a/django/db/models/loading.py b/django/db/models/loading.py index 2d84f045f7..d25e1199b5 100644 --- a/django/db/models/loading.py +++ b/django/db/models/loading.py @@ -1,40 +1,50 @@ -from django.conf import settings -_installed_models_cache = None +"Utilities for loading models and the modules that contain them." -def get_installed_models(): - """ - Returns a list of installed "models" packages, such as foo.models, - ellington.news.models, etc. This does NOT include django.models. - """ - global _installed_models_cache - if _installed_models_cache is not None: - return _installed_models_cache - _installed_models_cache = [] - for a in settings.INSTALLED_APPS: +from django.conf import settings + +__all__ = ('get_apps', 'get_app', 'get_models', 'get_model') + +_app_list = None # Cache of installed apps. + +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: - _installed_models_cache.append(__import__(a + '.models', '', '', [''])) + _app_list.append(__import__(app_name + '.models', '', '', [''])) except ImportError, e: pass - return _installed_models_cache + return _app_list -_installed_modules_cache = None +def get_app(app_label): + "Returns the module containing the models for the given app_label." + for app_name in settings.INSTALLED_APPS: + if app_label == app_name.split('.')[-1]: + return __import__('%s.models' % app_name, '', '', ['']) + raise ImproperlyConfigured, "App with label %s could not be found" % app_label -def add_model_module(mod, modules): - if hasattr(mod, '_MODELS'): - modules.append(mod) - for name in getattr(mod, '__all__', []): - submod = __import__("%s.%s" % (mod.__name__, name), '', '', ['']) - add_model_module(submod, modules) - -def get_installed_model_modules(): +def get_models(app_mod=None): """ - Returns a list of installed models, such as django.models.core, - ellington.news.models.news, foo.models.bar, etc. + Given a module containing models, returns a list of the models. Otherwise + returns a list of all installed models. """ - global _installed_modules_cache - if _installed_modules_cache is not None: - return _installed_modules_cache - _installed_modules_cache = [] - for mod in get_installed_models(): - add_model_module(mod, _installed_modules_cache) - return _installed_modules_cache + if app_mod: + return getattr(app_mod, '_MODELS', ()) + else: + model_list = [] + for app_mod in get_apps(): + model_list.extend(getattr(app_mod, '_MODELS', ())) + return model_list + +def get_model(app_label, model_name): + """ + Returns the model matching the given app_label and case-insensitive model_name. + Returns None if no model is found. + """ + for app_mod in get_apps(): + for model in get_models(app_mod): + if model._meta.object_name.lower() == model_name: + return model diff --git a/django/db/models/options.py b/django/db/models/options.py index 245b815c34..3d9abf9397 100644 --- a/django/db/models/options.py +++ b/django/db/models/options.py @@ -1,7 +1,7 @@ from django.db.models.related import RelatedObject from django.db.models.fields.related import ManyToMany from django.db.models.fields import AutoField -from django.db.models.loading import get_installed_model_modules +from django.db.models.loading import get_models from django.db.models.query import orderlist2sql from django.db.models.exceptions import FieldDoesNotExist from bisect import bisect @@ -127,13 +127,11 @@ class Options: try: # Try the cache first. return self._all_related_objects except AttributeError: - module_list = get_installed_model_modules() rel_objs = [] - for mod in module_list: - for klass in mod._MODELS: - for f in klass._meta.fields: - if f.rel and self == f.rel.to._meta: - rel_objs.append(RelatedObject(self, klass, f)) + for klass in get_models(): + for f in klass._meta.fields: + if f.rel and self == f.rel.to._meta: + rel_objs.append(RelatedObject(self, klass, f)) self._all_related_objects = rel_objs return rel_objs @@ -163,13 +161,11 @@ class Options: try: # Try the cache first. return self._all_related_many_to_many_objects except AttributeError: - module_list = get_installed_model_modules() rel_objs = [] - for mod in module_list: - for klass in mod._MODELS: - for f in klass._meta.many_to_many: - if f.rel and self == f.rel.to._meta: - rel_objs.append(RelatedObject(self, klass, f)) + for klass in get_models(): + for f in klass._meta.many_to_many: + if f.rel and self == f.rel.to._meta: + rel_objs.append(RelatedObject(self, klass, f)) self._all_related_many_to_many_objects = rel_objs return rel_objs