Django1.0移行ガイド

Django1.0への移行で必要なことを、ここいらで書いておく。
詳細はオフィシャルのドキュメントなりソースなりを読んでくだされ。
間違っているところは指摘してくれるとありがたい。

注意

  • 0.97preからの移行を前提とします。(newforms-adminがマージされる前)
  • この記事を書いている時点では、Djangoのリビジョンは8356(1.0beta1)です。
  • 初歩的なことですが、スペルミスに注意。

newforms-admin

newforms-adminブランチがマージされ、管理サイトが新しくなりました。
AdminSiteクラス(もしくはこれを継承したクラス)を使って管理サイトを表現します。
これに伴い、モデルなどに変更が必要です。

1. admin.pyの作成

AdminSiteはmodels.pyに書いてもいいのですが、ここではadmin.pyに書くことにします。
モデル内のAdminクラスをごっそりadmin.pyへ移動します。

before(models.pyの一部)
class MyTag(models.Model):
    name = models.CharField(blank=True, max_length=100)

    class Meta:
        verbose_name = 'MyTag'
        verbose_name_plural = 'MyTag'

    class Admin:
        pass

    def __unicode__(self):
        return self.name

class MyEntry(models.Model):
    title = models.CharField(blank=True, max_length=100)
    content = models.TextField(blank=True)
    pub_date = models.DateTimeField(blank=True, default=datetime.datetime.now)
    tags = models.ManyToManyField(MyTag, blank=True)

    class Meta:
        verbose_name = 'Entry'
        verbose_name_plural = 'Entries'
        ordering = ['-pub_date']

    class Admin:
        list_display = ['title', 'pub_date']
        search_fields = ['title', 'content']
        js = ['/static/js/customize.js']

    def __unicode__(self):
        return self.title
after(models.pyの一部)

Adminは削除。

class MyTag(models.Model):
    name = models.CharField(blank=True, max_length=100)

    class Meta:
        verbose_name = 'MyTag'
        verbose_name_plural = 'MyTag'

    def __unicode__(self):
        return self.name

class MyEntry(models.Model):
    title = models.CharField(blank=True, max_length=100)
    content = models.TextField(blank=True)
    pub_date = models.DateTimeField(blank=True, default=datetime.datetime.now)
    tags = models.ManyToManyField(MyTag, blank=True)

    class Meta:
        verbose_name = 'Entry'
        verbose_name_plural = 'Entries'
        ordering = ['-pub_date']

    def __unicode__(self):
        return self.title
after(admin.py)

アプリケーションのディレクトリにadmin.pyを新たに作成します。
admin.siteはAdminSiteのインスタンスです。
今までモデル内に書いていたAdminクラスは、ModelAdminを継承したクラスとして定義します。
jsはMediaクラスへ書く必要があるので注意してください。
admin.site.registerでAdminSiteへモデルを登録します。
registerの2つ目の引数にAdminクラスを指定します。省略可能です。
リレーションは自動的に判断されます(確認はしていないけどModelAdminの機能だと思います)。

from django.contrib import admin
from models import MyTag, MyEntry

class MyEntryAdmin(admin.ModelAdmin):
    list_display = ['title', 'pub_date']
    search_fields = ['title', 'content']

    class Media:
        js = ['/static/js/customize.js']

admin.site.register(MyTag)
admin.site.register(MyEntry, MyEntryAdmin)
2. urlsの変更

AdminSiteをurlsに設定します。

before(urls.py)
from django.conf.urls.defaults import *
urlpatterns = patterns('',
    (r'^admin/', include('django.contrib.admin.urls'),
)
after(urls.py)

autodiscoverを実行するとAdminがロードされます。

from django.conf.urls.defaults import *
from django.contrib import admin
admin.autodiscover()

urlpatterns = patterns('',
    (r'^admin/(.*)', admin.site.root),
)

formsパッケージ

newformsパッケージが廃止され、formsとoldformsのみになりました。
以前の暫定的に使っていたimport文を書き換える必要があります。

before
from django import newforms as forms
after
from django import forms

ImageField/FileField

ImageField/FileFieldの指すものが今まではファイルパスでしたが、
新たにFileクラスが増えて、このクラス(もしくは継承したクラス)のインスタンスを指すようになりました。
また、これに伴ってモデルのget_FOO_urlメソッドが廃止されました。urlプロパティを利用します。

models.py(変更なし)

モデルが以下のような場合。

class MyModel(models.Model):
    myphoto = models.ImageField(upload_to="img/uploads")
before
myinstance = MyModel.objects.all()[0]
photo_url = myinstance.get_myphoto_url()
after
myinstance = MyModel.objects.all()[0]
photo_url = myinstance.myphoto.url

UploadedFile

ファイルのアップロードでもImageField/FileFieldと同様に、Fileクラスを継承したUploadedFileが使われるようになりました。
filenameが廃止され、nameになっています。またchunksメソッドを使うことで大きなファイルへの対応もできるようになりました。

forms.pyの一部(変更なし)

フォームが以下のような場合。

class MyUploadForm(forms.Form):
    target_file = forms.FileField(widget=forms.FileInput)
before(views.pyの一部)
form = MyUploadForm(request.POST, request.FILES)
if form.is_valid():
    target_file = form.cleaned_data['target_file']
    f = open(target_file.filename, 'wb')
    f.write(target_file.content)
    f.close()
after(views.pyの一部)
form = MyUploadForm(request.POST, request.FILES)
if form.is_valid():
    target_file = form.cleaned_data['target_file']
    f = open(target_file.name, 'wb')
    for chunk in target_file.chunks():
        f.write(chunk)
    f.close()

Signal

新たにSignalクラスが追加され、シグナル(pre_saveやpost_saveなど)はこのクラスのインスタンスになりました。

before(models.pyの一部)
from django.db.models.signals import pre_save
from django.dispatch import dispatcher

def update(signal, sender, instance, **kwds):
    instance.pub_date = datetime.datetime.now()

dispatcher.connect(update, pre_save, MyEntry)
after(models.pyの一部)
from django.db.models.signals import pre_save

def update(signal, sender, instance, **kwds):
    instance.pub_date = datetime.datetime.now()

pre_save.connect(receiver=update, sender=MyEntry)

あとがきというか

ものによっては、ここに書いたほかにもまだ修正すべき点はあります。
結構な作業量になるかもしれないけれど、今のうちに移行を進めておくと1.0stableが出てすぐに運用を開始できるかも。
1.0までにまだ後方互換性のない変更が入る可能性が高い。
テストを書いておくと、こういう移行のときにかなり楽できる。
追記するかも。

追記

django.contrib.comments

コメントフレームワークはoldformsからnewforms(forms)に変更されました。これに伴い、テンプレートなどを書き換える必要があります。