シリアライザーのリレーション
アルゴリズムではなく、データ構造がプログラミングの中心です。
— ロブ・パイク
リレーショナルフィールドは、モデルのリレーションシップを表すために使用されます。ForeignKey
、ManyToManyField
、OneToOneField
のリレーションシップだけでなく、逆リレーションシップや GenericForeignKey
などのカスタムリレーションシップにも適用できます。
注: リレーショナルフィールドは relations.py
で宣言されていますが、慣例として、from rest_framework import serializers
を使用して serializers
モジュールからインポートし、serializers.<FieldName>
としてフィールドを参照する必要があります。
注: REST Framework は、select_related
や prefetch_related
の点でシリアライザーに渡されるクエリセットを自動的に最適化しようとはしません。これは、過剰な魔法になるためです。ソース属性を通じてORMリレーションにまたがるフィールドを持つシリアライザーは、データベースから関連オブジェクトを取得するために追加のデータベースヒットを必要とする可能性があります。このようなシリアライザーを使用しているときに発生する可能性のある追加のデータベースヒットを回避するためにクエリを最適化するのはプログラマーの責任です。
たとえば、次のシリアライザーは、トラックフィールドがプリフェッチされていない場合、評価するたびにデータベースヒットにつながります
class AlbumSerializer(serializers.ModelSerializer):
tracks = serializers.SlugRelatedField(
many=True,
read_only=True,
slug_field='title'
)
class Meta:
model = Album
fields = ['album_name', 'artist', 'tracks']
# For each album object, tracks should be fetched from database
qs = Album.objects.all()
print(AlbumSerializer(qs, many=True).data)
AlbumSerializer
が many=True
でかなり大きなクエリセットをシリアライズするために使用されている場合、深刻なパフォーマンスの問題になる可能性があります。次の方法で AlbumSerializer
に渡されるクエリセットを最適化すると
qs = Album.objects.prefetch_related('tracks')
# No additional database hits required
print(AlbumSerializer(qs, many=True).data)
問題を解決します。
リレーションシップの検査。
ModelSerializer
クラスを使用すると、シリアライザーフィールドとリレーションシップが自動的に生成されます。これらの自動的に生成されたフィールドを検査することは、リレーションシップスタイルをカスタマイズする方法を決定するのに役立つツールになります。
これを行うには、python manage.py shell
を使用してDjangoシェルを開き、シリアライザークラスをインポートしてインスタンス化し、オブジェクト表現を出力します...
>>> from myapp.serializers import AccountSerializer
>>> serializer = AccountSerializer()
>>> print(repr(serializer))
AccountSerializer():
id = IntegerField(label='ID', read_only=True)
name = CharField(allow_blank=True, max_length=100, required=False)
owner = PrimaryKeyRelatedField(queryset=User.objects.all())
APIリファレンス
さまざまなタイプのリレーショナルフィールドを説明するために、例としていくつかの単純なモデルを使用します。私たちのモデルは、音楽アルバムと、各アルバムにリストされているトラック用です。
class Album(models.Model):
album_name = models.CharField(max_length=100)
artist = models.CharField(max_length=100)
class Track(models.Model):
album = models.ForeignKey(Album, related_name='tracks', on_delete=models.CASCADE)
order = models.IntegerField()
title = models.CharField(max_length=100)
duration = models.IntegerField()
class Meta:
unique_together = ['album', 'order']
ordering = ['order']
def __str__(self):
return '%d: %s' % (self.order, self.title)
StringRelatedField
StringRelatedField
は、その __str__
メソッドを使用してリレーションシップのターゲットを表すために使用できます。
たとえば、次のシリアライザー
class AlbumSerializer(serializers.ModelSerializer):
tracks = serializers.StringRelatedField(many=True)
class Meta:
model = Album
fields = ['album_name', 'artist', 'tracks']
は、次の表現にシリアライズされます
{
'album_name': 'Things We Lost In The Fire',
'artist': 'Low',
'tracks': [
'1: Sunflower',
'2: Whitetail',
'3: Dinosaur Act',
...
]
}
このフィールドは読み取り専用です。
引数:
many
- 多対多のリレーションシップに適用する場合は、この引数をTrue
に設定する必要があります。
PrimaryKeyRelatedField
PrimaryKeyRelatedField
は、主キーを使用してリレーションシップのターゲットを表すために使用できます。
たとえば、次のシリアライザー
class AlbumSerializer(serializers.ModelSerializer):
tracks = serializers.PrimaryKeyRelatedField(many=True, read_only=True)
class Meta:
model = Album
fields = ['album_name', 'artist', 'tracks']
は、このような表現にシリアライズされます
{
'album_name': 'Undun',
'artist': 'The Roots',
'tracks': [
89,
90,
91,
...
]
}
デフォルトでは、このフィールドは読み取り/書き込み可能ですが、read_only
フラグを使用してこの動作を変更できます。
引数:
queryset
- フィールド入力を検証する際にモデルインスタンスの検索に使用されるクエリセット。リレーションシップは、クエリセットを明示的に設定するか、read_only=True
を設定する必要があります。many
- 多対多のリレーションシップに適用する場合は、この引数をTrue
に設定する必要があります。allow_null
-True
に設定すると、フィールドは null 可能リレーションシップに対してNone
または空の文字列の値を受け入れます。デフォルトはFalse
です。pk_field
- 主キーの値のシリアライズ/デシリアライズを制御するためのフィールドに設定します。たとえば、pk_field=UUIDField(format='hex')
は、UUID主キーをコンパクトな16進数表現にシリアライズします。
HyperlinkedRelatedField
HyperlinkedRelatedField
は、ハイパーリンクを使用してリレーションシップのターゲットを表すために使用できます。
たとえば、次のシリアライザー
class AlbumSerializer(serializers.ModelSerializer):
tracks = serializers.HyperlinkedRelatedField(
many=True,
read_only=True,
view_name='track-detail'
)
class Meta:
model = Album
fields = ['album_name', 'artist', 'tracks']
は、このような表現にシリアライズされます
{
'album_name': 'Graceland',
'artist': 'Paul Simon',
'tracks': [
'http://www.example.com/api/tracks/45/',
'http://www.example.com/api/tracks/46/',
'http://www.example.com/api/tracks/47/',
...
]
}
デフォルトでは、このフィールドは読み取り/書き込み可能ですが、read_only
フラグを使用してこの動作を変更できます。
注: このフィールドは、lookup_field
引数と lookup_url_kwarg
引数を使用して設定された、単一のURLキーワード引数を受け入れるURLにマップするオブジェクト用に設計されています。
これは、URLの一部として単一の主キーまたはスラッグ引数を含むURLに適しています。
より複雑なハイパーリンク表現が必要な場合は、下のカスタムハイパーリンクフィールドセクションで説明されているように、フィールドをカスタマイズする必要があります。
引数:
view_name
- リレーションシップのターゲットとして使用する必要があるビュー名。標準のルータークラスを使用している場合、これは<modelname>-detail
という形式の文字列になります。必須。queryset
- フィールド入力を検証する際にモデルインスタンスの検索に使用されるクエリセット。リレーションシップは、クエリセットを明示的に設定するか、read_only=True
を設定する必要があります。many
- 多対多のリレーションシップに適用する場合は、この引数をTrue
に設定する必要があります。allow_null
-True
に設定すると、フィールドは null 可能リレーションシップに対してNone
または空の文字列の値を受け入れます。デフォルトはFalse
です。lookup_field
- 参照されるビューのURLキーワード引数に対応する必要があり、検索に使用されるターゲット上のフィールド。デフォルトは'pk'
です。lookup_url_kwarg
- 検索フィールドに対応するURL confで定義されたキーワード引数の名前。デフォルトではlookup_field
と同じ値を使用します。format
- フォーマットサフィックスを使用している場合、ハイパーリンクフィールドは、format
引数を使用してオーバーライドしない限り、ターゲットに同じフォーマットサフィックスを使用します。
SlugRelatedField
SlugRelatedField
は、ターゲットのフィールドを使用してリレーションシップのターゲットを表すために使用できます。
たとえば、次のシリアライザー
class AlbumSerializer(serializers.ModelSerializer):
tracks = serializers.SlugRelatedField(
many=True,
read_only=True,
slug_field='title'
)
class Meta:
model = Album
fields = ['album_name', 'artist', 'tracks']
は、このような表現にシリアライズされます
{
'album_name': 'Dear John',
'artist': 'Loney Dear',
'tracks': [
'Airport Surroundings',
'Everything Turns to You',
'I Was Only Going Out',
...
]
}
デフォルトでは、このフィールドは読み取り/書き込み可能ですが、read_only
フラグを使用してこの動作を変更できます。
SlugRelatedField
を読み取り/書き込みフィールドとして使用する場合、通常、スラッグフィールドが unique=True
のモデルフィールドに対応していることを確認する必要があります。
引数:
slug_field
- ターゲットを表すために使用する必要があるターゲット上のフィールド。これは、特定のインスタンスを一意に識別するフィールドである必要があります。たとえば、username
です。必須queryset
- フィールド入力を検証する際にモデルインスタンスの検索に使用されるクエリセット。リレーションシップは、クエリセットを明示的に設定するか、read_only=True
を設定する必要があります。many
- 多対多のリレーションシップに適用する場合は、この引数をTrue
に設定する必要があります。allow_null
-True
に設定すると、フィールドは null 可能リレーションシップに対してNone
または空の文字列の値を受け入れます。デフォルトはFalse
です。
HyperlinkedIdentityField
このフィールドは、HyperlinkedModelSerializer の 'url'
フィールドのような ID リレーションシップとして適用できます。オブジェクトの属性にも使用できます。たとえば、次のシリアライザー
class AlbumSerializer(serializers.HyperlinkedModelSerializer):
track_listing = serializers.HyperlinkedIdentityField(view_name='track-list')
class Meta:
model = Album
fields = ['album_name', 'artist', 'track_listing']
は、このような表現にシリアライズされます
{
'album_name': 'The Eraser',
'artist': 'Thom Yorke',
'track_listing': 'http://www.example.com/api/track_list/12/',
}
このフィールドは常に読み取り専用です。
引数:
view_name
- リレーションシップのターゲットとして使用する必要があるビュー名。標準のルータークラスを使用している場合、これは<model_name>-detail
という形式の文字列になります。必須。lookup_field
- 参照されるビューのURLキーワード引数に対応する必要があり、検索に使用されるターゲット上のフィールド。デフォルトは'pk'
です。lookup_url_kwarg
- 検索フィールドに対応するURL confで定義されたキーワード引数の名前。デフォルトではlookup_field
と同じ値を使用します。format
- フォーマットサフィックスを使用している場合、ハイパーリンクフィールドは、format
引数を使用してオーバーライドしない限り、ターゲットに同じフォーマットサフィックスを使用します。
ネストされたリレーションシップ
以前に説明した別のエンティティへの参照とは対照的に、参照されるエンティティは、それを参照するオブジェクトの表現に埋め込まれたり、ネストされたりすることもあります。このようなネストされたリレーションシップは、シリアライザーをフィールドとして使用することで表現できます。
フィールドを多対多のリレーションシップを表すために使用する場合は、シリアライザーフィールドに many=True
フラグを追加する必要があります。
例
たとえば、次のシリアライザー
class TrackSerializer(serializers.ModelSerializer):
class Meta:
model = Track
fields = ['order', 'title', 'duration']
class AlbumSerializer(serializers.ModelSerializer):
tracks = TrackSerializer(many=True, read_only=True)
class Meta:
model = Album
fields = ['album_name', 'artist', 'tracks']
は、次のようなネストされた表現にシリアライズされます
>>> album = Album.objects.create(album_name="The Grey Album", artist='Danger Mouse')
>>> Track.objects.create(album=album, order=1, title='Public Service Announcement', duration=245)
<Track: Track object>
>>> Track.objects.create(album=album, order=2, title='What More Can I Say', duration=264)
<Track: Track object>
>>> Track.objects.create(album=album, order=3, title='Encore', duration=159)
<Track: Track object>
>>> serializer = AlbumSerializer(instance=album)
>>> serializer.data
{
'album_name': 'The Grey Album',
'artist': 'Danger Mouse',
'tracks': [
{'order': 1, 'title': 'Public Service Announcement', 'duration': 245},
{'order': 2, 'title': 'What More Can I Say', 'duration': 264},
{'order': 3, 'title': 'Encore', 'duration': 159},
...
],
}
書き込み可能なネストされたシリアライザー
デフォルトでは、ネストされたシリアライザーは読み取り専用です。ネストされたシリアライザーフィールドへの書き込み操作をサポートする場合は、子のリレーションシップをどのように保存する必要があるかを明示的に指定するために、create()
メソッドや update()
メソッドを作成する必要があります
class TrackSerializer(serializers.ModelSerializer):
class Meta:
model = Track
fields = ['order', 'title', 'duration']
class AlbumSerializer(serializers.ModelSerializer):
tracks = TrackSerializer(many=True)
class Meta:
model = Album
fields = ['album_name', 'artist', 'tracks']
def create(self, validated_data):
tracks_data = validated_data.pop('tracks')
album = Album.objects.create(**validated_data)
for track_data in tracks_data:
Track.objects.create(album=album, **track_data)
return album
>>> data = {
'album_name': 'The Grey Album',
'artist': 'Danger Mouse',
'tracks': [
{'order': 1, 'title': 'Public Service Announcement', 'duration': 245},
{'order': 2, 'title': 'What More Can I Say', 'duration': 264},
{'order': 3, 'title': 'Encore', 'duration': 159},
],
}
>>> serializer = AlbumSerializer(data=data)
>>> serializer.is_valid()
True
>>> serializer.save()
<Album: Album object>
カスタムリレーショナルフィールド
既存のリレーショナルスタイルが、必要な表現に適合しないまれなケースでは、モデルインスタンスから出力表現を生成する方法を正確に記述する、完全にカスタムのリレーショナルフィールドを実装できます。
カスタムリレーショナルフィールドを実装するには、RelatedField
をオーバーライドし、.to_representation(self, value)
メソッドを実装する必要があります。このメソッドは、フィールドのターゲットを value
引数として受け取り、ターゲットをシリアライズするために使用する必要がある表現を返す必要があります。value
引数は、通常、モデルインスタンスになります。
読み取り/書き込み可能なリレーショナルフィールドを実装する場合は、.to_internal_value(self, data)
メソッドも実装する必要があります。
context
に基づいて動的なクエリセットを提供するには、クラス上で.queryset
を指定したり、フィールドを初期化する際に指定する代わりに、.get_queryset(self)
をオーバーライドすることもできます。
例
例えば、トラックをその順序、タイトル、および時間を使用してカスタム文字列表現にシリアライズするリレーションフィールドを定義できます。
import time
class TrackListingField(serializers.RelatedField):
def to_representation(self, value):
duration = time.strftime('%M:%S', time.gmtime(value.duration))
return 'Track %d: %s (%s)' % (value.order, value.name, duration)
class AlbumSerializer(serializers.ModelSerializer):
tracks = TrackListingField(many=True)
class Meta:
model = Album
fields = ['album_name', 'artist', 'tracks']
このカスタムフィールドは、次のような表現にシリアライズされます。
{
'album_name': 'Sometimes I Wish We Were an Eagle',
'artist': 'Bill Callahan',
'tracks': [
'Track 1: Jim Cain (04:39)',
'Track 2: Eid Ma Clack Shaw (04:19)',
'Track 3: The Wind and the Dove (04:34)',
...
]
}
カスタムハイパーリンクフィールド
場合によっては、単一のルックアップフィールド以上のものを必要とするURLを表現するために、ハイパーリンクフィールドの動作をカスタマイズする必要があるかもしれません。
これは、HyperlinkedRelatedField
をオーバーライドすることで実現できます。オーバーライドできるメソッドは2つあります。
get_url(self, obj, view_name, request, format)
get_url
メソッドは、オブジェクトインスタンスをそのURL表現にマッピングするために使用されます。
view_name
およびlookup_field
属性がURL設定に正しく一致するように構成されていない場合、NoReverseMatch
が発生する可能性があります。
get_object(self, view_name, view_args, view_kwargs)
書き込み可能なハイパーリンクフィールドをサポートしたい場合は、受信URLをそれが表すオブジェクトにマッピングするために、get_object
もオーバーライドする必要があります。読み取り専用のハイパーリンクフィールドの場合、このメソッドをオーバーライドする必要はありません。
このメソッドの戻り値は、一致するURL設定引数に対応するオブジェクトである必要があります。
ObjectDoesNotExist
例外が発生する可能性があります。
例
例えば、次のように、2つのキーワード引数を取る顧客オブジェクトのURLがあるとします。
/api/<organization_slug>/customers/<customer_pk>/
これは、単一のルックアップフィールドのみを受け入れるデフォルトの実装では表現できません。
この場合、必要な動作を実現するためにHyperlinkedRelatedField
をオーバーライドする必要があります。
from rest_framework import serializers
from rest_framework.reverse import reverse
class CustomerHyperlink(serializers.HyperlinkedRelatedField):
# We define these as class attributes, so we don't need to pass them as arguments.
view_name = 'customer-detail'
queryset = Customer.objects.all()
def get_url(self, obj, view_name, request, format):
url_kwargs = {
'organization_slug': obj.organization.slug,
'customer_pk': obj.pk
}
return reverse(view_name, kwargs=url_kwargs, request=request, format=format)
def get_object(self, view_name, view_args, view_kwargs):
lookup_kwargs = {
'organization__slug': view_kwargs['organization_slug'],
'pk': view_kwargs['customer_pk']
}
return self.get_queryset().get(**lookup_kwargs)
このスタイルをジェネリックビューとともに使用したい場合は、正しいルックアップ動作を得るためにビューの.get_object
もオーバーライドする必要があることに注意してください。
一般的に、API表現には可能な限りフラットなスタイルをお勧めしますが、ネストされたURLスタイルも適度に使う場合は合理的です。
補足
queryset
引数
queryset
引数は、書き込み可能なリレーションシップフィールドでのみ必要とされます。その場合、プリミティブなユーザー入力からモデルインスタンスへのマッピングを行うモデルインスタンスのルックアップを実行するために使用されます。
バージョン2.xでは、ModelSerializer
クラスが使用されている場合、シリアライザクラスがqueryset
引数を自動的に決定できることがありました。
この動作は、書き込み可能なリレーションフィールドでは明示的なqueryset
引数を常に使用する動作に置き換えられました。
そうすることで、ModelSerializer
が提供する隠された「魔法」の量を減らし、フィールドの動作をより明確にし、ModelSerializer
のショートカットを使用したり、完全に明示的なSerializer
クラスを使用したりする間を簡単に移行できるようにします。
HTML表示のカスタマイズ
モデルの組み込みの__str__
メソッドは、choices
プロパティを設定するために使用されるオブジェクトの文字列表現を生成するために使用されます。これらの選択肢は、ブラウズ可能なAPIの選択HTML入力を設定するために使用されます。
このような入力のカスタム表現を提供するには、RelatedField
サブクラスのdisplay_value()
をオーバーライドします。このメソッドはモデルオブジェクトを受け取り、それを表すのに適切な文字列を返す必要があります。例:
class TrackPrimaryKeyRelatedField(serializers.PrimaryKeyRelatedField):
def display_value(self, instance):
return 'Track: %s' % (instance.title)
セレクトフィールドのカットオフ
ブラウズ可能なAPIでレンダリングされると、リレーションフィールドはデフォルトで最大1000個の選択可能な項目のみを表示します。より多くの項目が存在する場合は、「1000以上の項目…」と表示された無効なオプションが表示されます。
この動作は、非常に多くのリレーションシップが表示されるため、テンプレートが許容可能な時間内にレンダリングできなくなるのを防ぐことを目的としています。
この動作を制御するために使用できる2つのキーワード引数があります。
html_cutoff
- 設定した場合、これはHTMLセレクトドロップダウンによって表示される選択肢の最大数になります。制限を無効にするにはNone
に設定します。デフォルトは1000
です。html_cutoff_text
- 設定した場合、HTMLセレクトドロップダウンで最大項目数が切り捨てられた場合にテキストインジケーターが表示されます。デフォルトは"More than {count} items…"
です。
設定HTML_SELECT_CUTOFF
およびHTML_SELECT_CUTOFF_TEXT
を使用して、これらをグローバルに制御することもできます。
カットオフが適用されている場合、代わりにHTMLフォームでプレーンな入力フィールドを使用したい場合があります。style
キーワード引数を使用してそれを行うことができます。例:
assigned_to = serializers.SlugRelatedField(
queryset=User.objects.all(),
slug_field='username',
style={'base_template': 'input.html'}
)
逆リレーション
リバースリレーションシップは、ModelSerializer
クラスおよびHyperlinkedModelSerializer
クラスによって自動的に含まれるわけではないことに注意してください。リバースリレーションシップを含めるには、フィールドリストに明示的に追加する必要があります。例:
class AlbumSerializer(serializers.ModelSerializer):
class Meta:
fields = ['tracks', ...]
通常、フィールド名として使用できるリレーションシップに適切なrelated_name
引数が設定されていることを確認する必要があります。例:
class Track(models.Model):
album = models.ForeignKey(Album, related_name='tracks', on_delete=models.CASCADE)
...
リバースリレーションシップに関連名をまだ設定していない場合は、fields
引数で自動的に生成された関連名を使用する必要があります。例:
class AlbumSerializer(serializers.ModelSerializer):
class Meta:
fields = ['track_set', ...]
詳細については、リバースリレーションシップに関するDjangoドキュメントを参照してください。
ジェネリックリレーション
汎用外部キーをシリアライズする場合は、関係のターゲットをどのようにシリアライズするかを明示的に決定するために、カスタムフィールドを定義する必要があります。
例えば、他の任意のモデルとの汎用的な関係を持つタグの次のモデルがあるとします。
class TaggedItem(models.Model):
"""
Tags arbitrary model instances using a generic relation.
See: https://docs.djangoproject.com/en/stable/ref/contrib/contenttypes/
"""
tag_name = models.SlugField()
content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
object_id = models.PositiveIntegerField()
tagged_object = GenericForeignKey('content_type', 'object_id')
def __str__(self):
return self.tag_name
そして、タグが関連付けられている可能性のある次の2つのモデルがあります。
class Bookmark(models.Model):
"""
A bookmark consists of a URL, and 0 or more descriptive tags.
"""
url = models.URLField()
tags = GenericRelation(TaggedItem)
class Note(models.Model):
"""
A note consists of some text, and 0 or more descriptive tags.
"""
text = models.CharField(max_length=1000)
tags = GenericRelation(TaggedItem)
各インスタンスのタイプを使用して、シリアライズ方法を決定し、タグ付けされたインスタンスをシリアライズするために使用できるカスタムフィールドを定義できます。
class TaggedObjectRelatedField(serializers.RelatedField):
"""
A custom field to use for the `tagged_object` generic relationship.
"""
def to_representation(self, value):
"""
Serialize tagged objects to a simple textual representation.
"""
if isinstance(value, Bookmark):
return 'Bookmark: ' + value.url
elif isinstance(value, Note):
return 'Note: ' + value.text
raise Exception('Unexpected type of tagged object')
関係のターゲットをネストされた表現にする必要がある場合は、.to_representation()
メソッド内で必要なシリアライザを使用できます。
def to_representation(self, value):
"""
Serialize bookmark instances using a bookmark serializer,
and note instances using a note serializer.
"""
if isinstance(value, Bookmark):
serializer = BookmarkSerializer(value)
elif isinstance(value, Note):
serializer = NoteSerializer(value)
else:
raise Exception('Unexpected type of tagged object')
return serializer.data
GenericRelation
フィールドを使用して表現されたリバース汎用キーは、リレーションシップのターゲットのタイプが常に既知であるため、通常のリレーションフィールドタイプを使用してシリアライズできることに注意してください。
詳細については、汎用リレーションに関するDjangoドキュメントを参照してください。
Throughモデルを持つManyToManyFields
デフォルトでは、through
モデルが指定されたManyToManyField
をターゲットとするリレーションフィールドは読み取り専用に設定されます。
throughモデルを持つManyToManyField
を指すリレーションフィールドを明示的に指定する場合は、必ずread_only
をTrue
に設定してください。
throughモデルの追加フィールドを表現する場合は、throughモデルをネストされたオブジェクトとしてシリアライズできます。
サードパーティパッケージ
次のサードパーティパッケージも利用可能です。
DRFネストされたルーター
drf-nested-routersパッケージは、ネストされたリソースを操作するためのルーターとリレーションシップフィールドを提供します。
Rest Frameworkジェネリックリレーション
rest-framework-generic-relationsライブラリは、汎用外部キーの読み取り/書き込みシリアライズを提供します。