1

I'm trying to replace an existing function-based view using the login_required decorator with a Django REST framework API using a ModelViewSet. However, it seems like the authentication works a bit differently.

In order to try to adapt my unit tests to the Django REST framework case, I cloned https://github.com/encode/rest-framework-tutorial and added a tests.py at the top level directory:

import json

from django.contrib.auth.models import User
from django.test import TestCase, Client

from snippets.models import Snippet

class SnippetTestCase(TestCase):
    def setUp(self):
        self.username = 'john_doe'
        self.password = 'foobar'
        self.user = User.objects.create(username=self.username, password=self.password)

        self.client = Client()
        self.snippet = Snippet.objects.create(owner=self.user, code="Foo Bar")

    def test_1(self):
        self.client.login(username=self.username, password=self.password)
        response = self.client.post(
                path='http://localhost:8000/snippets/1/',
                data=json.dumps({'code': 'New code'}),
                content_type="application/json")
        self.assertEqual(response.status_code, 201)

However, this returns a 403 Forbidden response:

Kurts-MacBook-Pro:rest-framework-tutorial kurtpeek$ python manage.py test
Creating test database for alias 'default'...
System check identified no issues (0 silenced).
F
======================================================================
FAIL: test_1 (tutorial.tests.SnippetTestCase)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/Users/kurtpeek/Documents/source/rest-framework-tutorial/tutorial/tests.py", line 23, in test_1
    self.assertEqual(response.status_code, 201)
AssertionError: 403 != 201

----------------------------------------------------------------------
Ran 1 test in 0.049s

FAILED (failures=1)
Destroying test database for alias 'default'...

On the other hand, using HTTPie with the -a flag works fine:

Kurts-MacBook-Pro:rest-framework-tutorial kurtpeek$ http -a kurtpeek:foobar123 POST http://localhost:8000/snippets/ code="print 123"
HTTP/1.1 201 Created
Allow: GET, POST, HEAD, OPTIONS
Content-Length: 212
Content-Type: application/json
Date: Thu, 25 Jan 2018 00:11:59 GMT
Location: http://localhost:8000/snippets/2/
Server: WSGIServer/0.2 CPython/3.6.4
Vary: Accept, Cookie
X-Frame-Options: SAMEORIGIN

{
    "code": "print 123",
    "highlight": "http://localhost:8000/snippets/2/highlight/",
    "id": 2,
    "language": "python",
    "linenos": false,
    "owner": "kurtpeek",
    "style": "friendly",
    "title": "",
    "url": "http://localhost:8000/snippets/2/"
}

It seems to me like this test should fulfill the requirements of the IsOwnerOrReadonly permissions class:

from rest_framework import permissions


class IsOwnerOrReadOnly(permissions.BasePermission):
    """
    Custom permission to only allow owners of an object to edit it.
    """

    def has_object_permission(self, request, view, obj):
        # Read permissions are allowed to any request,
        # so we'll always allow GET, HEAD or OPTIONS requests.
        if request.method in permissions.SAFE_METHODS:
            return True

        # Write permissions are only allowed to the owner of the snippet.
        return obj.owner == request.user

because the same User is also the owner of the snippet. Any ideas why this is not working, or how I could construct test cases that do pass?

0

1 Answer 1

6

I found out that the Django REST framework has its own set of testing tools. For now, I'm subclassing from rest_framework.test.APITestCase instead of django.test.TestCase, and calling the .force_authenticate method on the thus created self.client:

import json

from django.contrib.auth.models import User
from django.test import TestCase

from rest_framework.test import APITestCase, force_authenticate

from snippets.models import Snippet

class SnippetTestCase(APITestCase):
    def setUp(self):
        self.username = 'john_doe'
        self.password = 'foobar'
        self.user = User.objects.create(username=self.username, password=self.password)
        self.client.force_authenticate(user=self.user)

    def test_1(self):
        response = self.client.post('/snippets/', {'code': 'Foo Bar'}, format='json')
        self.assertEqual(response.status_code, 201)

The test now passes.

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

Comments

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.