I have a Django app that uses the builtin login system.
I'm trying to write a unit test that verifies that the login url redirects to home if the user is already authenticated. Meaning, if an already authenticated user tries to visit the login url, they get redirected to home.
This is a built-in feature of Django's LoginView, and I have verified that it is indeed working. However, I'm not sure how to write a unit test for this.
Question: How do I verify that this redirect occurs via unit tests? LoginView returns a TemplateResponse, which doesn't have a url parameter.
What I have so far is this:
from django.test import TestCase
from django.urls import reverse
home = reverse('home')
login = reverse('login')
logout = reverse('logout')
signup = reverse('signup')
USER_CREDS = { some users credentials }
class TestLoginView(TestCase):
def test_login_redirect(self):
self.client.post(signup, USER_CREDS)
self.client.get(logout)
self.client.post(login, USER_CREDS)
response = self.client.get(login)
self.assertRedirects(response, home)
EDIT:
SimpleTestCase.assertRedirects does not work for TemplateResponse objects.
TemplateResponse objects do not contain a url parameter, which is required for SimpleTestCase.assertRedirect.
The method raises the following error: AttributeError: 'TemplateResponse' object has no attribute 'url'
This is not the same as the linked question, because the response object from a LoginView is a TemplateResponse, not an HttpResponse.
You can check that the login process redirects to the desired named URL with assertRedirects:
SimpleTestCase.assertRedirects(response, expected_url, status_code=302, target_status_code=200, msg_prefix='', fetch_redirect_response=True)
Asserts that the response returned a status_code redirect status,
redirected to expected_url (including any GET data), and that the
final page was received with target_status_code.
Then in your test:
self.assertRedirects(response, home)
Edit
I've just had a similar problem, few notes:
Instead of performing the login process, you can use TestCase.force_login that skips the authentication and verification steps.
class TestLoginView(TestCase):
def test_login_redirect(self):
self.client.force_login(user)
# logged user retrieves the login page
response = self.client.get(login)
self.assertRedirects(response, home)
Response should be a 302 redirect to home: <HttpResponseRedirect status_code=302, "text/html; charset=utf-8", url="/">
If you are not getting a redirect when doing response = self.client.get(login) that means that it is loading the login page, which is what you don't want, so you have to fix that error.
So the problem is not that TemplateResponse objects do not contain a url parameter, it is that your login page isn't redirecting a logged in user to the home page.
Related
test.html:
authorize
views.py:
from django.contrib.auth.decorators import login_required
#login_required
def myview(req):
user = req.user
return render(req, 'test.html')
For ebay's oauth process, you have to provide users with a link to ebay's server, which asks the user if they want to give credentials to you. If they accept, ebay redirects the user to a given url with a querystring containing the access key.
The problem is, when I authorize my app with ebay, the user gets redirected to my login page (despite already being logged in). If I remove the #login_required decorator, req.user returns AnonymousUser instead. This is a problem since I don't know which user to assign the access token to.
What am I missing here?
Note that I am using ngrok to tunnel my server, and I have no problems
rendering myview other than the fact that the user is Anonymous.
The problem was that when I logged the user in initially, I was using the domain localhost:8000 instead of my ngrok instance.
Logging my user in using my ngrok address fixed the problem.
I am trying to send a POST request to Django with JSON data in it and the view is returning response with JSON data. But when I send a request to it, it returns with 403 Forbidden error. I am using RESTClient to send/test POST requests.
I have read all about CSRF in documentation but its not very helpful. I am fairly new to Django and the other questions posted here are not helping me a lot.
The code in my view is:
from django.shortcuts import render
from django.http import HttpResponse;
import json;
def index(request):
if request.is_ajax():
if request.method == 'POST':
print 'Raw Data: "%s"' % request.body;
reply = json.loads(request.body);
return HttpResponse(reply);
else:
return HttpResponse("OK");
else:
return HttpResponse("OK");
In addition to #ArpitGoyal's answer you can also decorate your view with csrf_exempt:
This decorator marks a view as being exempt from the protection ensured by the middleware.
A few tips in case you do need CSRF protection:
Check CSRF token cookie name.
See CSRF_COOKIE_NAME for more information.
Add ensure_csrf_cookie decorator to your view.
According to the docs:
Warning
If your view is not rendering a template containing the csrf_token template tag, Django might not set the CSRF token cookie. This is common in cases where forms are dynamically added to the page. To address this case, Django provides a view decorator which forces setting of the cookie: ensure_csrf_cookie().
Assuming that CSRF token cookie name is csrftoken, try to send X-CSRFToken header.
$.ajax({
// Your options here.
headers: {'X-CSRFToken': getCookie('csrftoken')}
});
You should authenticate your client before making the request. From your call you are providing a ajax POST request hit.
Provide a header in your RESTClient: X-CSRFToken.
For more details view this
I'm a newcomer to the python/Django universe and just started a huge project I'm pretty excited about. I need to have my users login through Facebook and my app has a really specific user flow. I've set up django-allauth and everything works as I needed. I've overriden LOGIN_REDIRECT_URL so that my users land on the page I want when they log in.
BUT. When the user opens Facebook login dialog box and then closes it without logging in, the authentication_error.html template gets rendered by allauth.socialaccount.helpers.render_authentication_error, and this is not the behaviour I want. I want the user to simply be redirected to the login page.
Yes, I know I could simply override the template by putting it in my TEMPLATE_DIRS, but then the url wouldn't be the same.
I've come to the conclusion I needed a middleware to intercept the response to the http request.
from django.shortcuts import redirect
class Middleware():
"""
A middleware to override allauth user flow
"""
def __init__(self):
self.url_to_check = "/accounts/facebook/login/token/"
def process_response(self, request, response):
"""
In case of failed faceboook login
"""
if request.path == self.url_to_check and\
not request.user.is_authenticated():
return redirect('/')
return response
But I'm not sure about the efficiency of my solution nor the pythonesquitude (I juste came up with that word) of it. Is there anything else I could do to change that default django-allauth behavior without using a middleware or a signal?
Thanks!
Yes, I know I could simply override the template by putting it in my TEMPLATE_DIRS, but then the url wouldn't be the same.
Overriding a template won't change the URL. In your overridden template, you could do a client-side redirect to whatever URL you prefer.
I decided to use a middleware and redirect to home url in case a GET request is made on a URL of the form ^/accounts/.*$
from django.shortcuts import redirect
import re
class AllauthOverrideMiddleware():
"""
A middleware to implement a custom user flow
"""
def __init__(self):
# allauth urls
self.url_social = re.compile("^/accounts/.*$")
def process_request(self, request):
# WE CAN ONLY POST TO ALLAUTH URLS
if request.method == "GET" and\
self.url_social.match(request.path):
return redirect("/")
handlers:
- url: /secure_api/.*
script: _go_app
login: required
auth_fail_action: unauthorized
This code only brings me to a page saying "Login required to view page." Is there a way to instead redirect to my home page?
When you specify auth_fail_action: unauthorized, you get the page you are seeing (see here for the details). Changing unauthorized to redirect will take them to the login screen, but if you want to do more granular handling of users based on their logged-in status, your best bet is to do it inside of your code via the Users API. For instance (this is adapted from the docs), here is a simple example that would redirect a non-logged-in user to /:
from google.appengine.api import users
import webapp2
class MyHandler(webapp2.RequestHandler):
def get(self):
user = users.get_current_user()
if user:
# Do stuff here for logged-in users
else:
# Redirect if user is None, which is the case for non-logged-in users
self.redirect('/')
Maybe a stupid question here:
Is Requests(A python HTTP lib) support Django 1.4 ?
I use Requests follow the Official Quick Start like below:
requests.get('http://127.0.0.1:8000/getAllTracks', auth=('myUser', 'myPass'))
but i never get authentication right.(Of course i've checked the url, username, password again and again.)
The above url 'http://127.0.0.1:8000/getAllTracks' matches an url pattern of the url.py of a Django project, and that url pattern's callback is the 'getAllTracks' view of a Django app.
If i comment out the authentication code of the 'getAllTracks' view, then the above code works OK, but if i add those authentication code back for the view, then the above code never get authenticated right.
The authentication code of the view is actually very simple, exactly like below (The second line):
def getAllTracks(request):
if request.user.is_authenticated():
tracks = Tracks.objects.all()
if tracks:
# Do sth. here
Which means if i delete the above second line(with some indents adjustments of course), then the requests.get() operation do the right thing for me, but if not(keep the second line), then it never get it right.
Any help would be appreciated.
In Django authentication works in following way:
There is a SessionMiddleware and AuthenticationMiddleware. The process_request() of both these classes is called before any view is called.
SessionMiddleware uses cookies at a lower level. It checks for a cookie named sessionid and try to associate this cookie with a user.
AuthenticationMiddleware checks if this cookie is associated with an user then sets request.user as that corresponding user. If the cookie sessionid is not found or can't be associated with any user, then request.user is set to an instance of AnonymousUser().
Since Http is a stateless protocol, django maintains session for a particular user using these two middlewares and using cookies at a lower level.
Coming to the code, so that requests can work with django.
You must first call the view where you authenticate and login the user. The response from this view will contain sessionid in cookies.
You should use this cookie and send it in the next request so that django can authenticate this particular user and so that your request.user.is_authenticated() passes.
from django.contrib.auth import authenticate, login
def login_user(request):
user = authenticate(username=request.POST.get('username'), password=request.POST.get('password'))
if user:
login(request, user)
return HttpResponse("Logged In")
return HttpResponse("Not Logged In")
def getAllTracks(request):
if request.user.is_authenticated():
return HttpResponse("Authenticated user")
return HttpResponse("Non Authenticated user")
Making the requests:
import requests
resp = requests.post('http://127.0.0.1:8000/login/', {'username': 'akshar', 'password': 'abc'})
print resp.status_code
200 #output
print resp.content
'Logged In' #output
cookies = dict(sessionid=resp.cookies.get('sessionid'))
print cookies
{'sessionid': '1fe38ea7b22b4d4f8d1b391e1ea816c0'} #output
response_two = requests.get('http://127.0.0.1:8000/getAllTracks/', cookies=cookies)
Notice that we pass cookies using cookies keyword argument
print response_two.status_code
200 #output
print response_two.content
'Authenticated user' #output
So, our request.user.is_authenticated() worked properly.
response_three = requests.get('http://127.0.0.1:8000/hogwarts/getAllTracks/')
Notice we do not pass the cookies here.
print response_three.content
'Non Authenticated user' #output
I guess, auth keyword for Requests enables HTTP Basic authentication which is not what is used in Django. You should make a POST request to login url of your project with username and password provided in POST data, after that your Requests instance will receive a session cookie with saved authentication data and will be able to do successful requests to auth-protected views.
Might be easier for you to just set a cookie on initial authentication, pass that back to the client, and then for future requests expect the client to send back that token in the headers, like so:
r = requests.post('http://127.0.0.1:8000', auth=(UN, PW))
self.token = r.cookies['token']
self.headers = {'token': token}
and then in further calls you could, assuming you're in the same class, just do:
r = requests.post('http://127.0.0.1:8000/getAllTracks', headers=self.headers)