先日の Django Meet up で読んだ Tow Scoops of Django 1.6 に載っていた django-model-utils がいい感じだったので、ちょっと調べてみました。

このエントリーでは、以下の環境で動作確認しています。

  • Python 3.4
  • django-model-utils 2.0.3
  • Django 1.6.2

あんまりDjangoの詳しい使い方は解説はしません。 Djangoのチュートリアル ぐらいは読んでいる前提で書きます。

インストール

インストールは ドキュメント の方法でもいいですが、手っ取り早く使ってみたいなら、予めdjango-model-utilsが組み込まれたテンプレート django-twoscoops-project が便利です。

ただし、django-twoscoops-projectに組み込まれているdjango-model-utilsのバージョンは少し古いので、このままだと最新版の一部機能が使えません。

django-twoscoops-projectを使う場合は、以下の手順でdjango-model-utilsのバージョンを変更してください。

StatusField

まず、こんなmodelを作ります:

    from django.db import models


    class Article(models.Model):
        STATUSES = (
            (0, 'draft'),
            (1, 'published'),
        )
        status = models.IntegerField(choices=STATUSES)

ステータスでデータの状態を表す、よくある設計です。

この設計でArticleモデルを操作するコードを書くと:

    >>> article = Article.objects.create(status=1)

または:

    >>> article = Article.objects.create(status=Article.STATUSES[1][0])

となります。どちらもマジックナンバーになっていて分かりにくいコードですね。

マジックナンバーにならないように書くなら:

    from django.db import models


    class Article(models.Model):
        DRAFT, PUBLISHED = 0, 1
        STATUSES = (
            (DRAFT, 'draft'),
            (PUBLISHED, 'published'),
        )
        status = models.IntegerField(choices=STATUSES)

という手がありますが、ちょっと長ったらしいです。

django-model-utilsの StatusField を使うと、これをスッキリ書けます:

    from django.db import models
    from model_utils.fields import StatusField
    from model_utils import Choices


    class Article(models.Model):
        STATUS = Choices('draft', 'published')
        status = StatusField()

Articleモデルを操作するコードはこう書きます:

    >>> Article.objects.create(status=Article.STATUS.draft)

Monitorfield

さらに、 MonitorField で「ステータスが更新された日時を記録する」フィールドを作れます:

    from django.db import models
    from model_utils.fields import StatusField, MonitorField
    from model_utils import Choices


    class Article(models.Model):
        STATUS = Choices('draft', 'published')
        status = StatusField()
        status_changed = MonitorField(monitor='status')

これだけです。saveメソッドのオーバーライドなんて必要ありません!:

    >>> article = Article.objects.create(status=Article.STATUS.draft)
    >>> article.status_changed
    datetime.datetime(2014, 4, 7, 15, 30, 28, 513992, tzinfo=<UTC>)
    >>> article.status = Article.STATUS.published
    >>> article.save()
    >>> article.status_changed # 日時が記録されている
    datetime.datetime(2014, 4, 7, 15, 31, 54, 869299, tzinfo=<UTC>)

ステータスが特定の値のときのみ日時を記録したい、ということなら:

    from django.db import models
    from model_utils.fields import StatusField, MonitorField
    from model_utils import Choices


    class Article(models.Model):
        STATUS = Choices('draft', 'published')
        status = StatusField()
        status_changed = MonitorField(monitor='status', when=['published'])

これで、ステータスをpublishedにした時のみ日時が記録されます:

    >>> article = Article.objects.create(status=Article.STATUS.draft)
    >>> article.status_changed
    datetime.datetime(2014, 4, 7, 15, 44, 24, 933748, tzinfo=<UTC>)
    >>> article.status = Article.STATUS.draft
    >>> article.save()
    >>> article.status_changed # statusがpublishedではないので、日時が記録されない
    datetime.datetime(2014, 4, 7, 15, 44, 24, 933748, tzinfo=<UTC>)
    >>> article.status = Article.STATUS.published
    >>> article.save()
    >>> article.status_changed # statusがpublishedに変更されたので、日時が記録されている
    datetime.datetime(2014, 4, 7, 15, 46, 20, 313597, tzinfo=<UTC>)

SplitField

最後は SplitField です。

SplitFieldは、ブログ記事のように「先頭の文章を少しだけ表示させて、続きは別のページにリンクを貼って誘導する」といった仕様のアプリケーションを作るときに役立ちます。

まず、このコードを見てください:

    from django.db import models
    from model_utils.fields import SplitField
    from model_utils import Choices


    class Article(models.Model):
        title = models.CharField(max_length=100)
        body = SplitField()

bodyは、マーカー「<!– split –>」を境に先頭の文章と続きの文章に分けることができます1 :

    >>> article = Article.objects.create(title='hello!', body='先頭の文章ここまでがexcerptに出力されます\n\n<!-- split -->\n\nここから先はcontentに出力されます')
    >>> article.body.excerpt
    '先頭の文章ここまでがexcerptに出力されます\n'
    >>> article.body.content
    '先頭の文章ここまでがexcerptに出力されます\n\n<!-- split -->\n\nここから先はcontentに出力されます'

マーカーが含まれない文字列の場合は、最初の2つの段落[^2][^3]
をexcerptと判定します:

    >>> article = Article.objects.create(title='hello!', body='1行目\n\n2行目\n\n3行目\n4行目')
    >>> article.body.excerpt
    '1行目\n\n2行目'
    >>> article.body.content
    '1行目\n\n2行目\n\n3行目\n4行目'

Models編に続く!(かもしれない)


  1. マーカーになる値は設定ファイルのSPLIT_MARKERで変更できます。 ↩︎