启动django测试服务的最简单方法无外乎,

 python manage.py runserver

runserver是django默认提供的command,除了默认命令之外django支持自定义各种command。这种支持基于一定的约定,希望通过python manage.py运行的命令需要位于各app的management/commands目录下。

这非常方便,但也带来一定问题就是没法继续组织目录结构。commands下面的py文件很容易就会膨胀,所以必须自己对其进行一定扩展,使得我们能够在commands下面再任意拆分组织目录。

扩展的形式参照了默认command的运行方式,思路很简单:实现一个command,这个command接收参数,参数为真正要执行的command。

 # -*- coding:utf-8 -*-
 from django.core.management.base import BaseCommand
 from django.utils.importlib import import_module
 import importlib
 import re
 import os

 class Command(BaseCommand):
     '''
     执行位于management/commands子目录中的其它命令
     '''
     def __init__(self):
         super(Command, self).__init__()

     def handle(self, *args, **options):
         '''
         python manage.py exec
         '''
         if len(args) == 0:
             return
         command_name = args[0]
         self._exec_command(self._find_command(command_name), *args[1:], **options)

     def _find_command(self, name):
         '''
         寻找可以执行的命令
         '''
         def _find_name(path):
             return {path.split('/')[-1][:-3]:path}
         paths = self._filter_paths(self._list_all_pyfiles())
         commands = {}
         for path in paths:
             commands.update(_find_name(path))
         return commands[name]

     def _exec_command(self, command_path, *args, **options):
         '''
         执行命令
         '''
         def _build_import_name(path):
             '''
             从路径构造import需要的名字
             '''
             if path.startswith('/'):
                path = path[1:-3]
             return re.sub('/', '.', path)

         packagename = _build_import_name(command_path)
         instance = importlib.import_module(packagename)
         command = instance.Command()
         command.handle(*args, **options)

     def _list_all_pyfiles(self):
         '''
         列出可能包含命令的路径
         '''
         def _find_packagedir():
             '''
             寻找项目目录
             '''
             try:
                 from django.conf import settings
                 module = import_module(settings.SETTINGS_MODULE)
                 project_directory = setup_environ(module, settings.SETTINGS_MODULE)
             except (AttributeError, EnvironmentError, ImportError, KeyError):
                 logging_exception()
                 project_directory = ''
             return project_directory

         def _build_relative_path(result, project_dir):
             '''
             创建相对路径,移除项目目录
             '''
             length = len(project_dir)
             return [item[length:] for item in result]

         project_directory = _find_packagedir()
         result = []
         for base, folders, files in os.walk(project_directory):
             # 过滤svn目录
             if '.svn' in base:
                 continue
             # 过滤非py文件
             for file in files:
                 if not file.endswith('.py') or file.startswith('_'):
                     continue
                 result.append(os.path.join(base, file))
         return _build_relative_path(result, project_directory)

     def _filter_paths(self, paths):
         '''
         只处理自定义app中management/commands目录内的命令
         '''
         def _find_apps():
             try:
                 from django.conf import settings
                 apps = settings.INSTALLED_APPS
             except (AttributeError, EnvironmentError, ImportError):
                 apps = []
             return apps

         def _filter_path(path, apps):
             def _filter(path, appname):
                 return path.startswith('/' + appname + '/management/commands/')
             return reduce(lambda x, y: x or y, [_filter(path, appname) for appname in apps])

         apps = _find_apps()
         return [path for path in paths if _filter_path(path, apps)]