First let impliment user can only modify their own user data
Because we are using django auth.model User so we can add a custom permission:
class UserCanOnlyModifyHimSelfPermission(permissions.BasePermission):
def has_permission(self, request, view):
# watchout view.kwargs.get('pk') is a str because we are use it as a router
return request.method in SAFE_METHODS or (
request.user.is_authenticated and int(view.kwargs.get('pk')) == request.user.id)
class UserViewSet(mixins.ListModelMixin, mixins.RetrieveModelMixin, mixins.UpdateModelMixin, viewsets.GenericViewSet):
queryset = User.objects.all()
serializer_class = UserSerializer
permission_classes = [permissions.IsAuthenticatedOrReadOnly, UserCanOnlyModifyHimSelfPermission]
Test Case:
class UserTestCase(TestCase, TransactionTestCase):
databases = {'default'}
def setUp(self):
from django.core.management import call_command
call_command('migrate', verbosity=0, interactive=False, database='default')
self.defaultUserName = 'defaultUser'
self.defaultPassword = "defaultUserPassword"
self.defaultUser = User.objects.create_user(username=self.defaultUserName, password=self.defaultPassword)
self.defaultUserName2 = 'defaultUser2'
self.defaultPassword2 = "defaultUserPassword2"
self.defaultUser2 = User.objects.create_user(username=self.defaultUserName2, password=self.defaultPassword2)
def test_can_only_modify_self_info(self):
data = {"username": "userForTestToken"}
# before register shouldn't modify user's info
response = self.client.patch(f'/user/{self.defaultUser.id}/', data, )
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
# set header to mock login
token, created = Token.objects.get_or_create(user=self.defaultUser)
client = APIClient()
client.credentials(HTTP_AUTHORIZATION='Token ' + token.key)
# after login can modify
response = client.patch(f'/user/{self.defaultUser.id}/', data, )
self.assertEqual(response.status_code, status.HTTP_200_OK)
# cannot modify other's info
response = client.patch(f'/user/{self.defaultUser2.id}/', data, )
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
# Stop including any credentials
client.credentials()
Another way to implement
User can only modify themselves data
TLDR: add a owner field.
class Book(models.Model):
title = models.CharField(max_length=100)
owner = models.ForeignKey('auth.User', on_delete=models.CASCADE)
class BookSerializer(serializers.ModelSerializer):
# This will only return User.username
# owner = serializers.ReadOnlyField(source='owner.username')
# This will return full User info
# BUT it will cause owner field is required when deserialized.so we must declare read_only=True
owner = UserSerializer(read_only=True)
class Meta:
model = Book
fields = '__all__'
Test Case:
class BookTestCase(TestCase, TransactionTestCase):
databases = {'default'}
def setUp(self):
from django.core.management import call_command
call_command('migrate', verbosity=0, interactive=False, database='default')
# make sure we have a default user.
self.defaultUserName = 'bookCreator'
self.defaultPassword = "defaultUserPassword"
self.defaultUser = User.objects.create_user(username=self.defaultUserName, password=self.defaultPassword)
self.defaultUserName2 = 'bookCreator2'
self.defaultPassword2 = "defaultUserPassword2"
self.defaultUser2 = User.objects.create_user(username=self.defaultUserName2, password=self.defaultPassword2)
def test_user_can_only_modify_himself_book(self):
client = APIClient()
token1, created1 = Token.objects.get_or_create(user=self.defaultUser)
token2, created2 = Token.objects.get_or_create(user=self.defaultUser2)
# first we log in user1
client.credentials(HTTP_AUTHORIZATION='Token ' + token1.key)
# let user1 create book1
response = client.post(path='/book/', data={'title': 'book1'})
book1_id = response.data['id']
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
self.assertEqual(response.data['owner']['id'], self.defaultUser.id)
# user1 can modify book1
response = client.patch(path=f'/book/{book1_id}/', data={'title': 'book1-user1-modify'})
self.assertEqual(response.status_code, status.HTTP_200_OK)
# login user2
client.credentials(HTTP_AUTHORIZATION='Token ' + token2.key)
# user2 cannot modify book1
response = client.patch(path=f'/book/{book1_id}/', data={'title': 'book1-user2-modify'})
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
# at last, we check book1 are not modified by user2
self.assertEqual(
Book.objects.get(title='book1-user1-modify').owner.id, self.defaultUser.id
)
# Stop including any credentials
client.credentials()
Thanks for reading.