Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

DRF: accessing a SerializerMethodField during serializer validation

I'm using Django Rest Framework 3.0 and I have a model:

class Vote(models.Model):
    name = ...
    token = models.CharField(max_length=50)

where token is a unique identifier that I generate from the request IP information to prevent the same user voting twice

I have a serializer:

class VoteSerializer(serializers.ModelSerializer):
    name = ...
    token = serializers.SerializerMethodField()

    class Meta:
        model = Vote
        fields = ("id", "name", "token")

    def validate(self, data):
        if Rating.objects.filter(token=data['token'], name=data['name']).exists():
            raise serializers.ValidationError("You have already voted for this")
        return data

    def get_token(self, request):
        s = ''.join((self.context['request'].META['REMOTE_ADDR'], self.context['request'].META.get('HTTP_USER_AGENT', '')))
        return md5(s).hexdigest()

and a CreateView

But I am getting a

KeyError: 'token' 

when I try to post and create a new Vote. Why is the token field not included in the data when validating?

The docs mention:

It can be used to add any sort of data to the serialized representation of your object.

So I would have thought it would be also available during validate?

like image 757
Timmy O'Mahony Avatar asked Feb 11 '23 03:02

Timmy O'Mahony


1 Answers

Investigating, it seems that SerializerMethodField fields are called after validation has occurred (without digging into the code, I don't know why this is - it seems counter intuitive).

I have instead moved the relevant code into the view (which actually makes more sense conceptually to be honest).

To get it working, I needed to do the following:

class VoteCreateView(generics.CreateAPIView):
    serializer_class = VoteSerializer

    def get_serializer(self, *args, **kwargs):
        # kwarg.data is a request MergedDict which is immutable so we have
        # to copy the data to a dict first before inserting our token
        d = {}
        for k, v in kwargs['data'].iteritems():
            d[k] = v
        d['token'] = self.get_token()
        kwargs['data'] = d
        return super(RatingCreateView, self).get_serializer(*args, **kwargs)

    def get_token(self):
        s = ''.join((self.request.META['REMOTE_ADDR'], self.request.META.get('HTTP_USER_AGENT', '')))
        return md5(s).hexdigest()

I really hope this isn't the correct way to do this as it seems totally convoluted for what appears to be a pretty straight forward situation. Hopefully someone else can post a better approach to this.

like image 195
Timmy O'Mahony Avatar answered Feb 13 '23 22:02

Timmy O'Mahony