【问题标题】:Creating a Serializer to work with model relationships创建序列化程序以处理模型关系
【发布时间】:2020-05-05 16:22:36
【问题描述】:

我是 Django Rest Framework 的新手,想了解编写处理嵌套关系的序列化程序的公认做法是什么。

比如说,我有一个模型叫ClientInvoice(这只是一个说明性的例子):

class Client(models.Model)
    name = models.CharField(max_length=256)    


class Invoice(models.Model)
    client = models.ForeignKey(Client)
    date = models.DateTimeField()
    amount = models.DecimalField(max_digits=10, decimal_places=3)

我想为Client 创建一个支持以下用例的序列化程序:

  1. 创建一个Client
  2. 当我创建Invoice 时,请使用id 引用Client

假设我使用这个实现:

class ClientSerializer(serializers.ModelSerializer):
    class Meta:
        model = Client
        fields = ['id', 'name']


class InvoiceSerializer(serializers.ModelSerializer):
    client = ClientSerializer()

    class Meta:
        model = Invoice
        fields = ['id', 'client', 'date', 'amount']

    def create(self, data):
        client = Client.objects.get(pk=data['client']['id'])
        invoice = Invoice(client=client, 
                          date=datetime.fromisoformat(data['date']),
                          amount=Decimal(data['amount']))
        invoice.save()

使用此代码,如果我尝试创建 Invoice,我需要 POST 数据中的 client 对象也包含 name。没有name 字段(read_only=Truewrite_only=Truerequired=False)的配置允许我创建和读取Client,并且在创建Invoice 时不需要。

应该如何解决?

  • 请求中是否包含name 字段是公认的做法?
  • 我们能否以某种方式创建这样的嵌套模型? /api/Client/<id:client_id>/Invoice
  • 我们是否为每个模型创建多个 Serializer 类 - 一个用于它自己的视图集,另一个用于其他模型的视图集?

谢谢!

【问题讨论】:

    标签: django django-rest-framework django-serializer


    【解决方案1】:

    这是一种公认​​的做法,但它有其优点和缺点。实际的良好做法取决于您的实际需求。在这里,正如您所建议的,在创建发票时,您还需要在请求中发送客户名称,这不是必需的。为了克服这种需要,一种可能的做法如下:

    class ClientSerializer(serializers.ModelSerializer):
        class Meta:
            model = Client
            fields = ['id', 'name']
    
    
    class InvoiceSerializer(serializers.ModelSerializer):
        client = serializers.PrimaryKeyRelatedField(queryset=Client.objects.all())
    
        class Meta:
            model = Invoice
            fields = ['id', 'client', 'date', 'amount']
    

    使用这种方法,您只需在序列化程序中包含客户端的 ID。您只需要使用这种方法在请求集中发送一个客户端 ID,而无需在序列化程序上编写自定义创建方法。这种方法的缺点是;列出发票时您没有客户名称。因此,如果您需要在显示发票时显示客户名称,我们需要稍微改进一下这个解决方案:

    class InvoiceSerializer(serializers.ModelSerializer):
        client = serializers.PrimaryKeyRelatedField(queryset=Client.objects.all())
        client_details = ClientSerializer(source='client', read_only=True)
    
        class Meta:
            model = Invoice
            fields = ['id', 'client', 'client_details', 'date', 'amount']
    

    通过这种方法,我们添加了一个只读字段 client_details,它将数据保存在客户端序列化程序中。因此,对于写入操作,我们使用 client 字段,它只是一个 id,而要读取有关客户端的详细信息,我们使用 client_details 字段。

    另一种方法是定义一个单独的客户端序列化器,仅在 InvoiceSerializer 中用作子序列化器:

    class ClientSerializer(serializers.ModelSerializer):
        class Meta:
            model = Client
            fields = ['id', 'name']
    
    
    class InvoiceClientSerializer(serializers.ModelSerializer):
        name = serializers.CharField(read_only=True)
    
        class Meta:
            model = Client
            fields = ['id', 'name']
    
    class InvoiceSerializer(serializers.ModelSerializer):
        client = InvoiceClientSerializer()
    
        class Meta:
            model = Invoice
            fields = ['id', 'client', 'date', 'amount']
    
        def create(self, data):
            client = Client.objects.get(pk=data['client']['id'])
            invoice = Invoice(client=client, 
                              date=datetime.fromisoformat(data['date']),
                              amount=Decimal(data['amount']))
            invoice.save()
    

    在这种方法中,我们定义了一个特殊的客户端序列化程序,仅用于 InvoiceSerializer,其名称字段为只读。因此,在创建/更新发票时,您不需要发送客户名称,但在列出发票时,您会获得客户名称。这种方式的优势在于使用前一种方式,我们不需要为客户端字段使用两个单独的字段来写入和读取详细信息。

    对于您的第二个问题,DRF 不支持开箱即用,但您可以查看这个包,它提供了该功能并列在 DRF 自己的文档中:https://github.com/alanjds/drf-nested-routers

    【讨论】:

    • 非常感谢您的详细回复!我喜欢第一种和第三种方法,我自己也倾向于第三种方法。我不知道我可以用PrimaryKeyRelatedField 避免create() 方法! drf-nested-routers 看起来也很有用。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-12-14
    • 2020-07-30
    • 2017-10-30
    • 1970-01-01
    • 2017-05-09
    • 1970-01-01
    相关资源
    最近更新 更多