
2.2 startproject命令的实现原理
本节追踪startproject命令的执行过程,看看第1个创建Django项目的命令是如何实现的。创建Django项目的命令如下:

django-admin命令对应的代码如下:

代码转向management目录下__init__.py文件中的execute_from_command_line()方法:

从上面的代码可以看到,execute_from_command_line()方法先实例化ManagementUtility类,再调用ManagementUtility类中的execute()方法。继续追踪ManagementUtility类及其相关方法:



首先在初始化时设置self.argv值,该值保存了命令行传入的相关参数。由于前面调用execute_from_command_line()方法时没有传入参数,所以最终是通过sys.argv[:]得到命令行输入的。当命令行输入为django-admin startproject first_django时,sys.argv[:]的值为['django-admin','startproject','first_django']。在初始化后会调用ManagementUtility类的execute()方法,该方法的执行逻辑如下:
◎ 得到具体执行的subcommand命令,在这里是startproject命令。
◎ 调用parser.parse_known_args()方法,解析命令行选项得到options,同时调用handle_default_options()函数进一步处理选项值。
◎ 自动补全。由于这里没有设置相应的环境变量,所以self.autocomplete()没有发挥作用。
◎ 根据subcommand命令进行相应处理,主要是单独处理help(包含--help|-h选项)和version(包含--version选项)。对于其他subcommand命令,统一调用如下语句:

追踪目标到fetch_command()方法中:

在这段代码中,有前文讲解过的get_commands()函数和load_command_class()函数,因此fetch_command()方法就很容易理解了。下面在Python交互模式下演示上述操作,得到针对startproject命令的结果,即klass值,操作如下:

看到这里,klass的值就很清楚了,它对应命令文件中定义的Command对象。对于本次要追踪的startproject命令来说,就需要到startproject.py文件中追踪其Command类的实现代码:

注意,该Command类继承自TemplateCommand类,且只实现了一个handle()方法。该handle()方法调用了父类的handle()方法,并传递了一些参数。前文的最后一步调用了klass的run_from_argv()方法,但这里并没有,只能继续在父类中继续查找:


在TemplateCommand类中并没有run_from_argv()方法,但TemplateCommand类继承自BaseCommand类,而后者刚好定义了run_from_argv()方法。在2.1节中曾介绍过BaseCommand类,在BaseCommand类中run_from_argv()方法的执行逻辑如图2-1所示。

图2-1
startproject命令中的Command对象调用run_from_argv()方法的运行流程如图2-2所示。

图2-2
从图2-2中可知,startproject命令的核心处理方法是TemplateCommand类中的handler()方法。下面继续追踪handler()方法的实现代码:




上面的代码是startproject命令和startapp命令的核心,笔者已对大部分语句做了相关注释,以帮助读者理解整个函数的执行逻辑。下面简单说明django-admin startproject first_django命令在此处的执行过程:
(1)handle()方法校验项目的名称(first_django)是否合法。它的核心是isidentifier()方法。
(2)handle()方法根据target参数刞断是否创建项目。如果target参数为None,则取当前目录加上要创建的项目名称(这里是first_django)创建项目目录。
(3)extensions为元组,使用其默认值即可,即('.py',)。
(4)通过self.handle_template()函数得到生成Django项目的模板文件所在目录。该函数的实现源码如下:

从上面的代码可以看到,当没有指定template时,会返回path.join(django.__path__[0],'conf',subdir)这个目录。下面先在虚拟环境中看看这个目录的具体路径值:

再次查看该路径下的文件树情冴,直接使用tree命令即可。

接下来查看django-admin startproject first_django命令生成的Django项目的目录结构:

至此,可以想象后续的代码逻辑:先将这里的文件全部遍历出来,然后渲染,得到最终的内容,并把最终的内容写入创建Django项目的相应文件中。
(5)遍历得到该目录下的所有模板文件,进行渲染并写到最终的位置。渲染的关键代码如下:

这两句代码会调用Django中的模板层引擎执行渲染动作。为了更好地理解这两句代码,我们进行如下测试:

至此,关于startproject命令的执行过程就介绍完毕了。整个过程并不复杂,而且逻辑十分清晰。接下来学习在Django中使用manage.py执行的命令,比如常用的python manage.py shell、python manage.py migrate等命令,了解这些命令的运行原理。