Disallowed importing concrete models without an application.

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

Fixed #21680, #21719. Refs #21794.
This commit is contained in:
Aymeric Augustin 2015-02-09 22:41:57 +01:00
parent c7a6996df7
commit 1b8af4cfa0
4 changed files with 32 additions and 58 deletions

View File

@ -2,12 +2,10 @@ from __future__ import unicode_literals
import copy import copy
import inspect import inspect
import sys
import warnings import warnings
from itertools import chain from itertools import chain
from django.apps import apps from django.apps import apps
from django.apps.config import MODELS_MODULE_NAME
from django.conf import settings from django.conf import settings
from django.core import checks from django.core import checks
from django.core.exceptions import ( from django.core.exceptions import (
@ -88,48 +86,24 @@ class ModelBase(type):
meta = attr_meta meta = attr_meta
base_meta = getattr(new_class, '_meta', None) base_meta = getattr(new_class, '_meta', None)
app_label = None
# Look for an application configuration to attach the model to. # Look for an application configuration to attach the model to.
app_config = apps.get_containing_app_config(module) app_config = apps.get_containing_app_config(module)
if getattr(meta, 'app_label', None) is None: if getattr(meta, 'app_label', None) is None:
if app_config 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: if not abstract:
warnings.warn(msg, DeprecationWarning, stacklevel=2) raise RuntimeError(
"Model class %s.%s doesn't declare an explicit "
model_module = sys.modules[new_class.__module__] "app_label and either isn't in an application in "
package_components = model_module.__name__.split('.') "INSTALLED_APPS or else was imported before its "
package_components.reverse() # find the last occurrence of 'models' "application was loaded. " % (module, name))
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]}
else: else:
kwargs = {"app_label": app_config.label} app_label = app_config.label
else: new_class.add_to_class('_meta', Options(meta, app_label))
kwargs = {}
new_class.add_to_class('_meta', Options(meta, **kwargs))
if not abstract: if not abstract:
new_class.add_to_class( new_class.add_to_class(
'DoesNotExist', 'DoesNotExist',

View File

@ -24,9 +24,8 @@ Available ``Meta`` options
.. attribute:: Options.app_label .. attribute:: Options.app_label
If a model exists outside of an application in :setting:`INSTALLED_APPS` or If a model is defined outside of an application in
if it's imported before its application was loaded, it must define which :setting:`INSTALLED_APPS`, it must declare which app it belongs to::
app it is part of::
app_label = 'myapp' app_label = 'myapp'

View File

@ -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))

View File

@ -151,30 +151,11 @@ class ManageCommandTests(unittest.TestCase):
testrunner='test_runner.NonExistentRunner') 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): class CustomTestRunnerOptionsTests(AdminScriptTestCase):
def setUp(self): def setUp(self):
settings = { settings = {
'TEST_RUNNER': '\'test_runner.tests.CustomOptionsTestRunner\'', 'TEST_RUNNER': '\'test_runner.runner.CustomOptionsTestRunner\'',
} }
self.write_settings('settings.py', sdict=settings) self.write_settings('settings.py', sdict=settings)