I'm struggling to figure how to get the id of the user I created using the create_user() function in my unit test.
Unit test below:
from 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
DELETE_URL = reverse('user:delete_user')
class PrivateUserApiTests(TestCase):
"""Test the users API (private)"""
def setUp(self):
self.user = create_user(
email='tes111t#test.com',
password='test123',
name='name'
)
self.client = APIClient()
self.client.force_authenticate(user=self.user)
def test_user_successful_delete(self):
"""Test that user was succesfully deleted"""
payload = {'email': 'test#test123.com', 'password': 'test123'}
create_user(**payload) # Here is where I create the user I want to delete
user = get_user_model().objects.get()
res = self.client.delete(DELETE_URL) # I'm not sure how to pass the id of the user I just created.
The way my url works is: (the id passed in is the user that is deleted)
0.0.0.0:8000/api/user/delete-user/id
urls.py
urlpatterns = [
path('create/', views.CreateUserView.as_view(), name='create'),
path('token/', views.CreateTokenView.as_view(), name='token'),
path('me/', views.ManageUserView.as_view(), name='me'),
path('all_users/', views.RetrieveUsersView.as_view(), name='all_users'),
path('delete-user/<int:pk>', views.DeleteUserAPI.as_view(), name='delete_user')
]
When using the self.client.delete() How do i pass in the id?
Try using reverse like this:
response = self.client.delete(reverse('user', kwargs={'pk': user.pk}), follow=True)
Although that would require an API endpoint containing information about your user including their pk
Related
I need to test a simple Django project where users in a certain group have some permissions. Now in tests I know how to create a mock user and I have already tested that users not in the group can't access the data trow API, but now I have to test that users in group can access to data. How I can create and add the mock user the group?
Thanks in advance for your help.
Below my code:
import pytest
from django.contrib.auth import get_user_model
from django.contrib.auth.models import Group
from django.urls import reverse
from mixer.backend.django import mixer
from rest_framework.status import HTTP_403_FORBIDDEN, HTTP_200_OK
from rest_framework.test import APIClient
#pytest.fixture()
def superheroes(db):
return [
mixer.blend('superhero.SuperHero')
]
def get_client(user=None):
res = APIClient()
if user is not None:
res.force_login(user)
return res
# code to test users without permissions (it works!)
def test_anon_user_forbidden():
path = reverse('superheroes-list')
client = get_client()
response = client.get(path)
assert response.status_code == HTTP_403_FORBIDDEN
# code to test users with permission (so users that are in the group)
def test_avengers_user_ok(superheroes):
path = reverse('superheroes-list')
user = mixer.blend(get_user_model())
# add the user to specific group, but how?
client = get_client(user)
response = client.get(path)
assert response.status_code == HTTP_200_OK
I need to restrict access to the APIs I have defined in my view. Here is my views.py:
rom rest_framework import generics
from rest_framework import permissions
from .serializers import LocationSerializer, PartSerializer, PartLocationSerializer, SiteSerializer
from .models import Location, Part, PartLocation, Site, SPIUser
class SPIPermission(permissions.BasePermission):
"""
blah blah blah ...
"""
def has_permission(self, request, view):
try:
username = request.user.username
SPIUser.objects.get(username=username)
except SPIUser.DoesNotExist:
return False
if not request.user.is_authenticated:
return False
return True
class LocationList(generics.ListCreateAPIView):
# using get_queryset().order_by('id') prevents UnorderedObjectListWarning
queryset = Location.objects.get_queryset().order_by('id')
serializer_class = LocationSerializer
permission_classes = (SPIPermission,)
I want to demonstrate in my unit tests that your have to be an SPIUser to be able to access these api endpoints so I write a simple unit test like so:
from .models import Location, Part, PartLocation, Site, SPIUser
from .urls import urlpatterns
from my.APITestCase import RemoteAuthenticatedTest
from django.db.models import ProtectedError
from django.test import TransactionTestCase
from django.urls import reverse
from rest_framework import status
import django.db.utils
import os
class ViewTestCases(RemoteAuthenticatedTest):
def test_spi_permission(self):
url = reverse('spi:locationlist')
response = self.client.get(url)
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
SPIUser.objects.create(username=self.username)
response = self.client.get(url)
self.assertNotEquals(response.status_code, status.HTTP_403_FORBIDDEN)
This test fails with the this error message:
Failure
Traceback (most recent call last):
File "/apps/man/apman/spi/tests.py", line 21, in test_spi_permission
self.assertNotEquals(response.status_code, status.HTTP_403_FORBIDDEN)
AssertionError: 403 == 403
I noticed that the line in has_permission ...
username = request.user.username
... always sets the username to ''. So has_permission will always return False.
My unit test ViewTestCases inherits class RemoteAuthenticatedTest which is defined like so:
from rest_framework.test import APIClient,APITestCase
from django.contrib.auth.models import User
from rest_framework.authtoken.models import Token
class RemoteAuthenticatedTest(APITestCase):
client_class = APIClient
def setUp(self):
self.username = 'mister_neutron'
self.password = 'XXXXXXXXXXX'
self.user = User.objects.create_user(username= self.username,
email='mister_neutron#example.com',
password=self.password)
#authentication user
self.client.login(username=self.username, password=self.password)
Token.objects.create(user=self.user)
super(RemoteAuthenticatedTest, self).setUp()
So I thought that request.user.username would be mister_neutron.
What am I doing wrong here?
Ah heck. I forgot that I am using RemoteUser authentication so when I make my I need to set REMOTE_USER like so:
response = self.client.get(url, REMOTE_USER=self.username)
I have defined a custom action for a ViewSet
from rest_framework import viewsets
class UserViewSet(viewsets.ModelViewSet):
#action(methods=['get'], detail=False, permission_classes=[permissions.AllowAny])
def gender(self, request):
....
And the viewset is registered to url in the conventional way
from django.conf.urls import url, include
from rest_framework import routers
from api import views
router = routers.DefaultRouter()
router.register(r'users', views.UserViewSet, base_name='myuser')
urlpatterns = [
url(r'^', include(router.urls)),
]
The URL /api/users/gender/ works. But I don't know how to get it using reverse in unit test. (I can surely hard code this URL, but it'll be nice to get it from code)
According to the django documentation, the following code should work
reverse('admin:app_list', kwargs={'app_label': 'auth'})
# '/admin/auth/'
But I tried the following and they don't work
reverse('myuser-list', kwargs={'app_label':'gender'})
# errors out
reverse('myuser-list', args=('gender',))
# '/api/users.gender'
In the django-restframework documentation, there is a function called reverse_action. However, my attempts didn't work
from api.views import UserViewSet
a = UserViewSet()
a.reverse_action('gender') # error out
from django.http import HttpRequest
req = HttpRequest()
req.method = 'GET'
a.reverse_action('gender', request=req) # still error out
What is the proper way to reverse the URL of that action?
You can use reverse just add to viewset's basename action:
reverse('myuser-gender')
See related part of docs.
You can print all reversible url names of a given router at startup with the get_urls() method
In urls.py after the last item is registered to the router = DefaultRouter() variable, you can add:
#router = routers.DefaultRouter()
#router.register(r'users', UserViewSet)
#...
import pprint
pprint.pprint(router.get_urls())
The next time your app is initialized it will print to stdout something like
[<RegexURLPattern user-list ^users/$>,
<RegexURLPattern user-list ^users\.(?P<format>[a-z0-9]+)/?$>,
<RegexURLPattern user-detail ^users/(?P<pk>[^/.]+)/$>,
<RegexURLPattern user-detail ^users/(?P<pk>[^/.]+)\.(?P<format>[a-z0-9]+)/?$>,
#...
]
where 'user-list' and 'user-detail' are the names that can be given to reverse(...)
Based on DRF Doc.
from rest_framework import routers
router = routers.DefaultRouter()
view = UserViewSet()
view.basename = router.get_default_basename(UserViewSet)
view.request = None
or you can set request if you want.
view.request = req
In the end, you can get a reverse action URL and use it.
url = view.reverse_action('gender', args=[])
If you want to use UserViewset().reverse_action() specifically, you can do this by assigning a basename and request = None to your ViewSet:
from rest_framework import viewsets
class UserViewSet(viewsets.ModelViewSet):
basename = 'user'
request = None
#action(methods=['get'], detail=False, permission_classes=[permissions.AllowAny])
def gender(self, request):
....
and in urls.py:
router.register('user', UserViewSet, basename=UserViewSet.basename)
Then calling url = UserViewset().reverse_action('gender') or url = UserViewset().reverse_action(UserViewSet().gender.url_name) will return the correct url.
Edit:
Above method only works when calling reverse_action() only once because the ViewSetMixin.as_view() method overrides basename on instantiation. This can be solved by adding a custom subclass of GenericViewSet (or ModelViewSet if preferred) like so:
from django.utils.decorators import classonlymethod
from rest_framework.viewsets import GenericViewSet
class ReversibleViewSet(GenericViewSet):
basename = None
request = None
#classonlymethod
def as_view(cls, actions=None, **initkwargs):
basename = cls.basename
view = super().as_view(actions, **initkwargs)
cls.basename = basename
return view
and subclassing this class for the specific ViewSets, setting basename as desired.
The answer is reverse('myuser-gender').
Note! But remember that DRF will replace _ in the action name with -. That means if the action name is my_pretty_action in the reverse you should use reverse(app-my-pretty-action).
According to your code you would like to get the list of users you use
reverse("myuser-list")
details
reverse("myuser-detail",kwargs={"id":1})
In here you might notice i use list or detail by adding it to the reverse so if you add appname you can just input it thats because we are using viewsets
I am very new in the python world and now I building an application with Django 1.8 with the Rest Framework and I want to create a class view to DRY my code.
For example I want to have a class view for the students in my system
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
class SnippetList(APIView):
def getWorstStudents(self, request, format=None):
# Logic here
How can I assign a specified URL in urls.py to hit this method?
And also I have implemented REST framework JWT Auth
http://getblimp.github.io/django-rest-framework-jwt/ for token auth.
How can I restrict the access to allow only authenticated users can access to this url?
Thank you in advance!
Use routers with viewsets.
First, subclass your view from ModelViewSet instead of from APIView. Second, use the #list_route decorator in your getWorstStudents method. Third, tie everything up in your urls.py with a router.
It should look like (I have not tested the code):
views.py
class StudentsViewSet(viewsets.ViewSet):
#list_route(methods=['get'], permission_classes=(IsAuthenticated,)) # you can define who can access this view here
def getWorstStudents(self, request, format=None):
# Logic here
# routers.py
router = routers.DefaultRouter()
router.register(r'students', views.StudentsViewSet, base_name='student')
# urls.py
import .routers import router
urlpatterns = [
url(r'^', include(router.urls)),
]
The router will generate a view with the name student-getWorstStudents accessible from the students/getWorstStudents url.
You can set urls like any other Django app, documented here
# urls.py
from django.conf.urls import url
from somewhere import SnippetList
urlpatterns = [
url(r'^your/url/$', SnippetList.as_view()),
]
About DRY with your method, you can define the method you want to response to, and call the getWorstStudents (btw by convention I would call it get_worst_students). Let's say you want to response the post method:
# views.py
from rest_framework.response import Response
def getWorstStudents(params)
class SnippetList(APIView):
def post(self, request, *args, **kwargs):
# call getWorstStudents method here and response a Response Object
You can define getWorstStudents inside SnippetList class or in other file to import wherever you need it.
Finally, about authentication, DRF provides classes for this, documented here.
From docs, you need define this in your settings.py file:
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework.authentication.BasicAuthentication',
'rest_framework.authentication.SessionAuthentication',
)
}
And use it in your views:
from rest_framework.authentication import SessionAuthentication, BasicAuthentication
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from rest_framework.views import APIView
class ExampleView(APIView):
authentication_classes = (SessionAuthentication, BasicAuthentication)
permission_classes = (IsAuthenticated,)
def get(self, request, format=None):
content = {
'user': unicode(request.user), # `django.contrib.auth.User` instance.
'auth': unicode(request.auth), # None
}
return Response(content)
You can also define your own authentication class and set it in authentication_classes tuple. Custom authentication classes documented here.
After struggling mightily with this issue, I've come asking for a bit of help. I'm writing a test for a Django Rest Framework view, testing whether or not I can access the data whilst authenticated, and not. However, even when I'm authenticated, I still get 401 UNAUTHORIZED every time. Here's my tests:
from django.test import TestCase
from django.contrib.auth.models import User
from rest_framework.authtoken.models import Token
from rest_framework.test import APIRequestFactory, APIClient
from apps.core import models, admin
from apps.rest import views
class TestAPIViews(TestCase):
def setUp(self):
self.factory = APIRequestFactory()
self.client = APIClient()
self.user = User.objects.create_user('testuser', email='testuser#test.com', password='testing')
self.user.save()
token = Token.objects.create(user=self.user)
token.save()
def _require_login(self):
self.client.login(username='testuser', password='testing')
def test_ListAccounts_not_authenticated(self):
request = self.factory.get('/accounts/')
view = views.ListAccounts.as_view()
response = view(request)
self.assertEqual(response.status_code, 401,
'Expected Response Code 401, received {0} instead.'.format(response.status_code))
def test_ListAccounts_authenticated(self):
self.client._require_login()
print(self.user.is_authenticated()) # returns True
request = self.factory.get('/accounts/')
view = views.ListAccounts.as_view()
response = view(request)
self.assertEqual(response.status_code, 200,
'Expected Response Code 200, received {0} instead.'.format(response.status_code))
And here is the code for my DRF View:
from django.shortcuts import render
from django.contrib.auth import authenticate, login, logout
from django.db.models import Q
from apps.core import serializers, models
from apps.rest.permissions import IsAccountOwner
from rest_framework.views import APIView
from rest_framework import status, authentication, generics
from rest_framework.response import Response
from rest_framework.authtoken.models import Token
from rest_framework.authentication import SessionAuthentication, TokenAuthentication
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from apps.core import serializers, models
'''
Auth Mixin for Account Admin Access
'''
class AuthMixin:
authentication_classes = (authentication.TokenAuthentication,
authentication.SessionAuthentication)
permission_classes = (IsAuthenticated,)
class GenericList(AuthMixin, generics.ListAPIView):
def get_queryset(self):
# Stubbed out - doing nothing special right now
qs = self.model.objects.filter()
return qs
class ListAccounts(GenericList):
model = models.Account
serializer_class = serializers.AccountSerializer
As one can see, I'm calling login in the test_ListAccounts_authenticated, and then printing out whether or not I'm authenticated (Which returns True), but I get a 401 UNAUTHORIZED Error no matter what. Anything I'm missing? Thanks in advance.
Instead of calling self.factory.get(), call self.client.get().
I'm guessing self.client.login has no effect on self.factory.