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 フレームワークは、 django-admin.py startproject の生成するデフォルトの settings.py の INSTALLED_APPS リスト に入っています。リストから除外してしまっている場合や、 INSTALLED_APPS を自分で設定した場合には、 INSTALLED_APPS に 'django.contrib.contenttypes' を追加してフ レームワークをインストールしてください。
通常は、 contenttypes フレームワークをインストールしておいた方がよいで しょう。 Django にバンドルされている以下のアプリケーションには、 contenttypes フレームワークが必要だからです:
ContentType のインスタン スには 3 つのフィールドがあり、 3 つを組み合わせると、インストールされ ているモデルを一意に表現できます:
例を挙げて、 contenttypes の仕組みを見てみましょう。 contenttypes アプリケーションをインストールしておき、 INSTALLED_APPS 設定に sites アプリケーション を追加してから、 manage.py syncdb を実行してみてください。 django.contrib.sites.models.Site モデルがデータベースにインストールされ るはずです。それに伴って、以下の値がセットされた ContentType の新たなインスタ ンスが生成されているはずです:
例を挙げましょう。まず、 User モデル の ContentType インスタンスを 照合します:
>>> from django.contrib.contenttypes.models import ContentType
>>> user_type = ContentType.objects.get(app_label="auth", model="user")
>>> user_type
<ContentType: user>
取り出した User の ContentType から、 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() を組 み合わせると、非常に重要なユースケースを実現できます:
Django にバンドルされているアプリケーションには、後者のテクニックを使ってい るものがいくつかあります。例えば、 Django の認証フレームワークに入っている パーミッションのシステム では、 Permission モデルの中で、 ContentType への外部キー を張っています。そうすることで、 Permission は「ブログにエントリを追加」 や「ニュースストーリーを削除」といった権限を表現できます。
ContentType には ContentTypeManager` という カスタムマネジャがあります。このカスタムマネジャには、以下のメソッドが あります:
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 つのパートに分かれています:
ContentType への ForeignKey を定義します。
リレーション先のモデルインスタンスの主キー値を保存するためのモデル フィールドを定義します (ほとんどのモデルでは、 IntegerField または PositiveIntegerField です)。
このフィールドは、 GeneicRelation の対象モデルの主キーと同じ型でな ければなりません。例えば、モデルフィールドに IntegerField を使っ ているなら、対象モデルの主キーに CharField を使うことはできません。
GenericForeignKey を定 義します。 GenericForeignKey の引 数に、上で定義したフィールドの名前を指定します。フィールド名が GenericForeignKey の探 すデフォルトの名前である content_type や object_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 にリレーショ ンを張っている t1 や t2 といった TaggedItem も同時に削除される のです。
django.contrib.contenttypes.generic では、二つのクラス、 GenericInlineFormSet と GenericInlineModelAdmin を提供しています。これらのクラスを使うと、フォームや admin で一般化リレーショ ンを扱えます。詳しくは モデルフォームセット や admin のドキュメントを参照してください。
GenericInlineModelAdmin クラスは、 InlineModelAdmin の全てのプロパティを継承していますが、一般化リレーションを扱うために、 以下の二つの属性を備えています:
Aug 31, 2012