创建序列化程序以处理模型关系
Creating a Serializer to work with model relationships
我是 Django Rest Framework 的新手,想了解编写处理嵌套关系的序列化程序的公认做法是什么。
说,我有一个名为 Client
和 Invoice
的模型(这只是一个说明性示例):
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
创建一个支持以下用例的序列化程序:
- 创建一个
Client
- 当我创建
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=True
、write_only=True
、required=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 中使用的特殊客户端序列化程序,其名称字段为只读。因此,在创建/更新发票时,您不需要发送客户名称,但在列出发票时,您会获得客户名称。这种方法的优点是使用前一种方法,我们不需要使用两个单独的字段用于客户端字段的写入和读取细节。
对于你的第二个问题,DRF 不支持开箱即用,但你可以看看这个包,它提供了该功能并在 DRF 自己的文档中列出:https://github.com/alanjds/drf-nested-routers
我是 Django Rest Framework 的新手,想了解编写处理嵌套关系的序列化程序的公认做法是什么。
说,我有一个名为 Client
和 Invoice
的模型(这只是一个说明性示例):
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
创建一个支持以下用例的序列化程序:
- 创建一个
Client
- 当我创建
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=True
、write_only=True
、required=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 中使用的特殊客户端序列化程序,其名称字段为只读。因此,在创建/更新发票时,您不需要发送客户名称,但在列出发票时,您会获得客户名称。这种方法的优点是使用前一种方法,我们不需要使用两个单独的字段用于客户端字段的写入和读取细节。
对于你的第二个问题,DRF 不支持开箱即用,但你可以看看这个包,它提供了该功能并在 DRF 自己的文档中列出:https://github.com/alanjds/drf-nested-routers