From 0ce945a67151acf2c58bc35a47f4c3d45ff30085 Mon Sep 17 00:00:00 2001 From: Aymeric Augustin Date: Wed, 1 Jan 2014 18:05:12 +0100 Subject: [PATCH] Fixed #21018 -- Reversed precedence order for management commands. --- django/core/management/__init__.py | 2 +- docs/ref/settings.txt | 4 ++++ docs/releases/1.7.txt | 12 +++++++++++ .../complex_app/management/__init__.py | 0 .../management/commands/__init__.py | 0 .../management/commands/duplicate.py | 7 +++++++ .../simple_app/management/__init__.py | 0 .../management/commands/__init__.py | 0 .../management/commands/duplicate.py | 7 +++++++ tests/admin_scripts/tests.py | 20 ++++++++++++++++++- 10 files changed, 50 insertions(+), 2 deletions(-) create mode 100644 tests/admin_scripts/complex_app/management/__init__.py create mode 100644 tests/admin_scripts/complex_app/management/commands/__init__.py create mode 100644 tests/admin_scripts/complex_app/management/commands/duplicate.py create mode 100644 tests/admin_scripts/simple_app/management/__init__.py create mode 100644 tests/admin_scripts/simple_app/management/commands/__init__.py create mode 100644 tests/admin_scripts/simple_app/management/commands/duplicate.py diff --git a/django/core/management/__init__.py b/django/core/management/__init__.py index 1b9597ee3d..33cef35b4c 100644 --- a/django/core/management/__init__.py +++ b/django/core/management/__init__.py @@ -120,7 +120,7 @@ def get_commands(): # a settings module. django.setup() app_configs = apps.get_app_configs() - app_names = [app_config.name for app_config in app_configs] + app_names = [app_config.name for app_config in reversed(app_configs)] # Find and load the management module for each installed app. for app_name in app_names: diff --git a/docs/ref/settings.txt b/docs/ref/settings.txt index 62e7920c01..4181d152ef 100644 --- a/docs/ref/settings.txt +++ b/docs/ref/settings.txt @@ -1319,6 +1319,10 @@ Django installation. Each string should be a dotted Python path to: These rules apply regardless of whether :setting:`INSTALLED_APPS` references application configuration classes on application packages. +When several applications provide different versions of the same resource +(template, static file, management command, translation), the application +listed first in :setting:`INSTALLED_APPS` has precedence. + .. setting:: INTERNAL_IPS INTERNAL_IPS diff --git a/docs/releases/1.7.txt b/docs/releases/1.7.txt index b559445e7c..fced7a7508 100644 --- a/docs/releases/1.7.txt +++ b/docs/releases/1.7.txt @@ -697,6 +697,18 @@ following changes that take effect immediately: * The ``only_installed`` argument of ``get_model`` and ``get_models`` no longer exists, nor does the ``seed_cache`` argument of ``get_model``. +Management commands and order of :setting:`INSTALLED_APPS` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +When several applications provide management commands with the same name, +Django loads the command from the application that comes first in +:setting:`INSTALLED_APPS`. Previous versions loaded the command from the +applicatino that came last. + +This brings discovery of management commands in line with other parts of +Django that rely on the order of :setting:`INSTALLED_APPS`, such as static +files, templates, and translations. + Behavior of ``LocMemCache`` regarding pickle errors ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/tests/admin_scripts/complex_app/management/__init__.py b/tests/admin_scripts/complex_app/management/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/admin_scripts/complex_app/management/commands/__init__.py b/tests/admin_scripts/complex_app/management/commands/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/admin_scripts/complex_app/management/commands/duplicate.py b/tests/admin_scripts/complex_app/management/commands/duplicate.py new file mode 100644 index 0000000000..11b183843f --- /dev/null +++ b/tests/admin_scripts/complex_app/management/commands/duplicate.py @@ -0,0 +1,7 @@ +from django.core.management.base import NoArgsCommand + + +class Command(NoArgsCommand): + + def handle_noargs(self, **options): + self.stdout.write('complex_app') diff --git a/tests/admin_scripts/simple_app/management/__init__.py b/tests/admin_scripts/simple_app/management/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/admin_scripts/simple_app/management/commands/__init__.py b/tests/admin_scripts/simple_app/management/commands/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/admin_scripts/simple_app/management/commands/duplicate.py b/tests/admin_scripts/simple_app/management/commands/duplicate.py new file mode 100644 index 0000000000..a451f3991c --- /dev/null +++ b/tests/admin_scripts/simple_app/management/commands/duplicate.py @@ -0,0 +1,7 @@ +from django.core.management.base import NoArgsCommand + + +class Command(NoArgsCommand): + + def handle_noargs(self, **options): + self.stdout.write('simple_app') diff --git a/tests/admin_scripts/tests.py b/tests/admin_scripts/tests.py index 3587748c8e..e052a1a6d7 100644 --- a/tests/admin_scripts/tests.py +++ b/tests/admin_scripts/tests.py @@ -25,7 +25,7 @@ from django.test.utils import str_prefix from django.utils.encoding import force_text from django.utils._os import upath from django.utils.six import StringIO -from django.test import LiveServerTestCase +from django.test import LiveServerTestCase, TestCase test_dir = os.path.realpath(os.path.join(os.environ['DJANGO_TEST_TEMP_DIR'], 'test_project')) @@ -1469,6 +1469,24 @@ class CommandTypes(AdminScriptTestCase): self.assertOutput(out, str_prefix("EXECUTE:LabelCommand label=anotherlabel, options=[('no_color', False), ('pythonpath', None), ('settings', None), ('traceback', None), ('verbosity', %(_)s'1')]")) +class Discovery(TestCase): + + def test_precedence(self): + """ + Apps listed first in INSTALLED_APPS have precendence. + """ + with self.settings(INSTALLED_APPS=['admin_scripts.complex_app', + 'admin_scripts.simple_app']): + out = StringIO() + call_command('duplicate', stdout=out) + self.assertEqual(out.getvalue().strip(), 'complex_app') + with self.settings(INSTALLED_APPS=['admin_scripts.simple_app', + 'admin_scripts.complex_app']): + out = StringIO() + call_command('duplicate', stdout=out) + self.assertEqual(out.getvalue().strip(), 'simple_app') + + class ArgumentOrder(AdminScriptTestCase): """Tests for 2-stage argument parsing scheme.