创建序列化程序以处理模型关系

Creating a Serializer to work with model relationships

我是 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 对象也包含 namename 字段(read_only=Truewrite_only=Truerequired=False)没有允许我创建和读取 Client 的配置,并且在以下情况下不需要创建 Invoice.

如何解决?

谢谢!

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

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