2007年11月22日

Django: 初始化数据及安装时代码

作者 非鱼

原文地址:http://www.b-list.org/weblog/2007/nov/21/install-time/

PS:The B-List是个很牛的Django博客,大量的技巧和教程,英语好的一定要去订阅,怕看英语的,就等我的翻译吧。

经常被问到的一个问题是:我怎么样为我的app提供初始化数据?或者一个相似的问题:我怎么样保证我的程序在通过syncdb安装的同时运行某些代码。Django提供了多种途径实现这个功能,你可以根据具体的需求选择不同的实现方式。虽然这些功能在文档里都已经说明了,但是还是会遇到很多问题,所以我们今天仔细看看各种不同的方式之间的区别,了解哪一种更适合你。

提供SQL语句初始化数据

这是最古老的方式(从Django一开始就有这个功能)也是最简单的方式(不管是你需要做的部分,还是它所支持的功能)来为你的Application提供一些初始化数据:你只需要提供一个SQL文件,包含标准的Insert语句,Django会在创建了数据表以后执行你的SQL文件。

要使用该方式,你需要在你的app目录下增加一个"sql"目录,对应你的每个model所需要的初始化数据,提供一个"model名.sql"的文件,model名是跟你定义的model相同的名字(这里使用小写,也就是get_model()方法返回的名字或者在model的meta信息中定义的model名字)。比如,我有一个名为blog的application,其中包含一个Entry的model,那我就可以在blog里面的sql目录下放一个entry.sql文件,里面写insert语句就可以了。

在这个文件里你也可以放其它的SQL语句,不仅仅是INSERT,但是需要提醒你,同一个app的多个SQL初始化文件,其执行顺序是不一定的,而且也不是所有的SQL语法都支持。为了处理不同的数据库类型的SQL兼容性,Django会对该文件进行处理,而不是直接去执行它。

你可以使用manage.py的sqlcustom命令查看一个app是否提供了初始化SQL。

使用fixtures

Fixtures是一种新的提供初始化数据的方法,并且被Django的测试框架用来处理单元测试的测试数据。不同于SQL文件的是,使用fixture你可以提供一个被Django的serialization系统所能识别的序列化文件,它会被读取并自动转换成对应的model,然后保存进你的数据库。

你需要创建一个fixture文件(使用manage.py的dumpdata命令更简单),确保文件名为"initial_data",后缀名可以是json, xml, yaml, python其一。

把这个文件放到你的app目录下的fixtures目录里,它就会在执行syncdb的时候创建完你的数据表后自动读取并插入数据。

如果你有更多fixtures文件或者你没有在执行syncdb的时候提供fixture的话,你也可以使用manage.py的loaddata命令手工加载fixture。

使用post_syncdb信号

第三种,最复杂最强大的方法,是使用Django内部分发器提供的post_syncdb信号。你可能还不了解这个东西,分发器是一种方法,Django的许多程序,包括你自己的程序,可以用它来通知部分某个事件,只需要发送一个信号。post_syncdb是个Django内建的信号,当一个app的数据表被使用syncdb创建时被发送。

要使用这种方法,你需要在你的app目录下创建一个名为management.py的文件,添加下面的代码:
from django.dispatch import dispatcher
from django.db.models.signals import post_syncdb
from myapp import models as myapp
用你自己的app的名字替换最后一行的内容。比如我有一个app名为blog,那么这一行应该是这样:
from blog import models as blog_app

然后定义一个普通的Python函数,里面是你希望在安装时被执行的代码。一旦该函数被注册到分发器上(一会就能看到了),它就会在syncdb创建完数据库以后立刻被执行,所以这个函数可以实现你想要的任何功能,包括修改数据表,添加额外功能,或者使用Django的model API插入数据。

最后,把你的函数注册到分发器,使它监听post_syncdb信号。dispatcher.connect()方法包括三个参数:
1、你的函数名,必须是一个函数,而不是一个字符串
2、信号的名字,这里是post_syncdb
3、发送者,这里应该是你前面导入的那个app

继续blog的例子,你可以定义一个函数setup_blog(),并像这样来注册它:
dispatcher.connect(setup_blog, signal=post_syncdb, sender=blog_app)

执行syncdb时,Django会在你的app目录寻找management.py文件并导入,此时dispatcher.connect被执行并注册你的函数,当你的表被创建后,post_syncdb信号会发给你的application,分发器会确保你的函数会被调用。

Django的认证应用使用了这种方法来自动创建超级管理员,sites应用使用这种方法创建默认的站点。

如果你希望你的程序在任何其它的app被安装时做些什么,你可以忽略发送者参数,那么每次syncdb被调用安装了一个程序的时候你的代码都会被执行。如果你打算这么做,那么你需要确保你的函数接收另外的几个可选参数(分发器会正确的传递它们):
app参数,可以传递给get_models()以便取得刚刚被安装的app里面的model
created_models,是一个list,包含了刚刚实际被创建了表的model

有几个Django内置的应用使用了这个技巧:
认证应用在每个model被安装时为它们创建Permission对象
contenttypes框架在每个model被安装时为它们创建ContentType对象

随机应变

通常,这几种技术在不同的场合可以工作的很好,所以,挑选一个最适合你的:

  • 如果你需要提供一些初始化数据,还有一些额外的SQL(比如创建触发器),并且你知道你的app会被安装在哪种数据库上,那就使用SQL文件。如果需要插入的数据量很大,那么这也是个好方法,因为避免了不停的创建model对象的开销。
  • Fixtures适用于少量的初始化数据,因为它使用Django的序列化功能,所以不依赖于特定的数据库。它执行起来没有SQL快,因为要创建对象。另外,这个功能可以在你切换数据库平台的时候使用,比如我要把系统从Mysql切换到PostgreSQL,就可以使用fixtures来导入转出数据。
  • post_syncdb方式给你提供了最大的自由来运行任何的Python代码,但是写起来有点烦。所以适用于只用来创建单个的对象,比如超级管理员。或者你需要在每个应用被安装后做点什么。