关系字段用于表示模型关系。它们可以应用于 ForeignKey
、ManyToManyField
和OneToOneField
关系,也可以应用于反向关系和自定义关系(如GenericForeignKey
)。
注意:关系字段是在relations.py中声明的,但按照惯例,您应该使用 from rest_framework import serializers
从 serializers
模块导入它们,并将字段引用为 serializers.<FieldName>
注意:REST框架不会尝试根据 select_related
和 prefetc_related
自动优化传递给序列化程序的查询集,因为这太神奇了。具有通过其源属性跨越orm关系的字段的序列化程序可能需要额外的数据库命中才能从数据库中获取相关对象。程序员有责任优化查询,以避免在使用此类序列化程序时可能发生的额外数据库命中。
例如,如果未预取tracks字段,则以下序列化程序每次评估tracks字段时都会导致数据库命中:
py
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
的查询集:
py
qs = Album.objects.prefetch_related('tracks')
# No additional database hits required
print(AlbumSerializer(qs, many=True).data)
会解决此问题。
检查关系
使用ModelSerializer类时,将自动为您生成序列化程序字段和关系。检查这些自动生成的字段是确定如何自定义关系样式的有用工具。
要做到这一点,请使用 python manage.py shell
打开Django shell,然后导入序列化程序类,实例化它,并打印对象表示...
py
>>> 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())
1 api 参考
为了解释各种类型的关系字段,我们将使用几个简单的模型作为示例。我们的模型将用于音乐专辑,以及每张专辑中列出的曲目。
py
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)
1.1 StringRelatedField
StringRelatedField
可用于使用其__str__
方法表示关系的目标。
例如,以下序列化程序:
py
class AlbumSerializer(serializers.ModelSerializer):
tracks = serializers.StringRelatedField(many=True)
class Meta:
model = Album
fields = ['album_name', 'artist', 'tracks']
将序列化为以下表示形式:
py
{
'album_name': 'Things We Lost In The Fire',
'artist': 'Low',
'tracks': [
'1: Sunflower',
'2: Whitetail',
'3: Dinosaur Act',
...
]
}
此字段为只读字段。
参数:
many
- 如果应用于多对多关系,则应将此参数设置为True
。
1.2 PrimaryKeyRelatedField
PrimaryKeyRelatedField可用于使用其主键来表示关系的目标。
例如,以下序列化程序:
py
class AlbumSerializer(serializers.ModelSerializer):
tracks = serializers.PrimaryKeyRelatedField(many=True, read_only=True)
class Meta:
model = Album
fields = ['album_name', 'artist', 'tracks']
将序列化为以下表示形式:
py
{
'album_name': 'Undun',
'artist': 'The Roots',
'tracks': [
89,
90,
91,
...
]
}
默认情况下,此字段是读写的,尽管您可以使用read_only
标志更改此行为。
参数:
queryset
- 验证字段输入时用于模型实例查找的queryset。关系必须显式设置查询集,或者设置read_only=True
。many
- 如果应用于多对多关系,则应将此参数设置为True
。allow_null
- 如果设置为True
,则字段将接受None
值或可为null关系的空字符串。默认为False
。pk_field
--- 设置为用于控制主键值的序列化/反序列化的字段。例如,pk_field=UUIDField(format='hex')
会将UUID主键序列化为其紧凑的十六进制表示。
1.3 HyperlinkedRelatedField
HyperlinkedRelatedField可用于使用超链接来表示关系的目标。
例如,以下序列化程序:
py
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']
将序列化为以下表示形式:
py
{
'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
标志更改此行为。
注意:此字段是为映射到接受单个URL关键字参数的URL的对象设计的,如使用lookup_field
和lookup_url_kwarg
参数设置的。
这适用于包含单个主键或段塞参数作为URL一部分的URL。
如果需要更复杂的超链接表示,则需要自定义字段,如下面的自定义超链接字段部分所述。
参数:
view_name
- 应用作关系目标的视图名称。如果您使用的是标准路由器类,这将是一个格式为<modelname>-detail
的字符串。必需。
queryset
- 验证字段输入时用于模型实例查找的queryset。关系必须显式设置查询集,或者设置 read_only=True
。
many
- 如果应用于多对多关系,则应将此参数设置为True。
allow_null
-如果设置为True,则字段将接受None值或可为null关系的空字符串。默认为False。
lookup_field
- 目标上应用于查找的字段。应与引用视图上的URL关键字参数相对应。默认值为'pk'
。
lookup_url_kwarg
- 在url conf中定义的关键字参数的名称,该名称对应于查找字段。默认使用与lookup_field
相同的值。
format
- 如果使用格式后缀,则超链接字段将对目标使用相同的格式后缀,除非使用 format
参数重写。
1.4 SlugRelatedField
SlugRelatedField
可用于使用目标上的字段来表示关系的目标。
例如,以下序列化程序:
py
class AlbumSerializer(serializers.ModelSerializer):
tracks = serializers.SlugRelatedField(
many=True,
read_only=True,
slug_field='title'
)
class Meta:
model = Album
fields = ['album_name', 'artist', 'tracks']
将被序列化为如下表示形式:
py
{
'album_name': 'Dear John',
'artist': 'Loney Dear',
'tracks': [
'Airport Surroundings',
'Everything Turns to You',
'I Was Only Going Out',
...
]
}
默认情况下,此字段是读写的,尽管您可以使用 read_only
标志更改此行为。
当使用SlugRelatedField作为读写字段时,通常需要确保 slug 字段对应于unique=True
的模型字段。
参数:
-
slug_field
- 目标上用来表示目标的字段。这应该是一个唯一标识任何给定实例的字段。例如,username
。必需 -
queryset
- 验证字段输入时用于模型实例查找的queryset。关系必须显式设置查询集,或者设置read_only=True
。 -
many
- 如果应用于多对多关系,则应将此参数设置为True。 -
allow_null
- 如果设置为True
,则字段将接受None
值或可为null关系的空字符串。默认为False
。
1.5 HyperlinkedIdentityField
此字段可以作为标识关系应用,例如 HyperlinkedModelSerializer
上的 'url'
字段。它也可以用于对象上的属性。例如,以下序列化程序:
py
class AlbumSerializer(serializers.HyperlinkedModelSerializer):
track_listing = serializers.HyperlinkedIdentityField(view_name='track-list')
class Meta:
model = Album
fields = ['album_name', 'artist', 'track_listing']
将会被序列化为如下表达式:
py
{
'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
参数重写。
2 嵌套关系
与前面讨论的对另一个实体的引用相反,被引用的实体也可以嵌入或嵌套在引用它的对象的表示中。这种嵌套关系可以通过使用序列化程序作为字段来表示。
如果该字段用于表示多对多关系,则应将many=True标志添加到序列化程序字段中。
2.1 示例
py
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']
将序列化为如下嵌套表达式:
py
>>> 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},
...
],
}
2.2 可写嵌套序列化程序
默认情况下,嵌套的序列化程序是只读的。如果您想支持对嵌套序列化程序字段的写操作,则需要创建 create()
和/或 update()
方法,以便显式指定应如何保存子关系:
py
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>
3 自定义关系字段
在极少数情况下,现有的关系样式都不适合您需要的表示形式,您可以实现一个完全自定义的关系字段,该字段准确地描述了如何从 model instance 生成输出表示形式。
要实现自定义关系字段,您应该重写 RelatedField
,并实现 .to_representation(self, value)
方法。此方法将字段的目标作为值参数,并应返回用于序列化目标的表示形式。值参数通常是一个模型实例。
如果要实现读写关系字段,还必须实现 .to_internal_value(self,data)
方法。
要提供基于上下文的动态查询集,还可以重写 .get_queryset(self)
,而不是在类上或初始化字段时指定 .queryset
。
3.1 示例
例如,我们可以定义一个关系字段,使用其 order 顺序、title 标题和 dration 持续时间将歌曲序列化为自定义字符串表示:
py
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.title, duration)
class AlbumSerializer(serializers.ModelSerializer):
tracks = TrackListingField(many=True)
class Meta:
model = Album
fields = ['album_name', 'artist', 'tracks']
将会序列化为如下表示形式:
py
{
'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)',
...
]
}
4 自定义超链接字段
在某些情况下,您可能需要自定义超链接字段的行为,以便表示需要多个查找字段的URL。
您可以通过覆盖 HyperlinkedRelatedField
来实现这一点。有两种方法可以被重写:
get_url(self、obj、view_name、request、format)
get_url
方法用于将对象实例映射到其 url
表示形式。
如果 view_name
和 lookup_field
属性未配置为正确匹配URL conf,则可能引发NoReverseMatch
。
get_object(self、view_name、view_args、view_kwargs)
如果您想支持可写的超链接字段,那么您还需要覆盖 get_object
,以便将传入的URL映射回它们所代表的对象。对于只读超链接字段,不需要重写此方法。
此方法的返回值应该是与匹配的URL-conf参数相对应的对象。
可能引发ObjectDoesNotExist
异常。
4.1 示例
假设我们有一个 customer object 的URL,它接受两个关键字参数,如下所示:
/api/<organization_slug>/customers/<customer_pk>/
这不能用默认实现来表示,默认实现只接受单个查找字段。
在这种情况下,我们需要覆盖HyperlinkedRelatedField以获得我们想要的行为:
py
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样式也是合理的。
5 进一步说明
5.1 The queryset
argument
queryset
参数只对可写关系字段是必需的,在这种情况下,它用于执行从原始用户输入映射到模型实例的模型实例查找。
在2.x版本中,如果正在使用 ModelSerializer
类,序列化程序类有时可以自动确定 queryset
参数。
此行为现在被替换为始终使用可写关系字段的显式查询集参数。
这样做可以减少 ModelSerializer
提供的隐藏"魔法"数量,使字段的行为更加清晰,并确保在使用 ModelSerializer
快捷方式或使用完全显式的 Serializer
类之间切换是微不足道的。
5.2 自定义HTML显示
The built-in __str__
method of the model will be used to generate string representations of the objects used to populate the choices property.
这些选择用于在可浏览的API中填充 select
HTML input
。
要为此类输入提供自定义表示,请重写 RelatedField
子类的 display_value()
。此方法将接收一个模型对象,并应返回一个适合表示它的字符串。例如:
py
class TrackPrimaryKeyRelatedField(serializers.PrimaryKeyRelatedField):
def display_value(self, instance):
return 'Track: %s' % (instance.title)
5.3 Select field 截断
当在可浏览的API中呈现时,关系字段将默认为最多只显示1000个可选项。如果存在更多项目,则将显示一个禁用选项,其中包含"超过1000个项目..."。
此行为旨在防止模板由于显示了大量关系而无法在可接受的时间跨度内进行渲染。
有两个关键字参数可用于控制此行为:
html_cutoff-如果设置,这将是html选择下拉菜单显示的最大选择数。设置为"无"可禁用任何限制。默认值为1000。
html_cutoff_text-如果设置此项,则如果html选择下拉菜单中的最大项目数已被截断,则会显示一个文本指示符。默认为"超过{count}个项目..."
您也可以使用设置HTML_SELECT_CUTOFF_TEXT全局控制这些。
在强制执行截断的情况下,您可能希望在HTML表单中使用纯输入字段。您可以使用style关键字参数来执行此操作。例如:
py
assigned_to = serializers.SlugRelatedField(
queryset=User.objects.all(),
slug_field='username',
style={'base_template': 'input.html'}
)
5.4 反向关系
请注意,ModelSerializer和HyperlinkedModelSerializeer类不会自动包含反向关系。若要包含反向关系,必须将其显式添加到字段列表中。例如:
py
class AlbumSerializer(serializers.ModelSerializer):
class Meta:
fields = ['tracks', ...]
通常,您需要确保在关系上设置了一个适当的related_name参数,可以用作字段名。例如:
py
class Track(models.Model):
album = models.ForeignKey(Album, related_name='tracks', on_delete=models.CASCADE)
...
如果没有为反向关系设置相关名称,则需要在fields参数中使用自动生成的相关名称。例如:
py
class AlbumSerializer(serializers.ModelSerializer):
class Meta:
fields = ['track_set', ...]
5.5 一般关系
如果要序列化通用外键,则需要定义一个自定义字段,以明确确定如何序列化关系的目标。
例如,给定标签的以下模型,该模型与其他任意模型具有通用关系:
py
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
以及以下两个模型,它们可能具有关联的标签:
py
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)
我们可以定义一个自定义字段,该字段可用于序列化标记的实例,使用每个实例的类型来确定应如何序列化:
py
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()
方法中使用所需的序列化程序:
py
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文档
5.6 ManyToManyFields with a Through Model
默认情况下,以指定了 Through Model 的 ManyToManyField
为目标的关系字段设置为只读。
如果显式指定指向具有 Through Model 的ManyToManyField
的关系字段,请确保将 read_only
设置为 True
。
如果希望表示Through Model 上的额外字段,则可以将直通模型序列化为嵌套对象。