如何在 Django 中使用 django-south, 实现数据迁移 (data migrations)

在我们的博客中, 记录了我们在开发过程中所使用的技术和遇到的问题, 希望作为其他开发和设计者的一个学习交流平台.

如何在 Django 中使用 django-south, 实现数据迁移 (data migrations)


在本文中, 将会介绍Django-south的基本概念和基本用法, 帮助Django开发人员简化数据迁移的过程. 在django-admin命令中有syncdb指令, 其目的是根据model.py创建相应的数据库表. 但我们在开发的过程中, 经常会需要更改model, 删除或者增加Field, 这时, syncsb命令就不那么好用了, 因为syncsb无法自动更改数据库表结构. 因此, 我们时常需要手动删除数据库表, 再运行syncdb.

而有了south后, 情况就不同了. south为django带来了数据迁移功能, 它的主要目的是为django应用程序带来一个简单, 稳定, 与数据库管理系统无关的迁移层, 用来自动处理django中的数据表变化. 使用south提提供的工具, 你可以在不同版本的数据库结构中来回迁移,

south刚出来时只是一个相对无名的数据库迁移应用. 但渐渐地它成为了django中最为流行和普遍使用的数据迁移工具. 并且在django 1.7发布时, 数据迁移功能将会被整合到django中, 其提供的django-admin migrate也将代替syncdb命令. 因此, 我们不得不了解一下south的魅力所在.

1. 安装South

安装south的方式有许多中, 包括从pip安装, 直接使用south的Mercurial库安装, 或从操作系统自带的软件库中安装. 推荐使用pip, 将south安装到VirtualEnv中:

pip install South

然后在django的'south'添加到setting.py的INSTALLED_APPS中, 并运行syncdb, 创建south所需要的数据表:

./manage.py syncdb

2. 在新建的App中使用South

a. 前期准备

首先我们介绍在新app中使用south. 需要注意的是, south的迁移记录文件是储存在每个app下的migrations目录中的, 如果该app没有创建任何migrations, 那么该app还是通过syncdb来管理的.

创建新app, 并将其添加到INSTALLED_APPS中:

./manage.py startapp southtut

打开该app的model.py, 创建model:

from django.db import models

class Knight(models.Model):
    name = models.CharField(max_length=100)
    of_the_round_table = models.BooleanField()

b. 第一次迁移

south提供了多种建立迁移记录的方式, 有些是自动的, 有些是手动的. 而用的最多的可能就是--auto和--initial这两种自动创建的方式了.

--auto是根据之前的迁移记录, 与当前model作比较, 然后自动创建新的迁移记录. 例如当添加了一个新field时, --auto会注意到, 并自动生成添加了新栏的迁移记录.

你会注意到, --auto需要上一次的迁移记录才能创建新的迁移记录. 而--initial则可以用来为model中所有的表创建初始的迁移记录. 因此, 先使用--initial初始化迁移记录, 然后在model有所变动后, 使用--auto自动生成迁移记录

使用--initial为我们新建的app southtut创建初始迁移记录:

$ ./manage.py schemamigration southtut --initial
Creating migrations directory at '/home/andrew/Programs/litret/southtut/migrations'...
Creating __init__.py in '/home/andrew/Programs/litret/southtut/migrations'...
 + Added model southtut.Knight
Created 0001_initial.py. You can now apply this migration with: ./manage.py migrate southtut

c. 之后的迁移

此时, 在southtut的migrations下就自动生成reated 0001_initial.py文件, 这就是第一个迁移记录文件. 注意, 此时打开MySQL或PostreSQL, 你会发现没有名为southtut_knight的数据表, 因为数据库中的数据表并没有变化(未创建也未修改), 我们需要用到migrate命令来完成这一步:

$ ./manage.py migrate southtut
Running migrations for southtut:
- Migrating forwards to 0001_initial.
> southtut:0001_initial
- Loading initial data for southtut.

此时再打开MySQL或PostgreSQL, southtut_knight数据表已经创建好了. 但到现在为止, 这些功能都是sync能实现的. 接下来我们修改Knight model, 增加一个field:

from django.db import models

class Knight(models.Model):
    name = models.CharField(max_length=100)
    of_the_round_table = models.BooleanField()
    dances_whenever_able = models.BooleanField()

此时如果使用syncsb, 则django无法为你增加dances_whenever_able列, 因此, 我们使用south:

$ ./manage.py schemamigration southtut --auto
+ Added field dances_whenever_able on southtut.Knight
Created 0002_auto__add_field_knight_dances_whenever_able.py. You can now apply this migration with: ./manage.py migrate southtut

从输出的提示可以看到, south已经为你增加了新的迁移记录文件(0002_auto__add_field_knight_dances_whenever_able.py), south的命名方式是序列号+所做的更改. 接下来, 我们根据提示, 使用migrate命令修改数据库:

$ ./manage.py migrate southtut
Running migrations for southtut:
- Migrating forwards to 0002_auto__add_field_knight_dances_whenever_able.
> southtut:0002_auto__add_field_knight_dances_whenever_able
- Loading initial data for southtut.

3. 创建新的没有默认值的field

当修改model时, 创建没有默认值(default)的field后:

from django.db import models

class Knight(models.Model):
    name = models.CharField(max_length=100)
    of_the_round_table = models.BooleanField()
    dances_whenever_able = models.BooleanField()
    shrubberies = models.IntegerField(null=False)

此时再执行schemamigration, 会出现一些没有见过的选择:

./manage.py schemamigration southtut --auto
 ? The field 'Knight.shrubberies' does not have a default specified, yet is NOT NULL.
 ? Since you are adding or removing this field, you MUST specify a default
 ? value to use for existing rows. Would you like to:
 ?  1. Quit now, and add a default to the field in models.py
 ?  2. Specify a one-off value to use for existing columns now
 ? Please select a choice:

其中选项1意思是, 放弃本次自动迁移, 并退出, 你可以在field中添加default值后再执行schemamigration. 选项2的意思是, 为已经存在的行添加一个一次性的值. 当你选择2时, 会出现python提示行, 你可以使用python的datetime模块:

 ? Please select a choice: 2
 ? Please enter Python code for your one-off default value.
 ? The datetime module is available, so you can do e.g. datetime.date.today()
 >>> 0
 + Added field shrubberies on southtut.Knight
Created 0003_auto__add_field_knight_shrubberies.py. You can now apply this migration with: ./manage.py migrate southtut

此时你可以查看自动生成的迁移记录文件, south会为新建的栏添加默认值0, 这样数据库才不会报错. 然后我们再执行migrate:

 $ ./manage.py migrate southtut
 Running migrations for southtut:
  - Migrating forwards to 0003_auto__add_field_knight_shrubberies.
  > southtut:0003_auto__add_field_knight_shrubberies
  - Loading initial data for southtut.mou

4. --fake的使用

在团队开发时, 可能会遇到这样的问题: 你的代码已经和migrate之后保持一样. 那么这时你无法再使用python manage.py migrate, south会提示 无法合并, 此时我们就需要用到:

    python manage.py migrate --fake 0003

原文链接: http://www.weiguda.com/blog/2/