深入挖掘Migrations

Python部落组织翻译,禁止转载,欢迎转发


我们回来了! 上次我们了解了使用Django迁移系统的基本方法。 回到上次我们离开的地方,当事情该发生但是没有发生的时候我们该怎么做。好吧,那就是你不得不搜索你以前的migrations来尽量弄明白发生了什么。让我们更深入地了解migrations如何工作来帮你处理这些。


Migrations怎么知道哪些需要迁移


尝试这个,从bitcoin_tracker再次运行迁移(./manage.py migrate),发生了什么?什么也没有。这就是关键点。


默认情况下,Django不会在同一个数据库运行超过一次的一个迁移。这是被一个叫做django_migrations的表控制着,这个表是migrations第一次运行时在你的数据里创建的。对于每一个成功或者失败了的迁移,都有一个新的行插入了表里。


比如,在运行完我们初始的迁移后,这个表可能是这样的:

    ID app         name      applied
    1  historical_payments  0001_initial  2014-04-16 14:12:30.839899+08


不是十分有趣,因为只有一个迁移,但是随后的每个迁移,都会有新的一行被添加。


migrations下次运行时,会跳过在数据表中列出来的迁移文件。这就意味着,即使你手动更改了迁移文件,如果数据库中有它对应的入口,他也会被跳过。
这是有意义的,因为你一般不想运行两次迁移。但是不管因为什么原因,一个让他再次运行的方法是先删除数据库中对应的行(请注意,这不是一个“官方推荐的方式”,但是他会起作用)。至于你第一次运行migrations,Django会首先检查数据库结构,如果他和迁移相同(比如迁移没有运用任何改变),那么迁移将被当做假的,意味着并不真正运行,但是django_migrations表任然会更新。
相反,如果你想“撤销”一个特定的应用程序的所有迁移,你可以迁移到一个特殊的被称为零的迁移。


比如你输入:

    ./manage.py migrate historical_data zero.


除了使用零,您还可以使用任意的迁移,如果这个迁移是过去的,那么数据库会会滚到该迁移的状态,或者如果迁移尚未运行他就会向前滚动。多么强大的东西!


迁移文件


实际创建的迁移文件是什么呢?换句话说,当你运行./manage.py makemigrations 的时候,到底会发生什么?


Django迁移实际上创建了一个描述如哈在数据库中创建相应的表的迁移文件,事实上,你可以查看创建的迁移文件。别担心,他只是Python文件。


 别忘了git添加新的迁移文件夹,所以他是可以版本控制的。


historical_prices现在会有一个叫做/migrations的子文件夹,在这将存放这个应用的所有迁移文件。让我们看一看historical_data/migrations/0001_initial.py,这个文件里是初次迁移创建的代码。它应该看起来像这样:


# encoding: utf8  
from django.db import models, migrations
 
 
class Migration(migrations.Migration):
 
    dependencies = [  
    ]  
 
    operations = [  
        migrations.CreateModel(  
            name='PriceHistory',  
            fields=[  
                ('id', models.AutoField(verbose_name='ID',   
                serialize=False, primary_key=True,
                auto_created=True)),  
                ('date', models.DateTimeField(auto_now_add=True)),  
                ('price', models.DecimalField(decimal_places=2,  
                max_digits=5)),  
                ('volume', models.PositiveIntegerField()),  
                ('total_btc', models.PositiveIntegerField()),  
            ],  
            options={  
            },  
            bases=(models.Model,),  
        ),  
    ]


为了运行一个迁移,你必须创建一个从django.db.migrations.Migration继承的叫做Migration()的类。当你要运行一个迁移时(我们稍后将会做的),迁移框架会查找并执行这个类。


Migration类包含2个主要列表、依赖关系和操作。


迁移依赖关系

    依赖是一个必须在该迁移之前运行的迁移列表

在上面的情况,没有必须提前运行的东西,所以没有依赖关系。但是,比如说如果你有外键相关键,你就要保证在你添加一个外键之前先创键一个模块。所以,让我们假设我们有一个叫做main的应用,它定义了我们想要在外键中引用的一个数据表,那么我们的依赖列表可能是这样的:


dependencies = [
  ('main', '__first__'),
]


上面的依赖说,main应用的迁移必须要首先运行

你也可能对特定的文件有依赖关系,比如:


dependencies=[
  (‘main’,'0001_initial'),
]


