Browse Source

Fixed #8280 -- Allowed management command discovery for eggs

Thanks jdetaeye for the report, bhuztez and jdetaeye for the
initial patches, Tim Graham and Berker Peksag for the reviews.
Claude Paroz 10 years ago
parent
commit
6e1c9c6568

+ 5 - 5
django/core/management/__init__.py

@@ -3,6 +3,7 @@ from __future__ import unicode_literals
 import collections
 from importlib import import_module
 import os
+import pkgutil
 import sys
 
 import django
@@ -24,11 +25,10 @@ def find_commands(management_dir):
     Returns an empty list if no commands are defined.
     """
     command_dir = os.path.join(management_dir, 'commands')
-    try:
-        return [f[:-3] for f in os.listdir(command_dir)
-                if not f.startswith('_') and f.endswith('.py')]
-    except OSError:
-        return []
+    # Workaround for a Python 3.2 bug with pkgutil.iter_modules
+    sys.path_importer_cache.pop(command_dir, None)
+    return [name for _, name, is_pkg in pkgutil.iter_modules([command_dir])
+            if not is_pkg and not name.startswith('_')]
 
 
 def load_command_class(app_name, name):

+ 2 - 0
docs/releases/1.8.txt

@@ -371,6 +371,8 @@ Management Commands
 * Database connections are now always closed after a management command called
   from the command line has finished doing its job.
 
+* Commands from alternate package formats like eggs are now also discovered.
+
 * :djadmin:`dumpdata` now has the option :djadminopt:`--output` which allows
   specifying the file to which the serialized data is written.
 

BIN
tests/user_commands/eggs/basic.egg


+ 15 - 2
tests/user_commands/tests.py

@@ -1,13 +1,15 @@
 import os
 
+from django.apps import apps
 from django.db import connection
 from django.core import management
-from django.core.management import BaseCommand, CommandError
+from django.core.management import BaseCommand, CommandError, find_commands
 from django.core.management.utils import find_command, popen_wrapper
 from django.test import SimpleTestCase, ignore_warnings
-from django.test.utils import captured_stderr, captured_stdout
+from django.test.utils import captured_stderr, captured_stdout, extend_sys_path
 from django.utils import translation
 from django.utils.deprecation import RemovedInDjango20Warning
+from django.utils._os import upath
 from django.utils.six import StringIO
 
 
@@ -72,6 +74,17 @@ class CommandTests(SimpleTestCase):
             if current_path is not None:
                 os.environ['PATH'] = current_path
 
+    def test_discover_commands_in_eggs(self):
+        """
+        Test that management commands can also be loaded from Python eggs.
+        """
+        egg_dir = '%s/eggs' % os.path.dirname(upath(__file__))
+        egg_name = '%s/basic.egg' % egg_dir
+        with extend_sys_path(egg_name):
+            with self.settings(INSTALLED_APPS=['commandegg']):
+                cmds = find_commands(os.path.join(apps.get_app_config('commandegg').path, 'management'))
+        self.assertEqual(cmds, ['eggcommand'])
+
     def test_call_command_option_parsing(self):
         """
         When passing the long option name to call_command, the available option