Django源码解析(二) manage.py

Django源码解析(一) 开篇

Django源码解析(二) manage.py

Django源码解析(三) Django开发服务器,WSGI规范实现

Django源码解析(四) 中间件

Django源码解析(五) URL配置

 

一. 什么是manage.py

在使用django-admin.py创建Django项目时,manage.py会被自动生成在项目根目录下.用以对django项目实现命令行操作.

调用django.core.management .execute_manager()方法实现命令执行.

#!/usr/bin/env python
from django.core.management import execute_manager
import imp
try:
    imp.find_module('settings') # Assumed to be in the same directory.
except ImportError:
    import sys
    sys.stderr.write("Error: Can't find the file 'settings.py' in the directory containing %r. It appears you've customized things.\nYou'll have to run django-admin.py, passing it your settings module.\n" % __file__)
    sys.exit(1)

import settings

if __name__ == "__main__":
    execute_manager(settings)

二. manage.py与django-admin.py的区别

django-admin.py调用django.core.management .execute_from_command_line()方法实现命令执行.

#!/usr/bin/env python
from django.core import management

if __name__ == "__main__":
    management.execute_from_command_line()

manage.py与django-admin.py都使用了django.core.management包的方法.execute_manager()方法专门提供给manage.py使用,execute_from_command_line()专门

提供给django-admin.py使用.(地球人都看的出来,⊙﹏⊙b).

这俩个命令的主要差别在于execute_manager()多执行了一个setup_environ(settings_mod)方法.弄清这个方法的作用,就知道manage.py与django-admin.py的主要区别了.

def execute_manager(settings_mod, argv=None):
    """
    Like execute_from_command_line(), but for use by manage.py, a
    project-specific django-admin.py utility.
    """
    setup_environ(settings_mod)
    utility = ManagementUtility(argv)
    utility.execute()

