DRF:从嵌套序列化器验证方法中检索外部输入数据



我正在使用django-rest-framework的序列化器。我有两个连续化器一个嵌套。

class NestedSerializer(serializers.Serializer):
    value = AttributeValueField(required=True)
    name = serializers.CharField(required=True)
class OuterSerializer(serializers.Serializer):
    info = serializers.CharField()
    nested = NestedSerializer()

为了验证嵌套序列化器的数据,我需要从父序列机中检索输入数据,类似的内容:

class NestedSerializer(serializers.Serializer):
    ...
   def validate(self, data):
       # of course, it doesn't work, but thats the idea.
       info = self.parent.info
       # then validate the NestedSerializer with info.

我找不到从验证方法访问这些输入数据的任何方法。有什么建议么?感谢您的帮助:)。

validate()方法之前,DRF序列化器调用to_internal_value(self, data)。您将在那里获取父序列机的所有数据。因此,当您定义了序列化器中的validate()方法时,定义to_internal_value()方法并捕获Parent Serializer的数据。

您可以从嵌套序列化器validate()方法上访问父序列化合物的initial_data。我还添加了一些用于使用父字段run_validation()方法的代码,该方法将验证并返回to_internal_value()的内部值,这可能比处理初始数据更好。

class NestedSerializer(serializers.Serializer):
   def validate(self, data):
       # Retrieve the initial data, perhaps this is all you need.
       parent_initial_data = self.parent.initial_data
       info = parent_initial_data.get("info", None)
       # Get the corresponding field and use `run_validation` or `to_internal_value` if needed
       if info:
           info_field = self.parent.fields["info"]
           info = info_field.run_validation(info)
           # info = info_field.to_internal_value(info)  # If you don't want validation, but do want the internal value
       # Do your thing
       return data

尝试self.root.instance以在嵌套的序列化器中获取父实例。

这可能不是最好的想法,NestedSerializer不应意识到父对象。这将使您的代码难以维护,这也将使嵌套的器依赖于OuterSerializer。

相反,在OuterSerializer中定义validate(self, data)方法并在那里运行相互验证。

这是我现在正在做的事情,但我很想看到其他答案。

基本上,我已经为父序列机中的字段创建了一个自定义字段,该字段需要在子序列机中访问 - 在这种情况下为"客户"。然后覆盖to_internal_value()以将字段的验证数据添加为父序列机上的属性。

作为属性添加后,可以通过self.parent.<attribute_name>self.root.<attribute_name>

在子序列化器上访问它的子序列化器或访问子序列化器字段。
class CustomerField(serializers.PrimaryKeyRelatedField):
    def to_internal_value(self, data):
        # Set the parent serializer's `customer` attribute to the validated
        # Customer object.
        ret = super().to_internal_value(data)
        self.parent.customer = ret
        return ret
class DebitField(serializers.PrimaryKeyRelatedField):
    default_related_name = {
        'OnAccount': 'onaccounts',
        'Order': 'orders'
    }
    def get_queryset(self):
        # Method must be overridden so the `queryset` argument is not required.
        return super().get_queryset()
    def set_queryset_from_context(self, model_name):
        # Override the queryset depending on the model name.
        queryset = self.default_related_name[model_name]
        self.queryset = getattr(self.parent.customer, queryset)
    def to_internal_value(self, data):
        # Get the model from the `debit_type` and the object id from `debit`
        # then validate that the object exists in the related queryset.
        debit_type = data.pop('debit_type')
        self.set_queryset_from_context(debit_type)
        super().to_internal_value(data)
class PaymentLineSerializer(serializers.ModelSerializer):
    debit = DebitField()
    class Meta:
        model = PaymentLine
        fields = (
            'id',
            'payment',
            'debit_type',
            'debit',  # GenericForeignKey
            'amount',
        )
    def to_internal_value(self, data, *args):
        data['debit'] = {
            'debit': data.pop('debit'),
            'debit_type': data.pop('debit_type'),
        }
        ret = super().to_internal_value(data)
        return ret
    def to_representation(self, instance):
        data = super().to_representation(instance)
        data['debit'] = instance.debit._meta.object_name
        return data
class PaymentSerializer(serializers.ModelSerializer):
    customer = CustomerField(queryset=Customer.objects.all())
    class Meta:
        model = Payment
        fields = (
            'id',
            'customer',
            'method',
            'type',
            'date',
            'num_ref',
            'comment',
            'amount',
        )
    def __init__(self, *args, **kwargs):
        self.customer = None
        super().__init__(*args, **kwargs)
        self.fields['lines'] = PaymentLineSerializer(
            context=self.context,
            many=True,
            write_only=True,
        )

你几乎在那里!

使用 self.parent.initial_data 访问给出的数据给出的数据。

class NestedSerializer(serializers.Serializer):
    value = AttributeValueField(required=True)
    name = serializers.CharField(required=True)
    def validate(self, attrs):
        attrs = super().validate(attrs)
        the_input_data = self.parent.initial_data
        info = the_input_data['info']  # this will not be the "validated data
        # do something with your "info"
        return attrs

不要硬码field_name

self.parent.initial_data[self.field_name]

最新更新