这是一个对main应用中的叫做0001_initial的文件的依赖。
依赖也可以组合,这样你就有多个依赖关系。这个功能提供了更多的灵活性,你可以容纳依赖于来自不同应用的模块的外键。这也意味着迁移的编号(通常是0001,0002,0003,...)不用严格地按照他们应用的顺序来排序。你可以添加任何你想要的依赖,这样控制顺序就不必重新编号所有的迁移。
迁移操作

Migration()类中的第二个列表是operations列表。这是一个用于迁移的操作列表,一般来说,该操作可以在下面类型中的:

    CreateModel:正如你所猜测的:这会创建一个新的模块,参考上面的例子
    DeleteModel:删除数据库里的一个表;只是略过这个模块
    RenameModel:给出old_name和new_name,这会重命名模块
    AlterModelTable:更改与模块相关联的表的名称,同db_table
    AlterUniqueTogether:更改特别的约束
    AlteIndexTogether:更改模型的自定义索引

    AddField:就行它听起来那样,这有一个例子:
        python migrations.AddField( model_name='PriceHistory', name='market_cap', field=models.PositiveIntegerField(), ),
    RemoveField:我们不再想要那个字段,,删掉他吧
    RenameField:给出model_name,old_name和new_name,用new_name来替换字段的old_name 这儿也有几个“特别的”运算:
    RunSQL:这允许你通过原始的SQL语句并执行它作为模型的一部分
    RunPython:通过调用可执行的;对于像作为迁移的一部分数据加载这样的事情来说是非常有用的


    你甚至可以写自己的规则。 通常当你运行迁移的时候,Django会创造迁移必要的依赖和操作。当然,了解迁移文件本身,和他们是怎样工作的,会给你的工作带来更多的灵活性。


举例


让我们对我们的模块做更多的改变,来看看对迁移有什么影响:


class PriceHistory(models.Model):
    date = models.DateTimeField(auto_now_add=True)
    #bitcoin to the moon (we need more digits)
    price = models.DecimalField(max_digits=8, decimal_places=2)
    volume = models.PositiveIntegerField()
    total_btc = models.PositiveIntegerField()
    market_cap = models.PositiveIntegerField(null=True)


对比特币的看好,我们决定我们需要一个更大的数量的价格领域,我们也决定保持市场资本化的轨道。请注意我们是如何做的market_cap场空。如果我们没有,迁移将要求提供一个值,为所有现有的行(就像南不一样):


You are trying to add a non-nullable field 'market_cap' to PriceHistory without a default;
we can't do that (the database needs something to populate existing rows).
Please select a fix:
 1) Provide a one-off default now (will be set on all existing rows)
 2) Quit, and let me add a default in models.py


现在再次运行./manage.py makemigrations会产生一个新的迁移文件0002auto.


Remember that you can create a custom name for the migration with ./manage.py makemigrations --name


这个文件看起来是这样的:


class Migration(migrations.Migration):
 
    dependencies = [
        ('historical_data', '0001_initial'),
    ]
 
    operations = [
        migrations.AddField(
            model_name='PriceHistory',
            name='market_cap',
            field=models.PositiveIntegerField(null=True),
            preserve_default=True,
        ),
        migrations.AlterField(
            model_name='PriceHistory',
            name='price',
            field=models.DecimalField(max_digits=8, decimal_places=2),
        ),
    ]


注意‘dependencies’列表,它声明我们在运行这个之前必须运行我们的初始迁移。同时这个迁移有两个操作--AddField,它创建了我们新添加的market_cap,和AlterField,它更新了价格字段的max_digits。


了解这些操作调用迁移框架是非常重要的,它负责各种对setting.py电议的数据库的操作。


迁移支持所有的Django支持的标准数据库,所以如果你插入原始数据你可以手动创建任何你想要的迁移,而不用考虑基础的SQL语句,这些都会自动为你完成。


查看迁移

即使你不需要考虑迁移产生的SQL,如果你好奇,Django会给你覆盖,运行吧:

$ ./manage.py sqlmigration <app-name> <migration-name>


这样将会列出根据setting.py文件的指定的迁移产生的基础SQL,运行几次,你就会开始欣赏迁移的力量。


结论


我们到了另一结尾,但是会有更多的开始。在下一篇文章中我们会讨论数据迁移。Cheers!


英文原文:https://realpython.com/blog/python/digging-deeper-into-migrations/

译者:cmsl


 

2月15日11:00到13:00网站停机维护,13:00前恢复