Angular, Blockchain, Science とか

Angular, Blockchain, Science全般 の情報を主に書いていきます。

Django contenttype 一般化リレーション 公式ドキュメント和訳

分かりずらかったので和訳しました。Django1.10 です。またわかりやすくなるようにメモも入れています。

contenttypes フレームワーク

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 を使います。

ContentType モデル

class models.ContentType

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


app_label

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

model

モデルクラスの名前です。

さらに,次のプロパティも利用可能です。

name

人間可読なモデル名です。この値は、モデルの verbose_name から取り出されます。

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

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

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


それぞれの ContentType インスタンス には、インスタンスの表現しているモデルクラスを取得したり、モデルクラス のインスタンスを取り出したりするためのメソッドがあります:

models.ContentType.get_object_for_this_type(**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() を組 み合わせると、非常に重要なユースケースを実現できます:

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

2. 別のモデルから ContentType にリレーショ ンを張って、モデルのインスタンスと、 ContentType の表すモデ ルクラスを結び付け、モデルクラスのメソッドにアクセスできます。

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

ContentTypeManager カスタムマネジャ

class models.ContentTypeManager

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

clear_cache()

ロード済みの ContentType インスタンスを保持しておくための内部キャッシュをクリアします。このメソッドを自分で呼ぶことはほとんどないでしょう。 Django は必要になれば自動的にメソッドを呼び出します。

get_for_id(id)

コンテンツタイプをIDで検索する。このメソッドはget_for_model()と同じ共有キャッシュを使用するため、通常ContentType.objects.get(pk = id)メソッドよりもこのメソッドを使用することをお勧めします。

get_for_model(model)

モデルクラスやモデルのインスタンスを引数にとり、そのモデルを表す ContentType インスタンスを返します。for_concrete_model=False は ContentType のプロキシモデルのフェッチを許可する。

get_for_models(models)

バリアント(可変長)数のモデルクラスを取得し、それらを表すContentTypeインスタンスにモデルクラスをマッピングする辞書を返します。
for_concrete_models = Falseを指定すると、プロキシモデルのContentTypeを取得できます。

get_by_natural_key(app_label, model)¶

指定されたアプリケーションラベルとモデル名によって一意に識別されるContentTypeインスタンスを返します。 このメソッドの主な目的は、デシリアライズ時に自然キーを使用してContentTypeオブジェクトを参照できるようにすることです。

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

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

Generic relations 一般化リレーション

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

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

from django.db import models
from django.contrib.contenttypes.fields import GenericForeignKey
from django.contrib.contenttypes.models import ContentType

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

    def __str__(self):              # __unicode__ on Python 2
        return self.tag

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

class GenericForeignKey¶

GenericForeignKey のセットアップは、以下の 3 つのパートに分かれています:

 あなたのモデルにコンテンツタイプへの外部キーを与えます。 このフィールドの通常の名前は「content_type」です。

 リレーション先のモデルの主キー値を格納できるフィールドをモデルに与えます。 ほとんどのモデルでは、これはPositiveIntegerFieldを意味します。 このフィールドの通常の名前は "object_id"です。

 モデルにGenericForeignKeyを与え、上記の2つのフィールドの名前を渡します。 これらのフィールドの名前が "content_type"と "object_id"の場合、これを省略できます。これは、GenericForeignKeyが検索するデフォルトのフィールド名です。

for_concrete_model

 Falseの場合、フィールドはプロキシモデルを参照できます。 デフォルトはTrueです。 これは、for_concrete_model引数をget_for_model()に反映します。

*主キータイプの互換性

"object_id"フィールドは、関連するモデルの主キーフィールドと同じ型である必要はありませんが、その主キー値はget_db_prep_value()メソッドによって "object_id"フィールドと同じ型に強制的でなければなりません。

たとえば、IntegerFieldまたはCharFieldの主キーフィールドを持つモデルに汎用リレーションを許可する場合は、get_db_prep_value()によって整数を文字列に強制することができるため、モデルの「object_id」フィールドにCharFieldを使用できます。

柔軟性を最大限にするには、定義された最大長を持たないTextFieldを使用することができますが、これはデータベースのバックエンドによって大幅なパフォーマンスの低下を招く可能性があります。

フィールドタイプが最適なソリューションはありません。 あなたが指し示すと予想されるモデルを評価し、ユースケースに対してどのソリューションが最も効果的かを判断する必要があります。

ContentTypeオブジェクトへの参照のシリアライズ

ジェネリックリレーションを実装するモデルからデータをシリアル化する場合(たとえば、フィクスチャを生成する場合)、関連するContentTypeオブジェクトを一意に識別する自然キーを使用しているはずです。 詳細については、ナチュラルキーdumpdata --natural-foreignを参照してください。

これで、通常の 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> 

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は ModelForms も使えません。

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

class GenericRelation

related_query_name

このオブジェクトに戻る関連オブジェクトのリレーションは、デフォルトでは存在しません。 related_query_nameを設定すると、関連するオブジェクトからこのオブジェクトへのリレーションが作成されます。 これにより、関連オブジェクトからのクエリとフィルタリングが可能になります

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

from django.db import models
from django.contrib.contenttypes.fields import GenericRelation

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>]>

related_query_name setを使用してGenericRelationを定義すると、関連オブジェクトからのクエリが可能になります。

tags = GenericRelation(TaggedItem, related_query_name='bookmarks')

これにより、TaggedItemからのブックマークのフィルタリング、順序付け、およびその他のクエリ操作が可能になります。

>>> # Get all tags belonging to bookmarks containing `django` in the url
>>> TaggedItem.objects.filter(bookmarks__url__contains='django')
<QuerySet [<TaggedItem: django>, <TaggedItem: python>]>

もちろん、リバースリレーションシップを追加しない場合は、同じタイプのルックアップを手動で行うことができます。

>>> b = Bookmark.objects.get(url='https://www.djangoproject.com/')
>>> bookmark_type = ContentType.objects.get_for_model(b)
>>> TaggedItem.objects.filter(content_type__pk=bookmark_type.id, object_id=b.id)
<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オブジェクトが同時に削除されることを意味します。

ForeignKeyとは異なり、GenericForeignKeyはこの動作をカスタマイズするためにon_delete引数を受け入れません。 必要に応じて、単純にGenericRelationを使用しないことでカスケード削除を回避でき、pre_delete信号で代替動作を提供することができます。

Generic relations and aggregation

Djangoのデータベース集約APIは、GenericRelationで動作します。 たとえば、すべてのブックマークに含まれるタグの数を調べることができます。

>>> Bookmark.objects.aggregate(Count('tags'))
{'tags__count': 3}


参考サイト

www.techstricks.com

stackoverflow.com

英語きつすぎ・・・
頑張ってDjango勉強しましょう。