Django v1.0 documentation

contenttypes フレームワーク

revision-up-to:8961 (1.0)

Django には、 contenttypes アプリケーションが付属しています。 このアプリケーションを使うと、 Django で作成したプロジェクト中に存在する全 てのモデルを追跡できます。また、 contenttypes では、モデルを扱うための 高水準かつ汎用的なインタフェースを提供しています。

概要

contenttypes アプリケーションの心臓部は、 django.contrib.contenttypes.models で定義されている ContentType モデルです。 django.contrib.contenttypes.models.ContentType のインスタンスは、 プロジェクト上にインストールされているモデルの情報を表現したり保存したりし ています。 ContentType のイン スタンスは、新たにモデルをインストールすると自動的に生成されます。

ContentType のインスタンスは、 インスタンスの表現するモデルクラスを返したり、そのモデルクラスに対してオブ ジェクトをクエリするためのメソッドを提供しています。また、 ContentType モデルは ContentType を扱ったり、特定 のモデルの ContentType を取り 出すための カスタムマネジャ を持っています。

あるモデルから ContentType に リレーションを張れば、そのモデルからインストール済みの任意のモデルのインス タンスとの間に、「一般化リレーション(generic relation)」を張れます。

contenttypes フレームワークのインストール

contenttypes フレームワークは、 django-admin.py startproject の生成するデフォルトの settings.pyINSTALLED_APPS リスト に入っています。リストから除外してしまっている場合や、 INSTALLED_APPS を自分で設定した場合には、 INSTALLED_APPS'django.contrib.contenttypes' を追加してフ レームワークをインストールしてください。

通常は、 contenttypes フレームワークをインストールしておいた方がよいで しょう。 Django にバンドルされている以下のアプリケーションには、 contenttypes フレームワークが必要だからです:

  • admin アプリケーションは、管理インタフェース上で追加変更したオブ ジェクトの履歴を管理するために contenttypes を使います。
  • Django の 認証フレームワーク は、ユーザ パーミッションをモデルに結びつけるために contenttypes を使います。
  • Django のコメントシステム (django.contrib.comments) は、インス トールされている任意のモデルに対してコメントを付けられる機能を contenttypes で実現しています。

ContentType モデル

class models.ContentType

ContentType のインスタン スには 3 つのフィールドがあり、 3 つを組み合わせると、インストールされ ているモデルを一意に表現できます:

app_label
モデルの入っているアプリケーションの名前です。この名前はモデルの app_label 属性から取り出され、通常は Python の import パス の 末尾 の部分が入っています。例えば、アプリケーションが django.contrib.contenttypes なら、 app_labelcontenttypes です。
model
モデルクラスの名前です。
name
人間可読なモデル名です。この値は、モデルの verbose_name から取り出されます。

例を挙げて、 contenttypes の仕組みを見てみましょう。 contenttypes アプリケーションをインストールしておき、 INSTALLED_APPS 設定に sites アプリケーション を追加してから、 manage.py syncdb を実行してみてください。 django.contrib.sites.models.Site モデルがデータベースにインストールされ るはずです。それに伴って、以下の値がセットされた ContentType の新たなインスタ ンスが生成されているはずです:

  • app_label'sites' (django.contrib.sites の末尾) に設定 されています。
  • model'site' に設定されています。
  • name'site' に設定されています。

ContentType インスタンスのメソッド

class models.ContentType
ContentType インスタンス には、インスタンスの表現しているモデルクラスを取得したり、モデルクラス のインスタンスを取り出したりするためのメソッドがあります:
models.ContentType.get_object_for_this_type(**kwargs)
ContentType インスタンス の表現しているモデルでサポートされている 照合メソッドの引数 <field-lookups-intro> を取り、モデルインスタンスの get() 照合 <get-kwargs> を行って、対応するオブジェクトを返します。
models.ContentType.model_class()
ContentType インスタンス の表現しているモデルクラスを返します。

例を挙げましょう。まず、 User モデル の ContentType インスタンスを 照合します:

>>> from django.contrib.contenttypes.models import ContentType
>>> user_type = ContentType.objects.get(app_label="auth", model="user")
>>> user_type
<ContentType: user>

取り出した UserContentType から、 User モデルクラスを取り出したり、 User インスタンスを照合したりできます:

>>> user_type.model_class()
<class 'django.contrib.auth.models.User'>
>>> user_type.get_object_for_this_type(username='Guido')
<User: Guido>

get_object_for_this_type()model_class() を組 み合わせると、非常に重要なユースケースを実現できます:

  1. 二つのメソッドを使えば、特定のモデルクラスを import して使うのではな く、インストールされている全てのモデルに対して操作を行えるような一般 化された高水準のコードを書けます。実行時に app_labelmodel を指定して ContentType を照合し、 得られたモデルクラスからオブジェクトを取り出せるのです。
  2. 別のモデルから ContentType にリレーショ ンを張って、モデルのインスタンスと、 ContentType の表すモデ ルクラスを結び付け、モデルクラスのメソッドにアクセスできます。

Django にバンドルされているアプリケーションには、後者のテクニックを使ってい るものがいくつかあります。例えば、 Django の認証フレームワークに入っている パーミッションのシステム では、 Permission モデルの中で、 ContentType への外部キー を張っています。そうすることで、 Permission は「ブログにエントリを追加」 や「ニュースストーリーを削除」といった権限を表現できます。

ContentTypeManager カスタムマネジャ

class models.ContentTypeManager

