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



我是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时也不需要。

该如何解决?

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

谢谢!

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

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中使用的specia客户端序列化程序,其名称字段为只读。因此,在创建/更新发票时,您不需要发送客户名称,但在列出发票时,可以获得客户名称。与使用前的方法相比,这种方法的优势在于,我们不需要为客户端字段使用两个单独的字段来编写和阅读详细信息。

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

相关内容

  • 没有找到相关文章

最新更新