Author: russellm
Date: 2007-09-04 07:59:49 -0500 (Tue, 04 Sep 2007)
New Revision: 6047

Modified:
   django/trunk/django/core/management/__init__.py
   django/trunk/docs/django-admin.txt
Log:
Fixed #5212, #5222 -- Added the ability for users to register their own 
commands with django-admin. A previous attempt at this was introduced in 
[5923]-[5925], and rolled out in [5929].


Modified: django/trunk/django/core/management/__init__.py
===================================================================
--- django/trunk/django/core/management/__init__.py     2007-09-04 00:54:40 UTC 
(rev 6046)
+++ django/trunk/django/core/management/__init__.py     2007-09-04 12:59:49 UTC 
(rev 6047)
@@ -1,4 +1,5 @@
 import django
+from django.core.management.base import CommandError
 from optparse import OptionParser
 import os
 import sys
@@ -7,14 +8,62 @@
 # For backwards compatibility: get_version() used to be in this module.
 get_version = django.get_version
 
-def load_command_class(name):
+# A cache of loaded commands, so that call_command 
+# doesn't have to reload every time it is called
+_commands = None
+    
+def find_commands(path):
     """
+    Given a path to a management directory, return a list of all the command 
names 
+    that are available. Returns an empty list if no commands are defined.
+    """
+    command_dir = os.path.join(path, 'commands')
+    try:
+        return [f[:-3] for f in os.listdir(command_dir) if not 
f.startswith('_') and f.endswith('.py')]
+    except OSError:
+        return []
+
+def load_command_class(module, name):
+    """
     Given a command name, returns the Command class instance. Raises
-    ImportError if it doesn't exist.
+    Raises ImportError if a command module doesn't exist, or AttributeError
+    if a command module doesn't contain a Command instance.
     """
-    # Let the ImportError propogate.
-    return getattr(__import__('django.core.management.commands.%s' % name, {}, 
{}, ['Command']), 'Command')()
+    # Let any errors propogate.
+    return getattr(__import__('%s.management.commands.%s' % (module, name), 
{}, {}, ['Command']), 'Command')()
 
+def get_commands(load_user_commands=True):
+    """
+    Returns a dictionary of instances of all available Command classes.
+    Core commands are always included; user-register commands will also
+    be included if ``load_user_commands`` is True.
+
+    This works by looking for a management.commands package in 
+    django.core, and in each installed application -- if a commands 
+    package exists, it loads all commands in that application.
+
+    The dictionary is in the format {name: command_instance}.
+    
+    The dictionary is cached on the first call, and reused on subsequent
+    calls.
+    """
+    global _commands
+    if _commands is None:
+        _commands = dict([(name, load_command_class('django.core',name)) 
+                            for name in find_commands(__path__[0])])
+        if load_user_commands:
+            # Get commands from all installed apps
+            from django.db import models
+            for app in models.get_apps():
+                try:
+                    app_name = '.'.join(app.__name__.split('.')[:-1])
+                    path = 
os.path.join(os.path.dirname(app.__file__),'management')
+                    _commands.update(dict([(name, 
load_command_class(app_name,name)) 
+                                                    for name in 
find_commands(path)]))
+                except AttributeError:
+                    raise CommandError, "Management command '%s' in 
application '%s' doesn't contain a Command instance.\n" % (name, app_name)
+    return _commands
+
 def call_command(name, *args, **options):
     """
     Calls the given command, with the given options and args/kwargs.
@@ -26,9 +75,12 @@
         call_command('shell', plain=True)
         call_command('sqlall', 'myapp')
     """
-    klass = load_command_class(name)
-    return klass.execute(*args, **options)
-
+    try:
+        command = get_commands()[name]
+    except KeyError:
+        raise CommandError, "Unknown command: %r\n" % name
+    return command.execute(*args, **options)
+    
 class ManagementUtility(object):
     """
     Encapsulates the logic of the django-admin.py and manage.py utilities.
@@ -37,21 +89,13 @@
     by editing the self.commands dictionary.
     """
     def __init__(self):
-        self.commands = self.default_commands()
+        # The base management utility doesn't expose any user-defined commands
+        try:
+            self.commands = get_commands(load_user_commands=False)
+        except CommandError, e:
+            sys.stderr.write(str(e))
+            sys.exit(1)
 
-    def default_commands(self):
-        """
-        Returns a dictionary of instances of all available Command classes.
-
-        This works by looking for and loading all Python modules in the
-        django.core.management.commands package.
-
-        The dictionary is in the format {name: command_instance}.
-        """
-        command_dir = os.path.join(__path__[0], 'commands')
-        names = [f[:-3] for f in os.listdir(command_dir) if not 
f.startswith('_') and f.endswith('.py')]
-        return dict([(name, load_command_class(name)) for name in names])
-
     def usage(self):
         """
         Returns a usage string, for use with optparse.
@@ -133,7 +177,11 @@
     represents django-admin.py.
     """
     def __init__(self, project_directory):
-        super(ProjectManagementUtility, self).__init__()
+        try:
+            self.commands = get_commands()
+        except CommandError, e:
+            sys.stderr.write(str(e))
+            sys.exit(1)
 
         # Remove the "startproject" command from self.commands, because
         # that's a django-admin.py command, not a manage.py command.

Modified: django/trunk/docs/django-admin.txt
===================================================================
--- django/trunk/docs/django-admin.txt  2007-09-04 00:54:40 UTC (rev 6046)
+++ django/trunk/docs/django-admin.txt  2007-09-04 12:59:49 UTC (rev 6047)
@@ -619,3 +619,32 @@
     * Press [TAB] to see all available options.
     * Type ``sql``, then [TAB], to see all available options whose names start
       with ``sql``.
+
+Customized actions
+==================
+
+**New in Django development version**
+
+If you want to add an action of your own to ``manage.py``, you can.
+Simply add a ``management/commands`` directory to your application.
+Each python module in that directory will be discovered and registered as
+a command that can be executed as an action when you run ``manage.py``::
+
+    /fancy_blog
+        __init__.py
+        models.py
+        /management
+            __init__.py
+            /commands
+                __init__.py
+                explode.py
+        views.py
+        
+In this example, ``explode`` command will be made available to any project
+that includes the ``fancy_blog`` application in ``settings.INSTALLED_APPS``.
+
+The ``explode.py`` module has only one requirement -- it must define a class
+called ``Command`` that extends ``django.core.management.base.BaseCommand``.
+
+For more details on how to define your own commands, look at the code for the
+existing ``django-admin.py`` commands, in ``/django/core/management/commands``.


--~--~---------~--~----~------------~-------~--~----~
You received this message because you are subscribed to the Google Groups 
"Django updates" group.
To post to this group, send email to [email protected]
To unsubscribe from this group, send email to [EMAIL PROTECTED]
For more options, visit this group at 
http://groups.google.com/group/django-updates?hl=en
-~----------~----~----~----~------~----~------~--~---

Reply via email to