DRF:使用嵌套序列化程序进行简单的外键分配?

Joh*_*ork 52 python django django-rest-framework

使用Django REST Framework,标准的ModelSerializer将允许通过将ID作为整数进行POST来分配或更改ForeignKey模型关系.

从嵌套的序列化器中获取此行为的最简单方法是什么?

注意,我只讨论分配现有数据库对象,而不是嵌套创建.

我过去已经在序列化程序中添加了"id"字段以及自定义createupdate方法,但这对我来说是一个看似简单且常见的问题,我很想知道最好的方法.

class Child(models.Model):
    name = CharField(max_length=20)

class Parent(models.Model):
    name = CharField(max_length=20)
    phone_number = models.ForeignKey(PhoneNumber)
    child = models.ForeignKey(Child)

class ChildSerializer(ModelSerializer):
    class Meta:
        model = Child

class ParentSerializer(ModelSerializer):
    # phone_number relation is automatic and will accept ID integers
    children = ChildSerializer() # this one will not

    class Meta:
        model = Parent
Run Code Online (Sandbox Code Playgroud)

jos*_*son 40

以下是Kevin的答案所讨论的一个例子,如果你想采用这种方法并使用2个单独的字段.

在你的models.py中......

class Child(models.Model):
    name = CharField(max_length=20)

class Parent(models.Model):
    name = CharField(max_length=20)
    phone_number = models.ForeignKey(PhoneNumber)
    child = models.ForeignKey(Child)
Run Code Online (Sandbox Code Playgroud)

然后serializers.py ...

class ChildSerializer(ModelSerializer):
    class Meta:
        model = Child

class ParentSerializer(ModelSerializer):
    # if child is required
    child = ChildSerializer(read_only=True) 
    # if child is a required field and you want write to child properties through parent
    # child = ChildSerializer(required=False)
    # otherwise the following should work (untested)
    # child = ChildSerializer() 

    child_id = serializers.PrimaryKeyRelatedField(
        queryset=Child.objects.all(), source='child', write_only=True)

    class Meta:
        model = Parent
Run Code Online (Sandbox Code Playgroud)

设置source=child允许child_id作为子进程默认情况下不会被覆盖(我们想要的行为).child_id使child_id可以写入,但防止它显示在响应中,因为id已经显示在ChildSerializer中

  • 我收到以下错误消息:`调用Parent.objects.create()时出现TypeError。这可能是因为您在序列化器类上有一个可写字段,该字段不是Parent.objects.create()的有效参数。您可能需要将该字段设置为只读,或重写ParentSerializer.create()方法以正确处理此问题。 (3认同)

Kev*_*own 38

这里最好的解决方案是使用两个不同的字段:一个用于阅读,另一个用于写入.如果不做一些繁重的工作,很难在一个领域找到你想要的东西.

只读字段将是您的嵌套序列化程序(ChildSerializer在本例中),它将允许您获得您期望的相同嵌套表示.大多数人将此定义为公正child,因为他们已经通过这一点编写了前端并且更改它会导致问题.

只写字段将是a PrimaryKeyRelatedField,这是您通常用于根据主键分配对象的字段.这不一定是只写的,特别是如果你试图在接收的内容和发送的内容之间保持对称,但听起来这可能最适合你.该字段应该有一个source设置为(外键字段child在这个例子中),因此适当分配给它的创建和更新.


这已经在讨论组中提出过几次,我认为这仍然是最好的解决方案.感谢Sven Maurer指出它.


JPG*_*JPG 32

使用两个不同的领域将是确定(如@Kevin布朗@joslarson提到的),但我认为这不是完美的(对我来说).因为从一个密钥(child)获取数据并将数据发送到另一个密钥(child_id)可能对前端开发人员来说有点模棱两可.(完全没有冒犯)


那么,我在这里建议的是,覆盖将要完成工作的to_representation()方法ParentSerializer.

def to_representation(self, instance):
    response = super().to_representation(instance)
    response['child'] = ChildSerializer(instance.child).data
    return response
Run Code Online (Sandbox Code Playgroud)



完整的Serializer表示

class ChildSerializer(ModelSerializer):
    class Meta:
        model = Child
        fields = '__all__'


class ParentSerializer(ModelSerializer):
    class Meta:
        model = Parent
        fields = '__all__'

    def to_representation(self, instance):
        response = super().to_representation(instance)
        response['child'] = ChildSerializer(instance.child).data
        return response
Run Code Online (Sandbox Code Playgroud)



这种方法的优点?

通过使用此方法,我们不需要两个单独的字段来创建和读取.在这里,创建和阅读都可以通过使用child 密钥来完成.


示例有效负载以创建parent实例

{
        "name": "TestPOSTMAN_name",
        "phone_number": 1,
        "child": 1
    }
Run Code Online (Sandbox Code Playgroud)



截图
POSTMAN截图

  • 我很惊讶 DRF 需要解决这个问题才能做到这一点。像这里的大多数人一样,典型的用例是将ForeignKey 对象作为数据返回,但接受它们作为PK。我认为这实际上是比创建或专门读取外键更常见的用例...... (7认同)
  • 我好几天都在寻找这样的答案.这种简单很美.+1肯定. (4认同)
  • 这是一个优雅的解决方案,但有一些缺点。drf 的模式生成器无法检测嵌套序列化程序,因此模式只会将该字段显示为 PrimaryKeyRelatedField。对于某些项目来说可能是可以接受的,但是当你想用 redoc 或 swagger 来展示你的 API 模式时,这可能是一个问题。所以我更喜欢两场解决方案,即使它并不简单和漂亮。 (3认同)
  • 你的通用方法看起来对我很有吸引力,但我总是得到 `unhashable type: 'ReturnDict'` - 虽然 `to_representation` 方法工作正常 - 小心猜测可能是什么问题?也许我会就此提出一个具体问题。 (3认同)