I am trying to test a view which only shows a template thus far. To access the view the user needs the permission 'projects.can_view'.
I am trying to create a user with this permission in the test db using a fixture, then I am trying to log a client in with the username and password of my user in the db. I try to visit the view but I get a 302 error (redirect to login view.) I can verify that I actually pass the permissions correctly:
My fixture:
#pytest.fixture()
def create_users_with_permissions():
User = get_user_model()
u = User.objects.create(
username="test",
password="pass",
)
permission = Permission.objects.get(name='Can view project')
u.user_permissions.add(permission)
u = User.objects.get(username='test')
print(u.has_perm('appname.view_project'))
return u
I query the user again to delete the permissions cache like explained in the doc (https://docs.djangoproject.com/en/3.0/topics/auth/default/). When I print out print(u.has_perm('appname.view_project')) it returns True. Thus I assume I have a user with this specific permission created.
Now I pass the fixture to my test and log pytests client-fixture in:
def test_xyz(
self, client, create_users_with_permissions
):
client.login(
username=create_users_with_permissions.username,
password=create_users_with_permissions.password
)
url = reverse('urlforview')
response = client.get(url)
assert response.status_code == 200
This fails with error code: assert 302 == 200
If I create a superuser in the db and log in with the respective user it works. When I hardcode username and password it also fails. What am I missing here?
Thanks for every help or suggestion, very much appreciated!
My view only has this:
class ProjectCustomerList(PermissionRequiredMixin, TemplateView):
template_name = 'mytemplate.html'
permission_required = 'projects.can_view'
I have created users for my unit tests in two ways:
1) Create a fixture for "auth.user" that looks roughly like this:
{
"pk": 1,
"model": "auth.user",
"fields": {
"username": "homer",
"is_active": 1,
"password":
"sha1$72cd3$4935449e2cd7efb8b3723fb9958fe3bb100a30f2",
...
}
}
I've left out the seemingly unimportant parts.
2) Use 'create_user' in the setUp function (although I'd rather keep
everything in my fixtures class):
def setUp(self):
User.objects.create_user('homer', 'ho...#simpson.net', 'simpson')
Note that the password is simpson in both cases.
I've verified that this info is correctly being loaded into the test database time and time again. I can grab the User object using User.objects.get. I can verify the password is correct using 'check_password.' The user is active.
Yet, invariably, self.client.login(username='homer', password='simpson') FAILS. I'm baffled as to why. I think I've read every single Internet discussion pertaining to this. Can anybody help?
The login code in my unit test looks like this:
login = self.client.login(username='homer', password='simpson')
self.assertTrue(login)
Thanks.
The code that doesn't work:
from django.contrib.auth.models import User
from django.test import Client
user = User.objects.create(username='testuser', password='12345')
c = Client()
logged_in = c.login(username='testuser', password='12345')
Why doesn't it work?
In the snippet above, when the User is created the actual password hash is set to be 12345. When the client calls the login method, the value of the password argument, 12345, is passed through the hash function, resulting in something like
hash('12345') = 'adkfh5lkad438....'
This is then compared to the hash stored in the database, and the client is denied access because 'adkfh5lkad438....' != '12345'
The Solution
The proper thing to do is call the set_password function, which passes the given string through the hash function and stores the result in User.password.
In addition, after calling set_password we must save the updated User object to the database:
user = User.objects.create(username='testuser')
user.set_password('12345')
user.save()
c = Client()
logged_in = c.login(username='testuser', password='12345')
An easier way is to use force_login, new in Django 1.9.
force_login(user, backend=None)
For example:
class LoginView(TestCase):
def setUp(self):
self.client.force_login(User.objects.get_or_create(username='testuser')[0])
Check that django.contrib.sessions is added to INSTALLED_APPS because client.login() checks that it is and will always return false if it is not:
https://docs.djangoproject.com/es/1.9/topics/http/sessions/#enabling-sessions
Can you check like below,
from django.test import TransactionTestCase, Client
class UserHistoryTest(TransactionTestCase):
self.user = User.objects.create(username='admin', password='pass#123', email='admin#admin.com')
self.client = Client() # May be you have missed this line
def test_history(self):
self.client.login(username=self.user.username, password='pass#123')
# get_history function having login_required decorator
response = self.client.post(reverse('get_history'), {'user_id': self.user.id})
self.assertEqual(response.status_code, 200)
This test case worked for me.
If you are using rest_framework, make sure session-based authentication is enabled. That was my issue.
Go to your settings.py file and check that REST_FRAMEWORK -> DEFAULT_AUTHENTICATION_CLASSES includes SessionAuthentication:
REST_FRAMEWORK = {
"DEFAULT_AUTHENTICATION_CLASSES": [
"rest_framework.authentication.TokenAuthentication",
"rest_framework.authentication.SessionAuthentication",
],
...
}
It looks like the login method uses the vanilla Django session-based approach, so if you were only using rest_framework's token auth that's going to fail.
from django.test import TestCase
from django.contrib.auth.models import User
from django.test import Client
class MyProfile(TestCase):
#classmethod
def setUpClass(self):
self.username = 'dummy' + data + '#gmail.com'
self.password = 'Dummy#123'
user = User.objects.create(username=self.username)
user.set_password(self.password)
user.save()
c = Client()
self.client_object = c.login(username=self.username, password=self.password)
self.content_type = "application/json"
response = self.client_object.post('/api/my-profile/', content_type=self.content_type)
If you just need to have an authenticated user during testing the test cases you can use force_login which does not need any authentication properties just pass the user object.
def test_something_view(self):
client = Client()
client.force_login(self.user)
response = client.post(reverse('your custom url'), follow=True)
self.assertEqual(response.status_code, 200)
If anyone still following this , I think the attributes 'is_staff' and 'is_active' should be kept True for successfully logging in......
self.user = User.objects.create(username='testuser',password='pwd',is_active=1,is_staff=1)
This is my first test in Django - it's a simple view test to assert that the response in 200.
I'm using the authentication so I'm creating a test user in the first place:
class SettingsTests(TestCase):
def setUp(self):
self.client = Client()
self.username = 'test_user'
self.email = 'test#whatever.com'
self.password = 'test'
self.user = User.objects.create_user(self.username, self.email, self.password)
def tearDown(self):
self.user.delete()
Here's the actual test where I'm trying to test the view:
def test_settings_view_is_diplayed_correctly(self):
login = self.client.login(username = self.username, password = self.password)
response = self.client.get('/')
self.assertEqual(response.status_code, 200)
This always returns 404, however if I try the same in the shell it works and returns 200!
Can somebody please tell me what's wrong here?
The problem, as suggested by Alasdair was the view. I had a get_object_or_404 shortcut and did not provide the needed object. Hence, the view was returning 404 and the test was failing...
I want to log when session hash verification fails. The logging code should be inserted inside this https://github.com/django/django/blob/master/django/contrib/auth/init.py#L183 if block.
I am trying to figure out what would be the best way to implement this. Currently it looks like I will need to override the whole django.contrib.auth.middleware.AuthenticationMiddleware.
Do you have any tips for me?
Why don't you copy get_user function and put the logger like you want to:
from django.contrib.auth import *
def your_get_user(request):
"""
Returns the user model instance associated with the given request session.
If no user is retrieved an instance of `AnonymousUser` is returned.
"""
from django.contrib.auth.models import User, AnonymousUser
user = None
try:
user_id = _get_user_session_key(request)
backend_path = request.session[BACKEND_SESSION_KEY]
except KeyError:
pass
else:
if backend_path in settings.AUTHENTICATION_BACKENDS:
backend = load_backend(backend_path)
user = backend.get_user(user_id)
# Verify the session
if ('django.contrib.auth.middleware.SessionAuthenticationMiddleware'
in settings.MIDDLEWARE_CLASSES and hasattr(user, 'get_session_auth_hash')):
session_hash = request.session.get(HASH_SESSION_KEY)
session_hash_verified = session_hash and constant_time_compare(
session_hash,
user.get_session_auth_hash()
)
if not session_hash_verified:
log = logging.getLogger("YourLog")
log.debug(session_hash)
request.session.flush()
user = None
return user or AnonymousUser()
And use this like you want to in your code
I have a question about how the code run in inheritance in Python. It might look like a dummy question somehow, but I a new to Python.
This a code snippet from some Facebook application I am working on:
class BaseHandler(webapp.RequestHandler):
facebook = None
user = None
def initialize(self, request, response):
"""General initialization for every request"""
super(BaseHandler, self).initialize(request, response)
try:
self.init_facebook()
except Exception, ex:
self.log_exception(ex)
raise
def init_facebook(self):
"""Sets up the request specific Facebook and user instance"""
facebook = Facebook()
user = None
# Initially Facebook request comes in as a POST with a signed_request
if u'signed_request' in self.request.POST:
facebook.load_signed_request(self.request.get('signed_request'))
# We reset the method to GET because a request from Facebook with a
# signed_request uses POST for security reasons, despite it
# actually being a GET. In a web application this causes loss of request.POST data.
self.request.method = u'GET'
self.set_cookie(
'u', facebook.user_cookie, datetime.timedelta(minutes=1440))
elif 'u' in self.request.cookies:
facebook.load_signed_request(self.request.cookies.get('u'))
# Try to load or create a user object
if facebook.user_id:
user = User.get_by_key_name(facebook.user_id)
if user:
# Update stored access_token
if facebook.access_token and \
facebook.access_token != user.access_token:
user.access_token = facebook.access_token
user.put()
# Refresh data if we failed in doing so after a realtime ping.
if user.dirty:
user.refresh_data()
# Restore stored access_token if necessary
if not facebook.access_token:
facebook.access_token = user.access_token
if not user and facebook.access_token:
me = facebook.api(u'/me', {u'fields': _USER_FIELDS})
try:
friends = [user[u'id'] for user in me[u'friends'][u'data']]
user = User(key_name=facebook.user_id,
user_id=facebook.user_id, friends=friends,
access_token=facebook.access_token, name=me[u'name'],
email=me.get(u'email'), picture=me[u'picture'])
user.put()
except KeyError, ex:
pass # Ignore if can't get the minimum fields.
self.facebook = facebook
self.user = user
This is another class that inherits from BaseHandler
class RecentRunsHandler(BaseHandler):
"""Show recent runs for the user and friends"""
def get(self):
if self.user:
friends = {}
for friend in select_random(
User.get_by_key_name(self.user.friends), 30):
friends[friend.user_id] = friend
self.render(u'runs',
friends=friends,
user_recent_runs=Run.find_by_user_ids(
[self.user.user_id], limit=5),
friends_runs=Run.find_by_user_ids(friends.keys()),
)
else:
self.render(u'welcome')
Does the initialize function in the BaseHandler get called when the RecentRunsHandler is called?
I am asking this because if after the user "allow" the application (and the user data is saved in the database) ... The application still redirects him to the welcoming page where the Facebook-login button exists.
To make it more clear, the application can't know that the user has authorized him before.
Probably not. Perhaps you should def initialize to def __init__? Python objects get instantiated through the __init__ method, not through any initialize() method. Since you don't seem to have any explicit calls to initialize(), it will probably not be called.