Pārlūkot izejas kodu

Disallowed importing concrete models without an application.

Removed fragile algorithm to find which application a model belongs to.

Fixed #21680, #21719. Refs #21794.
Aymeric Augustin 10 gadi atpakaļ
vecāks
revīzija
1b8af4cfa0

+ 9 - 35
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}
-
-        else:
-            kwargs = {}
+                app_label = app_config.label
 
-        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',

+ 2 - 3
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'
 

+ 20 - 0
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))

+ 1 - 20
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)