I am testing a view and my test looks like:
def test_profile(self, user_id):
user = User.objects.create_user(username="myusername", password="password", email="abc#testmail.com")
self.client.user = user
print(user.id)
request = self.client.get("/account/profile/{}/".format(user_id), follow=True)
self.assertEqual(request.status_code, 200)
Here my profile view has a login_required decorator. How can I set user to request.user?
I was trying to do the same myself but found out that Django Test Client does not set the user in the request and it is not possible to set request.user while using Client any other way. I used RequestFactory to that.
def setUp(self):
self.request_factory = RequestFactory()
self.user = User.objects.create_user(
username='javed', email='javed#javed.com', password='my_secret')
def test_my_test_method(self):
payload = {
'question_title_name': 'my first question title',
'question_name': 'my first question',
'question_tag_name': 'first, question'
}
request = self.request_factory.post(reverse('home'), payload)
request.user = self.user
response = home_page(request)
More about request factory here
Try this:
from django.test import TestCase, Client
from django.contrib.auth.models import User
class YourTestCase(TestCase):
def test_profile(self, user_id):
user = User.objects.create(username='testuser')
user.set_password('12345')
user.save()
client = Client()
client.login(username='testuser', password='12345')
response = client.get("/account/profile/{}/".format(user.id), follow=True)
self.assertEqual(response.status_code, 200)
Here, I first create the user and set the login credentials for the user. Then I create a client and login with that user. So in your views.py, when you do request.user, you will get this user.
If you use django.test you can do something like that:
self.client.force_login(user)
This works:
self.client.force_authenticate(user=user)
If you have a response, you can access response.context['user'].
If you need a response object, just call any view that will create a context, e.g. response = self.client.get('/').
Related
The reason I am asking is I have the fields username,password and otp_token. The otp_token is challenging to create, therefore I was wondering if there is a way create an authenticated user at the beginning of the test file to carry out the rest of the django tests as an authenticated user?
For example, how to pass a logged in user to the following
def some_test(self):
login = self.client.login(username='testUser', password='testPassword')
response = self.client.get(reverse('page1:conent1'))
self.assertEqual(response.status_code, 200)
related question
Not sure how are you generating that token, but I think you can use some dummy data in test
from django.test import TestCase, Client
def setUp(self):
self.user = User.objects.create(username='<USERNAME>',
email='<EMAIL>', otp_token='<YOUR_VALUE>')
self.user.set_password(<PASSWORD>)
self.user.save()
self.client = Client()
def some_test(self):
login = self.client.login(username='<USERNAME>', password='<PASSWORD>')
...
I'm developing an API backend using the Django Rest Framework. I had initially developed it using Session Authentication, unaware that it could not be used for sending to a mobile application. I encountered trouble with respect to CSRF protection while trying user login in Postman.
Now, since I have to shift to Token-based Authentication to make it work, how do I go about doing so? I would like to how to implement it quickly. I have browsed through tutorials and answers on stackoverflow, but am unable to implement this in practise
Also, is Token Authentication the most suitable method for authentication? Should I use the default provided DRF module or JWT or some other implementation? Could I use token authentication simply for user login, and session authentication for the other 3 APIs?
class UserLogin(APIView):
queryset = User.objects.all()
serializer_class = UserSerializer
def post(self, request, format='json'):
username = request.POST.get('username')
email = request.POST.get('email')
password = request.POST.get('password')
user = EmailBackend.authenticate(self,username = email, password = password)
if user:
id = user.id
return Response(id, status=status.HTTP_201_CREATED)
else:
return Response("Failure", status=HTTP_400_BAD_REQUEST)
class UserRegistration(APIView):
queryset = User.objects.all()
serializer_class = UserSerializer
def post(self, request, format='json'):
serializer = UserSerializer(data=request.data)
if serializer.is_valid():
user = serializer.save()
if user:
return Response('Success', status=status.HTTP_201_CREATED)
else:
return Response(serializer.errors, status=HTTP_400_BAD_REQUEST)
class RecommendationQuestions(generics.ListCreateAPIView):
def post(self, request, format = 'json'):
"""Save the post data when logging in."""
uid = request.data['user_id']
resp_list = MovieSerializer.provide_movie_choices(uid)
return Response(resp_list, status=status.HTTP_400_BAD_REQUEST)
class RecommendationGenerator(generics.ListCreateAPIView):
queryset = Ratings.objects.all()#.filter(id__in=(1,2))
serializer_class= RatingsSerializer#(queryset,many=True)
def post(self, request, format='json'):
many = isinstance(request.data, list)
serializer = RatingsSerializer(data = request.data, many = many)
x = 0
if serializer.is_valid():
uid = [d['userId'] for d in serializer.data]
resp_list = RatingsSerializer.generate_recommendations(self, uid[0])
return Response(resp_list, status=status.HTTP_201_CREATED)
else:
return Response(serializer.errors,status=status.HTTP_400_BAD_REQUEST)
This is the views.py for the APIs.
Token authentication setup
You enable TokenAuthentication by including
'rest_framework.authtoken'
in INSTALLED_APPS settings (documentation).
You must run migrate after that. After you run migration, you need to create tokens for your users. Here is an example code that does that:
from django.contrib.auth.models import User
from rest_framework.authtoken.models import Token
users = User.objects.all()
for user in users:
token, created = Token.objects.get_or_create(user=user)
You run this only once. Also, you need to create token for every new user. You can automate that with post_save signal:
from django.contrib.auth.models import User
from django.dispatch import receiver
from django.db.models.signals import post_save
from rest_framework.authtoken.models import Token
#receiver(post_save, sender=User)
def create_auth_token(sender, instance=None, created=False, **kwargs):
if created:
Token.objects.create(user=instance)
Additionally, you have to add configure authentication classes by including
'rest_framework.authentication.TokenAuthentication'
in your settings 'DEFAULT_AUTHENTICATION_CLASSES' (documentation)
Last thing you need to do is add url for token authentication to your urls.py:
from rest_framework.authtoken import views as drf_views
urlpatterns += [
path('api-token-auth/', drf_views.obtain_auth_token)
]
Session-based authentication is meant for logging to your API with your browser. Token-based Authentication is stateless, which means that the server doesn't store any state about the client session on the server. Read more about the difference here. If you login via Token-based authentication, you won't have a session and won't be able to access API in any other way but via token.
Authentication example
Below is a sample code for token authentication in Python with the use of requests library.
# Authentication
import requests
r = requests.post(<add your token auth url here>, data={'username': 'my_username', 'password': 'my_password'})
if r.status_code == 200:
response = r.json()
token = response['token']
print(token)
Token must be used for every other API request. It's sent via headers.
# Consume API
import requests
headers = {'Authorization': 'Token {}'.format(<your token here>)}
# Request method is either GET, POST, PUT, PATCH or DELETE
r = requests.request(method=<request method>, url=<url to api>, headers=headers)
# or you can also use
# requests.get(url=<url to api>, headers=headers) or
# requests.post(url=<url to api>, headers=headers, data=<your data>) etc.
I would recommend you to use JWT, it much more safety than what rest_framework.authtoken is provided Such as a pair of token/refresh token to set for your main token small expiration time. That reduces the chance for the token to be stolen or corrupted. Also inside your JWT token, you can store payload which is very useful in many cases.
There is a very good library for DRF which implements all aspects of using JWT with DRF and it's pretty flexible to adapt to your purposes.
http://getblimp.github.io/django-rest-framework-jwt/
Could I use token authentication simply for user login, and session authentication for the other 3 APIs?
Yes, you definitely can. Each instance of APIView has property 'authentication_classes' and you can set SessionAuthentication specifically for APIs you want.
For example:
class RecommendationQuestions(generics.ListCreateAPIView):
authentication_classes = (SessionAuthentication, )
def post(self, request, format = 'json'):
"""Save the post data when logging in."""
uid = request.data['user_id']
resp_list = MovieSerializer.provide_movie_choices(uid)
return Response(resp_list, status=status.HTTP_400_BAD_REQUEST)
Or you can use both
class RecommendationQuestions(generics.ListCreateAPIView):
authentication_classes = (SessionAuthentication, JSONWebTokenAuthentication)
def post(self, request, format = 'json'):
"""Save the post data when logging in."""
uid = request.data['user_id']
resp_list = MovieSerializer.provide_movie_choices(uid)
return Response(resp_list, status=status.HTTP_400_BAD_REQUEST)
I am using DRF with the JWT package for authentication. Now, I'm trying to write a unit test that authenticates itself with a JWT token. No matter how I try it, I can't get the test API client to authenticate itself via JWT. If I do the same with an API client (in my case, Postman), everything works.
This is the test case:
from django.urls import reverse
from rest_framework.test import APITestCase
from rest_framework_jwt.settings import api_settings
from backend.factories import member_factory
jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER
jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER
class MemberTests(APITestCase):
def test_get_member(self):
member = member_factory()
payload = jwt_payload_handler(member.user)
token = jwt_encode_handler(payload)
self.client.credentials(Authorization='JWT {0}'.format(token))
response = self.client.get(reverse('member-detail', kwargs={'pk': member.pk}))
assert response.status_code == 200
But I always get a 401 Authentication credentials were not provided.
In response.request I see the token is there, it's just not being applied I guess.
If I rewrite the test to use rest_framework.test.RequestsClient and actually send it to the live_server URL, it works.
Any help on this?
P.S.: I am aware of force_authenticate() and login, but I would like my unit tests to access the API the same as the API client will in production.
Try setting up a new APIClient for this test. This is how my own test looks like
def test_api_jwt(self):
url = reverse('api-jwt-auth')
u = user_model.objects.create_user(username='user', email='user#foo.com', password='pass')
u.is_active = False
u.save()
resp = self.client.post(url, {'email':'user#foo.com', 'password':'pass'}, format='json')
self.assertEqual(resp.status_code, status.HTTP_400_BAD_REQUEST)
u.is_active = True
u.save()
resp = self.client.post(url, {'username':'user#foo.com', 'password':'pass'}, format='json')
self.assertEqual(resp.status_code, status.HTTP_200_OK)
self.assertTrue('token' in resp.data)
token = resp.data['token']
#print(token)
verification_url = reverse('api-jwt-verify')
resp = self.client.post(verification_url, {'token': token}, format='json')
self.assertEqual(resp.status_code, status.HTTP_200_OK)
resp = self.client.post(verification_url, {'token': 'abc'}, format='json')
self.assertEqual(resp.status_code, status.HTTP_400_BAD_REQUEST)
client = APIClient()
client.credentials(HTTP_AUTHORIZATION='JWT ' + 'abc')
resp = client.get('/api/v1/account/', data={'format': 'json'})
self.assertEqual(resp.status_code, status.HTTP_401_UNAUTHORIZED)
client.credentials(HTTP_AUTHORIZATION='JWT ' + token)
resp = client.get('/api/v1/account/', data={'format': 'json'})
self.assertEqual(resp.status_code, status.HTTP_200_OK)
The following answer applies if you are using Simple JWT and pytest, and Python 3.6+. You need to create a fixture, I have called it api_client, and you need to get the token for an existing user.
from django.contrib.auth.models import User
from rest_framework.test import APIClient
from rest_framework_simplejwt.tokens import RefreshToken
import pytest
#pytest.fixture
def api_client():
user = User.objects.create_user(username='john', email='js#js.com', password='js.sj')
client = APIClient()
refresh = RefreshToken.for_user(user)
client.credentials(HTTP_AUTHORIZATION=f'Bearer {refresh.access_token}')
return client
Notice that in the fixture above, the user is created there, but you can use another fixture to create the user and pass it to this one. The key element is the following line:
refresh = RefreshToken.for_user(user)
This line allows you to create tokens manually as explained in the docs. Once you have that token, you can use the method credentials in order to set headers that will then be included on all subsequent requests by the test client. Notice that refresh.access_token contains the access token.
This fixture has to be used in your tests that you require the user to be authenticated as in the following example:
#pytest.mark.django_db
def test_name_of_your_test(api_client):
# Add your logic here
url = reverse('your-url')
response = api_client.get(url)
data = response.data
assert response.status_code == HTTP_200_OK
# your asserts
I had similar issue, enclosed I send you my solution just to have more code to compare (tests.py).
from django.urls import reverse
from rest_framework import status
from rest_framework.test import APITestCase
from django.contrib.auth.models import User
class AuthViewsTests(APITestCase):
def setUp(self):
self.username = 'usuario'
self.password = 'contrasegna'
self.data = {
'username': self.username,
'password': self.password
}
def test_current_user(self):
# URL using path name
url = reverse('tokenAuth')
# Create a user is a workaround in order to authentication works
user = User.objects.create_user(username='usuario', email='usuario#mail.com', password='contrasegna')
self.assertEqual(user.is_active, 1, 'Active User')
# First post to get token
response = self.client.post(url, self.data, format='json')
self.assertEqual(response.status_code, status.HTTP_200_OK, response.content)
token = response.data['token']
# Next post/get's will require the token to connect
self.client.credentials(HTTP_AUTHORIZATION='JWT {0}'.format(token))
response = self.client.get(reverse('currentUser'), data={'format': 'json'})
self.assertEqual(response.status_code, status.HTTP_200_OK, response.content)
Well, since i was using django unit test client, i just created a simple base test class with a bearer token property:
import json
from django.test import TestCase
from django.contrib.auth import User
from rest_framework.test import APIClient
from rest_framework_simplejwt.tokens import RefreshToken
class TestCaseBase(TestCase):
#property
def bearer_token(self):
# assuming there is a user in User model
user = User.objects.get(id=1)
refresh = RefreshToken.for_user(user)
return {"HTTP_AUTHORIZATION":f'Bearer {refresh.access_token}'}
In my django unit tests:
class SomeTestClass(TestCaseBase):
url = "someurl"
def test_get_something(self):
self.client.get(self.url, **self.bearer_token)
def test_post_something(self):
self.client.post(self.url, data={"key":"value"}, **self.bearer_token)
Inspired by #dkarchmer, this is my code working.
I am using a custom user model which the email is used for authentication.
Pay attention to using email field for authentication requests.
If I use username, the response is 400_BAD_REQUEST.
The 401_UNAUTHORIZED usually means the credentials are not correct or the user is not activated.
def test_unusual(self):
User = get_user_model()
email = 'user#test.com'
password = 'userpass1'
username = 'user'
user = User.objects.create_user(
username=username, email=email, password=password)
user.is_active = False
user.save()
obtain_url = reverse('token_obtain_pair')
resp = self.client.post(
obtain_url, {'email': email, 'password': password}, format='json')
self.assertEqual(resp.status_code, status.HTTP_401_UNAUTHORIZED)
user.is_active = True
user.save()
resp = self.client.post(
obtain_url, {'email': email, 'password': password}, format='json')
self.assertEqual(resp.status_code, status.HTTP_200_OK)
Postman interacts with your actual database. Django uses separate database for it's test case running. Therefore a new user record needs to be created again inside your test definition before authentication testing. Hope this helps.
I'm using DRF and simple-jwt and I had to use Bearer instead of JWT in the http auth header: HTTP_AUTHORIZATION=f'Bearer {token}'
Full code:
def setUp(self):
username = "tim#me.com"
password = "strongP#assword!"
self.user = User.objects.create_user(username, username, password)
jwt_fetch_data = {
'username':username,
'password':password
}
url = reverse('token_obtain_pair')
response = self.client.post(url, jwt_fetch_data, format='json')
token = response.data['access']
self.client.credentials(HTTP_AUTHORIZATION=f'Bearer {token}')
def test_post(self):
response = self.client.get('/some-url/',
data={'format': 'json'}
)
self.assertEqual(response.status_code, status.HTTP_200_OK)
from rest_framework.test import APITestCase
from django.contrib.auth import get_user_model
from django.urls import reverse
from rest_framework import status
from rest_framework_simplejwt.tokens import RefreshToken
User = get_user_model()
class TestCaseBase(APITestCase):
#property
def bearer_token(self):
# assuming there is a user in User model
user = User.objects.create_user(
email='test#user.me', password='12345678'
)
refresh = RefreshToken.for_user(user)
return {"HTTP_AUTHORIZATION": f'Bearer {refresh.access_token}'}
class CategoriesTestClass(TestCaseBase):
url = reverse('categories-list')
def test_get_list_no_auth(self):
response = self.client.get(self.url)
self.assertEqual(
response.status_code, status.HTTP_401_UNAUTHORIZED, response.data
)
def test_get_list(self):
response = self.client.get(self.url, **self.bearer_token)
self.assertEqual(response.status_code, status.HTTP_200_OK)`enter code here`
So I'm doing TDD with Django and I'm stuck on the following problem.
Test method for class from TestCase
def test_home_page_can_save_POST_request(self):
request = HttpRequest
request.method = 'POST'
request.POST['item_text'] = 'A new list item'
response = home_page(request)
self.assertIn('A new list item', response.content.decode())
And I'm getting error as:
request.POST['item_text'] = 'A new list item'
AttributeError: type object 'HttpRequest' has no attribute 'POST'
But HttpRequest has the attribute 'POST' as per Django docs.
Thansk guys!
You forgot the parentheses after HttpRequest :)
That's why django is saying HttpRequest has no attribute 'POST'
You should consider using RequestFactory for your test cases.
Example from the docs:
from django.contrib.auth.models import AnonymousUser, User
from django.test import TestCase, RequestFactory
from .views import MyView, my_view
class SimpleTest(TestCase):
def setUp(self):
# Every test needs access to the request factory.
self.factory = RequestFactory()
self.user = User.objects.create_user(
username='jacob', email='jacob#…', password='top_secret')
def test_details(self):
# Create an instance of a GET request.
request = self.factory.get('/customer/details')
# Recall that middleware are not supported. You can simulate a
# logged-in user by setting request.user manually.
request.user = self.user
# Or you can simulate an anonymous user by setting request.user to
# an AnonymousUser instance.
request.user = AnonymousUser()
# Test my_view() as if it were deployed at /customer/details
response = my_view(request)
# Use this syntax for class-based views.
response = MyView.as_view()(request)
self.assertEqual(response.status_code, 200)
When testing, if you need a request object, you can also use the request factory to generate one
Reference from the docs:
https://docs.djangoproject.com/en/1.11/topics/testing/advanced/
from django.contrib.auth.models import AnonymousUser, User
from django.test import TestCase, RequestFactory
from .views import MyView, my_view
class SimpleTest(TestCase):
def setUp(self):
# Every test needs access to the request factory.
self.factory = RequestFactory()
self.user = User.objects.create_user(
username='jacob', email='jacob#…', password='top_secret')
def test_details(self):
# Create an instance of a GET request.
request = self.factory.get('/customer/details')
# Recall that middleware are not supported. You can simulate a
# logged-in user by setting request.user manually.
request.user = self.user
# Or you can simulate an anonymous user by setting request.user to
# an AnonymousUser instance.
request.user = AnonymousUser()
# Test my_view() as if it were deployed at /customer/details
response = my_view(request)
# Use this syntax for class-based views.
response = MyView.as_view()(request)
self.assertEqual(response.status_code, 200)
In your case, your test could look like this:
from django.test import RequestFactory
...
def test_home_page_can_save_POST_request(self):
factory = RequestFactory()
response = factory.post('PATH_TO_YOUR_VIEW', data={
'item_text': 'A new list item',
})
self.assertIn('A new list item', response.content.decode())
I have two solution.
1.- Create request object with RequestFactory and calling at views.
def test_home_page_can_save_a_POST_request(self):
request = self.factory.post("/", data={'item_text': 'A new list item',})
response = home_page(request)
self.assertIn('A new list item', response.content.decode())
2.- Or use the response of RequestFactory().
def test_home_page_can_save_a_POST_request(self):
response = self.factory.post("/", data={'item_text': 'A new list item',})
self.assertIn('A new list item', response.readlines()[3].decode())
I have started using Django's testing framework, and everything was working fine until I started testing authenticated pages.
For the sake of simplicity, let's say that this is a test:
class SimpleTest(TestCase):
def setUp(self):
user = User.objects.create_user('temporary', 'temporary#gmail.com', 'temporary')
def test_secure_page(self):
c = Client()
print c.login(username='temporary', password='temporary')
response = c.get('/users/secure/', follow=True)
user = User.objects.get(username='temporary')
self.assertEqual(response.context['email'], 'temporary#gmail.com')
After I run this test, it fails, and I see that printing return value of login() returns True, but response.content gets redirected to login page (if login fails authentication decorator redirects to login page). I have put a break point in decorator that does authentication:
def authenticate(user):
if user.is_authenticated():
return True
return False
and it really returns False. Line 4 in test_secure_page() properly retrieves user.
This is the view function:
#user_passes_test(authenticate, login_url='/users/login')
def secure(request):
user = request.user
return render_to_response('secure.html', {'email': user.email})
Of course, if I try to login through application (outside of test), everything works fine.
The problem is that you're not passing RequestContext to your template.
Also, you probably should use the login_required decorator and the client built in the TestCase class.
I'd rewrite it like this:
#views.py
from django.contrib.auth.decorators import login_required
from django.shortcuts import render
from django.contrib.auth import get_user_model
#login_required(login_url='/users/login')
def secure(request):
user = request.user
return render(request, 'secure.html', {'email': user.email})
#tests.py
class SimpleTest(TestCase):
def setUp(self):
User = get_user_model()
user = User.objects.create_user('temporary', 'temporary#gmail.com', 'temporary')
def test_secure_page(self):
User = get_user_model()
self.client.login(username='temporary', password='temporary')
response = self.client.get('/manufacturers/', follow=True)
user = User.objects.get(username='temporary')
self.assertEqual(response.context['email'], 'temporary#gmail.com')
It can often be useful to use a custom auth backend that bypassess any sort of authentication during testing:
from django.contrib.auth import get_user_model
class TestcaseUserBackend(object):
def authenticate(self, testcase_user=None):
return testcase_user
def get_user(self, user_id):
User = get_user_model()
return User.objects.get(pk=user_id)
Then, during tests, add yourapp.auth_backends.TestcaseUserBackend to your AUTHENTICATION_BACKENDS:
AUTHENTICATION_BACKENDS = [
"akindi.testing.auth_backends.TestcaseUserBackend",
]
Then, during tests, you can simply call:
from django.contrib.auth import login
user = User.objects.get(…)
login(testcase_user=user)
Token based authentication:
I was in same situation. I found solution in which actually I did generate a user for login purpose in setUp method. Then later in the test methods, I tried to get the token and passed it along with request data.
setUp:
create a user
self.pravesh = User.objects.create(
email='psj.aaabbb#gmail.com',
first_name='Pravesh',
last_name='aaabbb',
phone='5456165156',
phonecountrycode='91'
)
set password for the user
self.password = 'example password'
self.pravesh.set_password(self.password)
test_method:
create client
client.login(email=self.pravesh.email, password=self.password)
get token (in case of token auth)
token = Token.objects.create(user=self.pravesh)
pass login information
response = client.post(
reverse('account:post-data'),
data = json.dumps(self.data),
HTTP_AUTHORIZATION='Token {}'.format(token),
content_type = 'application/json'
)