ContentType には ContentTypeManager` という カスタムマネジャがあります。このカスタムマネジャには、以下のメソッドが あります:

clear_cache()
ロード済みの ContentType インスタ ンスを保持しておくための内部キャッシュをクリアします。このメソッド を自分で呼ぶことはほとんどないでしょう。 Django は必要に応じて自動 的にメソッドを呼び出します。
get_for_model(model)
モデルクラスやモデルのインスタンスを引数にとり、そのモデルを表す ContentType インスタ ンスを返します。

ContentType を扱いたいけれど も、わざわざモデルのメタデータを取り出して ContentType を手動で照合する のが面倒な場合には get_for_model() メソッ ドが特に便利です:

>>> from django.contrib.auth.models import User
>>> user_type = ContentType.objects.get_for_model(User)
>>> user_type
<ContentType: user>

一般化リレーション

上で述べた Permission モデルの例のよう に、自作のモデルから ContentType に外部キーを張れ ば、モデルを別のモデルクラスに結び付けられます。しかし、もう一歩踏み込めば、 ContentType を使って真の一般 化リレーション (または多態性: polymorphic リレーション) を実現できます。

簡単な例として、以下のようなタグシステムを挙げましょう:

from django.db import models
from django.contrib.contenttypes.models import ContentType
from django.contrib.contenttypes import generic

class TaggedItem(models.Model):
    tag = models.SlugField()
    content_type = models.ForeignKey(ContentType)
    object_id = models.PositiveIntegerField()
    content_object = generic.GenericForeignKey('content_type', 'object_id')

    def __unicode__(self):
        return self.tag

通常の ForeignKey は、他のモデル を「指し示す」だけに過ぎません。従って、 TaggedItem モデルで ForeignKey を使う場合、ただ一つの モデルに対してしかタグを保存できません。この問題を解決し、任意のモデルにリ レーションを張れるようにするために、 contenttypes アプリケーションでは、 特殊なフィールドタイプ、 django.contrib.contenttypes.generic.GenericForeignKey を提供してい ます。 GenericForeignKey のセッ トアップは、以下の 3 つのパートに分かれています:

  1. ContentType への ForeignKey を定義します。

  2. リレーション先のモデルインスタンスの主キー値を保存するためのモデル フィールドを定義します (ほとんどのモデルでは、 IntegerField または PositiveIntegerField です)。

    このフィールドは、 GeneicRelation の対象モデルの主キーと同じ型でな ければなりません。例えば、モデルフィールドに IntegerField を使っ ているなら、対象モデルの主キーに CharField を使うことはできません。

  3. GenericForeignKey を定 義します。 GenericForeignKey の引 数に、上で定義したフィールドの名前を指定します。フィールド名が GenericForeignKey の探 すデフォルトの名前である content_typeobject_id の場合に は、引数を省略してかまいません。

これで、通常の ForeignKey に似た API を実現できます。 TaggedItem はリレーションを張っている対象のモデル インスタンスを返す content_object フィールドを持つようになります。 content_object の値は、 TaggedItem を生成するときに指定できます:

>>> from django.contrib.auth.models import User
>>> guido = User.objects.get(username='Guido')
>>> t = TaggedItem(content_object=guido, tag='bdfl')
>>> t.save()
>>> t.content_object
<User: Guido>

逆方向の一般化リレーション

リレーション対象にどのモデルがよく使われるのかが分かっていれば、 「逆方向の」一般化リレーションを張って、 API を追加できます。例を挙げましょ う:

class Bookmark(models.Model):
    url = models.URLField()
    tags = generic.GenericRelation(TaggedItem)

これで、 Bookmark インスタンスは tags 属性をそなえます。この属性を 使うと、インスタンスにリレーションを張っている TaggedItems を取り出せま す:

>>> b = Bookmark(url='http://www.djangoproject.com/')
>>> b.save()
>>> t1 = TaggedItem(content_object=b, tag='django')
>>> t1.save()
>>> t2 = TaggedItem(content_object=b, tag='python')
>>> t2.save()
>>> b.tags.all()
[<TaggedItem: django>, <TaggedItem: python>]

逆方向のリレーションを張らなければ、手動で照合する必要があります:

>>> b = Bookmark.objects.get(url='http://www.djangoproject.com/)>>> 
bookmark_type = ContentType.objects.get_for_model(b)
>>> TaggedItem.objects.filter(content_type__pk=bookmark_type.id,
...                           object_id=b.id)
[<TaggedItem: django>, <TaggedItem: python>]

GenericRelation` を持つオブジェ クトを削除すると、このオブジェクトに GenericForeignKey でリレーショ ンを張っているオブジェクトも全て削除されるので注意してください。つまり、上 の例では、 Bookmark オブジェクト b を削除すると、 b にリレーショ ンを張っている t1t2 といった TaggedItem も同時に削除される のです。

フォームや admin で一般化リレーションを扱う

django.contrib.contenttypes.generic では、二つのクラス、 GenericInlineFormSetGenericInlineModelAdmin を提供しています。これらのクラスを使うと、フォームや admin で一般化リレーショ ンを扱えます。詳しくは モデルフォームセットadmin のドキュメントを参照してください。

class generic.GenericInlineModelAdmin

GenericInlineModelAdmin クラスは、 InlineModelAdmin の全てのプロパティを継承していますが、一般化リレーションを扱うために、 以下の二つの属性を備えています:

ct_field
モデル内の ContentType の外部キーフィールド名です。デフォルトの値は content_type です。
ct_fk_field
リレーション先オブジェクトの ID を表す整数フィールドの名前です。 デフォルトの値は object_id です。