最主要显示setup_environ(settings_mod)方法功能的代码:

    # Set DJANGO_SETTINGS_MODULE appropriately.
    if original_settings_path:
        os.environ['DJANGO_SETTINGS_MODULE'] = original_settings_path
    else:
        os.environ['DJANGO_SETTINGS_MODULE'] = '%s.%s' % (project_name, settings_name)

    # Import the project module. We add the parent directory to PYTHONPATH to
    # avoid some of the path errors new users can have.
    sys.path.append(os.path.join(project_directory, os.pardir))
    project_module = import_module(project_name)
    sys.path.pop()

    可见setup_environ(settings_mod)方法的主要功能,即manage.py与django-admin.py的主要区别:

    • manage.py设置一个名为DJANGO_SETTINGS_MODULE的系统环境变量.

    例如我的django项目根目录为”E:\workspace\django\pearl”,settings模块的文件名为settings.py.DJANGO_SETTINGS_MODULE会被设置成为”pearl.settings”

    • manage.py把当前项目目录的父目录加入到PYTHONPATH,项目目录作为包导入.

    例如我的django项目根目录为”E:\workspace\django\pearl”.把”E:\workspace\django”加入到PYTHONPATH,导入包名为”pearl”.

    以上2点实际上使在使用manage.py时,不用像使用django-admin.py时输入”--settings”与”--pythonpath”参数.提供更便捷的操作.

    注: 当直接使用django-admin.py时,需要设置'DJANGO_SETTINGS_MODULE'环境变量或”—settings”选项.

    三. manage.py命令执行过程

    通过execute_manager()与execute_from_command_line()方法,可以看到Django命令的执行主要通过django.core.management.ManagementUtility类执行.

    ManagementUtility类的execute()方法为执行入口.

    命令的执行过程:

    1.解析命令,获得要执行的子命令名称.通过继承至OptionParser的类LaxOptionParser解析命令行.

    2.获得所有可执行子命令的完整模块路径.

    命令文件在django/core/management/commands目录下与INSTALLED_APPS(settings.py文件中定义)/management/commands目录下.

    通过ManagementUtility.fetch_command()方法中,调用get_commands()方法实现.

    3.返回子命令对应同名模块中的Command类的实例.

    4.执行命令.调用Command类的实例的run_from_argv()方法.

     

    def handle_default_options(options):
        """
        此方法设定'DJANGO_SETTINGS_MODULE'与pythonpath,即在manage.py中设定的2个内容
        """
        if options.settings:
            os.environ['DJANGO_SETTINGS_MODULE'] = options.settings
        if options.pythonpath:
            sys.path.insert(0, options.pythonpath)
    
    
    class ManagementUtility(object):
        """
        Encapsulates the logic of the django-admin.py and manage.py utilities.
    
        A ManagementUtility has a number of commands, which can be manipulated
        by editing the self.commands dictionary.
        """
        def __init__(self, argv=None):
        	# 获取输入参数
            self.argv = argv or sys.argv[:]
            # 执行命令的文件,manage.py或django-admin.py
            self.prog_name = os.path.basename(self.argv[0])
    
        ############################################################################
        # 省略
        ############################################################################
    
        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.py" or "manage.py") if it can't be found.
            """
            try:
            	# 获得django/core/management/commands目录下与INSTALLED_APPS/management/commands目录下的子命令对应的模块前缀
            	# 如"cleanup"对应模块前缀"django.core"
                app_name = get_commands()[subcommand]
            except KeyError:
                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:
            	# load_command_class的核心代码,返回指定子命令模块Command类的实例.
            	# module = import_module('%s.management.commands.%s' % (app_name, name))
        		# return module.Command()
                klass = load_command_class(app_name, subcommand)
            return klass
    
        ############################################################################
        # 省略
        ############################################################################
    
        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.
            主要执行入口.
            通过继承至OptionParser的类LaxOptionParser解析命令行.执行相应的子命令.
            """
            # Preprocess options to extract --settings and --pythonpath.
            # These options could affect the commands that are available, so they
            # must be processed early.
            parser = LaxOptionParser(usage="%prog subcommand [options] [args]",
                                     version=get_version(),
                                     option_list=BaseCommand.option_list)
            self.autocomplete()
            try:
                options, args = parser.parse_args(self.argv)
                # 如果输入了settings与pythonpath参数的话,设置这俩个参数
                handle_default_options(options)
            except:
                pass # Ignore any option errors at this point.
    
            try:
            	# 要执行的子命令 如: "manage.py runserver"中的"runserver"
                subcommand = self.argv[1]
            except IndexError:
            	# 未输入子命令的话,打印"help"信息
                subcommand = 'help' # Display help if no arguments were given.
    
            if subcommand == 'help':
                if len(args) > 2:
                    self.fetch_command(args[2]).print_help(self.prog_name, args[2])
                else:
                    parser.print_lax_help()
                    sys.stderr.write(self.main_help_text() + '\n')
                    sys.exit(1)
            # Special-cases: We want 'django-admin.py --version' and
            # 'django-admin.py --help' to work, for backwards compatibility.
            elif self.argv[1:] == ['--version']:
                # LaxOptionParser already takes care of printing the version.
                pass
            elif self.argv[1:] in (['--help'], ['-h']):
                parser.print_lax_help()
                sys.stderr.write(self.main_help_text() + '\n')
            else:
            	# 找到相应子命令,并执行.
                self.fetch_command(subcommand).run_from_argv(self.argv)

    插播广告: settings.SETTINGS_MODULE

    时常用到django.conf.settings的SETTINGS_MODULE属性来关联到我们自己项目的settings模块.它是如何实现的呢?

    嘿嘿,这个是通过前面提到的DJANGO_SETTINGS_MODULE系统环境变量来实现的.来看这个实现的核心代码.

    ENVIRONMENT_VARIABLE = "DJANGO_SETTINGS_MODULE"
    
    settings_module = os.environ[ENVIRONMENT_VARIABLE]
    
    self.SETTINGS_MODULE = settings_module

    四. 命令

    Django命令需要写在与命令行子命令同名的模块中.模块必须包含一个名为Command的类作为调用入口.

    django.core.management.BaseCommand类作为所有命令的基类.还同时提供了AppCommand LabelCommand和NoArgsCommand三个辅助类.

    自定义命令可继承自BaseCommand或其他三个辅助类,同时重写父类的不同方法即可.

    简单介绍

    1. BaseCommand

    所有命令的基础类,预留一个handle()方法,供子类重写.

    def handle(self, *args, **options):
            """
            The actual logic of the command. Subclasses must implement
            this method.
    
            """
            raise NotImplementedError()

    2. AppCommand

    接受一个或多个installed application的名字作为第一部分参数.实现对App操作.预留一个handle_app()方法供子类重写.

    def handle_app(self, app, **options):
            """
            Perform the command's actions for ``app``, which will be the
            Python module corresponding to an application name given on
            the command line.
    
            """
            raise NotImplementedError()

    3. LabelCommand

    接受一个或多个标签参数,预留一个handle_label()方法供子类重写.

    def handle_label(self, label, **options):
            """
            Perform the command's actions for ``label``, which will be the
            string as given on the command line.
    
            """
            raise NotImplementedError()

    4. NoArgsCommand

    不接受参数.预留一个handle_label()方法供子类重写.

    def handle_noargs(self, **options):
            """
            Perform this command's actions.
    
            """
            raise NotImplementedError()
    posted @ 2011-07-30 00:23  左奕  阅读(9468)  评论(2编辑  收藏  举报