
Property changes on: .
___________________________________________________________________
Name: svn:ignore
   - build
dist
*.egg-info
MANIFEST

   + build
dist
*.egg-info
MANIFEST
.settings
.project
.pydevproject


Index: django/core/management/base.py
===================================================================
--- django/core/management/base.py	(revision 6365)
+++ django/core/management/base.py	(working copy)
@@ -9,6 +9,17 @@
 class CommandError(Exception):
     pass
 
+def handle_default_options(options):
+    """
+    Include any default options that all commands should accept
+    here so that ManagementUtility can handle them before searching
+    for user commands.
+    """
+    if options.settings:
+        os.environ['DJANGO_SETTINGS_MODULE'] = options.settings
+    if options.pythonpath:
+        sys.path.insert(0, options.pythonpath)
+                
 class BaseCommand(object):
     # Metadata about this command.
     option_list = (
@@ -51,14 +62,11 @@
     def print_help(self, prog_name, subcommand):
         parser = self.create_parser(prog_name, subcommand)
         parser.print_help()
-
+        
     def run_from_argv(self, argv):
         parser = self.create_parser(argv[0], argv[1])
         options, args = parser.parse_args(argv[2:])
-        if options.settings:
-            os.environ['DJANGO_SETTINGS_MODULE'] = options.settings
-        if options.pythonpath:
-            sys.path.insert(0, options.pythonpath)
+        handle_default_options(options)
         self.execute(*args, **options.__dict__)
 
     def execute(self, *args, **options):
Index: django/core/management/__init__.py
===================================================================
--- django/core/management/__init__.py	(revision 6365)
+++ django/core/management/__init__.py	(working copy)
@@ -28,6 +28,13 @@
     klass = load_command_class(name)
     return klass.execute(*args, **options)
 
+def find_commands(command_dir):
+    return [f[:-3] for f in os.listdir(command_dir) if not f.startswith('_') and f.endswith('.py')]
+
+class LaxOptionParser(OptionParser):
+    def error(self, msg):
+        pass
+
 class ManagementUtility(object):
     """
     Encapsulates the logic of the django-admin.py and manage.py utilities.
@@ -39,7 +46,7 @@
         self.argv = argv or sys.argv[:]
         self.prog_name = os.path.basename(self.argv[0])
         self.commands = self.default_commands()
-
+        
     def default_commands(self):
         """
         Returns a dictionary of instances of all available Command classes.
@@ -50,7 +57,7 @@
         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')]
+        names = find_commands(command_dir)
         return dict([(name, load_command_class(name)) for name in names])
 
     def main_help_text(self):
@@ -65,8 +72,51 @@
         commands.sort()
         for cmd in commands:
             usage.append('  %s' % cmd)
+        usage += self.user_commands_usage()
         return '\n'.join(usage)
+        
+        
+    def user_commands_usage(self):
+        """
+        Returns a list of lines that list each app and the commands it provides.
+        """
+        usage = []
+        try:
+            apps = django.conf.settings.INSTALLED_APPS
+        except (KeyError, EnvironmentError):
+            apps = ()
+        for app in apps:
+            mod = __import__(app)
+            components = app.split('.')
+            for comp in components[1:]:
+                mod = getattr(mod, comp)
+            command_dir = os.path.join(os.path.split(os.path.abspath(mod.__file__))[0],
+                                       'management', 'commands')
+            if os.path.exists(command_dir):
+                commands = find_commands(command_dir)
+                if commands:
+                    commands = ['%s.%s' % (app, cmd) for cmd in commands]
+                    commands.sort()
+                    usage.append('')
+                    usage.append('From app %s:' % app)
+                    for cmd in commands:
+                        usage.append('  %s' % cmd)
+        return usage
+    
 
+    def fetch_user_command(self, subcommand):
+        """
+        Attempts to fetch a user defined command. Commands are written in the
+        form  path.to.app.command, so that the last word should correspond to a 
+        module in the path.to.app.management.commands module and should 
+        contain a Command class that is a subclass of BaseCommand.
+        """
+        subcommand = subcommand.split('.')
+        app_path = '.'.join(subcommand[:-1])
+        from django.conf import settings
+        command_name = subcommand[-1]
+        return getattr(__import__('%s.management.commands.%s' % (app_path, command_name), {}, {}, ['Command']), 'Command')()
+    
     def fetch_command(self, subcommand):
         """
         Tries to fetch the given subcommand, printing a message with the
@@ -76,23 +126,32 @@
         try:
             return self.commands[subcommand]
         except KeyError:
-            sys.stderr.write("Unknown command: %r\nType '%s help' for usage.\n" % (subcommand, self.prog_name))
-            sys.exit(1)
+            # subcommand not provided by django, may be provided by an app
+            return self.fetch_user_command(subcommand)
 
     def execute(self):
         """
         Given the command-line arguments, this figures out which subcommand is
         being run, creates a parser appropriate to that command, and runs it.
         """
+        from django.core.management.base import BaseCommand, handle_default_options
+        parser = LaxOptionParser(version=get_version(),
+                                 option_list=BaseCommand.option_list)
+        options, args = parser.parse_args(self.argv)
+        handle_default_options(options)
         try:
-            subcommand = self.argv[1]
+            subcommand = args[1]
         except IndexError:
             sys.stderr.write("Type '%s help' for usage.\n" % self.prog_name)
             sys.exit(1)
 
         if subcommand == 'help':
-            if len(self.argv) > 2:
-                self.fetch_command(self.argv[2]).print_help(self.prog_name, self.argv[2])
+            if len(args) > 2:
+                try:
+                    self.fetch_command(args[2]).print_help(self.prog_name, self.argv[2])
+                except (ValueError, ImportError):
+                    sys.stderr.write("Unknown command: %r\nType '%s help' for usage.\n" % (args[2], self.prog_name))
+                    sys.exit(1)
             else:
                 sys.stderr.write(self.main_help_text() + '\n')
                 sys.exit(1)
Index: docs/django-admin.txt
===================================================================
--- docs/django-admin.txt	(revision 6365)
+++ docs/django-admin.txt	(working copy)
@@ -735,3 +735,34 @@
     * 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 of the form ``app_path.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, the ``fancy_blog.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``.
