文档章节

【Django源码浅析】-Django命令系统

Archer弓兵
 Archer弓兵
发布于 2017/04/09 18:38
字数 1248
阅读 65
收藏 0

鉴于笔者水平有限,文中不免出现一些错误,还请多多指教!

好了,下边是正文....

首先大概看一下Django 项目的主要目录,初步建立一下Django源码的世界观。

├── django          //工程代码存放路径
├── docs            //文档
├── extras          
├── js_tests        //测试
├── scripts         //脚本
└── tests           //单元测试

Django核心代码主要在django目录下边

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-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、命令内容

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业务的工作逻辑,提供了统一的命令调用接口使得命令系统更易于扩展。

 

© 著作权归作者所有

共有 人打赏支持
Archer弓兵
粉丝 5
博文 34
码字总数 14928
作品 0
杭州
程序员
Django1.8 django-admin和manage命令简要分析

startproject和startapp 创建一个项目(Project) Django使用django-admin startproject [projectname]命令生成project。 django-admin命令行的路径为:django/bin/django-admin.py 以djang......

donhui
2015/11/23
1K
3
PYTHON资源入口汇总

官网 入口 官方文档 英文 document 2.7.6 入口| 标准库 document 3.x 入口 The Hitchhiker’s Guide to Python 入口 Python Monk 入口 中文 document 2.7 入口 非官方 google的python文档 Go...

好铁
2016/02/12
99
0
Django学习笔记(一)

Django是python web框架中的一种,下面我就介绍一些自己在学习过程中遇到的问题。 官方网站:https://www.djangoproject.com,这个是我们在学习过程中需要重点参考的文档,而且我们需要经常翻...

彩虹的夜晚
2017/10/20
0
0
CentOS 6.4下安装Django

Django是一个基于python的网站开发框架,笔者学过了python的基本知识,对用python进行网站开发颇有兴趣。于是想安装一个在本地研究一下。 笔者本地操作系统是:CentOS 6.4,Linux内核:2.6.3...

临峰不畏
2014/06/08
0
0
Django 链接MySQL

Python版本:3.5.1 Django版本:1.9 前面将Python升级完,然后将pip安装,然后是安装Django,高高兴兴的使用命令: django-admin.py startproject first 把Django名为first的项目也建立了,然...

Edenwy
2017/06/13
0
0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

文件的压缩与解压(linux)

Linux下*.tar.gz文件解压缩命令 1.压缩命令:   命令格式:tar -zcvf 压缩后文件名.tar.gz 被压缩文件名 可先切换到当前目录下。压缩文件名和被压缩文件名都可加入路径。 2.解压缩命令: ...

qimh
24分钟前
1
0
invalid character found in the request target 异常

这个异常时因为Tomcat 9不支持请求格式出现“{”等非法字符的问题 因为tomcat版本问题遇到的坑,记录一下。 问题 今天由于要测试一下订单详情页的异步查询,在本地起了一个服务,发送的请求是...

edwardGe
28分钟前
1
0
发现抓包软件fiddler的bug

1个请求他跳转之后,直接400,被拦在了Apache,使用fiddler 的,replay requests 是同样的结果,但是replay composer确是正常的。 也就是说这replay requests 是发原来的包,replay composer...

NLGBZJ
38分钟前
1
0
linux screen 命令详解

shell关闭后, 主机仍然运行 screen命令 启动jenkins以后, screen, 然后按ctrl+a 再按d 这样暂停了子界面, 这时候回到了父界面 用screen –ls查看目前子界面的状态 [root@free /]# screen -l...

SuShine
39分钟前
1
0
mac机器切换无线网络导致网页不能打开的问题

问题: 公司和家里使用不同的WI-FI,每次从家到公司时自动切换网络后,公司的许多地址不能访问, ping域名是可以ping同的,但是网页却打不开... 问题分析: 初步猜想是DNS缓存的问题? 对于MAC系统没...

Lennie002
42分钟前
1
0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部