joe
joe

Reputation: 9503

How to user Django REST serializer do validation on reserved key?

It might a simple question with eye blink workaround. But I can not be able to get it done. I am now creating the webhook endpoint. And stuck at serializer class My class can not use from as a class property

Python 3.6.4
Django==1.11.9
djangorest==3.7.7

@pytest.fixture
def like_object():
    """LIKE object response from Facebook"""
    return {
        "object": "page",
        "entry": [{
            "changes": [{
                "field": "feed",
                "value": {
                    "item": "reaction",
                    "verb": "add",
                    "reaction_type": "like",
                    "created_time": 1516183830,
                    "post_id": "1331351323541869_1844740022202994",
                    "from": {
                        "name": "Elcoie Sieve",
                        "id": "1639217166122728"
                    },
                    "parent_id": "1331351323541869_1844740022202994"
                }
            }],
            "time": 1516183830, "id": "1331351323541869"
        }]
    }

serializers.py

class FacebookReactionSerializer(serializers.Serializer):
    """
    value serializer the inner most of the payload
    """
    item = serializers.CharField()
    verb = serializers.CharField()
    reaction_type = serializers.CharField()
    created_time = serializers.IntegerField(
        validators=[MinValueValidator(0), MaxValueValidator(4086831600)]
    )  # Limit the maximum epoch to 2099 July 4th 7:00AM
    post_id = serializers.CharField(max_length=40)
    from = FromSerializer()
    parent_id = serializers.CharField()

    def validate(self, attrs):
        """
        `from` is a python reserved word the add _ to distinguish it from them
        :param attrs:
        :return:
        """
        from_ = attrs.get('from')
        pass

    def create(self, validated_data):
        pass

    def update(self, instance, validated_data):
        pass

Attempts:

ffrom = FromSerializer(source='from')

Does not work

Attempt2:

class FacebookReactionSerializer(serializers.Serializer):
    """
    value serializer the inner most of the payload
    """
    item = serializers.CharField()
    verb = serializers.CharField()
    reaction_type = serializers.CharField()
    created_time = serializers.IntegerField(
        validators=[MinValueValidator(0), MaxValueValidator(4086831600)]
    )  # Limit the maximum epoch to 2099 July 4th 7:00AM
    post_id = serializers.CharField(max_length=40)
    from_key = FromSerializer()
    parent_id = serializers.CharField()

    def to_representation(self, instance):
        """
        https://stackoverflow.com/questions/47630356/using-the-reserved-word-class-as-field-name-in-django-and-django-rest-framewor
        :param instance:
        :return:
        """
        data = super().to_representation(instance)
        keys = list(data.keys())
        keys.insert(keys.index('from_key'), 'from')
        keys.remove('from_key')
        from_key = data.pop('from_key')
        data.update({'from': from_key})
        response = OrderedDict((k, data[k]) for k in keys)
        return response

    def create(self, validated_data):
        pass

    def update(self, instance, validated_data):
        pass

Testcase failed here is the breakpoint

(Pdb) serializer
FacebookReactionSerializer(data={'item': 'reaction', 'verb': 'add', 'reaction_type': 'like', 'created_time': 1516183830, 'post_id': '1331351323541869_1844740022202994', 'from': {'name': 'Krittuch Onnom', 'id': '1639217166122728'}, 'parent_id': '1331351323541869_1844740022202994'}):
    item = CharField()
    verb = CharField()
    reaction_type = CharField()
    created_time = IntegerField(validators=[<rest_framework.compat.MinValueValidator object>, <rest_framework.compat.MaxValueValidator object>])
    post_id = CharField(max_length=40)
    from_key = FromSerializer():
        name = CharField()
        id = CharField()
    parent_id = CharField()
(Pdb) serializer.errors
*** AssertionError: You must call `.is_valid()` before accessing `.errors`.
(Pdb) serializer.is_valid()
False
(Pdb) serializer.errors
{'from_key': ['This field is required.']}

