鉴于笔者水平有限,文中难免出现一些错误,还请多多指教!python
好了,下边是正文....django
首先大概看一下Django 项目的主要目录,初步创建一下Django源码的世界观。bash
├── django //工程代码存放路径 ├── docs //文档 ├── extras ├── js_tests //测试 ├── scripts //脚本 └── tests //单元测试
Django核心代码主要在django目录下边app
django/ ├── apps(app模块) ├── bin(可执行命令) ├── conf(配置) ├── contrib(其余开发者贡献代码) ├── core(核心组件) ├── db(ORM模块) ├── dispatch ├── forms(表单模块) ├── http ├── middleware(中间件) ├── template(模板) ├── templatetags ├── test(测试代码) ├── urls(url路由模块) ├── utils(工具代码) └── views(视图模块)
在django中咱们经常使用的命令主要有两个,一个是django-admin,一个是xxxx,咱们先看一下django-adminide
一、命令位置函数
lion@localhost:~/django/django$ whereis django-admin django-admin: /usr/local/bin/django-admin /usr/local/bin/django-admin.py /usr/local/bin/django-admin.pyc
二、命令内容工具
lion@localhost:~/django/django$ cat /usr/local/bin/django-admin #!/usr/bin/python # -*- coding: utf-8 -*- import re import sys from django.core.management import execute_from_command_line if __name__ == '__main__': sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0]) sys.exit(execute_from_command_line())
其实对比不难发现,django-admin命令其实对应的是django源码中的.django/bin/django-admin.py这个文件。单元测试
django-admin.py 引用了django.core中的management,并调用了其execute_from_command_line函数。学习
注:在最新版中django-admin和manage.py中调用的都是execute_from_command_line函数了,较旧版本的django中可能不一样。测试
因此要分析django的命令系统,就要从execute_from_command_line函数入手。
execute_from_command_line函数定义:
def execute_from_command_line(argv=None): """ A simple method that runs a ManagementUtility. """ utility = ManagementUtility(argv) utility.execute()
函数初始化ManagementUtility类,传入argv(也就是命令行参数)参数,并执行execute方法
execute方法:
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. """ try: subcommand = self.argv[1] except IndexError: subcommand = 'help' # Display help if no arguments were given. # Preprocess options to extract --settings and --pythonpath. # These options could affect the commands that are available, so they # must be processed early. parser = CommandParser(None, usage="%(prog)s subcommand [options] [args]", add_help=False) parser.add_argument('--settings') parser.add_argument('--pythonpath') parser.add_argument('args', nargs='*') # catch-all try: options, args = parser.parse_known_args(self.argv[2:]) handle_default_options(options) except CommandError: pass # Ignore any option errors at this point. no_settings_commands = [ 'help', 'version', '--help', '--version', '-h', 'startapp', 'startproject', 'compilemessages', ] try: settings.INSTALLED_APPS except ImproperlyConfigured as exc: self.settings_exception = exc # A handful of built-in management commands work without settings. # Load the default settings -- where INSTALLED_APPS is empty. if subcommand in no_settings_commands: settings.configure() if settings.configured: # Start the auto-reloading dev server even if the code is broken. # The hardcoded condition is a code smell but we can't rely on a # flag on the command class because we haven't located it yet. if subcommand == 'runserver' and '--noreload' not in self.argv: try: autoreload.check_errors(django.setup)() except Exception: # The exception will be raised later in the child process # started by the autoreloader. Pretend it didn't happen by # loading an empty list of applications. apps.all_models = defaultdict(OrderedDict) apps.app_configs = OrderedDict() apps.apps_ready = apps.models_ready = apps.ready = True # In all other cases, django.setup() is required to succeed. else: django.setup() self.autocomplete() if subcommand == 'help': if '--commands' in args: sys.stdout.write(self.main_help_text(commands_only=True) + '\n') elif len(options.args) < 1: sys.stdout.write(self.main_help_text() + '\n') else: self.fetch_command(options.args[0]).print_help(self.prog_name, options.args[0]) # Special-cases: We want 'django-admin --version' and # 'django-admin --help' to work, for backwards compatibility. elif subcommand == 'version' or self.argv[1:] == ['--version']: sys.stdout.write(django.get_version() + '\n') elif self.argv[1:] in (['--help'], ['-h']): sys.stdout.write(self.main_help_text() + '\n') else: self.fetch_command(subcommand).run_from_argv(self.argv)
此方法主要解析命令行参数,加载settings配置,若是setting配置成功则执行django.setup函数(此函数主要是加载App),最后一步调用的核心命令为fetch_command命令,并执行run_from_argv函数
先看一下fetch_command函数
def fetch_command(self, subcommand): """ Tries to fetch the given subcommand, printing a message with the appropriate command called from the command line (usually "django-admin" or "manage.py") if it can't be found. """ # Get commands outside of try block to prevent swallowing exceptions commands = get_commands() try: app_name = commands[subcommand] except KeyError: if os.environ.get('DJANGO_SETTINGS_MODULE'): # If `subcommand` is missing due to misconfigured settings, the # following line will retrigger an ImproperlyConfigured exception # (get_commands() swallows the original one) so the user is # informed about it. settings.INSTALLED_APPS else: sys.stderr.write("No Django settings specified.\n") sys.stderr.write( "Unknown command: %r\nType '%s help' for usage.\n" % (subcommand, self.prog_name) ) sys.exit(1) if isinstance(app_name, BaseCommand): # If the command is already loaded, use it directly. klass = app_name else: klass = load_command_class(app_name, subcommand) return klass
这个fetch_command函数相似一个工厂函数,由get_commands函数扫描出全部的子命令,包括managemen中的子命令和app下的managemen中commands的子命令(自定义),而后根据传入的subcommand初始化Command类。
若是子命令不在commands字典内的话,会抛出一个“Unknown command”的提示,若是子命令存在则返回初始化的Command类。
接着视角在返回到execute函数中,接着
self.fetch_command(subcommand).run_from_argv(self.argv)
将会调用fetch_command(subcommand)初始化Command类的run_from_argv方法。run_from_argv由各个Command的基类BaseCommand定义,最终将会调用各个子类实现的handle方法。从而执行子命令的业务逻辑。
至此,命令调用的逻辑基本完成。
笔者随笔:
经过阅读这一部分的代码,其中最值得学习的地方在于fetch_commands函数,这是一个运用工厂方法的最佳实践,这样不但最大程度的解耦了代码实现,同时使得命令系统更易于扩展(App 自定义子命令就是一个很好的说明)
再有一点就是Command基类的定义,对于各类子命令的定义,基类完整的抽象出了command业务的工做逻辑,提供了统一的命令调用接口使得命令系统更易于扩展。