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].

git-svn-id: http://code.djangoproject.com/svn/django/trunk@6047 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
Russell Keith-Magee 2007-09-04 12:59:49 +00:00
parent 4e476b44af
commit f25b8cdbcd
2 changed files with 99 additions and 22 deletions

View File

@ -1,4 +1,5 @@
import django import django
from django.core.management.base import CommandError
from optparse import OptionParser from optparse import OptionParser
import os import os
import sys import sys
@ -7,13 +8,61 @@ import textwrap
# For backwards compatibility: get_version() used to be in this module. # For backwards compatibility: get_version() used to be in this module.
get_version = django.get_version 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 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. # Let any errors propogate.
return getattr(__import__('django.core.management.commands.%s' % name, {}, {}, ['Command']), 'Command')() 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): def call_command(name, *args, **options):
""" """
@ -26,8 +75,11 @@ def call_command(name, *args, **options):
call_command('shell', plain=True) call_command('shell', plain=True)
call_command('sqlall', 'myapp') call_command('sqlall', 'myapp')
""" """
klass = load_command_class(name) try:
return klass.execute(*args, **options) command = get_commands()[name]
except KeyError:
raise CommandError, "Unknown command: %r\n" % name
return command.execute(*args, **options)
class ManagementUtility(object): class ManagementUtility(object):
""" """
@ -37,20 +89,12 @@ class ManagementUtility(object):
by editing the self.commands dictionary. by editing the self.commands dictionary.
""" """
def __init__(self): def __init__(self):
self.commands = self.default_commands() # The base management utility doesn't expose any user-defined commands
try:
def default_commands(self): self.commands = get_commands(load_user_commands=False)
""" except CommandError, e:
Returns a dictionary of instances of all available Command classes. sys.stderr.write(str(e))
sys.exit(1)
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): def usage(self):
""" """
@ -133,7 +177,11 @@ class ProjectManagementUtility(ManagementUtility):
represents django-admin.py. represents django-admin.py.
""" """
def __init__(self, project_directory): 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 # Remove the "startproject" command from self.commands, because
# that's a django-admin.py command, not a manage.py command. # that's a django-admin.py command, not a manage.py command.

View File

@ -619,3 +619,32 @@ distribution. It enables tab-completion of ``django-admin.py`` and
* Press [TAB] to see all available options. * Press [TAB] to see all available options.
* Type ``sql``, then [TAB], to see all available options whose names start * Type ``sql``, then [TAB], to see all available options whose names start
with ``sql``. 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``.