Django testing - test if logged in user is owner of userdetails - django

So I've made this little app where users have an account page were they can see and update their own details.
Now I'm writing some tests for the DetailView and UpdateView. I would like to verify that the logged in user is indeed the owner of the user details.
I've created a testdatabase with different users and added three tests.
Check if the user is logged in
When a user is logged in does he get the right template
When a user is logged in does he see his own details only
The problem is in the last test, I tried to retrieve the logged in user and compare this with the current user details but this isn't working. Any ideas?
Edit: The test now passes successfully, so the user data belongs to the user who is logged in.
However this doesn't feel like a valid test method. Even though the user matches the owner of the details I'm looking for a way to verify if a user would be able to access the details of someone else.
Before, I would use the user id in the url like;
urls.py
path('account/<int:pk>/', views.AccountDetailView.as_view(), name='account_detail'),
So someone would be able to edit the urlpath and access someone else his details if the LoginRequiredMixin wasn't added.
By using get_object(self):, this is no longer possible, what's the best way to test this possibility?
views.py
class AccountDetailView(LoginRequiredMixin, DetailView):
model = User
template_name = 'account.html'
'''
Retrieve user id from "self.request.user" instead of retrieving the user_id
from the URL. This way we can eliminate the user id in the URL.
'''
def get_object(self):
return self.request.user
test_views.py
class LoggedInTestCase(TestCase):
'''
Setup LoginInTestCase
'''
def setUp(self):
guest = User.objects.create_user(username='john.doe', email='john#doe.com', password='1234')
guest_other = User.objects.create_user(username='david.doe', email='david#doe.com', password='5678')
class AccountDetailViewTests(LoggedInTestCase):
'''
Test the UserDetailView which shows the user details
'''
def test_login_required_redirection(self):
'''
Test if only logged in users can view the user detail page
'''
self.url = reverse('account_detail')
login_url = reverse('account_login')
response = self.client.get(self.url)
self.assertRedirects(response, '{login_url}?next={url}'.format(login_url=login_url, url=self.url))
def test_logged_in_uses_correct_template(self):
'''
Test if logged in user gets to see the correct template
'''
login = self.client.login(username='john.doe', password='1234')
response = self.client.get(reverse('account_detail'))
# Check if our guest is logged in
self.assertEqual(str(response.context['user']), 'john.doe')
# Check for response "succes"
self.assertEqual(response.status_code, 200)
# Check if we get the correct template
self.assertTemplateUsed(response, 'account.html')
def test_accountdetails_belong_to_logged_in_user(self):
'''
Test if logged in user can only see the details that belong to him
'''
login = self.client.login(username='john.doe', password='1234')
response = self.client.get(reverse('account_detail'))
# Check if our guest is logged in matches the
user = User.objects.get(username='john.doe') #edited
self.assertEqual(response.context['user'], user)
# Check for response "success"
self.assertEqual(response.status_code, 200)

The client.login method returns True if the login succeeded, it does not return the user.
You can fetch the user from the database.
user = User.objects.get(username='john.doe')
self.assertEqual(response.context['user'], user)
Or compare the string as in your other test.
self.assertEqual(str(response.context['user']), 'john.doe')

Related

'NoneType' object has no attribute 'keys' in my cart.html in django

i crated add to cart funtionality for my website what problem i am facing right now is when a user logged in add something to cart and then got to the section od add to cart it load the page and show added items but when a user logged out and again signed in to the site and directly go add to cart page whitout adding any item it shows the above mentioned error i guess that every time it logged session got clear but i don't want it to happen any idea what causing the problem?
my views.py for cart
class Cart(View):
def get (self, request):
ids = (list(request.session.get('cart').keys()))
sections = Section.get_sections_by_id(ids)
print(sections)
return render(request, 'cart.html', {'sections': sections})
Yes it flushes the session during logout. You can check the source code here.
To keep the session you can store the added product in persistent memory. May be you can store in database.
[docs]def logout(request):
"""
Remove the authenticated user's ID from the request and flush their session
data.
"""
# Dispatch the signal before the user is logged out so the receivers have a
# chance to find out *who* logged out.
user = getattr(request, 'user', None)
if not getattr(user, 'is_authenticated', True):
user = None
user_logged_out.send(sender=user.__class__, request=request, user=user)
# remember language choice saved to session
language = request.session.get(LANGUAGE_SESSION_KEY)
request.session.flush()
if language is not None:
request.session[LANGUAGE_SESSION_KEY] = language
if hasattr(request, 'user'):
from django.contrib.auth.models import AnonymousUser
request.user = AnonymousUser()
EDIT:
There can be multiple ways to do this :
1.) You can store it in cookies : Solution
2.) Overriding logout method with you custom logout : Solution
3.) Use database table to store the cart info.

how do I make sure a particular user is the one logged in?

