Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

Fixed #5212, #5222 -- Added the ability for users to register their o…

…wn commands with django-admin. A previous attempt at this was introduced in [5923]-[5925], and rolled out in [5929].

git-svn-id: http://code.djangoproject.com/svn/django/trunk@6047 bcc190cf-cafb-0310-a4f2-bffc1f526a37
  • Loading branch information...
commit f25b8cdbcdc99812299e9081cd3b03ddc08dcf74 1 parent 4e476b4
Russell Keith-Magee authored September 04, 2007
92  django/core/management/__init__.py
... ...
@@ -1,4 +1,5 @@
1 1
 import django
  2
+from django.core.management.base import CommandError
2 3
 from optparse import OptionParser
3 4
 import os
4 5
 import sys
@@ -7,13 +8,61 @@
7 8
 # For backwards compatibility: get_version() used to be in this module.
8 9
 get_version = django.get_version
9 10
 
10  
-def load_command_class(name):
  11
+# A cache of loaded commands, so that call_command 
  12
+# doesn't have to reload every time it is called
  13
+_commands = None
  14
+    
  15
+def find_commands(path):
  16
+    """
  17
+    Given a path to a management directory, return a list of all the command names 
  18
+    that are available. Returns an empty list if no commands are defined.
  19
+    """
  20
+    command_dir = os.path.join(path, 'commands')
  21
+    try:
  22
+        return [f[:-3] for f in os.listdir(command_dir) if not f.startswith('_') and f.endswith('.py')]
  23
+    except OSError:
  24
+        return []
  25
+
  26
+def load_command_class(module, name):
11 27
     """
12 28
     Given a command name, returns the Command class instance. Raises
13  
-    ImportError if it doesn't exist.
  29
+    Raises ImportError if a command module doesn't exist, or AttributeError
  30
+    if a command module doesn't contain a Command instance.
  31
+    """
  32
+    # Let any errors propogate.
  33
+    return getattr(__import__('%s.management.commands.%s' % (module, name), {}, {}, ['Command']), 'Command')()
  34
+
  35
+def get_commands(load_user_commands=True):
14 36
     """
15  
-    # Let the ImportError propogate.
16  
-    return getattr(__import__('django.core.management.commands.%s' % name, {}, {}, ['Command']), 'Command')()
  37
+    Returns a dictionary of instances of all available Command classes.
  38
+    Core commands are always included; user-register commands will also
  39
+    be included if ``load_user_commands`` is True.
  40
+
  41
+    This works by looking for a management.commands package in 
  42
+    django.core, and in each installed application -- if a commands 
  43
+    package exists, it loads all commands in that application.
  44
+
  45
+    The dictionary is in the format {name: command_instance}.
  46
+    
  47
+    The dictionary is cached on the first call, and reused on subsequent
  48
+    calls.
  49
+    """
  50
+    global _commands
  51
+    if _commands is None:
  52
+        _commands = dict([(name, load_command_class('django.core',name)) 
  53
+                            for name in find_commands(__path__[0])])
  54
+        if load_user_commands:
  55
+            # Get commands from all installed apps
  56
+            from django.db import models
  57
+            for app in models.get_apps():
  58
+                try:
  59
+                    app_name = '.'.join(app.__name__.split('.')[:-1])
  60
+                    path = os.path.join(os.path.dirname(app.__file__),'management')
  61
+                    _commands.update(dict([(name, load_command_class(app_name,name)) 
  62
+                                                    for name in find_commands(path)]))
  63
+                except AttributeError:
  64
+                    raise CommandError, "Management command '%s' in application '%s' doesn't contain a Command instance.\n" % (name, app_name)
  65
+    return _commands
17 66
 
18 67
 def call_command(name, *args, **options):
19 68
     """
@@ -26,9 +75,12 @@ def call_command(name, *args, **options):
26 75
         call_command('shell', plain=True)
27 76
         call_command('sqlall', 'myapp')
28 77
     """
29  
-    klass = load_command_class(name)
30  
-    return klass.execute(*args, **options)
31  
-
  78
+    try:
  79
+        command = get_commands()[name]
  80
+    except KeyError:
  81
+        raise CommandError, "Unknown command: %r\n" % name
  82
+    return command.execute(*args, **options)
  83
+    
32 84
 class ManagementUtility(object):
33 85
     """
34 86
     Encapsulates the logic of the django-admin.py and manage.py utilities.
@@ -37,20 +89,12 @@ class ManagementUtility(object):
37 89
     by editing the self.commands dictionary.
38 90
     """
39 91
     def __init__(self):
40  
-        self.commands = self.default_commands()
41  
-
42  
-    def default_commands(self):
43  
-        """
44  
-        Returns a dictionary of instances of all available Command classes.
45  
-
46  
-        This works by looking for and loading all Python modules in the
47  
-        django.core.management.commands package.
48  
-
49  
-        The dictionary is in the format {name: command_instance}.
50  
-        """
51  
-        command_dir = os.path.join(__path__[0], 'commands')
52  
-        names = [f[:-3] for f in os.listdir(command_dir) if not f.startswith('_') and f.endswith('.py')]
53  
-        return dict([(name, load_command_class(name)) for name in names])
  92
+        # The base management utility doesn't expose any user-defined commands
  93
+        try:
  94
+            self.commands = get_commands(load_user_commands=False)
  95
+        except CommandError, e:
  96
+            sys.stderr.write(str(e))
  97
+            sys.exit(1)
54 98
 
55 99
     def usage(self):
56 100
         """
@@ -133,7 +177,11 @@ class ProjectManagementUtility(ManagementUtility):
133 177
     represents django-admin.py.
134 178
     """
135 179
     def __init__(self, project_directory):
136  
-        super(ProjectManagementUtility, self).__init__()
  180
+        try:
  181
+            self.commands = get_commands()
  182
+        except CommandError, e:
  183
+            sys.stderr.write(str(e))
  184
+            sys.exit(1)
137 185
 
138 186
         # Remove the "startproject" command from self.commands, because
139 187
         # that's a django-admin.py command, not a manage.py command.
29  docs/django-admin.txt
@@ -619,3 +619,32 @@ distribution. It enables tab-completion of ``django-admin.py`` and
619 619
     * Press [TAB] to see all available options.
620 620
     * Type ``sql``, then [TAB], to see all available options whose names start
621 621
       with ``sql``.
  622
+
  623
+Customized actions
  624
+==================
  625
+
  626
+**New in Django development version**
  627
+
  628
+If you want to add an action of your own to ``manage.py``, you can.
  629
+Simply add a ``management/commands`` directory to your application.
  630
+Each python module in that directory will be discovered and registered as
  631
+a command that can be executed as an action when you run ``manage.py``::
  632
+
  633
+    /fancy_blog
  634
+        __init__.py
  635
+        models.py
  636
+        /management
  637
+            __init__.py
  638
+            /commands
  639
+                __init__.py
  640
+                explode.py
  641
+        views.py
  642
+        
  643
+In this example, ``explode`` command will be made available to any project
  644
+that includes the ``fancy_blog`` application in ``settings.INSTALLED_APPS``.
  645
+
  646
+The ``explode.py`` module has only one requirement -- it must define a class
  647
+called ``Command`` that extends ``django.core.management.base.BaseCommand``.
  648
+
  649
+For more details on how to define your own commands, look at the code for the
  650
+existing ``django-admin.py`` commands, in ``/django/core/management/commands``.

0 notes on commit f25b8cd

Please sign in to comment.
Something went wrong with that request. Please try again.