dealertrack / django-rest-framework-braces

Collection of utilities for working with django rest framework (DRF)
Other
73 stars 20 forks source link

For Nested Serializers, the EnforceValidationFieldMixin.run_validation Method is Never Called #28

Open NdubisiOnuora opened 5 years ago

NdubisiOnuora commented 5 years ago

Even though EnforceValidationFieldMixin (https://github.com/dealertrack/django-rest-framework-braces/blob/master/drf_braces/serializers/enforce_validation_serializer.py#L10) is added to the MRO of the Serializer and all nested Serializers, the Serializer's.run_validation method still does not run the EnforceValidationFieldMixin.run_validation method or those of any subclasses of EnforceValidationFieldMixin that might override run_validation.

The current problem that I'm trying to solve is to ensure that I can drop errors for invalid data sent to any of the nested Serializers. One example is one in which the nested Serializer expects a dictionary but a list is sent to it instead.

Example:

from drf_braces.serializers.enforce_validation_serializer import create_enforce_validation_serializer, EnforceValidationFieldMixin

from rest_framework import fields, serializers
from rest_framework.fields import empty
from rest_framework.serializers import Serializer

class DropInvalidFieldsMixin(EnforceValidationFieldMixin):

    def run_validation(self, data=empty):
        self.parent.must_validate_fields = []
        try:
            return super(EnforceValidationFieldMixin, self).run_validation(data)
        except serializers.ValidationError:
            field_name = getattr(self, "field_name")
            raise fields.SkipField(
                'This field "{}" is being skipped as per enforce validation logic.'
                ''.format(field_name)
            )

class PlayerSerializer(Serializer):
    name = fields.CharField()

class PlayersSerializer(Serializer):
    player = PlayerSerializer(many=True)

class GameSerializer(Serializer):
    name = fields.CharField()
    country_of_origin = fields.CharField(required=False)
    players = PlayersSerializer(required=False)

serializer_class = create_enforce_validation_serializer(GameSerializer, strict_mode_by_default=False)

serializer = serializer_class(data={"name": "Chop the Money", "country_of_origin": "Nigeria", "players": [{"name": "Olusegun Obasanjo"}, {"name": "Sani Abacha"}] })

>>> serializer.is_valid()
>>> {'players': {u'non_field_errors': [ErrorDetail(string=u'Invalid data. Expected a dictionary, but got list.', code=u'invalid')]}}

I expect that the GameSerializer be valid and the incorrect data in the PlayersSerializer be dropped especially since self.parent.must_validate_fields is set to an empty list in DropInvalidFieldsMixin.run_validation.

shosca commented 5 years ago

Shouldn't you do this instead?:

class PlayerSerializer(Serializer):
    name = fields.CharField()

class GameSerializer(Serializer):
    name = fields.CharField()
    country_of_origin = fields.CharField(required=False)
    players = PlayersSerializer(required=False, many=True)
NdubisiOnuora commented 5 years ago

@shosca: I can't modify the definition of GameSerializer, since another service expects it to be in that format. I'm piggybacking on that service, which does 99% of what I want. 😄