When using PUT, all the fields should be sent to the API for update. When using PATCH, not all fields are required for update. The fields not in the payload are not edited
1) Should allow only authenticated user to retrive specific fields (set permission and authentication classes in the view and define get method. Specify the fields to be returned in UserSerializer)
2) Should not allow POST call (extend from RetrieveAndUpdateView)
3) Allow updates from authenticated users (define update method in UserSerializer)
rom django.test import TestCase
from django.contrib.auth import get_user_model
from django.urls import reverse
from rest_framework.test import APIClient
from rest_framework import status
ME_URL = reverse("user:me")
def create_user(**kwargs):
return get_user_model().objects.create_user(**kwargs)
class PrivateUsersApiTests(TestCase):
"""Test API requests that require authentication"""
def setUp(self):
self.user = create_user(
email='test@test.com',
password='testpass',
name='name'
)
self.client = APIClient()
# test that authentication is required for users
def test_retrieve_user_unautherized(self):
""" Test that authentication is required for users"""
res = self.client.get(ME_URL)
self.assertEqual(res.status_code, status.HTTP_401_UNAUTHORIZED)
# Test retrieving profile for logged in user
# Also test if the response contain the expected data
def test_retrieve_profile_successful(self):
# log in client
self.client.force_authenticate(user=self.user)
res = self.client.get(ME_URL)
self.assertEqual(res.status_code, status.HTTP_200_OK)
self.assertEqual(res.data, {
'name': self.user.name,
'email': self.user.email
})
# Test if post if not allowed in to this api
def test_post_me_not_allowed(self):
self.client.force_authenticate(user=self.user)
res = self.client.post(ME_URL, {})
self.assertEqual(res.status_code, status.HTTP_405_METHOD_NOT_ALLOWED)
# test update user profile
def test_update_user_profile(self):
self.client.force_authenticate(user=self.user)
payload = {'name': 'new name', 'password': 'new pass'}
# with patch you can just send the fields to be updated.
# other fields wont be affected.
# with PUT you have to send the entire record
res = self.client.patch(ME_URL, payload)
self.user.refresh_from_db()
self.assertEqual(self.user.name, payload['name'])
self.assertTrue(self.user.check_password(payload['password']))
self.assertEqual(res.status_code, status.HTTP_200_OK)
Modify UserSerializer class to have update method
# This serializer is for for an API that will add data to a model
class UserSerializer(serializers.ModelSerializer):
"""serializer for user object"""
class Meta:
model = get_user_model()
fields = ('email', 'password', 'name')
# the password should be write only.
# it should not be serialized when get is called
# we specify extra kwargs for each field
# list of accepted args for can be found under core argument section of
# https://www.django-rest-framework.org/api-guide/fields/
# for password field, args under serializer.CharField are also valid
extra_kwargs = {'password': {'write_only': True, 'min_length': 5}}
# create is called when we use the CreateAPI view
# which takes a POST request to create a user
def create(self, validated_data):
return get_user_model().objects.create_user(**validated_data)
# update a user, setting the password correctly and return it
def update(self, model_instance, validated_data):
# we have to upate password separately from other data
# so pop the password if it is available or return none as default
password = validated_data.pop('password', None)
# update all other fields in the model
user = super().update(model_instance, validated_data)
if password:
user.set_password(password)
user.save()
return user
Create a view that extends RetrieveAndUpdateView and define get method
# view for API retrieving and updating user info
class ManageUserView(generics.RetrieveUpdateAPIView):
serializer_class = UserSerializer
authentication_classes = (authentication.TokenAuthentication,)
permission_classes = (permissions.IsAuthenticated,)
# retrieve and return authenticated user
# this method is also required for update (patch)
def get_object(self):
# authentication class assigns user to request
return self.request.user