revision-up-to: | 11321 (1.1) unfinished |
---|
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.py
の INSTALLED_APPS
リスト
に入っています。リストから除外してしまっている場合や、
INSTALLED_APPS
を自分で設定した場合には、
INSTALLED_APPS
に 'django.contrib.contenttypes'
を追加してフ
レームワークをインストールしてください。
通常は、 contenttypes
フレームワークをインストールしておいた方がよいで
しょう。 Django にバンドルされている以下のアプリケーションには、
contenttypes
フレームワークが必要だからです:
admin
アプリケーションは、管理インタフェース上で追加変更したオブ ジェクトの履歴を管理するためにcontenttypes
を使います。- Django の
認証フレームワーク
は、ユーザ パーミッションをモデルに結びつけるためにcontenttypes
を使います。- Django のコメントシステム (
django.contrib.comments
) は、インス トールされている任意のモデルに対してコメントを付けられる機能をcontenttypes
で実現しています。
ContentType
モデル¶models.
ContentType
¶ContentType
のインスタン
スには 3 つのフィールドがあり、 3 つを組み合わせると、インストールされ
ているモデルを一意に表現できます:
app_label
¶モデルの入っているアプリケーションの名前です。この名前はモデルの
app_label
属性から取り出され、通常は Python の import パス
の 末尾 の部分が入っています。例えば、アプリケーションが
django.contrib.contenttypes
なら、 app_label
は
contenttypes
です。
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
インスタンスのメソッド¶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>
取り出した 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()
を組
み合わせると、非常に重要なユースケースを実現できます:
- 二つのメソッドを使えば、特定のモデルクラスを import して使うのではな く、インストールされている全てのモデルに対して操作を行えるような一般 化された高水準のコードを書けます。実行時に
app_label
とmodel
を指定してContentType
を照合し、 得られたモデルクラスからオブジェクトを取り出せるのです。- 別のモデルから
ContentType
にリレーショ ンを張って、モデルのインスタンスと、ContentType
の表すモデ ルクラスを結び付け、モデルクラスのメソッドにアクセスできます。
Django にバンドルされているアプリケーションには、後者のテクニックを使ってい
るものがいくつかあります。例えば、 Django の認証フレームワークに入っている
パーミッションのシステム
では、 Permission
モデルの中で、
ContentType
への外部キー
を張っています。そうすることで、
Permission
は「ブログにエントリを追加」
や「ニュースストーリーを削除」といった権限を表現できます。
ContentTypeManager
カスタムマネジャ¶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 つのパートに分かれています:
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>
Due to the way GenericForeignKey
is implemented, you cannot use such fields directly with filters (filter()
and exclude()
, for example) via the database API. They aren’t normal field
objects. These examples will not work:
# This will fail
>>> TaggedItem.objects.filter(content_object=guido)
# This will also fail
>>> TaggedItem.objects.get(content_object=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>]
Just as django.contrib.contenttypes.generic.GenericForeignKey
accepts the names of the content-type and object-ID fields as
arguments, so too does GenericRelation
; if the model which has the
generic foreign key is using non-default names for those fields, you
must pass the names of the fields when setting up a
GenericRelation
to it. For example, if the TaggedItem
model
referred to above used fields named content_type_fk
and
object_primary_key
to create its generic foreign key, then a
GenericRelation
back to it would need to be defined like so:
tags = generic.GenericRelation(TaggedItem, content_type_field='content_type_fk', object_id_field='object_primary_key')
逆方向のリレーションを張らなければ、手動で照合する必要があります:
>>> 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>]
Note that if the model with a GenericForeignKey
that you’re referring to uses a non-default value for ct_field
or fk_field
(e.g. the django.contrib.comments
app uses ct_field="object_pk"
),
you’ll need to pass content_type_field
and object_id_field
to
GenericRelation
.:
comments = generic.GenericRelation(Comment, content_type_field="content_type", object_id_field="object_pk")
GenericRelation`
を持つオブジェ
クトを削除すると、このオブジェクトに
GenericForeignKey
でリレーショ
ンを張っているオブジェクトも全て削除されるので注意してください。つまり、上
の例では、 Bookmark
オブジェクト b
を削除すると、 b
にリレーショ
ンを張っている t1
や t2
といった TaggedItem
も同時に削除される
のです。
Django’s database aggregation API <topics-db-aggregation
doesn’t work with a
GenericRelation
. For example, you
might be tempted to try something like:
Bookmark.objects.aggregate(Count('tags'))
This will not work correctly, however. The generic relation adds extra filters
to the queryset to ensure the correct content type, but the aggregate
method
doesn’t take them into account. For now, if you need aggregates on generic
relations, you’ll need to calculate them without using the aggregation API.
django.contrib.contenttypes.generic
では、二つのクラス、
GenericInlineFormSet
と
GenericInlineModelAdmin
を提供しています。これらのクラスを使うと、フォームや admin で一般化リレーショ
ンを扱えます。詳しくは モデルフォームセット
や admin のドキュメントを参照してください。
generic.
GenericInlineModelAdmin
¶GenericInlineModelAdmin
クラスは、 InlineModelAdmin
の全てのプロパティを継承していますが、一般化リレーションを扱うために、
以下の二つの属性を備えています:
ct_field
¶モデル内の ContentType
の外部キーフィールド名です。デフォルトの値は content_type
です。
ct_fk_field
¶リレーション先オブジェクトの ID を表す整数フィールドの名前です。
デフォルトの値は object_id
です。
Oct 26, 2017