摘要: 基于Django version 1.11.0 . alpha版本
鑒于筆者水平有限沪编,文中不免出現(xiàn)一些錯(cuò)誤寸爆,還請多多指教苍凛!
好了龄寞,下邊是正文....
首先大概看一下Django 項(xiàng)目的主要目錄壁拉,初步建立一下Django源碼的世界觀谬俄。
├── django //工程代碼存放路徑
├── docs //文檔
├── extras
├── js_tests //測試
├── scripts //腳本
└── tests //單元測試
Django核心代碼主要在django目錄下邊
django/
├── apps(app模塊)
├── bin(可執(zhí)行命令)
├── conf(配置)
├── contrib(其他開發(fā)者貢獻(xiàn)代碼)
├── core(核心組件)
├── db(ORM模塊)
├── dispatch
├── forms(表單模塊)
├── http
├── middleware(中間件)
├── template(模板)
├── templatetags
├── test(測試代碼)
├── urls(url路由模塊)
├── utils(工具代碼)
└── views(視圖模塊)
在django中我們常用的命令主要有兩個(gè),一個(gè)是django-admin弃理,一個(gè)是xxxx,我們先看一下django-admin
1溃论、命令位置
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
2、命令內(nèi)容
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())
其實(shí)對比不難發(fā)現(xiàn)案铺,django-admin命令其實(shí)對應(yīng)的是django源碼中的.django/bin/django-admin.py這個(gè)文件蔬芥。
django-admin.py 引用了django.core中的management,并調(diào)用了其execute_from_command_line函數(shù)控汉。
注:在最新版中django-admin和manage.py中調(diào)用的都是execute_from_command_line函數(shù)了笔诵,較舊版本的django中可能不同。
所以要分析django的命令系統(tǒng)姑子,就要從execute_from_command_line函數(shù)入手乎婿。
execute_from_command_line函數(shù)定義:
def execute_from_command_line(argv=None):
"""
A simple method that runs a ManagementUtility.
"""
utility = ManagementUtility(argv)
utility.execute()
函數(shù)初始化ManagementUtility類,傳入argv(也就是命令行參數(shù))參數(shù)街佑,并執(zhí)行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)
此方法主要解析命令行參數(shù)谢翎,加載settings配置捍靠,如果setting配置成功則執(zhí)行django.setup函數(shù)(此函數(shù)主要是加載App),最后一步調(diào)用的核心命令為fetch_command命令森逮,并執(zhí)行run_from_argv函數(shù)
先看一下fetch_command函數(shù)
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
這個(gè)fetch_command函數(shù)類似一個(gè)工廠函數(shù)榨婆,由get_commands函數(shù)掃描出所有的子命令,包括managemen中的子命令和app下的managemen中commands的子命令(自定義)褒侧,然后根據(jù)傳入的subcommand初始化Command類良风。
如果子命令不在commands字典內(nèi)的話,會(huì)拋出一個(gè)“Unknown command”的提示闷供,如果子命令存在則返回初始化的Command類烟央。
接著視角在返回到execute函數(shù)中,接著
self.fetch_command(subcommand).run_from_argv(self.argv)
將會(huì)調(diào)用fetch_command(subcommand)初始化Command類的run_from_argv方法歪脏。run_from_argv由各個(gè)Command的基類BaseCommand定義疑俭,最終將會(huì)調(diào)用各個(gè)子類實(shí)現(xiàn)的handle方法。從而執(zhí)行子命令的業(yè)務(wù)邏輯婿失。
至此钞艇,命令調(diào)用的邏輯基本完成。
筆者隨筆:
通過閱讀這一部分的代碼移怯,其中最值得學(xué)習(xí)的地方在于fetch_commands函數(shù)香璃,這是一個(gè)運(yùn)用工廠方法的最佳實(shí)踐,這樣不但最大程度的解耦了代碼實(shí)現(xiàn)舟误,同時(shí)使得命令系統(tǒng)更易于擴(kuò)展(App 自定義子命令就是一個(gè)很好的說明)
再有一點(diǎn)就是Command基類的定義葡秒,對于各種子命令的定義,基類完整的抽象出了command業(yè)務(wù)的工作邏輯嵌溢,提供了統(tǒng)一的命令調(diào)用接口使得命令系統(tǒng)更易于擴(kuò)展眯牧。