NeilG
NeilG

Reputation: 4160

In Django Rest Framework how can I return errors (warnings) from validation / serialization without failing the validation?

I have an import that takes data from a foreign API and updates a Django model. The code to do this passes the existing model instance and the incoming data to the serializer class to get an updated instance to save.

For this update I don't want to fail the validation, or the update, if some of the incoming data is wrong, but I would like to notify the errors (warnings) so they can be logged outside of the serializer.

Is there a DRF way of doing this, or any way of doing this?

To be clear: I still want the serializer to validate and raise for other less complex fields, but not the special fields that will need additional processing.

I'm currently over-riding to_internal_value in the serializer so that I can deal with the strangely formed data and record error messages before I let the incoming data through to super().to_internal_value(): https://www.django-rest-framework.org/api-guide/serializers/#overriding-serialization-and-deserialization-behavior

My work-around is to add a warnings attribute to the serializer to save the messages in. The to_internal_value over-ride can then selectively remove bad fields from the incoming data and record warning messages in the warnings list.

Update: I think this is going to work but I'm still wondering if there's a better Django built-in approach.

Upvotes: 3

Views: 3235

Answers (2)

NeilG
NeilG

Reputation: 4160

I'm going to answer my own question because I have worked out my intended solution was more reasonable than I thought. If I get a better answer I'll select it instead.

To be able to raise on some invalid fields but not others requires the incoming serialization to be intercepted to avoid the standard behaviour, which is why to_internal_value must be overridden.

To be able to pass back any warnings without raising an exception requires some persistent attribute somewhere, logically on the Serializer object I suppose, because we can't rely on the processing not raising somewhere down the line once we pass execution back to Django ModelSerializer.

This all seems quite solid in retrospect, so I guess I was just being cautious because I hadn't gone this route before. I'd really appreciate anyone raising any gotchas.

Django already has the errors OrderedDict that it uses in the validation exception to formalise per-field error messages. The trouble is, this can't be accessed until is_valid() is called, so another attribute is needed.

Here is a simplified example implementation. This will work just like the Django ModelSerializer, except the warnings attribute can be referenced after calling is_valid() for reporting back what happened:

class AnObjectSerializer(serializers.ModelSerializer):
    """
    Serialize normal fields using Django ModelSerializer magic to raise when invalid.
    Intercept incoming crazy foreign fields and record warnings when invalid without raise.
    """
    CRAZY_FIELDS = ('foreign_omicron', 'foreign_delta', 'foreign_crazy')
    warnings = {}  # Some field failures will not invalidate but are reported here

    def to_internal_value(self, data):
        """Try to add incoming foreign crazy fields or warn if not."""
        foo = data['foo']
        for field in self.CRAZY_FIELDS:
            value = data.pop(field, None) or {}
            value = value.get('foobar') or ''  # Dig crazy field out of the incoming blob
            value = value.lower()  # Normalize crazy field somewhat

            # Do not raise serializers.ValidationError to allow AnObject load to continue
            # But report errors back to caller for logging in warnings list
            if value:
                try:
                    some_external_validation(value)
                except serializers.ValidationError:
                    self.warnings[field] = f'{foo}: foobar "{value}" has a problem'
                    value = ''
            data[field] = value

        validated_data = super().to_internal_value(data)
        return validated_data

    class Meta:
        model = AnObject
        fields = (
            'foo', 'bar', 'baz', 'qux',
            'foreign_omicron', 'foreign_delta', 'foreign_crazy',
        )

Upvotes: 1

Uzzal H. Mohammad
Uzzal H. Mohammad

Reputation: 801

If this is a one time import only you may:

You can iterate over each object , validate it using serializer manually. If the data is valid you may update else you keep a log file with the exceptional objects that failed validation.

A serializer wont do what you are asking, you need to catch the error or you need to modify the validation function for the fields to pass, if you dont want it to fail. he standard purpose of serializer that it will raise error on invalid data.

Upvotes: 0

Related Questions