diff --git a/django/db/models/base.py b/django/db/models/base.py index 9f93d86f98..173b12a768 100644 --- a/django/db/models/base.py +++ b/django/db/models/base.py @@ -2,34 +2,41 @@ from __future__ import unicode_literals import copy import inspect -from itertools import chain import sys import warnings +from itertools import chain from django.apps import apps from django.apps.config import MODELS_MODULE_NAME from django.conf import settings from django.core import checks -from django.core.exceptions import (FieldDoesNotExist, ObjectDoesNotExist, - MultipleObjectsReturned, FieldError, ValidationError, NON_FIELD_ERRORS) -from django.db import (router, connections, transaction, DatabaseError, - DEFAULT_DB_ALIAS, DJANGO_VERSION_PICKLE_KEY) +from django.core.exceptions import ( + NON_FIELD_ERRORS, FieldDoesNotExist, FieldError, ImproperlyConfigured, + MultipleObjectsReturned, ObjectDoesNotExist, ValidationError, +) +from django.db import ( + DEFAULT_DB_ALIAS, DJANGO_VERSION_PICKLE_KEY, DatabaseError, connections, + router, transaction, +) from django.db.models import signals from django.db.models.constants import LOOKUP_SEP from django.db.models.deletion import Collector from django.db.models.fields import AutoField -from django.db.models.fields.related import (ForeignObjectRel, ManyToOneRel, - OneToOneField, add_lazy_relation) +from django.db.models.fields.related import ( + ForeignObjectRel, ManyToOneRel, OneToOneField, add_lazy_relation, +) from django.db.models.manager import ensure_default_manager from django.db.models.options import Options from django.db.models.query import Q -from django.db.models.query_utils import DeferredAttribute, deferred_class_factory +from django.db.models.query_utils import ( + DeferredAttribute, deferred_class_factory, +) from django.utils import six from django.utils.deprecation import RemovedInDjango19Warning from django.utils.encoding import force_str, force_text from django.utils.functional import curry from django.utils.six.moves import zip -from django.utils.text import get_text_list, capfirst +from django.utils.text import capfirst, get_text_list from django.utils.translation import ugettext_lazy as _ from django.utils.version import get_version @@ -115,8 +122,14 @@ class ModelBase(type): app_label_index = package_components.index(MODELS_MODULE_NAME) + 1 except ValueError: app_label_index = 1 - kwargs = {"app_label": package_components[app_label_index]} - + try: + kwargs = {"app_label": package_components[app_label_index]} + except IndexError: + raise ImproperlyConfigured( + 'Unable to detect the app label for model "%s." ' + 'Ensure that its module, "%s", is located inside an installed ' + 'app.' % (new_class.__name__, model_module.__name__) + ) else: kwargs = {"app_label": app_config.label} diff --git a/tests/app_loading/tests.py b/tests/app_loading/tests.py index 71f1a49b63..4d7337e1c4 100644 --- a/tests/app_loading/tests.py +++ b/tests/app_loading/tests.py @@ -1,8 +1,11 @@ from __future__ import unicode_literals import os +import sys from django.apps import apps +from django.core.exceptions import ImproperlyConfigured +from django.db import models from django.test import TestCase from django.test.utils import extend_sys_path from django.utils._os import upath @@ -75,3 +78,26 @@ class GetModelsTest(TestCase): self.assertNotIn( "NotInstalledModel", [m.__name__ for m in apps.get_models()]) + + def test_exception_raised_if_model_declared_outside_app(self): + + class FakeModule(models.Model): + __name__ = str("models_that_do_not_live_in_an_app") + + sys.modules['models_not_in_app'] = FakeModule + + def declare_model_outside_app(): + models.base.ModelBase.__new__( + models.base.ModelBase, + str('Outsider'), + (models.Model,), + {'__module__': 'models_not_in_app'}) + + msg = ( + 'Unable to detect the app label for model "Outsider." ' + 'Ensure that its module, "models_that_do_not_live_in_an_app", ' + 'is located inside an installed app.' + ) + + with self.assertRaisesMessage(ImproperlyConfigured, msg): + declare_model_outside_app()