Django REST framework API 指南(11):序列化·关系

官方原文连接
本系列文章 github 地址
转载请注明出处html

Serializer 关系

关系字段用于表示模型关系。 它们能够应用于 ForeignKeyManyToManyFieldOneToOneField 关系,反向关系以及 GenericForeignKey 等自定义关系。python

注意: 关系字段在 relations.py 中声明,但按照惯例,你应该从 serializers 模块导入它们,使用 from rest_framework import serializers 引入,并像 serializers.<FieldName> 这样引用字段。git

检查关系。

在使用 ModelSerializer 类时,将自动为你生成序列化字段和关系。检查这些自动生成的字段能够学习如何定制关系风格。github

为此,使用 python manage.py shell 打开 Django shell,而后导入序列化类,实例化它并打印对象表示形式...shell

>>> from myapp.serializers import AccountSerializer
>>> serializer = AccountSerializer()
>>> print repr(serializer)  # Or `print(repr(serializer))` in Python 3.x.
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 参考

为了解释各类类型的关系字段,咱们将为咱们的示例使用几个简单的模型。咱们的模型将使用音乐专辑,以及每张专辑中列出的曲目。django

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 __unicode__(self):
        return '%d: %s' % (self.order, self.title)
复制代码

StringRelatedField

StringRelatedField 用于使用 __unicode__ 方法表示关系。json

例如,下面的序列化类。api

class AlbumSerializer(serializers.ModelSerializer):
    tracks = serializers.StringRelatedField(many=True)

    class Meta:
        model = Album
        fields = ('album_name', 'artist', 'tracks')
复制代码

将序列化为如下形式。bash

{
    'album_name': 'Things We Lost In The Fire',
    'artist': 'Low',
    'tracks': [
        '1: Sunflower',
        '2: Whitetail',
        '3: Dinosaur Act',
        ...
    ]
}
复制代码

该字段是只读的。app

参数:

  • 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,那么该字段将接受 None 值或可为空的关系的空字符串。默认为 False
  • pk_field - 设置为一个字段来控制主键值的序列化/反序列化。例如, pk_field=UUIDField(format='hex') 会将 UUID 主键序列化为其紧凑的十六进制表示形式。

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 标志更改此行为。


注意:该字段是为映射到接受单个 URL 关键字参数的 URL 的对象而设计的,如使用 lookup_fieldlookup_url_kwarg 参数设置的对象。

这适用于包含单个主键或 slug 参数做为 URL 一部分的 URL。

若是须要更复杂的超连接表示,你须要自定义该字段,稍后会详解。


参数

  • view_name - 用做关系目标的视图名称。若是你使用的是标准路由器类,则这将是一个格式为 <modelname>-detail 的字符串。必填.
  • queryset - 验证字段输入时用于模型实例查询的查询集。必须显式地设置查询集,或设置 read_only=True
  • many - 若是应用于一对多关系,则应将此参数设置为 True
  • allow_null - 若是设置为 True,那么该字段将接受 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 用做读写字段时,一般须要确保 slug 字段与 unique=True 的模型字段相对应。

参数

  • slug_field - 用来表示目标的字段。这应该是惟一标识给定实例的字段。例如, username必填
  • queryset - 验证字段输入时用于模型实例查询的查询集。必须显式地设置查询集,或设置 read_only=True
  • many - 若是应用于一对多关系,则应将此参数设置为 True
  • allow_null - 若是设置为 True,那么该字段将接受 None 值或可为空的关系的空字符串。默认为 False

HyperlinkedIdentityField

此字段能够做为身份关系应用,例如 HyperlinkedModelSerializer 上的 'url' 字段。它也能够用于对象的属性。例如,如下序列化类:

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 - 用做关系目标的视图名称。若是你使用的是标准路由器类,则这将是一个格式为 <modelname>-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 的动态查询集,还能够覆盖 .get_queryset(self),而不是在类上指定 .queryset 或初始化该字段。

举个栗子

例如,咱们能够定义一个关系字段,使用它的顺序,标题和持续时间将音轨序列化为自定义字符串表示。

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 来实现此目的。有两个能够被覆盖的方法:

get_url(self, obj, view_name, request, format)

get_url 方法用于将对象实例映射到其 URL 表示。

若是 view_namelookup_field 属性未配置为正确匹配 URL conf,可能会引起 NoReverseMatch

get_object(self, queryset, view_name, view_args, view_kwargs)

若是您想支持可写的超连接字段,那么您还须要重写 get_object,以便将传入的 URL 映射回它们表示的对象。对于只读超连接字段,不须要重写此方法。

此方法的返回值应该是与匹配的 URL conf 参数对应的对象。

可能会引起 ObjectDoesNotExist 异常。

举个栗子

假设咱们有一个带有两个关键字参数的 customer 对象的 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 在内部帮咱们完成的工做),使字段的行为更加清晰,并确保在使用 ModelSerializer 快捷方式(高度封装过,使用简单)或使用彻底显式的 Serializer 类之间转换是微不足道的。

自定义 HTML 显示

模型内置的 __str__ 方法用来生成用于填充 choices 属性的对象的字符串表示形式。这些 choices 用于在可浏览的 API 中填充选择的 HTML input。

要为这些 input 提供自定义表示,请重写 RelatedField 子类的 display_value() 方法。这个方法将接收一个模型对象,而且应该返回一个适合表示它的字符串。例如:

class TrackPrimaryKeyRelatedField(serializers.PrimaryKeyRelatedField):
    def display_value(self, instance):
        return 'Track: %s' % (instance.title)
复制代码

Select field cutoffs

在渲染可浏览的 API 关系字段时,默认只显示最多 1000 个可选 item。若是存在更多项目,则会显示 "More than 1000 items…" 的 disabled 选项。

此行为旨在防止因为显示大量关系而致使模板没法在可接受的时间范围内完成渲染。

有两个关键字参数可用于控制此行为:

  • html_cutoff - 设置 HTML select 下拉菜单中显示的选项的最大数量。设置为 None 能够禁用任何限制。默认为 1000
  • html_cutoff_text - 设置一个文本字符串,在 HTML select 下拉菜单超出最大显示数量时显示。默认是 "More than {count} items…"

你还能够在 settings 中用 HTML_SELECT_CUTOFFHTML_SELECT_CUTOFF_TEXT 来全局控制这些设置。

在强制执行 cutoff 的状况下,您可能但愿改成在 HTML 表单中使用简单的 input 字段。你可使用 style 关键字参数来作到这一点。例如:

assigned_to = serializers.SlugRelatedField(
   queryset=User.objects.all(),
   slug_field='username',
   style={'base_template': 'input.html'}
)
复制代码

反向关系

请注意,反向关系不会自动包含在 ModelSerializerHyperlinkedModelSerializer 类中。要包含反向关系,您必须明确将其添加到字段列表中。例如:

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', ...)
复制代码

通用关系

若是要序列化通用外键,则须要自定义字段,以明确肯定如何序列化关系。

例如,给定一个如下模型的标签,该标签与其余任意模型具备通用关系:

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 __unicode__(self):
        return self.tag_name
复制代码

如下两种模式能够用相关的标签:

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 字段表示的反向通用键可使用常规关系字段类型进行序列化,由于关系中目标的类型老是已知的。

具备 Through 模型的 ManyToManyFields

默认状况下,将指定带有 through 模型的 ManyToManyField 的关系字段设置为只读。

若是你要明确指定一个指向具备 through 模型的 ManyToManyField 的关系字段,请确保将 read_only 设置为 True

相关文章
相关标签/搜索