contenttypes フレームワーク¶
Django には contenttypes アプリケーションが含まれており、 Django を使ったプロジェクトにインストールされているすべてのモデルを追跡し、モデルを扱うための高レベルで汎用的なインターフェースを提供します。
概要¶
contenttypes アプリケーションの中心にあるのは、 django.contrib.contenttypes.models.ContentType にある ContentType モデルです。 ContentType のインスタンスは、プロジェクトにインストールされたモデルに関する情報を表し、格納します。新しい ContentType のインスタンスは、新しいモデルがインストールされるたびに自動的に作成されます。
ContentType のインスタンスは ContentType が表すモデルクラスを返すメソッドと、それらのモデルからオブジェクトを問い合わせるメソッドを持っています。また、 ContentType を操作するためのメソッドや、特定のモデルの ContentType のインスタンスを取得するためのメソッドを追加する カスタムマネージャ もあります。
あなたのモデルと ContentType の間のリレーションシップを利用することで、あなたのモデルのインスタンスとインストールされている任意のモデルのインスタンスとの間で「汎用的(ジェネリック)」なリレーションシップを有効にすることもできます。
contenttypes フレームワークをインストールする¶
contenttypes フレームワークは、django-admin startproject によって作成されるデフォルトの INSTALLED_APPS リストに含まれていますが、もし削除したり、手動で INSTALLED_APPS リストを設定した場合は、 'django.contrib.contenttypes' を INSTALLED_APPS 設定に追加することで有効にできます。
通常は、contenttypes フレームワークをインストールしておくのがよいでしょう。Django の他のバンドルアプリケーションのいくつかはそれを必要とします:
- admin アプリケーションは、管理インターフェイスを通じて追加・変更された各オブジェクトの履歴を記録するためにこれを使用します。 
- Django の - 認証フレームワークは、ユーザーの権限を特定のモデルに紐づけるのに使用されます。
ContentType モデル¶
- class ContentType[ソース]¶
- ContentTypeの各インスタンスは、2 つのフィールドを持ち、それらを合わせると、インストールされたモデルを一意に特定します:- app_label¶
- モデルが属するアプリケーションの名前です。これはモデルの - app_label属性から取得され、アプリケーションの Python インポートパスの 最後 の部分のみが含まれます。例えば、- django.contrib.contenttypesの- app_labelは- contenttypesになります。
 - model¶
- モデルクラスの名前。 
 - さらに、次のプロパティが利用可能です: - name[ソース]¶
- コンテンツタイプの人間が読める名前です。これはモデルの - verbose_name属性から取得されます。
 
どのように動作するか、例を見てみましょう。すでに contenttypes アプリケーションがインストールされていて、 sites アプリケーション を INSTALLED_APPS 設定に追加し、 manage.py migrate を実行してインストールすると、 django.contrib.sites.models.Site というモデルがデータベースにインストールされます。それと一緒に ContentType の新しいインスタンスが以下の値で作成されます:
ContentType インスタンスのメソッド¶
各 ContentType インスタンスには、そのインスタンスが表すモデルに移動したり、 ContentType からオブジェクトを取得したりするためのメソッドがあります。
- ContentType.get_object_for_this_type(using=None, **kwargs)[ソース]¶
- モデルが表す - ContentTypeに対する有効な lookup 引数 のセットを受け取り、そのモデルで- get() ルックアップを実行し、対応するオブジェクトを返します。- using引数を使用することで、デフォルトとは異なるデータベースを指定できます。Changed in Django 5.1:- using引数が追加されました。
- 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 に対するクエリ、または 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¶
- class ContentTypeManager[ソース]¶
- ContentTypeにはカスタムマネージャー- ContentTypeManagerもあり、以下のメソッドが追加されています:- clear_cache()[ソース]¶
- ContentTypeが使用する内部キャッシュをクリアし、- ContentTypeインスタンスを作成したモデルの追跡を行います。おそらく、このメソッドを自分で呼び出す必要はほとんどありません。必要なタイミングでDjangoが自動的に呼び出します。
 - get_for_id(id)[ソース]¶
- ContentTypeを ID でルックアップします。このメソッドは- get_for_model()と同じ共有キャッシュを使用するので、通常の- ContentType.objects.get(pk=id)よりもこのメソッドを使用することをお勧めします。
 - get_for_model(model, for_concrete_model=True)[ソース]¶
