Reputation: 6270
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.
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
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