Ihor Kaharlichenko
Ihor Kaharlichenko

Reputation: 6270

Conditionally restrict field update

The problem

Consider I have such a model and a viewset:

class Employee(models.Model):
    name = models.CharField(max_length=100)
    salary = models.PositiveIntegerField()

class EmployeeViewSet(viewsets.ModelViewSet):
    model = Employee

I want to be able to restrict field updates based on the user issuing request.

For example, an employee should be able to change his name, but not salary; a manager, on the other hand, could change both employee's salary and name.

I also need to provide proper HTTP status codes indicating that particular field change is forbidden, for example if an employee PUT's a payload with salary field changed he should get 403 FORBIDDEN instead of 200 OK.

Things I tried so far

I tried changing the serializer based on the request's user and made several different serializers each specifying different read-only fields:

class EmployeeRestrictedSerializer(serializers.HyperlinkedModelSerializer):
    class Meta(object):
        model = Employee
        fields = ('name', 'salary')
        read_only_fields = ('salary',)

class EmployeeViewSet(viewsets.ModelViewSet):
    model = Employee

    def get_serializer_class(self):
        if not self.request.user.is_staff:
            return EmployeeRestrictedSerializer

        return super(EmployeeViewSet, self).get_serializer_class()

This approach prevents salary from being changed by simply ignoring the salary field from the PUT payload completely. Also given the name is valid 200 OK is returned (instead of 403 FORBIDDEN) making the user believe that he managed to change the salary as well, which was not the case.

I found a mailing list post with quite a similar problem, though it hasn't been replied to.

Upvotes: 2

Views: 1232

Answers (1)

AdelaN
AdelaN

Reputation: 3536

I don't think you need two serializers for this.

Here's how I would do this: - create a custom permission class - see here - use PATCH instead of PUT for the update, because PATCH allows for certain fields to miss from the request - in the has_object_permission method, check your type of user and return an appropriate response:

def has_object_permission(self, request, view, obj):
    if request.user is manager:            
        return True

    # Employee must not try to modify 'salary' field.
    salary = request.DATA.get('salary', None)
    return salary is None

This will return by default a 403 Forbidden when the employee tries to change the salary field.

Of course, if you are using django templates, a better approach, in my opinion, would be to make that field readonly in the template for 'employee' user.

Upvotes: 1

Related Questions