so I have some code which creates a user but how do I make sure every url that the user goes to will be in his/her own user session such that request.user will be the user I created?
def liked(request):
try:
sp = Spotify(auth_manager=oauth)
liked = sp.current_user_saved_tracks(limit=30)['items']
spotify_user = sp.current_user()
user__ , created =
User.objects.get_or_create(username=spotify_user['uri'],
first_name=spotify_user["display_name"])
userprofile = UserProfile.objects.get_or_create(user=user__)[0]
i have other views i want only accessible to the user created or gotten from the get_or_create, i reckon I'd have to use the #login_required decorator but then again I do not know what constitutes this "login" with respect to the user I created. How do I do ensure that user is the logged in user?
Django's default MIDDLEWARE takes care of this for you.
In every request object or self.request there is a user object.
Django checks the session via the cookie that the browser sends with every request, and puts the user object in the current request.
An example in a Class based view:
class ExampleView(TemplateView):
template = 'example.html'
def get_context_data(self, **kwargs):
context = super(ExampleView, self).get_context_data(**kwargs)
user = self.request.user
users_data = ExampleData.objects.filter(user=user)
Once a user is logged in, every other page is put into the session. Just log that user in first.
If you want to check if the user is logged in
In class based views;
if self.request.user.is_authenticated
In function based view
if request.user.is_authenticated
Ok as i understand you need to limit some users to access some pages and not for others .. i suggest to use permissions way
add permission to any Model you Have Like this
then when you create your special user do this to assign this permission to him like that : myuser.user_permissions.add('can_view_all_clients', 'another_perm', ...)
For Function Based Views :
from django.contrib.auth.decorators import permission_required
#permission_required("YourModel.can_view_all_clients")
def your_special_view(request):
return 'your view'
For Class Based View :
from django.contrib.auth.mixins import PermissionRequiredMixin
from django.views.generic import DetailView
class ModelDetailView(PermissionRequiredMixin, DetailView):
template_name = "model_detail.html"
model = Model
permission_required = "can_view_all_clients"

How to implement tokenauthentication before views?

I have been learning how to use djangorestframework token authentication using different blog posts and youtube videos.
As for reference, I was following the blog here: https://chrisbartos.com/articles/how-to-implement-token-authentication-with-django-rest-framework/
I could not understand how are we going to check the token before accessing any page. I mean, I am developing an app, that exposes the todos a user creates through the rest framework. I have added a login that saves a user and returns the token created for that person. Now,I want to check that token to find the todos api that the person created and view it in my browser in a seperate url.
As an example:
Once I login through localhost:8000/api/v1/login,
I should get the todos created by me at api/v1/todos in json rest api format.
And if I go to api/v1/todos/1/, it should give me the details of the todo, as I have created in the serializers.
I would like to add some more info:
So, say I have created a class for the login form. It will create a token for me.
So the following is the login in the views:
def signin(request):
username = password = ''
if request.method == 'POST':
username = request.POST['username']
password = request.POST['password']
user = authenticate(username=username, password=password)
if user is not None:
if user.is_active:
return HttpResponse('Logged In')
else:
return HttpResponse('Wrong credentials')
return render(request,'login.html')
So, I want to create a token for this. As mentioned in the djangorestframework documentation https://www.django-rest-framework.org/api-guide/authentication/#tokenauthentication
it creates a seperate view for accessing the api-auth-token that is obtained from the function obtain_auth_token. But, how do I apply this function to save the token from current login in a class based view.
Also, how do I pass this in another class based view, such that it shows no authentication in case I have not logged in but gives me the api in json when authenticated?
https://www.django-rest-framework.org/api-guide/authentication/#tokenauthentication
Add rest_framework.authentication.TokenAuthentication to the DEFAULT_AUTHENTICATION_CLASSES in the REST_FRAMEWORK options in your Django settings.py file.
Add rest_framework.authtoken to your INSTALLED_APPS in settings.py
You can use the #authentication_classes decorator before the views you want to protect like so:
#authentication_classes((TokenAuthentication,))
def sample_function(request):
You'll also need to create tokens for your users, which is documented in that initial link.
class loginview(APIView):
permission_classes = [
permissions.AllowAny # Anyone can Login
]
def post(self,request):
email_address = request.data.get('email')
user_request = get_object_or_404(
User,
email=email_address,
)
username = user_request.username
password = request.data.get("password")
user = authenticate(username=username, password=password)
id_u = user.id
if not user:
return Response({"error": "Login failed"},
status=status.HTTP_401_UNAUTHORIZED)
token, _ = Token.objects.get_or_create(user=user)
return Response({"token": token.key,'id':id_u})
Here is some sample code you can use to obtain Token while using the Login API From the App Frontend. Auth Token can be accessed from the Token model. Don't forget to add:
from rest_framework.authtoken.models import Token
Also Add rest_framework.authtoken to installed apps in the settings.py

Django Tastypie Import Error on resources

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

Test that user was logged in successfully

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.