My problem: I'm writing tests and I'm getting 302 responses instead of 200. I figure this is kind of expected because when a user isn't logged in and isn't assigned to the group Employee_Management, they will always be redirected.
So I'm trying to create a user and add it to the group, but I have no idea how to check if the user is actually logged in, or if it is part of the group. I get no errors from the setUp, but the test still fails and gives me a 302. So my guess is I've messed up my setUp. Ideas?
view:
#method_decorator(group_required('Employee_Management'), name='dispatch')
class ListActiveView(TemplateView):
def get(self, request):
users = User.objects.all().exclude(is_superuser=True)
return render(request, 'user/list_active.html', {
'users': users,
})
urls:
app_name = 'user'
urlpatterns = [path('list_active/', ListActiveView.as_view(), name='list_active')]
test:
class TestListActive(TestCase):
def setUp(self):
user = User.objects.create(username='testuser', password='testuserpass')
emp_man = Group.objects.create(name='Employee_Management')
user.groups.add(emp_man)
c = Client()
c.login(username='testuser', password='testuserpass')
def test_list_active_url(self):
response = self.client.get(reverse('user:list_active'))
self.assertEquals(response.status_code, 200)
Your problem is that you use two Client instances.
The first one (c) is the one you create in setUp, which your user is logged to but that is never used again.
The other one is the TestCase instance's client (self.client) which you use in test_list_active_url and which your user is not logged in to.
To fix that, always use the TestCase instance's client:
class TestListActive(TestCase):
def setUp(self):
user = User.objects.create(username='testuser', password='testuserpass')
emp_man = Group.objects.create(name='Employee_Management')
user.groups.add(emp_man)
self.client.login(username='testuser', password='testuserpass')
def test_list_active_url(self):
response = self.client.get(reverse('user:list_active'))
self.assertEquals(response.status_code, 200)
Related
I know how to write django test code.
But I don't know how to test with views that inlcude requests.get code
View code is like this.
class CertificateAPIView(APIView):
permission_classes = [IsAuthenticated, IsOwner]
def put(self, request):
imp = request.data.get("imp")
response = requests.get('another server')
uid = response.get('uid')
if uid == None:
return Response(
{"detail": ValidationError(_("Certification is failed"))},
status=status.HTTP_400_BAD_REQUEST,
)
... more condition branch logics.
return ...
imp value is automatically created by 'another server' that I am not managing.
This code is related with another server response and check condition branch with response.
But before getting response with imp vlue, we can't check whether response is error.
I am trying to test that my custom permissions in DRF work properly, so I need to imitate some requests made by users with different rights (the rights are stored as attributes in the db in a table linked to the default User table).
I am using RequestFactory for setting the request user, but it doesn't seem to work properly, as it still sees the request user as AnonymousUser, and returns AttributeError when checking the permission.
Here's an example of my testcase:
class TestSingleClient(TestCase):
def setUp(self):
self.factory = RequestFactory()
self.user = User.objects.create(
username='admin',
password='admin',
first_name='Name',
last_name='Surname',
email='user#gmail.com',
is_staff=True
)
AccountProfile.objects.create(
phone='8999999999',
occupation='tst_occupation',
user=self.user
)
Client.objects.create(
name='Client1',
type=0)
def test_get_single_client(self):
client = Client.objects.get(name='Client1')
request = self.factory.get('/system/clients/{}/'.format(client.id))
request.user = self.user
response = ClientView.as_view()(request, id=client.id)
client_data = Client.objects.get(id=client.id)
serializer = ClientSerializer(client_data)
self.assertEqual(response.data, serializer.data)
self.assertEqual(response.status_code, status.HTTP_200_OK)
which results in:
File "***/core/extra.py", line 10, in my_permissions_func
roles = AccessRoles.objects.filter(user_groups=user.user_profile.group_id, can_read=True).\
AttributeError: 'AnonymousUser' object has no attribute 'user_profile'
Any suggestions for fixing that?
P.S. A similar topic was discussed here: Django RequestFactory doesn't store a User, which didn't bring me any closer to the answer.
I'm continuing my journey of testing my Django Rest Framework application as I add new views and more functionality. I must admit, at this stage, I'm finding testing harder than actually coding and building my app. I feel that there are far fewer resources on testing DRF available than there are resources talking about building a REST framework with DRF. C'est la vie, though, I soldier on.
My issue that I'm currently facing is that I'm receiving a 403 error when testing one of my DRF ViewSets. I can confirm that the view, and its permissions work fine when using the browser or a regular python script to access the endpoint.
Let's start with the model that is used in my ViewSet
class QuizTracking(models.Model):
case_attempt = models.ForeignKey(CaseAttempt, on_delete=models.CASCADE)
user = models.ForeignKey(CustomUser, on_delete=models.CASCADE)
answer = models.ForeignKey(Answer, on_delete=models.CASCADE)
timestamp = models.DateTimeField(auto_now_add=True)
Of note here is that there is a FK to a user. This is used when determining permissions.
Here is my test function. I have not included code for the entire class for the sake of brevity.
def test_question_retrieve(self):
"""
Check that quiz tracking/ID returns a 200 OK and the content is correct
"""
jim = User(username='jimmy', password='monkey123', email='jimmy#jim.com')
jim.save()
quiz_tracking = QuizTracking(answer=self.answer, case_attempt=self.case_attempt, user=jim)
quiz_tracking.save()
request = self.factory.get(f'/api/v1/progress/quiz-tracking/{quiz_tracking.id}')
# How do I refernce my custom permission using Permission.objects.get() ?
# permission = Permission.objects.get()
# jim.user_permissions.add(permission)
self.test_user.refresh_from_db()
force_authenticate(request, user=jim)
response = self.quiz_detail_view(request, pk=quiz_tracking.id)
print(response.data)
print(jim.id)
print(quiz_tracking.user.id)
self.assertContains(response, 'answer')
self.assertEqual(response.status_code, status.HTTP_200_OK)
In the above code, I define a user, jim and a quiz_tracking object owned by jim.
I build my request, force_authenticate the requst and the execute my request and store the response in response.
The interesting things to note here are:
- jim.id and quiz_tracking.user.id are the same value
- I receive a 403 response with
{'detail': ErrorDetail(string='You do not have permission to perform this action.', code='permission_denied')}
You may have noticed that I have commented out permission = Permission.objects.get() My understanding is that I need to pass this my Permission Class, which in my case is IsUser. However, there is no record of this in my DB and hence Permission.objects.get('IsUSer') call fails.
So my questions are as follows:
- How do I authenticate my request so that I receive a 200 OK?
- Do I need to add a permission to my user in my tests, and if so, which permission and with what syntax?
Below is my view and below that is my custom permission file defining IsUser
class QuickTrackingViewSet(viewsets.ModelViewSet):
serializer_class = QuizTrackingSerializser
def get_queryset(self):
return QuizTracking.objects.all().filter(user=self.request.user)
def get_permissions(self):
if self.action == 'list':
self.permission_classes = [IsUser, ]
elif self.action == 'retrieve':
self.permission_classes = [IsUser, ]
return super(self.__class__, self).get_permissions()
N.B. If I comment out def get_permissions(), then my test passes without problem.
My custom permission.py
from rest_framework.permissions import BasePermission
class IsSuperUser(BasePermission):
def has_permission(self, request, view):
return request.user and request.user.is_superuser
class IsUser(BasePermission):
def has_object_permission(self, request, view, obj):
if request.user:
if request.user.is_superuser:
return True
else:
return obj == request.user
else:
return False
Cheers,
C
It seems that the problem is your permission IsUser.
You compare QuizTracking instance with user instance.
change this line
return obj == request.user
to
return obj.user.id == request.user.id
One off topic suggestion
You can write your IsUser class in the following way which is easier to read
class IsUser(BasePermission):
def has_permission(self, request, view):
return request.user and request.user.is_authenticated
def has_object_permission(self, request, view, obj):
return request.user.is_superuser or obj.user.id == request.user.id
Note that has_object_permission is only called if the view level has_permission returns True.
I have something strange going on that I can't seem to crack. I'm building an API with Tastypie and when I issue this call in my browser against localserver, it works fine: localserver/api/v1/userfavorite/?user__username=testowner
However, in my code, I'm getting an error: "int() argument must be a string or a number, not 'SimpleLazyObject'". I realize it has to do with the user being treated as a request.user object, but I can't figure out where/why. I'm very confused why it works when issuing the API call in the browser, but in the code it is not working.
Here is my code:
# views.py
#login_required
def favorites(request):
'''
display a list of posts that a user has marked as favorite
'''
user = request.user
favorites_url = settings.BASE_URL + "/api/v1/userfavorite/?user__username=" + user.username
favorites = get_json(favorites_url)
return render(request, "maincontent/favorites.html", {'favorites':favorites})
# resources.py
class UserFavoriteResource(ModelResource):
'''
manage post favorites by a user. Users can use a favorites list
to easily view posts that they have liked or deemed important.
'''
user = fields.ForeignKey(UserResource, 'user')
post = fields.ForeignKey('blog.api.resources.PostResource', 'post', full=True)
class Meta:
queryset = UserFavorite.objects.all()
allowed_methods = ['get', 'post', 'delete']
authentication = Authentication()
authorization = Authorization()
filtering = {
'user':ALL_WITH_RELATIONS
}
def hydrate_user(self, bundle):
# build the current user to save for the favorite instance
bundle.data['user'] = bundle.request.user
return bundle
def get_object_list(self, request):
# filter results to the current user
return super(UserFavoriteResource, self).get_object_list(request)\
.filter(user=request.user)
# utils.py
def get_json(url):
# return the raw json from a request without any extraction
data = requests.get(url).json()
return data
Some notes:
1. I have the post method working to create the UserFavorite item
2. I can verify that the favorites_url is being generated correctly
3. I have tried hardcoding the favorites_url as well, same error.
EDIT: 4. I am logged in while doing this, and have verified that request.user returns the user
This doesn't work because there is Anonymous user in your request.user. You are using Authentication it does not require user to be logged in. So if you perform requests call that request is not authenticated and request.user is AnonymousUser and that error occurs when you try to save Anonymous user to db. Tastypie documentation advices to not using browsers to testing things up, just curl instead. Browsers stores a lot of data and yours one probably remembered you have been logged to admin panel in localhost:8000 on another tab that's why it worked in browser.
I would prefer something like this:
def hydrate_user(self, bundle):
"""\
Currently logged user is default.
"""
if bundle.request.method in ['POST', 'PUT']:
if not bundle.request.user.is_authenticated():
raise ValidationError('Must be logged in')
bundle.obj.user = bundle.request.user
bundle.data['user'] = \
'/api/v1/userauth/user/{}'.format(bundle.request.user.pk)
return bundle
How can I test that a user is logged in after submitting the registration form?
I tried the following but it returns True even before I added the login logic to my registration view.
def test_that_user_gets_logged_in(self):
response = self.client.post(reverse('auth-registration'),
{ 'username':'foo',
'password1':'bar',
'password2':'bar' } )
user = User.objects.get(username='foo')
assert user.is_authenticated()
The code that's being tested:
class RegistrationView(CreateView):
template_name = 'auth/registration.html'
form_class = UserCreationForm
success_url = '/'
def auth_login(self, request, username, password):
'''
Authenticate always needs to be called before login because it
adds which backend did the authentication which is required by login.
'''
user = authenticate(username=username, password=password)
login(request, user)
def form_valid(self, form):
'''
Overwrite form_valid to login.
'''
#save the user
response = super(RegistrationView, self).form_valid(form)
#Get the user creditials
username = form.cleaned_data['username']
password = form.cleaned_data['password1']
#authenticate and login
self.auth_login(self.request, username, password)
return response
You can use the get_user method of the auth module. It says it wants a request as parameter, but it only ever uses the session attribute of the request. And it just so happens that our Client has that attribute.
from django.contrib import auth
user = auth.get_user(self.client)
assert user.is_authenticated
This is not the best answer. See https://stackoverflow.com/a/35871564/307511
Chronial has given
an excellent example on how to make this assertion below. His answer
better than mine for nowadays code.
The most straightforward method to test if a user is logged in is by testing the Client object:
self.assertIn('_auth_user_id', self.client.session)
You could also check if a specific user is logged in:
self.assertEqual(int(self.client.session['_auth_user_id']), user.pk)
As an additional info, the response.request object is not a HttpRequest object; instead, it's an ordinary dict with some info about the actual request, so it won't have the user attribute anyway.
Also, testing the response.context object is not safe because you don't aways have a context.
Django's TestClient has a login method which returns True if the user was successfully logged in.
The method is_authenticated() on the User model always returns True. False is returned for request.user.is_authenticated() in the case that request.user is an instance of AnonymousUser, which is_authenticated() method always returns False.
While testing you can have a look at response.context['request'].user.is_authenticated().
You can also try to access another page in test which requires to be logged in, and see if response.status returns 200 or 302 (redirect from login_required).
Where are you initialising your self.client? What else is in your setUp method? I have a similar test and your code should work fine. Here's how I do it:
from django.contrib.auth.models import User
from django.test import TestCase
from django.test.client import Client
class UserTestCase(TestCase):
def setUp(self):
self.client = Client()
def testLogin(self):
print User.objects.all() # returns []
response = self.client.post(reverse('auth-registration'),
{ 'username':'foo',
'password1':'bar',
'password2':'bar' } )
print User.objects.all() # returns one user
print User.objects.all()[0].is_authenticated() # returns True
EDIT
If I comment out my login logic, I don't get any User after self.client.post(. If you really want to check if the user has been authenticated, use the self.client to access another url which requires user authentication. Continuing from the above, access another page:
response = self.client.get(reverse('another-page-which-requires-authentication'))
print response.status_code
The above should return 200 to confirm that the user has authenticated. Anything else, it will redirect to the login page with a 302 code.
There is another succinct way, using wsgi_request in response:
response = self.client.post('/signup', data)
assert response.wsgi_request.user.is_authenticated()
and #Chronial 's manner is also available with wsgi_request:
from django.contrib import auth
user = auth.get_user(response.wsgi_request)
assert user.is_authenticated()
Because response.wsgi_request object has a session attribute.
However, I think using response.wsgi_request.user is more simple.