前言
django的命令行在整个的django web开发中都会经常用到,而且是必须得用到。所以,能够了解下django的命令行实现其实是非常有帮助的。
如果大家比较关心django命令的详细说明和使用,可以查看这里。
命令行执行入口
django通过django-admin.py和manage.py来执行命令,以下是这两个文件的源码:
1 from django.core import management 2 3 if __name__ == "__main__": 4 management.execute_from_command_line()
它们都调用了management模块下的execute_from_command_line()方法。
这个方法是在django/core/management/__init__.py中定义:
1 def execute_from_command_line(argv=None): 2 """Run a ManagementUtility.""" 3 utility = ManagementUtility(argv) 4 utility.execute()
实现非常简单:生成一个ManagementUtility对象,并让这个对象执行相应的命令行命令。所以主要的工作都是在ManagementUtility这个类中实现的。
ManagementUtility类
python是一门面向的对象的语言,django作为python的一个著名web框架,它所使用当然也是面向对象的思想。所以我们在分析源码的时候应该尽量用面向对象的思想去思考。
ManagementUtility具有3个属性,我们可以从它的__init__函数中看到。
1 def __init__(self, argv=None): 2 self.argv = argv or sys.argv[:] # 从传入的参数获得,如果没有传入参数就从sys.argv中去取 3 self.prog_name = os.path.basename(self.argv[0]) 4 if self.prog_name == '__main__.py': 5 self.prog_name = 'python -m django' 6 self.settings_exception = None
self.argv:命令行信息,包括命令和参数
self.prog_name:程序名
self.settings_excepiton:settings的异常信息,发现settings的设置有异常,会将异常信息存在这个变量里面
ManagementUtility主要的方法是execute(),它完成了command执行的所有过程。
1. 我们知道,django的命令行是具有一定的格式的,都是 command subcommand [arguments],arguments有时是可选的。所以execute方法第一步就是获得subcommand,以便确定后续执行什么任务。
1 try: 2 subcommand = self.argv[1] 3 except IndexError: 4 subcommand = 'help' # Display help if no arguments were given.
这里提一下,为什么不先获取command呢?其实command是系统用来找程序入口的。
2. 用命令解析器CommandParser解析命令行。CommandParser继承了argparse模块的ArgumentParser类,但它只是对ArgumentParser的异常处理进行了加强。
1 class CommandParser(ArgumentParser): 2 """ 3 Customized ArgumentParser class to improve some error messages and prevent 4 SystemExit in several occasions, as SystemExit is unacceptable when a 5 command is called programmatically. 6 """ 7 def __init__(self, cmd, **kwargs): 8 self.cmd = cmd 9 super().__init__(**kwargs) 10 11 def parse_args(self, args=None, namespace=None): 12 # Catch missing argument for a better error message 13 if (hasattr(self.cmd, 'missing_args_message') and 14 not (args or any(not arg.startswith('-') for arg in args))): 15 self.error(self.cmd.missing_args_message) 16 return super().parse_args(args, namespace) 17 18 def error(self, message): 19 if self.cmd._called_from_command_line: 20 super().error(message) 21 else: 22 raise CommandError("Error: %s" % message)
3. 解析器解析出了subcommand的arguments,然后fetch_command根据subcommand导入相应的command包并生成相应的command对象,然后调用command对象的print_help方法或者run_from_argv方法去执行相应的命令。
1 if subcommand == 'help': 2 if '--commands' in args: # only print the commands only 3 sys.stdout.write(self.main_help_text(commands_only=True) + '\n') 4 elif len(options.args) < 1: # print out the usages 5 sys.stdout.write(self.main_help_text() + '\n') 6 else: 7 self.fetch_command(options.args[0]).print_help(self.prog_name, options.args[0]) 8 # Special-cases: We want 'django-admin --version' and 9 # 'django-admin --help' to work, for backwards compatibility. 10 elif subcommand == 'version' or self.argv[1:] == ['--version']: 11 sys.stdout.write(django.get_version() + '\n') 12 elif self.argv[1:] in (['--help'], ['-h']): 13 sys.stdout.write(self.main_help_text() + '\n') 14 else: 15 self.fetch_command(subcommand).run_from_argv(self.argv)
最后看一眼fetch_command的代码:
1 def fetch_command(self, subcommand): 2 """ 3 Try to fetch the given subcommand, printing a message with the 4 appropriate command called from the command line (usually 5 "django-admin" or "manage.py") if it can't be found. 6 """ 7 # Get commands outside of try block to prevent swallowing exceptions 8 commands = get_commands() 9 try: 10 app_name = commands[subcommand] 11 except KeyError: 12 if os.environ.get('DJANGO_SETTINGS_MODULE'): 13 # If `subcommand` is missing due to misconfigured settings, the 14 # following line will retrigger an ImproperlyConfigured exception 15 # (get_commands() swallows the original one) so the user is 16 # informed about it. 17 settings.INSTALLED_APPS 18 else: 19 sys.stderr.write("No Django settings specified.\n") 20 sys.stderr.write( 21 "Unknown command: %r\nType '%s help' for usage.\n" 22 % (subcommand, self.prog_name) 23 ) 24 sys.exit(1) 25 if isinstance(app_name, BaseCommand): 26 # If the command is already loaded, use it directly. 27 klass = app_name 28 else: 29 klass = load_command_class(app_name, subcommand) 30 return klass
这里主要用了load_command_class去导入相应的subcommand模块,并生成了一个command类对象。
1 def load_command_class(app_name, name): 2 """ 3 Given a command name and an application name, return the Command 4 class instance. Allow all errors raised by the import process 5 (ImportError, AttributeError) to propagate. 6 """ 7 module = import_module('%s.management.commands.%s' % (app_name, name)) 8 #print("import %s %s" %(app_name, name)) 9 return module.Command()
我们可以去django/core/management/command/目录下随便找一个command模块看一眼,比如说check.py,每个模块都有一个command类并继承自BaseCommand。上面提到的print_help方法或者run_from_argv方法都是在BaseCommand类中实现。
1 class Command(BaseCommand): 2 help = "Checks the entire Django project for potential problems." 3 4 requires_system_checks = False 5 6 #...
-------------------------------------------------
以上是我的一点粗浅的理解,如果有觉得不对的地方,请多多指教,非常感谢!
2018-03-21 17:57:17