鑑於筆者水平有限,文中難免出現一些錯誤,還請多多指教!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業務的工做邏輯,提供了統一的命令調用接口使得命令系統更易於擴展。