Django REST framework - 序列器关系

简介

数据结构而非算法是编程的核心。

— Rob Pike

关系字段用于表示模型间的关系。它们可以应用于 ForeignKeyManyToManyFieldOneToOneField 关系,以及反向关系和自定义关系(如 GenericForeignKey)。


注意: 关系字段在 relations.py 中声明,但按照惯例,应从 serializers 模块导入,使用 from rest_framework import serializers,并以 serializers. 的形式引用字段。



注意: REST框架不会自动优化传递给序列器的查询集,使其使用 select_relatedprefetch_related,因为这会显得过于“化魔术”。如果序列器的字段通过其 source 属性跨越了 ORM 关系,则可能需要额外的数据库查询来获取相关对象。程序员有责任优化查询,以避免在使用此类序列器时发生额外的数据库查询。

例如,以下序列器如果未预先获取相关对象,每次评估 tracks 字段时都会导致数据库查询:

class AlbumSerializer(serializers.ModelSerializer):
    tracks = serializers.SlugRelatedField(
        many=True,
        read_only=True,
        slug_field='title'
    )

    class Meta:
        model = Album
        fields = ['album_name', 'artist', 'tracks']

# 对于每个专辑对象,都会从数据库中获取 tracks
qs = Album.objects.all()
print(AlbumSerializer(qs, many=True).data)

如果使用 AlbumSerializer 序列化一个较大的查询集且 many=True,这可能会成为一个严重的性能问题。使用以下方式优化传递给 AlbumSerializer 的查询集:

qs = Album.objects.prefetch_related('tracks')
# 无需额外的数据库查询
print(AlbumSerializer(qs, many=True).data)

可以解决这个问题。


检查关系

当使用 ModelSerializer 类时,序列器字段和关系将自动为你生成。检查这些自动生成的字段可以作为确定如何自定义关系风格的有用工具。

为此,可以打开 Django shell(使用 python manage.py shell),导入序列器类,实例化它,然后打印对象表示:

>>> 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 可用于使用目标对象的 __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 可用于使用目标对象的主键来表示关系的目标。

例如,以下序列器:

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 可用于使用超链接来表示关系的目标。

例如,以下序列器:

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 :应作为关系目标使用的视图名称。如果你使用的是标准路由器类,这将是一个格式为 -detail 的字符串。必需
  • queryset :在验证字段输入时用于模型实例查找的查询集。关系必须明确设置查询集,或者设置 read_only=True

    • many :如果应用于多对多关系,应将此参数设置为 True
    • allow_null :如果设置为 True ,对于可为空的关系,字段将接受 None 或空字符串的值。默认为 False
    • lookup_field :目标对象上用于查找的字段。应与引用视图上的 URL 关键字参数对应。默认为 'pk'
    • lookup_url_kwarg :URL 配置中定义的与查找字段对应的关键词参数的名称。默认为使用与 lookup_field 相同的值。
    • format :如果使用格式后缀,超链接字段将为目标使用相同的格式后缀,除非通过设置 format 参数来覆盖。

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

超链接身份字段

此字段可以作为身份关系应用,例如 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 :应作为关系目标使用的视图名称。如果你使用的是标准路由器类,这将是一个格式为 -detail 的字符串。必需
  • lookup_field :目标对象上用于查找的字段。应与引用视图上的 URL 关键字参数对应。默认为 'pk'
  • lookup_url_kwarg :URL 配置中定义的查找与字段对应的关键词参数的名称。默认为使用与 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.objects.create(album=album, order=2, title='What More Can I Say', duration=264)

>>> Track.objects.create(album=album, order=3, title='Encore', duration=159)

>>> 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()


自定义关系字段

在极少数情况下,如果现有的任何关系风格都不适合你需要的表示,则可以实现一个完全自定义的关系字段,该字段精确地描述了如何从模型实例生成输出表示。

要实现自定义关系字段,应重载 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 配置,则可能会引发 NoReverseMatch 异常。

get_object(self, view_name, view_args, view_kwargs)

