Code

Ticket #14087: namespaced_managment_commands.diff

File namespaced_managment_commands.diff, 10.7 KB (added by nfg, 3 years ago)

find_managment_module with support for namespaced packages, with tests.

Line 
1diff --git a/django/core/management/__init__.py b/django/core/management/__init__.py
2index 85bf324..f34a60c 100644
3--- a/django/core/management/__init__.py
4+++ b/django/core/management/__init__.py
5@@ -1,11 +1,10 @@
6 import os
7 import sys
8 from optparse import OptionParser, NO_DEFAULT
9-import imp
10 
11 import django
12 from django.core.management.base import BaseCommand, CommandError, handle_default_options
13-from django.utils.importlib import import_module
14+from django.utils.importlib import import_module, find_modules
15 
16 # For backwards compatibility: get_version() used to be in this module.
17 get_version = django.get_version
18@@ -39,7 +38,7 @@ def find_management_module(app_name):
19     parts.append('management')
20     parts.reverse()
21     part = parts.pop()
22-    path = None
23+    paths = None
24 
25     # When using manage.py, the project module is added to the path,
26     # loaded, then removed from the path. This means that
27@@ -48,15 +47,17 @@ def find_management_module(app_name):
28     # module, we need look for the case where the project name is part
29     # of the app_name but the project directory itself isn't on the path.
30     try:
31-        f, path, descr = imp.find_module(part,path)
32+        modules = find_modules(part, paths)
33+        paths = [m[1] for m in modules]
34     except ImportError,e:
35         if os.path.basename(os.getcwd()) != part:
36             raise e
37 
38     while parts:
39         part = parts.pop()
40-        f, path, descr = imp.find_module(part, path and [path] or None)
41-    return path
42+        modules = find_modules(part, paths)
43+        paths = [m[1] for m in modules]
44+    return paths[0]
45 
46 def load_command_class(app_name, name):
47     """
48diff --git a/django/utils/importlib.py b/django/utils/importlib.py
49index ef4d0e4..1507a2b 100644
50--- a/django/utils/importlib.py
51+++ b/django/utils/importlib.py
52@@ -1,5 +1,6 @@
53 # Taken from Python 2.7 with permission from/by the original author.
54 import sys
55+import imp
56 
57 def _resolve_name(name, package, level):
58     """Return the absolute name of the module to be imported."""
59@@ -34,3 +35,29 @@ def import_module(name, package=None):
60         name = _resolve_name(name[level:], package, level)
61     __import__(name)
62     return sys.modules[name]
63+
64+
65+def find_modules(name, path=None):
66+    """Find all modules with name 'name'
67+   
68+    Unlike find_module in the imp package this returns a list of all
69+    matched modules.
70+    """
71+    results = []
72+    if path is None: path = sys.path
73+    for p in path:
74+        importer = sys.path_importer_cache.get(p, None)
75+        if importer is None:
76+            find_module = imp.find_module
77+        else:
78+            find_module = importer.find_module
79+       
80+        try:
81+            result = find_module(name, [p])
82+            if result is not None:
83+                results.append(result)
84+        except ImportError:
85+            pass
86+    if not results:
87+        raise ImportError("No module named %.200s" % name)
88+    return results
89diff --git a/tests/regressiontests/admin_scripts/lib1/nons_app/__init__.py b/tests/regressiontests/admin_scripts/lib1/nons_app/__init__.py
90new file mode 100644
91index 0000000..e69de29
92diff --git a/tests/regressiontests/admin_scripts/lib1/nons_app/management/__init__.py b/tests/regressiontests/admin_scripts/lib1/nons_app/management/__init__.py
93new file mode 100644
94index 0000000..e69de29
95diff --git a/tests/regressiontests/admin_scripts/lib1/nons_app/management/commands/__init__.py b/tests/regressiontests/admin_scripts/lib1/nons_app/management/commands/__init__.py
96new file mode 100644
97index 0000000..e69de29
98diff --git a/tests/regressiontests/admin_scripts/lib1/nons_app/management/commands/nons_app_command1.py b/tests/regressiontests/admin_scripts/lib1/nons_app/management/commands/nons_app_command1.py
99new file mode 100644
100index 0000000..a393663
101--- /dev/null
102+++ b/tests/regressiontests/admin_scripts/lib1/nons_app/management/commands/nons_app_command1.py
103@@ -0,0 +1,9 @@
104+from django.core.management.base import BaseCommand
105+
106+class Command(BaseCommand):
107+    help = 'Test managment commands in non-namespaced app'
108+    requires_model_validation = False
109+    args = ''
110+
111+    def handle(self, *labels, **options):
112+        print 'EXECUTE:nons_app_command1'
113diff --git a/tests/regressiontests/admin_scripts/lib1/nons_app/models.py b/tests/regressiontests/admin_scripts/lib1/nons_app/models.py
114new file mode 100644
115index 0000000..e69de29
116diff --git a/tests/regressiontests/admin_scripts/lib1/nsapps/__init__.py b/tests/regressiontests/admin_scripts/lib1/nsapps/__init__.py
117new file mode 100644
118index 0000000..32f26d8
119--- /dev/null
120+++ b/tests/regressiontests/admin_scripts/lib1/nsapps/__init__.py
121@@ -0,0 +1,6 @@
122+# http://packages.python.org/distribute/setuptools.html#namespace-packages
123+try:
124+    __import__('pkg_resources').declare_namespace(__name__)
125+except ImportError:
126+    from pkgutil import extend_path
127+    __path__ = extend_path(__path__, __name__)
128diff --git a/tests/regressiontests/admin_scripts/lib1/nsapps/app1/__init__.py b/tests/regressiontests/admin_scripts/lib1/nsapps/app1/__init__.py
129new file mode 100644
130index 0000000..e69de29
131diff --git a/tests/regressiontests/admin_scripts/lib1/nsapps/app1/management/__init__.py b/tests/regressiontests/admin_scripts/lib1/nsapps/app1/management/__init__.py
132new file mode 100644
133index 0000000..e69de29
134diff --git a/tests/regressiontests/admin_scripts/lib1/nsapps/app1/management/commands/__init__.py b/tests/regressiontests/admin_scripts/lib1/nsapps/app1/management/commands/__init__.py
135new file mode 100644
136index 0000000..e69de29
137diff --git a/tests/regressiontests/admin_scripts/lib1/nsapps/app1/management/commands/app1_command1.py b/tests/regressiontests/admin_scripts/lib1/nsapps/app1/management/commands/app1_command1.py
138new file mode 100644
139index 0000000..2f479bb
140--- /dev/null
141+++ b/tests/regressiontests/admin_scripts/lib1/nsapps/app1/management/commands/app1_command1.py
142@@ -0,0 +1,9 @@
143+from django.core.management.base import BaseCommand
144+
145+class Command(BaseCommand):
146+    help = 'Test managment commands in namespaced apps'
147+    requires_model_validation = False
148+    args = ''
149+
150+    def handle(self, *labels, **options):
151+        print 'EXECUTE:app1_command1'
152diff --git a/tests/regressiontests/admin_scripts/lib1/nsapps/app1/models.py b/tests/regressiontests/admin_scripts/lib1/nsapps/app1/models.py
153new file mode 100644
154index 0000000..e69de29
155diff --git a/tests/regressiontests/admin_scripts/lib2/nsapps/__init__.py b/tests/regressiontests/admin_scripts/lib2/nsapps/__init__.py
156new file mode 100644
157index 0000000..32f26d8
158--- /dev/null
159+++ b/tests/regressiontests/admin_scripts/lib2/nsapps/__init__.py
160@@ -0,0 +1,6 @@
161+# http://packages.python.org/distribute/setuptools.html#namespace-packages
162+try:
163+    __import__('pkg_resources').declare_namespace(__name__)
164+except ImportError:
165+    from pkgutil import extend_path
166+    __path__ = extend_path(__path__, __name__)
167diff --git a/tests/regressiontests/admin_scripts/lib2/nsapps/app2/__init__.py b/tests/regressiontests/admin_scripts/lib2/nsapps/app2/__init__.py
168new file mode 100644
169index 0000000..e69de29
170diff --git a/tests/regressiontests/admin_scripts/lib2/nsapps/app2/management/__init__.py b/tests/regressiontests/admin_scripts/lib2/nsapps/app2/management/__init__.py
171new file mode 100644
172index 0000000..e69de29
173diff --git a/tests/regressiontests/admin_scripts/lib2/nsapps/app2/management/commands/__init__.py b/tests/regressiontests/admin_scripts/lib2/nsapps/app2/management/commands/__init__.py
174new file mode 100644
175index 0000000..e69de29
176diff --git a/tests/regressiontests/admin_scripts/lib2/nsapps/app2/management/commands/app2_command1.py b/tests/regressiontests/admin_scripts/lib2/nsapps/app2/management/commands/app2_command1.py
177new file mode 100644
178index 0000000..b9e20a7
179--- /dev/null
180+++ b/tests/regressiontests/admin_scripts/lib2/nsapps/app2/management/commands/app2_command1.py
181@@ -0,0 +1,9 @@
182+from django.core.management.base import BaseCommand
183+
184+class Command(BaseCommand):
185+    help = 'Test managment commands in namespaced apps'
186+    requires_model_validation = False
187+    args = ''
188+
189+    def handle(self, *labels, **options):
190+        print 'EXECUTE:app2_command1'
191diff --git a/tests/regressiontests/admin_scripts/lib2/nsapps/app2/models.py b/tests/regressiontests/admin_scripts/lib2/nsapps/app2/models.py
192new file mode 100644
193index 0000000..e69de29
194diff --git a/tests/regressiontests/admin_scripts/tests.py b/tests/regressiontests/admin_scripts/tests.py
195index 94d8f93..9a29808 100644
196--- a/tests/regressiontests/admin_scripts/tests.py
197+++ b/tests/regressiontests/admin_scripts/tests.py
198@@ -84,6 +84,8 @@ class AdminScriptTestCase(unittest.TestCase):
199         test_dir = os.path.dirname(os.path.dirname(__file__))
200         project_dir = os.path.dirname(test_dir)
201         base_dir = os.path.dirname(project_dir)
202+        lib1_dir = os.path.join(os.path.dirname(__file__), 'lib1')
203+        lib2_dir = os.path.join(os.path.dirname(__file__), 'lib2')
204         ext_backend_base_dirs = self._ext_backend_paths()
205 
206         # Remember the old environment
207@@ -101,7 +103,7 @@ class AdminScriptTestCase(unittest.TestCase):
208             os.environ['DJANGO_SETTINGS_MODULE'] = settings_file
209         elif 'DJANGO_SETTINGS_MODULE' in os.environ:
210             del os.environ['DJANGO_SETTINGS_MODULE']
211-        python_path = [test_dir, base_dir]
212+        python_path = [test_dir, base_dir, lib1_dir, lib2_dir]
213         python_path.extend(ext_backend_base_dirs)
214         os.environ[python_path_var_name] = os.pathsep.join(python_path)
215 
216@@ -1197,3 +1199,35 @@ class ArgumentOrder(AdminScriptTestCase):
217         out, err = self.run_manage(args)
218         self.assertNoOutput(err)
219         self.assertOutput(out, "EXECUTE:BaseCommand labels=('testlabel',), options=[('option_a', 'x'), ('option_b', 'y'), ('option_c', '3'), ('pythonpath', None), ('settings', 'alternate_settings'), ('traceback', None), ('verbosity', '1')]")
220+
221+
222+class NamespacePackagedApps(AdminScriptTestCase):
223+    def setUp(self):
224+        self.write_settings('settings.py', apps=['nons_app', 'nsapps.app1','nsapps.app2'])
225+       
226+    def tearDown(self):
227+        self.remove_settings('settings.py')
228+
229+    def test_help(self):
230+        out, err = self.run_manage(['help'])
231+        self.assertOutput(err, "nons_app_command1")
232+        self.assertOutput(err, "app1_command1")
233+        self.assertOutput(err, "app2_command1")
234+
235+    def test_nons_app(self):
236+        args = ['nons_app_command1']
237+        out, err = self.run_manage(args)
238+        self.assertNoOutput(err)
239+        self.assertOutput(out, "EXECUTE:nons_app_command1")
240+
241+    def test_nsapps(self):
242+        args = ['app1_command1']
243+        out, err = self.run_manage(args)
244+        self.assertNoOutput(err)
245+        self.assertOutput(out, "EXECUTE:app1_command1")
246+
247+        args = ['app2_command1']
248+        out, err = self.run_manage(args)
249+        self.assertNoOutput(err)
250+        self.assertOutput(out, "EXECUTE:app2_command1")
251+