Attempt3:
Also add the `._declared_fields['from_key']

class FacebookReactionSerializer(serializers.Serializer):
    item = serializers.CharField()
    verb = serializers.CharField()
    reaction_type = serializers.CharField()
    created_time = serializers.IntegerField(
        validators=[MinValueValidator(0), MaxValueValidator(4086831600)]
    )  # Limit the maximum epoch to 2099 July 4th 7:00AM
    post_id = serializers.CharField(max_length=40)
    from_key = FromSerializer()
    parent_id = serializers.CharField()

    def to_representation(self, instance):
        data = super().to_representation(instance)
        keys = list(data.keys())
        keys.insert(keys.index('from_key'), 'from')
        keys.remove('from_key')
        from_key = data.pop('from_key')
        data.update({'from': from_key})
        response = OrderedDict((k, data[k]) for k in keys)
        return response

    def create(self, validated_data):
        pass

    def update(self, instance, validated_data):
        pass

FacebookReactionSerializer._declared_fields["from"] = serializers.CharField(source="from_key")

Debugging line:

(Pdb) serializer.is_valid()
False
(Pdb) serializer.errors
{'from_key': ['This field is required.'], 'from': ['Not a valid string.']}

I am getting close, but it is not enough

Attempt4.1:

Not work. Remain the same error. from_key still remain. I wrote from child serializer. I must use FromSerializer not CharField.

    class FacebookReactionSerializer(serializers.Serializer):
    """
    value serializer the inner most of the payload
    """
    item = serializers.CharField()
    verb = serializers.CharField()
    reaction_type = serializers.CharField()
    created_time = serializers.IntegerField(
        validators=[MinValueValidator(0), MaxValueValidator(4086831600)]
    )  # Limit the maximum epoch to 2099 July 4th 7:00AM
    post_id = serializers.CharField(max_length=40)
    from_key = FromSerializer()
    parent_id = serializers.CharField()

    def create(self, validated_data):
        pass

    def update(self, instance, validated_data):
        pass


FacebookReactionSerializer._declared_fields["from"] = FromSerializer(source="from_key")

I can add from as a key now. But it does not remove the from_key out from the validation logic

(Pdb) serializer
FacebookReactionSerializer(data={'item': 'reaction', 'verb': 'add', 'reaction_type': 'like', 'created_time': 1516183830, 'post_id': '1331351323541869_1844740022202994', 'from': {'name': 'Krittuch Onnom', 'id': '1639217166122728'}, 'parent_id': '1331351323541869_1844740022202994'}):
    item = CharField()
    verb = CharField()
    reaction_type = CharField()
    created_time = IntegerField(validators=[<rest_framework.compat.MinValueValidator object>, <rest_framework.compat.MaxValueValidator object>])
    post_id = CharField(max_length=40)
    from_key = FromSerializer():
        name = CharField()
        id = CharField()
    parent_id = CharField()
    from = FromSerializer(source='from_key'):
        name = CharField()
        id = CharField()
(Pdb) serializer.is_valid()
False
(Pdb) serializer.errors
{'from_key': ['This field is required.']}

Question:
What is your workaround when from(reserved word) is a key and that key is a python class property?

Upvotes: 3

Views: 2337

Answers (1)

joe
joe

Reputation: 9503

@cezar Thank you very much!
The solution is so simple! I was confused at the early time. Because the given solutions are using ModelSerializer by that you have to put fields and coupling with model properties.

In my example is bare plain Serializer I don't need any of them.

From now on I would attacking this kind of problem by Serializer class

class FacebookReactionSerializer(serializers.Serializer):
    """
    value serializer the inner most of the payload
    """
    item = serializers.CharField()
    verb = serializers.CharField()
    reaction_type = serializers.CharField()
    created_time = serializers.IntegerField(
        validators=[MinValueValidator(0), MaxValueValidator(4086831600)]
    )  # Limit the maximum epoch to 2099 July 4th 7:00AM
    post_id = serializers.CharField(max_length=40)
    parent_id = serializers.CharField()

    def create(self, validated_data):
        pass

    def update(self, instance, validated_data):
        pass


FacebookReactionSerializer._declared_fields["from"] = FromSerializer()

Here is my debugging lines

(Pdb) serializer
FacebookReactionSerializer(data={'item': 'reaction', 'verb': 'add', 'reaction_type': 'like', 'created_time': 1516183830, 'post_id': '1331351323541869_1844740022202994', 'from': {'name': 'Krittuch Onnom', 'id': '1639217166122728'}, 'parent_id': '1331351323541869_1844740022202994'}):
    item = CharField()
    verb = CharField()
    reaction_type = CharField()
    created_time = IntegerField(validators=[<rest_framework.compat.MinValueValidator object>, <rest_framework.compat.MaxValueValidator object>])
    post_id = CharField(max_length=40)
    parent_id = CharField()
    from = FromSerializer():
        name = CharField()
        id = CharField()
(Pdb) serializer.is_valid()
True

Upvotes: 3

Related Questions