Django源碼淺析-命令系統(tǒng)

摘要: 基于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ò)展眯牧。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市赖草,隨后出現(xiàn)的幾起案子学少,更是在濱河造成了極大的恐慌,老刑警劉巖秧骑,帶你破解...
    沈念sama閱讀 212,029評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件版确,死亡現(xiàn)場離奇詭異,居然都是意外死亡乎折,警方通過查閱死者的電腦和手機(jī)绒疗,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,395評論 3 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來骂澄,“玉大人吓蘑,你說我怎么就攤上這事。” “怎么了磨镶?”我有些...
    開封第一講書人閱讀 157,570評論 0 348
  • 文/不壞的土叔 我叫張陵溃蔫,是天一觀的道長。 經(jīng)常有香客問我琳猫,道長伟叛,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,535評論 1 284
  • 正文 為了忘掉前任脐嫂,我火速辦了婚禮痪伦,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘雹锣。我一直安慰自己,他們只是感情好癞蚕,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,650評論 6 386
  • 文/花漫 我一把揭開白布蕊爵。 她就那樣靜靜地躺著,像睡著了一般桦山。 火紅的嫁衣襯著肌膚如雪攒射。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,850評論 1 290
  • 那天恒水,我揣著相機(jī)與錄音会放,去河邊找鬼。 笑死钉凌,一個(gè)胖子當(dāng)著我的面吹牛咧最,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播御雕,決...
    沈念sama閱讀 39,006評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼矢沿,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了酸纲?” 一聲冷哼從身側(cè)響起捣鲸,我...
    開封第一講書人閱讀 37,747評論 0 268
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎闽坡,沒想到半個(gè)月后栽惶,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,207評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡疾嗅,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,536評論 2 327
  • 正文 我和宋清朗相戀三年外厂,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片宪迟。...
    茶點(diǎn)故事閱讀 38,683評論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡酣衷,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出次泽,到底是詐尸還是另有隱情穿仪,我是刑警寧澤席爽,帶...
    沈念sama閱讀 34,342評論 4 330
  • 正文 年R本政府宣布,位于F島的核電站啊片,受9級特大地震影響只锻,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜紫谷,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,964評論 3 315
  • 文/蒙蒙 一齐饮、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧笤昨,春花似錦祖驱、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,772評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至崇裁,卻和暖如春匕坯,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背拔稳。 一陣腳步聲響...
    開封第一講書人閱讀 32,004評論 1 266
  • 我被黑心中介騙來泰國打工葛峻, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人巴比。 一個(gè)月前我還...
    沈念sama閱讀 46,401評論 2 360
  • 正文 我出身青樓术奖,卻偏偏與公主長得像,于是被迫代替她去往敵國和親匿辩。 傳聞我的和親對象是個(gè)殘疾皇子腰耙,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,566評論 2 349

推薦閱讀更多精彩內(nèi)容