nullpobug勉強会03 "ContentTypesを使おう"

>03
たぶん。

去る土曜に開催されました月イチnullpobug勉強会。今回のテーマはContentTypes。

  1. メンバー

講師:id:nullpobug
生徒:id:feiz

ContentTypesってなんだ

インスタンスとモデル名とを結びつける仕組み。とでも言えばいいんだろうか・・・


Djangoで以下のようにModelを継承してみる。

class Update(models.Model):
    name = models.CharField(max_length=64)
    date = models.DateTimeField()

class Todo(Update):
    task_name = models.CharField(max_length=64)
    target_date = models.DateField()

class Entry(Update):
    title = models.CharField(max_length=64)
    body = models.TextField()

で、Updateモデルからデータを引くと、TodoのインスタンスとEntryのインスタンスが混ざったリストが引けちゃいます。
Update.dateで降順ソートして最新n件を表示するような汎用ビューと、日付と名前と更新内容を表示するテンプレートを書けばサイト更新履歴のようなものがすぱっと実装できます。素晴らしい。

がしかし、テンプレート上でforで回して表示しようとしてみると頭を抱えてしまう。
あくまでUpdateモデルから引いてるので見た目上はこれらはUpdateのインスタンスです。
しかしそれがTodoなのかEntryなのか分からないと何をどう表示すればいいのか分かりません。
このような「このインスタンスはどのモデルのインスタンスなのか」を判別して処理を分ける必要がある時にContentTypesがとても便利です。

中身

ContentTypesのメインはContentTypeモデルです。
ContentTypeモデルの中にはプロジェクトにインストールされている全てのモデルの情報が記録されています。*1
これにリレーションを張る事でインスタンスとモデル名とが結びつけられます。

適用してみる

UpdateモデルにContentTypeのフィールドを持たせます。

from django.contrib.contenttypes.models import ContentType

class Update(models.Model):
    
    title = models.CharField(max_length=64)
    date = models.DateTimeField()
    
    content_type = models.ForeignKey(ContentType)

リレーションが張れました。
ちなみにデフォルトのDjangoプロジェクトだとContentTypesは最初からインストールされてます。もしデフォルトから変更してる場合はインストールしておいて下さい。


あとはSaveをオーバーライドして自動でcontent_typeに値が入るように。

#Updateモデルに追加
    def save(self):
        
        self.content_type = ContentType.objects.get_for_model(self.__class__)
        super(Update, self).save()

get_for_modelはクラスかクラスのインスタンスを引数にしてそれらを表すContentTypeインスタンスを返してくれます。

これでUpdate.objects.all()などとしたときもcontent_typeの値で元の型が判別出来ます。
あとは表示ですが

#Updateに追加
    def display(self):
            obj = getattr(self, self.content_type.name)
            return render_to_string("myapp/_" + self.content_type.name + ".html", {"object": obj})

displayメソッドなんてのを親クラスに持たせて、テンプレートではupdate.displayを表示するようにすればらくちん。
displayの返り値の内容は"_" + "モデル名" + ".html"って名前のテンプレートでそのインスタンスレンダリングした内容。
こうしておけば新しいモデルを追加したいときの作業が

  • Updateを継承してモデルを作る
  • テンプレートフォルダに「_モデル名.html」というテンプレートを追加

の2つだけになるので更にらくちんです。

まとめ

ContentTypesを使うと「いろんな型が混ざったモデル」が簡単に実現できます。
更新履歴以外にも使えるところはいくらでもありますので、是非とも利用してみて下さい。

*1:ContentTypeにデータを登録しているのはsyncdbコマンドだそうです。なのでsyncdbでデータベースを作ってる限りは完全に自動でContentTypeが更新されていきますが、直でSQLを叩いてテーブルを作った場合ではこの限りではありません。