2.4.3 追踪makemigrations命令

makemigrations命令对应的代码如下:

从上面的代码可以看到,在makemigrations命令中,Command类直接继承自BaseCommand类,所以它的执行流程相对比较简单,重点是Command类中的handle()方法。handle()方法的实现如下:

针对handle()方法,笔者整理了7处代码进行讲解:

第1处代码非常简单,得到MigrationLoader对象,在前面已多次使用过。

第2处代码是检测迁移文件之间的一致性。该代码段中的aliases_to_check默认为['default']。这个列表中的元素实际上对应着settings.py文件中DATABASES的key,而该key对应的value值正是以字典形式保存的数据库连接信息。在DATABASES中配置的数据库连接信息会在一开始全部处理后保存在django.db.connections中,需要通过key来获取相应的数据库连接信息,最后通过MigrationLoader对象的check_consistent_history(connection)方法检测迁移文件之间的一致性。该方法会根据connection搜索该数据库迁移表中的数据进行刞断,具体的刞断逻辑可以通过直接阅读方法的源码得到。第一次执行makemigrations命令时,数据库中的迁移表还未生成,没有仸何迁移数据,所以检查直接通过。

第3处代码是检测迁移文件之间的冲突。detect_conflicts()方法的源码如下:

从上面的代码可以看到,当在同一应用下存在多个属于叶子节点的迁移文件时,会被检测为冲突。下面看一个操作示例:

在上面的示例中,给shell_test应用添加了2个迁移文件,并都加到MigrationLoader对象中。由于没有添加依赖,所以在shell_test应用下有2个叶子节点,会被检测为冲突。在给('shell_test','00001_xxxxx)'添加内部应用依赖后,在每个应用下只有1个叶子节点,不会被检测为冲突。下面看本次命令追踪的检测冲突情冴:

从上面的结果可以看出,在Django中,默认应用的迁移文件都不存在冲突。在笔者手工创建的shell_test应用下只有一个迁移文件,也不存在冲突。

第4处代码比较简单,就是当检测到有冲突且设置为不合并(merge=False)时,直接抛出异常。

第5处代码也比较简单,如果有冲突且设置了合并参数,就对这些迁移文件进行合并处理。

第6处代码是检查应用中的模型类与项目已有的迁移文件中保存的模型信息是否一致。这里是通过一个changes检测机制得到相应变化的。该检测机制比较复杂,代码量较大,本书后续不会直接分析该自动检测类,而是观察检测结果,为后面分析迁移文件的生成代码做准备。

第7处代码是根据上一步得到的变化信息进行处理。如果没有变化,提示No changes detected。如果有变化,调用write_migration_files()方法生成一个新的迁移文件。

通过上面的分析可以清楚地看到,迁移文件的生成需要2个条件:得到changes和调用write_migration_files()方法。为了演示changes的输出,这里先初除shell_test应用下的所有迁移数据:

再手工生成MigrationAutodetector对象,并调用该对象的changes()方法获取shell_test应用下的迁移变化数据:

从上面的代码可以看到,当使用makemigrations命令给shell_test应用生成迁移文件时,得到的changes为一个字典形式,每个key代表着对应的应用标签,其value值为一个列表,列表的元素为Migration对象。这些Migration对象将被写入对应的迁移文件中。文件名既可以由外部指定,也可以由Django默认指定。最后看write_migration_files()方法,其源码如下:

write_migration_files()方法通过循环遍历changes来生成迁移文件,而迁移文件的内容正是其中保存的Migration类。其中,MigrationWriter对象的as_string()方法可以将传入的Migration对象转成字符串形式。下面使用as_string()方法输出上面得到的Migration对象:

上面输出的结果正是第一次执行makemigrations命令时得到的迁移文件内容。下面给出一些简单的操作示例,以帮助读者更好地理解MigrationWriter类的功能:

在追踪makemigrations命令的执行过程中,最难的部分在于检测当前模型类与已存在迁移文件中的模型类的变化部分(即得到changes值),而这一工作最终是由MigrationAutodetector对象的changes()方法来承担的。至此,我们已基本掌握了makemigrations命令。