Django には contenttypes アプリケーションが含まれており、 Django を使ったプロジェクトにインストールされているすべてのモデルを追跡し、モデルを扱うための高レベルで汎用的なインターフェースを提供します。
contenttypes アプリケーションの中心にあるのは、 django.contrib.contenttypes.models.ContentType にある ContentType モデルです。 ContentType のインスタンスは、プロジェクトにインストールされたモデルに関する情報を表し、格納します。新しい ContentType のインスタンスは、新しいモデルがインストールされるたびに自動的に作成されます。
ContentType のインスタンスは ContentType が表すモデルクラスを返すメソッドと、それらのモデルからオブジェクトを問い合わせるメソッドを持っています。また、 ContentType を操作するためのメソッドや、特定のモデルの ContentType のインスタンスを取得するためのメソッドを追加する カスタムマネージャ もあります。
あなたのモデルと ContentType の間のリレーションシップを利用することで、あなたのモデルのインスタンスとインストールされている任意のモデルのインスタンスとの間で「汎用的(ジェネリック)」なリレーションシップを有効にすることもできます。
contenttypes フレームワークは、django-admin startproject によって作成されるデフォルトの INSTALLED_APPS リストに含まれていますが、もし削除したり、手動で INSTALLED_APPS リストを設定した場合は、 'django.contrib.contenttypes' を INSTALLED_APPS 設定に追加することで有効にできます。
通常は、contenttypes フレームワークをインストールしておくのがよいでしょう。Django の他のバンドルアプリケーションのいくつかはそれを必要とします:
admin アプリケーションは、管理インターフェイスを通じて追加・変更された各オブジェクトの履歴を記録するためにこれを使用します。
Django の 認証フレームワーク は、ユーザーの権限を特定のモデルに紐づけるのに使用されます。
ContentType モデル¶ContentType の各インスタンスは、2 つのフィールドを持ち、それらを合わせると、インストールされたモデルを一意に特定します:
モデルが属するアプリケーションの名前です。これはモデルの app_label 属性から取得され、アプリケーションの Python インポートパスの 最後 の部分のみが含まれます。例えば、 django.contrib.contenttypes の app_label は contenttypes になります。
モデルクラスの名前。
さらに、次のプロパティが利用可能です:
コンテンツタイプの人間が読める名前です。これはモデルの verbose_name 属性から取得されます。
どのように動作するか、例を見てみましょう。すでに contenttypes アプリケーションがインストールされていて、 sites アプリケーション を INSTALLED_APPS 設定に追加し、 manage.py migrate を実行してインストールすると、 django.contrib.sites.models.Site というモデルがデータベースにインストールされます。それと一緒に ContentType の新しいインスタンスが以下の値で作成されます:
ContentType インスタンスのメソッド¶各 ContentType インスタンスには、そのインスタンスが表すモデルに移動したり、 ContentType からオブジェクトを取得したりするためのメソッドがあります。
モデルが表す ContentType に対する有効な lookup 引数 のセットを受け取り、そのモデルで get() ルックアップ を実行し、対応するオブジェクトを返します。 using 引数を使用することで、デフォルトとは異なるデータベースを指定できます。
この 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 に対するクエリ、または 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() を併用することで、2つの非常に重要なユースケースが可能になります:
これらのメソッドを使うことで、インストールされた任意のモデルに対してクエリを実行する高レベルの汎用コードを書くことができます。特定のモデルクラスをインポートして使うのではなく、実行時に app_label と model を ContentType ルックアップに渡すことで、モデルクラスを操作したり、そこからオブジェクトを取得したりすることができます。
ContentType は、別のモデルと関連付けることができ、これによってそのインスタンスを特定のモデルクラスに紐付けることができます。これらのメソッドを使用することで、これらのモデルクラスにアクセスできます。
Django のバンドルされているアプリケーションのいくつかは、後者のテクニックを利用しています。例えば、Django の認証フレームワークの パーミッションシステム は、 Permission モデルを ContentType の外部キーとして使っています。これにより、 Permission は「ブログのエントリを追加できる」とか「ニュース記事を削除できる」といった概念を表現できます。
ContentTypeManager¶ContentType にはカスタムマネージャー ContentTypeManager もあり、以下のメソッドが追加されています:
Clears an internal cache used by
ContentType to keep track
of models for which it has created
ContentType instances. You
probably won't need to call this method in application code yourself;
Django will call it automatically when it's needed.
You may need to clear the cache when testing, to reset between tests, or after preparing test state. For example:
class ContentTypesTests(TestCase):
def setUp(self):
ContentType.objects.clear_cache()
self.addCleanup(ContentType.objects.clear_cache)
ContentType を ID でルックアップします。このメソッドは get_for_model() と同じ共有キャッシュを使用するので、通常の ContentType.objects.get(pk=id) よりもこのメソッドを使用することをお勧めします。
モデルクラスかモデルのインスタンスを受け取り、そのモデルを表す ContentType インスタンスを返します。 for_concrete_model=False はプロキシモデルの ContentType を取得することを許可します。
モデルクラスを任意の個数受け取り、モデルクラスを ContentType インスタンスにマッピングした辞書を返します。 for_concrete_models=False により、プロキシモデルの ContentType を取得できます。
与えられたアプリケーションラベルとモデル名で一意に識別される ContentType インスタンスを返します。このメソッドの主な目的は、 ContentType オブジェクトをデシリアライズ時に ナチュラルキー から参照できるようにすることです。
The get_for_model() method is especially
useful when you know you need to work with a
ContentType but don't
want to go to the trouble of obtaining the model's metadata to perform a manual
lookup:
>>> from django.contrib.auth.models import User
>>> ContentType.objects.get_for_model(User)
<ContentType: user>
ContentType に自分のモデルの外部キーを追加することで、上記の Permission モデルの例のように、モデル自身を他のモデルクラスに効果的に結びつけることができます。しかし、もう一歩進んで ContentType を使うことで、モデル間の本当に汎用的な (時に "多相的 (ポリモーフィック)" と呼ばれる) リレーションシップを実現できます。
たとえば、次のようにしてタグ付けシステムに使用できます:
from django.contrib.contenttypes.fields import GenericForeignKey
from django.contrib.contenttypes.models import ContentType
from django.db import models
class TaggedItem(models.Model):
tag = models.SlugField()
content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
object_id = models.PositiveBigIntegerField()
content_object = GenericForeignKey("content_type", "object_id")
def __str__(self):
return self.tag
class Meta:
indexes = [
models.Index(fields=["content_type", "object_id"]),
]
通常の ForeignKey は、ほかのモデルを単一指定することしかできません。つまり、 TaggedItem モデルが ForeignKey を使用する場合、タグを格納する単一のモデルを選択する必要があります。contenttypes アプリケーションは特別なフィールド型(GenericForeignKey) を提供し、これにより、リレーションシップを任意のモデルと設定できます。
GenericForeignKey の設定には3つの部分があります:
モデルに ContentType への ForeignKey を持たせます。通常、このフィールドの名前は "content_type" となります。
モデルに、リレーション先モデルの主キー値を格納できるフィールドを与えます。ほとんどのモデルの場合、これは PositiveBigIntegerField を意味します。このフィールドの通常の名前は "object_id" です。
モデルに GenericForeignKey を与え、上記の2つのフィールド名を渡します。これらのフィールドの名前が "content_type" と "object_id" である場合、省略できます。これらは GenericForeignKey が探すデフォルトのフィールド名です。
ForeignKey とは異なり、データベースインデックスは GenericForeignKey には自動的に作成 されません ので、 Meta.indexes を使って独自の複数カラムインデックスを追加することをお勧めします。この動作は将来 変更されるかも知れません 。
False の場合、フィールドはプロキシモデルを参照できます。デフォルトは True です。これは get_for_model() への引数 for_concrete_model を反映したものです。
主キーの型の互換性
"object_id" フィールドは、リレーション先モデルの主キーフィールドと同じ型である必要はありませんが、その主キーの値は、"object_id" フィールドの get_db_prep_value() メソッドによって同じ型に変換可能でなければなりません。
たとえば、 IntegerField または CharField のどちらかの主キーフィールドを持つモデルに対してジェネリックリレーションを許可したい場合、モデルの "object_id" フィールドには CharField を使用できます。整数は get_db_prep_value() によって文字列に変換できるためです。
最大の柔軟性を得るには、定義された最大長がない TextField を使用できますが、データベースのバックエンドによっては、それにより重大なパフォーマンスペナルティが発生する可能性があります。
どのフィールドタイプが最適かについて、汎用的な解決策はありません。参照するモデルを評価し、ユースケースに最も効果的な解決策を決定してください。
ContentType オブジェクトへの参照をシリアライズする
ジェネリックリレーションを実装したモデルからデータをシリアライズする場合 (例えば fixtures を生成する場合)、関連する ContentType オブジェクトを一意に識別するためにナチュラルキーを使用する必要があるでしょう。詳しくは ナチュラルキー と dumpdata --natural-foreign を参照してください。
これにより、通常の ForeignKey と同じような API が利用できるようになります。各 TaggedItem は、関連するオブジェクトを返す 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>
リレーション先オブジェクトが削除された場合、 content_type フィールドと object_id フィールドは元の値のままとなり、 GenericForeignKey は None を返します:
>>> guido.delete()
>>> t.content_object # returns None
GenericForeignKey が実装されているため、このようなフィールドをデータベース API 経由のフィルタ (filter() や exclude() など) で直接使用することはできません。 GenericForeignKey は通常のフィールドオブジェクトではないので、これらの例は動作 しません :
# This will fail
>>> TaggedItem.objects.filter(content_object=guido)
# This will also fail
>>> TaggedItem.objects.get(content_object=guido)
同様に、 GenericForeignKey は ModelForm 内には表示されません。
リレーション先オブジェクトに対する逆のリレーションはデフォルトでは存在しません。 related_query_name を設定すると、リレーション先オブジェクトからこのオブジェクトへのリレーションが作成されます。これにより、リレーション先オブジェクトからのクエリやフィルタリングが可能になります。
どのモデルをよく使うかわかっている場合は、"逆" ジェネリックリレーションシップを追加して、追加のAPIを有効にすることもできます。たとえば:
from django.contrib.contenttypes.fields import GenericRelation
from django.db import models
class Bookmark(models.Model):
url = models.URLField()
tags = GenericRelation(TaggedItem)
各 "Bookmark" インスタンスには、 tags という属性があります。これを使用して、リレーション先の TaggedItems を取得できます。
>>> b = Bookmark(url="https://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()
<QuerySet [<TaggedItem: django>, <TaggedItem: python>]>
また、 add(), create(), set() を使ってリレーションシップを作成することもできます:
>>> t3 = TaggedItem(tag="Web development")
>>> b.tags.add(t3, bulk=False)
>>> b.tags.create(tag="Web framework")
<TaggedItem: Web framework>
>>> b.tags.all()
<QuerySet [<TaggedItem: django>, <TaggedItem: python>, <TaggedItem: Web development>, <TaggedItem: Web framework>]>
>>> b.tags.set([t1, t3])
>>> b.tags.all()
<QuerySet [<TaggedItem: django>, <TaggedItem: Web development>]>
remove() メソッドは指定されたモデルオブジェクトを一括で削除します。
>>> b.tags.remove(t3)
>>> b.tags.all()
<QuerySet [<TaggedItem: django>]>
>>> TaggedItem.objects.all()
<QuerySet [<TaggedItem: django>]>
clear() メソッドは、インスタンスのリレーション先のすべてのオブジェクトを一括削除するために使用できます。
>>> b.tags.clear()
>>> b.tags.all()
<QuerySet []>
>>> TaggedItem.objects.all()
<QuerySet []>
related_query_name を設定して GenericRelation を定義すると、リレーション先オブジェクトからクエリを行うことができます。
tags = GenericRelation(TaggedItem, related_query_name="bookmark")
これにより、TaggedItem から Bookmark に対するフィルタリング、ソート、その他のクエリ操作が可能になります:
>>> # Get all tags belonging to bookmarks containing `django` in the url
>>> TaggedItem.objects.filter(bookmark__url__contains="django")
<QuerySet [<TaggedItem: django>, <TaggedItem: python>]>
related_query_name を追加しない場合、同様のルックアップを手動で行うことができます:
>>> bookmarks = Bookmark.objects.filter(url__contains="django")
>>> bookmark_type = ContentType.objects.get_for_model(Bookmark)
>>> TaggedItem.objects.filter(content_type__pk=bookmark_type.id, object_id__in=bookmarks)
<QuerySet [<TaggedItem: django>, <TaggedItem: python>]>
GenericForeignKey が content-type と object-ID フィールドの名前を引数として受け取るのと同様に、 GenericRelation も引数として受け取ります。ジェネリック外部キーを持つモデルがこれらのフィールドにデフォルト以外の名前を使用している場合、 GenericRelation を設定する際にフィールド名を渡す必要があります。例えば、上記の TaggedItem モデルが content_type_fk と object_primary_key というフィールドを使用してジェネリック外部キーを作成した場合、 GenericRelation を以下のように定義する必要があります:
tags = GenericRelation(
TaggedItem,
content_type_field="content_type_fk",
object_id_field="object_primary_key",
)
また、 GenericRelation を持つオブジェクトを削除した場合、 GenericForeignKey を持つオブジェクトも削除されることに注意してください。上の例では、 Bookmark オブジェクトが削除された場合、それを指す TaggedItem オブジェクトも同時に削除されることを意味します。
GenericForeignKey は、ForeignKey とは異なり、 on_delete 引数を受け入れず、この動作をカスタマイズすることはできません。必要なら、 GenericRelation を使用せずに、カスケード削除を回避でき、 代替の動作は pre_delete シグナルを介して提供できます。
Django のデータベース集計 API は GenericRelation で動作します。例えば、すべてのブックマークのタグの数を調べることができます:
>>> Bookmark.objects.aggregate(Count("tags"))
{'tags__count': 3}
django.contrib.contenttypes.forms モジュールは、下記を提供します:
GenericForeignKey を使用するためのフォームセットファクトリ、 generic_inlineformset_factory() 。
modelformset_factory() を使用して GenericInlineFormSet を返します。
デフォルトの content_type と object_id と異なる場合は、それぞれ ct_field と fk_field を指定する必要があります。その他のパラメータは modelformset_factory() と inlineformset_factory() で説明されているものと同様です。
for_concrete_model 引数は GenericForeignKey の for_concrete_model 引数に対応します。
django.contrib.contenttypes.admin モジュールは、 GenericTabularInline と GenericStackedInline (GenericInlineModelAdmin のサブクラス) を提供します。
これらのクラスと関数は、フォームや admin アプリケーションでジェネリックリレーションを利用できるようにします。詳細に、モデルフォームセット および admin アプリケーション のドキュメントを参照してください。
GenericInlineModelAdmin クラスは InlineModelAdmin クラスの全てのプロパティを継承します。しかし、ジェネリックリレーションを扱うための独自のプロパティをいくつか追加しています:
モデルの外部キーフィールド ContentType の名前。デフォルトは content_type です。
リレーション先オブジェクトの ID を表す整数フィールドの名前。デフォルトは object_id です。
GenericInlineModelAdmin のサブクラスで、それぞれスタックレイアウトと表形式のレイアウトを持っています。
GenericPrefetch()¶このルックアップは Prefetch() に類似しており、GenericForeignKey 専用に使用されるべきです。querysets 引数は異なる ContentType 向けのクエリセットのリストを受け入れます。これは、結果が均質でない GenericForeignKey に便利です。
>>> from django.contrib.contenttypes.prefetch import GenericPrefetch
>>> bookmark = Bookmark.objects.create(url="https://www.djangoproject.com/")
>>> animal = Animal.objects.create(name="lion", weight=100)
>>> TaggedItem.objects.create(tag="great", content_object=bookmark)
>>> TaggedItem.objects.create(tag="awesome", content_object=animal)
>>> prefetch = GenericPrefetch(
... "content_object", [Bookmark.objects.all(), Animal.objects.only("name")]
... )
>>> TaggedItem.objects.prefetch_related(prefetch).all()
<QuerySet [<TaggedItem: Great>, <TaggedItem: Awesome>]>
12月 16, 2025