2

I'm trying to test a PUT method in django rest framework. I get HttpResponsePermanentRedirect instead of response object. My views for a put method are set to send status 200 after successful update. Error:

self.assertEqual(response.data, serializer.data) AttributeError: 'HttpResponsePermanentRedirect' object has no attribute 'data'

tests.py

class PostTestGetAndPutMethod(APITestCase):
    def setup(self):
        Post.objects.create(title="POST CREATED", content="POST WAS CREATED")
        Post.objects.create(title="POST CREATED 2", content="POST WAS CREATED 2")
        Post.objects.create(title="POST CREATED 3", content="POST WAS CREATED 3")

    def test_get_posts(self):
        '''
        Ensure we can get list of posts
        '''
        # get API response 
        response = self.client.get(reverse('posts'))
        # get data from DB
        posts = Post.objects.all()
        # convert it to JSON
        serializer = PostSerializer(posts, many=True)
        # check the status 
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        self.assertEqual(response.data, serializer.data)

    def test_update_put_post(self):
        '''
        Check if we can update post 
        '''
        data = {'title': 'POST MODIFIED', 'content': 'CONTENT MODIFIED'}
        response = self.client.put('/posts/1', data)
        serializer = PostSerializer(data)
        self.assertEqual(response.data, serializer.data)
        self.assertEqual(response.status_code, status.HTTP_200_OK)

views.py

@api_view(['GET', 'PUT', 'DELETE'])
def post_detail(request, pk):
    """
    Retrieve, update or delete a code snippet.
    """
    try:
        post = Post.objects.get(pk=pk)
    except Post.DoesNotExist:
        return Response(status=status.HTTP_404_NOT_FOUND)

    if request.method == 'GET':
        serializer = PostSerializer(post)
        return Response(data=serializer.data, status=status.HTTP_200_OK)

    elif request.method == 'PUT':
        serializer = PostSerializer(post, data=data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data, status=status.HTTP_200_OK)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

    elif request.method == 'DELETE':
        post.delete()
        return Response(status=status.HTTP_204_NO_CONTENT)

EDIT test should look like this:

class PostTestPutMethod(APITestCase):
    def setUp(self):
        # Posts to be modified 
        self.first_post = Post.objects.create(title="POST CREATED", content="POST WAS CREATED")
        self.second_post = Post.objects.create(title="POST CREATED 2", content="POST WAS CREATED 2")

        self.valid_post = {
            "title" : "post is changed",
            "content": "post is changed"
        }
        self.invalid_post = {
            "title": "",
            "content": "post change"
        }
    def test_valid_update_post(self):
        '''
        Validated data case 
        '''
        response = self.client.put(
            reverse('post_detail', kwargs={'pk': self.first_post.pk}),
            data = json.dumps(self.valid_post),
            content_type = 'application/json'
        )
        self.assertEqual(response.status_code, status.HTTP_200_OK)

    def test_invalid_update_post(self):
        '''
        Invalid data case 
        '''
        response = self.client.put(
            reverse('post_detail', kwargs={'pk': self.second_post.pk}),
            data = json.dumps(self.invalid_post),
            content_type = 'application/json'
        )
        self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)


Also there is small mistake right here in views: serializer = PostSerializer(post, data=data) and should be replaced with serializer = PostSerializer(post, data=request.data)

1
  • Note, your view does not allow creation: you'll be given a 404 response. Commented Mar 27, 2020 at 21:12

1 Answer 1

2

In the first test, you nailed it:

response = self.client.get(reverse('posts'))

Next up, you hard code the path, missing the slash at the end.

response = self.client.put('/posts/1', data)

Django will then try to redirect /posts/1 to /posts/1/. Add a slash or find the correct name for reverse() in the test, and you should be good.

Also, I can really recommend using DRF generic views. Less code, more sanity checks: https://www.django-rest-framework.org/api-guide/generic-views/

Sign up to request clarification or add additional context in comments.

1 Comment

Note that you can disable this behavior in Django by using APPEND_SLASH = False in your settings.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.