创建一个序列化器来处理模型关系

问题描述 投票:0回答:1

我是Django Rest Framework的新手,想了解一下,在编写Serializer时,有哪些公认的做法是可以使用嵌套关系的。

比如说,我有一个叫做 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,请参考 Client 用其 id.

比方说,我使用这个实现。

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我需要的是 client 对象,在POST数据中包含 name 也是。没有配置的 name 领域(read_only=True, write_only=True, required=False),让我可以创建和读取 Client 以及创建时不需要 Invoice.

如何解决这个问题?

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

谢谢!我是Django Rest Framework的新手,我想了解一下,在编写Serializer的时候,有什么公认的做法,可以让Serializer在其他模型的视图集上使用?

django django-rest-framework django-serializer
1个回答
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。使用这种方法,你只需要在requset中发送一个客户端id,而不需要在serializer上写一个自定义的create方法。这种方法的缺点是;你在列出发票时没有客户名称,所以如果你需要在显示发票时显示客户名称,我们需要改进一下这个解决方案。

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']

通过这种方法,我们增加了一个只读字段。客户端详情,将数据保存在客户端的serilaizer中。所以对于写操作,我们使用 客户 字段,这只是一个id,如果要读取客户的详细信息,我们使用的是 客户端详情 字段。

另一种方法是定义一个单独的客户端序列化器,作为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.comalanjdsdrf-nested-routers。

© www.soinside.com 2019 - 2024. All rights reserved.