如果你想支持可写的超链接字段,则还需要重载 get_object,以便将传入的 URL 映射回它所表示的对象。对于只读的超链接字段,无需重载此方法。

此方法的返回值应为与匹配的 URL 配置参数对应的对象。

可能会引发 ObjectDoesNotExist 异常。

示例

假设我们有一个客户对象的 URL,它需要两个关键词参数,如下所示:

/api//customers//

这无法用默认实现表示,因为它只接受一个查找字段。

在这种情况下,我们需要重载 HyperlinkedRelatedField 以获得所需的行为:

from rest_framework import serializers
from rest_framework.reverse import reverse

class CustomerHyperlink(serializers.HyperlinkedRelatedField):
    # 我们将这些定义为类属性,因此无需将它们作为参数传递。
    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 风格也是可以接受的。


其他注意事项

查询集参数

查询集参数仅在可写关系字段中需要,在这种情况下,它用于执行模型实例查找,将原始用户输入映射到模型实例。

在 2.x 版本中,如果使用 ModelSerializer 类,则有时可以自动确定序列器类的查询集参数。

现在,这种行为已被替换为总是使用显式的查询集参数用于可写关系字段。

这样做减少了 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 项……”。

这种行为旨在防止由于显示非常大量的关系而导致模板无法在可接受的时间内渲染。

可以使用以下两个关键字参数来控制此行为:

  • html_cutoff :如果设置此参数,它将是 HTML 选择下拉菜单中显示的最大选择项数。设置为 None 可禁用任何限制。默认为 1000
  • html_cutoff_text :如果设置此参数,它将在 HTML 选择下拉菜单中显示一个文本指示器,表明已达到最大项数。默认为“超过 {count} 项……”。

你还可以通过设置 HTML_SELECT_CUTOFFHTML_SELECT_CUTOFF_TEXT 全局设置来控制这些行为。

在强制执行截止的情况下,你可能希望在 HTML 表单中使用一个普通的输入字段而不是选择字段。你可以通过 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', ...]

有关反向关系的更多信息,请参见 Django 文档。

泛型关系

如果你想要序列化泛型外键,则需要定义一个自定义字段,以明确确定如何序列化关系的目标。

例如,给定以下标签模型,它使用泛型关系与其他任意模型关联:

class TaggedItem(models.Model):
    """
    使用泛型关系标记任意模型实例。

    参见: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

以及以下两个模型,它们可以有关联的标签:

class Bookmark(models.Model):
    """
    书签由一个 URL 和 0 个或多个描述性标签组成。
    """
    url = models.URLField()
    tags = GenericRelation(TaggedItem)

class Note(models.Model):
    """
    笔记由一些文本和 0 个或多个描述性标签组成。
    """
    text = models.CharField(max_length=1000)
    tags = GenericRelation(TaggedItem)

我们可以定义一个自定义字段,用于序列化标记的实例,使用每个实例的类型来确定如何序列化它:

class TaggedObjectRelatedField(serializers.RelatedField):
    """
    用于 `tagged_object` 泛型关系的自定义字段。
    """

    def to_representation(self, value):
        """
        将标记对象序列化为简单的文本表示。
        """
        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):
        """
        使用书签序列器序列化书签实例,
        使用笔记序列器序列化笔记实例。
        """
        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 文档中的泛型关系部分。

具有中间模型的 ManyToManyFields

默认情况下,目标为具有指定 through 模型的 ManyToManyField 的关系字段被设置为只读。

如果你显式指定一个指向具有中间模型的 ManyToManyField 的关系字段,请确保将 read_only 设置为 True

如果你希望表示中间模型上的额外字段,则可以将中间模型序列化为嵌套对象。


第三方包

以下第三方包也是可用的:

  • DRF 嵌套路由器 :drf-nested-routers 包提供了用于处理嵌套资源的路由器和关系字段。
  • REST 框架通用关系 :rest - framework - generic - relations 库提供了用于通用外键的读写序列化。
  • REST 框架 gm2m 关系 :rest - framework - gm2m - relations 库提供了对 django - gm2m 的读写序列化支持。

你可能感兴趣的:(djangopython)