- モデルクラスかモデルのインスタンスを受け取り、そのモデルを表す - ContentTypeインスタンスを返します。- for_concrete_model=Falseはプロキシモデルの- ContentTypeを取得することを許可します。
 - get_for_models(*models, for_concrete_models=True)[ソース]¶
- モデルクラスを任意の個数受け取り、モデルクラスを - ContentTypeインスタンスにマッピングした辞書を返します。- for_concrete_models=Falseにより、プロキシモデルの- ContentTypeを取得できます。
 - get_by_natural_key(app_label, model)[ソース]¶
- 与えられたアプリケーションラベルとモデル名で一意に識別される - ContentTypeインスタンスを返します。このメソッドの主な目的は、- ContentTypeオブジェクトをデシリアライズ時に ナチュラルキー から参照できるようにすることです。
 
get_for_model() メソッドは、 ContentType を扱う必要があることがわかっているが、わざわざモデルのメタデータを取得して手動でルックアップを行うのは面倒だという場合に特に便利です:
>>> 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) を提供し、これにより、リレーションシップを任意のモデルと設定できます。
- class GenericForeignKey[ソース]¶
- GenericForeignKeyの設定には3つの部分があります:- モデルに - ContentTypeへの- ForeignKeyを持たせます。通常、このフィールドの名前は "content_type" となります。
- モデルに、リレーション先モデルの主キー値を格納できるフィールドを与えます。ほとんどのモデルの場合、これは - PositiveBigIntegerFieldを意味します。このフィールドの通常の名前は "object_id" です。
- モデルに - GenericForeignKeyを与え、上記の2つのフィールド名を渡します。これらのフィールドの名前が "content_type" と "object_id" である場合、省略できます。これらは- GenericForeignKeyが探すデフォルトのフィールド名です。
 - ForeignKeyとは異なり、データベースインデックスは- GenericForeignKeyには自動的に作成 されません ので、- Meta.indexesを使って独自の複数カラムインデックスを追加することをお勧めします。この動作は将来 変更されるかも知れません 。- for_concrete_model¶
- 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 内には表示されません。
逆ジェネリックリレーション (Reverse generic relation)¶
- class GenericRelation[ソース]¶
- リレーション先オブジェクトに対する逆のリレーションはデフォルトでは存在しません。 - 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 シグナルを介して提供できます。
ジェネリックリレーション (generic relation) と集計 (aggregation)¶
Django のデータベース集計 API は GenericRelation で動作します。例えば、すべてのブックマークのタグの数を調べることができます:
>>> Bookmark.objects.aggregate(Count("tags"))
{'tags__count': 3}
フォームにおけるジェネリックリレーション¶
django.contrib.contenttypes.forms モジュールは、下記を提供します:
- GenericForeignKeyを使用するためのフォームセットファクトリ、- generic_inlineformset_factory()。
- generic_inlineformset_factory(model, form=ModelForm, formset=BaseGenericInlineFormSet, ct_field='content_type', fk_field='object_id', fields=None, exclude=None, extra=3, can_order=False, can_delete=True, max_num=None, formfield_callback=None, validate_max=False, for_concrete_model=True, min_num=None, validate_min=False, absolute_max=None, can_delete_extra=True)[ソース]¶
- modelformset_factory()を使用して- GenericInlineFormSetを返します。- デフォルトの - content_typeと- object_idと異なる場合は、それぞれ- ct_fieldと- fk_fieldを指定する必要があります。その他のパラメータは- modelformset_factory()と- inlineformset_factory()で説明されているものと同様です。- for_concrete_model引数は- GenericForeignKeyの- for_concrete_model引数に対応します。
admin におけるジェネリックリレーション¶
django.contrib.contenttypes.admin モジュールは、 GenericTabularInline と GenericStackedInline (GenericInlineModelAdmin のサブクラス) を提供します。
これらのクラスと関数は、フォームや admin アプリケーションでジェネリックリレーションを利用できるようにします。詳細に、モデルフォームセット および admin アプリケーション のドキュメントを参照してください。
- class GenericInlineModelAdmin[ソース]¶
- GenericInlineModelAdminクラスは- InlineModelAdminクラスの全てのプロパティを継承します。しかし、ジェネリックリレーションを扱うための独自のプロパティをいくつか追加しています:- ct_field¶
- モデルの外部キーフィールド - ContentTypeの名前。デフォルトは- content_typeです。
 - ct_fk_field¶
- リレーション先オブジェクトの ID を表す整数フィールドの名前。デフォルトは - object_idです。
 
- class GenericStackedInline[ソース]¶
- 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>]>
 
          