Jeremy Satterfield
Coding, Making and Tulsa Life

Abusing Django Rest Framework Part 3: Object-level read-only fields

DRF has tools to control access in a few ways. Serializers make it easy to select what fields can be accessed and whether or not they are read-only. Permissions are great for restricting access to objects at all or even making certain objects read-only. But there are also cases where you might only want to allow access to a field on a specific object but leave that field restricted on other objects, or vice-versa.

In our case, we had an endpoint for modifying users, which has a field to dictate whether the user is an account admin. Our view already uses permissions to verify that the request user is allow to modify these items at all, but we wanted to make sure the user couldn't accidentally remove their own admin access but still be able to change other fields on this endpoint.

Whenever a Serializer is instantiated, the get_fields method is called to look at the attributes and decide what fields to include and instantiate them. As part of the serialzer instantiation the view also passes the request, view and format_kwarg as context to the serializer which is attached to the instance. This means we can use the request or view, or objects attached to them in to change attributes of the fields, in our case making one of them readonly.

class SampleSerializer(serializers.ModelSerializer):
    ...

    def get_fields(self, *args, **kwargs):
        fields = super(SampleSerializer, self).get_fields(*args, **kwargs)
        request = self.context.get('request', None)
        view = self.context.get('view', None)

        if (request and view and getattr(view, 'object', None) and
                request.user == view.object.user):
            fields['is_admin'].read_only = True

        return fields

The one issue with this method is that you can not nest this serializer and have this change work. Whenever a Serializer is nested on to another, the parent gets one instance of this child at the time that the server process starts, which gets reused for all future instances of said parent. Due to lots of complicated handling on DRF's part, that I'll leave to you to read up on, everything just works the way you expect most of the time. However, we are trying to access the request and view objects that clearly don't exist at the time the server process starts.