From 1b8af4cfa023161924a45fb572399d2f3071bf5b Mon Sep 17 00:00:00 2001 From: Aymeric Augustin Date: Mon, 9 Feb 2015 22:41:57 +0100 Subject: [PATCH] Disallowed importing concrete models without an application. Removed fragile algorithm to find which application a model belongs to. Fixed #21680, #21719. Refs #21794. --- django/db/models/base.py | 44 ++++++++----------------------------- docs/ref/models/options.txt | 5 ++--- tests/test_runner/runner.py | 20 +++++++++++++++++ tests/test_runner/tests.py | 21 +----------------- 4 files changed, 32 insertions(+), 58 deletions(-) create mode 100644 tests/test_runner/runner.py diff --git a/django/db/models/base.py b/django/db/models/base.py index 9291fc8e1d..d906f2e461 100644 --- a/django/db/models/base.py +++ b/django/db/models/base.py @@ -2,12 +2,10 @@ from __future__ import unicode_literals import copy import inspect -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 ( @@ -88,48 +86,24 @@ class ModelBase(type): meta = attr_meta base_meta = getattr(new_class, '_meta', None) + app_label = None + # Look for an application configuration to attach the model to. app_config = apps.get_containing_app_config(module) if getattr(meta, 'app_label', None) is None: - if app_config is None: - # If the model is imported before the configuration for its - # application is created (#21719), or isn't in an installed - # application (#21680), use the legacy logic to figure out the - # app_label by looking one level up from the package or module - # named 'models'. If no such package or module exists, fall - # back to looking one level up from the module this model is - # defined in. - - # For 'django.contrib.sites.models', this would be 'sites'. - # For 'geo.models.places' this would be 'geo'. - - msg = ( - "Model class %s.%s doesn't declare an explicit app_label " - "and either isn't in an application in INSTALLED_APPS or " - "else was imported before its application was loaded. " - "This will no longer be supported in Django 1.9." % - (module, name)) if not abstract: - warnings.warn(msg, DeprecationWarning, stacklevel=2) - - model_module = sys.modules[new_class.__module__] - package_components = model_module.__name__.split('.') - package_components.reverse() # find the last occurrence of 'models' - try: - app_label_index = package_components.index(MODELS_MODULE_NAME) + 1 - except ValueError: - app_label_index = 1 - kwargs = {"app_label": package_components[app_label_index]} + raise RuntimeError( + "Model class %s.%s doesn't declare an explicit " + "app_label and either isn't in an application in " + "INSTALLED_APPS or else was imported before its " + "application was loaded. " % (module, name)) else: - kwargs = {"app_label": app_config.label} + app_label = app_config.label - else: - kwargs = {} - - new_class.add_to_class('_meta', Options(meta, **kwargs)) + new_class.add_to_class('_meta', Options(meta, app_label)) if not abstract: new_class.add_to_class( 'DoesNotExist', diff --git a/docs/ref/models/options.txt b/docs/ref/models/options.txt index 5fd363f0d9..2f4f5aff71 100644 --- a/docs/ref/models/options.txt +++ b/docs/ref/models/options.txt @@ -24,9 +24,8 @@ Available ``Meta`` options .. attribute:: Options.app_label - If a model exists outside of an application in :setting:`INSTALLED_APPS` or - if it's imported before its application was loaded, it must define which - app it is part of:: + If a model is defined outside of an application in + :setting:`INSTALLED_APPS`, it must declare which app it belongs to:: app_label = 'myapp' diff --git a/tests/test_runner/runner.py b/tests/test_runner/runner.py new file mode 100644 index 0000000000..8dc4c74111 --- /dev/null +++ b/tests/test_runner/runner.py @@ -0,0 +1,20 @@ +from django.test.runner import DiscoverRunner + + +class CustomOptionsTestRunner(DiscoverRunner): + + def __init__(self, verbosity=1, interactive=True, failfast=True, option_a=None, option_b=None, option_c=None, **kwargs): + super(CustomOptionsTestRunner, self).__init__(verbosity=verbosity, interactive=interactive, + failfast=failfast) + self.option_a = option_a + self.option_b = option_b + self.option_c = option_c + + @classmethod + def add_arguments(cls, parser): + parser.add_argument('--option_a', '-a', action='store', dest='option_a', default='1'), + parser.add_argument('--option_b', '-b', action='store', dest='option_b', default='2'), + parser.add_argument('--option_c', '-c', action='store', dest='option_c', default='3'), + + def run_tests(self, test_labels, extra_tests=None, **kwargs): + print("%s:%s:%s" % (self.option_a, self.option_b, self.option_c)) diff --git a/tests/test_runner/tests.py b/tests/test_runner/tests.py index d94d264321..4a3f24515c 100644 --- a/tests/test_runner/tests.py +++ b/tests/test_runner/tests.py @@ -151,30 +151,11 @@ class ManageCommandTests(unittest.TestCase): testrunner='test_runner.NonExistentRunner') -class CustomOptionsTestRunner(DiscoverRunner): - - def __init__(self, verbosity=1, interactive=True, failfast=True, option_a=None, option_b=None, option_c=None, **kwargs): - super(CustomOptionsTestRunner, self).__init__(verbosity=verbosity, interactive=interactive, - failfast=failfast) - self.option_a = option_a - self.option_b = option_b - self.option_c = option_c - - @classmethod - def add_arguments(cls, parser): - parser.add_argument('--option_a', '-a', action='store', dest='option_a', default='1'), - parser.add_argument('--option_b', '-b', action='store', dest='option_b', default='2'), - parser.add_argument('--option_c', '-c', action='store', dest='option_c', default='3'), - - def run_tests(self, test_labels, extra_tests=None, **kwargs): - print("%s:%s:%s" % (self.option_a, self.option_b, self.option_c)) - - class CustomTestRunnerOptionsTests(AdminScriptTestCase): def setUp(self): settings = { - 'TEST_RUNNER': '\'test_runner.tests.CustomOptionsTestRunner\'', + 'TEST_RUNNER': '\'test_runner.runner.CustomOptionsTestRunner\'', } self.write_settings('settings.py', sdict=settings)