Django源码解析(二) manage.py
Django源码解析(三) Django开发服务器,WSGI规范实现
一. 什么是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()
-
manage.py设置一个名为DJANGO_SETTINGS_MODULE的系统环境变量.
可见setup_environ(settings_mod)方法的主要功能,即manage.py与django-admin.py的主要区别:
例如我的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()