首页 > 解决方案 > 如何从模型 m2m 关系中预先选择自定义 DRF MultipleChoiceField 中的选项?

问题描述

我的Book模型可以Tag通过 m2m 关系拥有与之关联的模型,这些标签总是通过 FKTagCategory模型进一步分类。

class TagCategory(models.Model):
    name = models.CharField(...)

class Tag(models.Model):
    name = models.CharField(...)
    category = models.ForeignKey(to=TagCategory, ...)

class Book(models.Model):
    tags = models.ManyToManyField(to=Tag, ...)

使用来自 DRF 的序列化程序,我希望为最终用户BookSerializer提供一个MultipleChoiceField从中选择多个标签的选项。如果我使用serializers.ModelSerializer并且不触摸任何东西,它就可以正常工作。

但我实际上想按类别对标签/选择进行分组,这不是默认行为,所以我必须定义自己的MultipleChoiceField并在其中进行选择分组并提醒字段从模型的正确属性中choices获取它:sourceBook

class BookSerializer(serializers.ModelSerializer):
    tags_input = serializers.MultipleChoiceField(
        choices=[
            [category.name, [[tag.pk, tag.name]
                for tag in category.tags.all()]] for category in Category.objects.all()],
        source="tags",
        write_only=True,
    )
    fields = ("tags_input",)

这似乎适用于Book通过 m2m 创建模型和分配标签。但是,当使用标签序列化现有模型以进行更新时,初始标签/选项未按预期预先选择。设置initial="tags"似乎没有改变任何东西。

如何在序列化模型时成功检索和预选标签/选项Book.tags

标签: django-rest-frameworkdjango-serializer

解决方案


好的,所以我在这里的困惑是认为它ModelSerializer实际上会使用MultipleChoiceField,所以我可以击败它并自己定义它:不。tags是 m2m 关系,所以我需要一个RelatedFieldmany=True某个地方。

首先,由于我从同一个模型字段序列化和反序列化,我不应该做tags_input我想出的解决方法。我需要一个可以做所有事情的领域:

class TagChoiceRelatedField(serializers.RelatedField):
    queryset = Tags.objects.all()

    def to_representation(self, value):
        return value.pk

    def get_choices(self, cutoff=None):
        return {
            category.name:
                {tag.pk: tag.name for tag in category.tags.all()}
            for category in TagCategory.objects.all()
        }

    def to_internal_value(self, data):
        try:
            tag = self.get_queryset().get(pk=data)
        except ObjectDoesNotExist:
            raise ValidationError()

        return tag

class BookSerializer(serializers.ModelSerializer):
    tags = TagChoiceRelatedField(many=True)

    class Meta:
        fields = ("tags",)

序列化程序现在使用该字段来:

  • 预期用其 pk (to_representation) 序列化每个标签
  • 预期使用 pk (to_internal_value) 反序列化每个标签
  • 根据类别(get_choices)很好地对标签/选择进行分组
  • 如果适用,最后用书实例的标签初始化后者(内部的东西)

推荐阅读