I want to write a Django test that looks at all the urls in one particular app, and makes sure that hitting them without being authenticated redirects me to the login page.
Something like:
from django.test import TestCase
from django.urls import reverse
from my_app.urls import urlpatterns
class AuthTest(TestCase):
def _test_url(self, url):
response = self.client.get(url)
self.assertRedirects(response, expected_url=reverse("login_app:login")+ "?next=" + url)
def test_auth(self):
for url in urlpatterns.SOMETHING:
with self.subTest(url=url):
self._test_url(url)
Is there a way to get the actual list of urls in each urlpattern for my app?
And, related question: for those URLs that require a pk, is there a way to automatically use pk=1? My fixtures will make sure that pk=1 exists for all those urls.
As our applications needs authentication by default, we used a middleware to make sure the user is login except in case of some exempted paths like API Endpoints that are secure by our means
The middleware goes like this
from django.http import HttpResponseRedirect, HttpResponse
from django.conf import settings
class UserMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
state = request.user.is_authenticated
path = request.META["PATH_INFO"]
if not state and not path in settings.EXEMPTED_PATHS:
url = settings.LOGIN_URL
if not "next" in path:
if "?" in URL:
url += '&'
else: url += "?"
url += "next=%s" % (
settings.BASE_URL + path).replace("//", "/")
if len(request.GET) > 0:
if "?" in URL:
url += "?"
else: url += "&"
url += "%s" % request.GET.urlencode().replace("//", "/")
return HttpResponseRedirect(url)
return self.get_response(request)
I am writing a custom basic LoginRequiredMiddleware for my Django project and so far everything works as expected. I am using process_request because I thought I wanted the request to be checked for authentication before the view is processed.
However, this middleware fails at a test that other developers have written:
self.assertEqual(resp.status_code, status.HTTP_401_UNAUTHORIZED)
AssertionError: 302 != 401
This happens because the view is discovered even if the user is not authenticated when it should actually return a status.HTTP_401_UNAUTHORIZED. How can I rewrite my middleware to comply with the test and return the correct status code?
middleware.py
import re
from django.conf import settings
from django.shortcuts import redirect
from django.contrib.auth import logout
from django.utils.deprecation import MiddlewareMixin
EXEMPT_URLS = [re.compile(settings.LOGIN_URL.lstrip('/'))]
if hasattr(settings, 'LOGIN_EXEMPT_URLS'):
EXEMPT_URLS += [re.compile(url) for url in settings.LOGIN_EXEMPT_URLS]
class LoginRequiredMiddleware(MiddlewareMixin):
def process_request(self, request):
assert hasattr(request, 'user')
path = request.path_info.lstrip('/')
url_is_exempt = any(url.match(path) for url in EXEMPT_URLS)
if path == settings.LOGOUT_URL.lstrip('/'):
logout(request)
if request.user.is_authenticated and url_is_exempt:
return redirect(settings.LOGIN_REDIRECT_URL)
elif request.user.is_authenticated or url_is_exempt:
return None
else:
return redirect(f"{settings.LOGIN_URL}?next=/{path}")
Something like:
class Redirect401Response(HttpResponseRedirect):
status_code = 401
def process_request(request):
...
return Redirect401Response(resolve_url(f"{settings.LOGIN_URL}?next=/{path}"))
?
The 302 stems from the return redirect(f'{settings.LOGIN_URL}?next=/{path}') that you're doing to the login URL in the last step. A redirect will (and should, I'm disagreeing here with the answer which suggests redirecting and sending a 401) always yield a status code 302. You probably want to do a return HttpResponse('Unauthorized', status=401) but then you loose the convenience of forwarding your users to the login URL.
I have referred to the code shared on this website http://onecreativeblog.com/post/59051248/django-login-required-middleware
to make a login required middleware. The problem is it is running into redirect loop.
import re
from django.conf import settings
from django.urls import reverse
from django.shortcuts import redirect
from django.contrib.auth import logout
EXEMPT_URLS = [re.compile(settings.LOGIN_URL.lstrip('/'))]
if hasattr(settings, 'LOGIN_EXEMPT_URLS'):
EXEMPT_URLS += [re.compile(url) for url in
settings.LOGIN_EXEMPT_URLS]
class LoginRequiredMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
response = self.get_response(request)
return response
def process_view(self, request, view_func, view_args, view_kwargs):
assert hasattr(request, 'user')
path = request.path_info.lstrip('/')
if not request.user.is_authenticated():
if not any(url.match(path) for url in EXEMPT_URLS):
return redirect(settings.LOGIN_URL)
I have looked into stackoverflow and tried almost every solution but in vain.
Settings url
LOGIN_REDIRECT_URL='/blog/index/'
LOGIN_EXEMPT_URLS= (
r'^blog/signup/$',
r'^blog/logout/$',
)
Any help would be appreciated! Thanks in advance.
I want the user not to navigate back once he is logged out. I used login_required(login_url = "/login/") before my definition of logout view in my views.py. I am still able to navigate back after logging out. How can I ask the user to log in again? Also, If I type URL for my home page (say localhost:8000/home) directly in the browser, I am able to access the page without login. How can I correct this issue?
I have cleared browser cache too and still, no use.
my views.py:
from django.shortcuts import render
from django.http import HttpResponseRedirect
from django.contrib import auth
from django.views.decorators import csrf
from django.contrib.auth.decorators import login_required
def login(request):
c={}
# c.update(csrf(request))
return render(request,'login.html',c)
def auth_view(request):
username = request.POST.get('username','')
password = request.POST.get('password','')
user = auth.authenticate(username=username, password=password)
if user is not None:
auth.login(request, user)
return HttpResponseRedirect('/home')
#login_required(login_url='/login/')
def logout(request):
request.session.flush()
auth.logout(request)
return render(request,'logout.html')
If you want to protect your home page or other pages with login, you should decorate them with login_required decorator. For example,
#login_required(login_url='/login/')
def home(request):
# Your code goes here
I have a django website with many urls and views. Now I have asked to redirect all non-authenticated users to a certain landing page. So, all views must check if user.is_authenticated() and return to a new set of landing pages.
Can it be done in a pretty way, instead of messing with my views.py/urls.py that much?
There is a simpler way to do this, just add the "login_url" parameter to #login_required and if the user is not login he will be redirected to the login page. You can find it here
from django.contrib.auth.decorators import login_required
#login_required(login_url='/accounts/login/')
def my_view(request):
...
You can use Middleware.
Something like this will check user auth every request:
class AuthRequiredMiddleware(object):
def process_request(self, request):
if not request.user.is_authenticated():
return HttpResponseRedirect(reverse('landing_page')) # or http response
return None
Docs: process_request
Also, don't forget to enable it in settings.py
MIDDLEWARE_CLASSES = (
...
'path.to.your.AuthRequiredMiddleware',
)
see the docs for login required decorator
from django.contrib.auth.decorators import login_required
#login_required
def my_view(request):
...
another option is to add it to your urls.py patterns, see this answer
urlpatterns = patterns('',
(r'^foo/$', login_required(direct_to_template), {'template': 'foo_index.html'}),
)
As of Django 1.10, the custom middleware classes must implement the new style syntax. You can use the following class to verify that the user is logged in while trying to access any views.
from django.shortcuts import HttpResponseRedirect
class AuthRequiredMiddleware(object):
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
# Code to be executed for each request before
# the view (and later middleware) are called.
response = self.get_response(request)
if not request.user.is_authenticated: # in Django > 3 this is a boolean
return HttpResponseRedirect('login')
# Code to be executed for each request/response after
# the view is called.
return response
You can avoid specifying login_url by setting LOGIN_URL.
Therefore, in settings.py add:
LOGIN_URL = '<some_url>'
And in your views.py annotate relevant functions with only #login_required:
#login_required
def some_view_function(request):
If you need to redirect within a view function, you can do so with:
return redirect_to_login(request.get_full_path())
This can be done with middleware.
I've found a really nifty djangosnippet that does exactly what you are asking for. You can find it here, and it looks like:
from django.http import HttpResponseRedirect
from django.conf import settings
from re import compile
EXEMPT_URLS = [compile(settings.LOGIN_URL.lstrip('/'))]
if hasattr(settings, 'LOGIN_EXEMPT_URLS'):
EXEMPT_URLS += [compile(expr) for expr in settings.LOGIN_EXEMPT_URLS]
class LoginRequiredMiddleware:
"""
Middleware that requires a user to be authenticated to view any page other
than LOGIN_URL. Exemptions to this requirement can optionally be specified
in settings via a list of regular expressions in LOGIN_EXEMPT_URLS (which
you can copy from your urls.py).
Requires authentication middleware and template context processors to be
loaded. You'll get an error if they aren't.
"""
def process_request(self, request):
assert hasattr(request, 'user'), "The Login Required middleware\
requires authentication middleware to be installed. Edit your\
MIDDLEWARE_CLASSES setting to insert\
'django.contrib.auth.middlware.AuthenticationMiddleware'. If that doesn't\
work, ensure your TEMPLATE_CONTEXT_PROCESSORS setting includes\
'django.core.context_processors.auth'."
if not request.user.is_authenticated():
path = request.path_info.lstrip('/')
if not any(m.match(path) for m in EXEMPT_URLS):
return HttpResponseRedirect(settings.LOGIN_URL)
All you have to do is to save the file as middleware.py and include the class in you're settings.py, i.e.
MIDDLEWARE_CLASSES += ('projectname.common.middleware.RequireLoginMiddleware',)
You can also define a LOGIN_URL in settings.py, so that you'll be redirected to your custom login page. The default LOGIN_URL is '/accounts/login/'.
Maybe too late but in django 1.9+ it's too easy.
Django introduced Login Required mixin for generic classes and this a great example
here by William S. Vincent
simply in your view add LoginRequiredMixin as parent class
from django.contrib.auth.mixins import LoginRequiredMixin
class BlogUpdateView(LoginRequiredMixin, UpdateView):
model = Post
template_name = 'post_edit.html'
fields = ['title', 'body']
Also you can use login_required decorator for method request
from django.contrib.auth.decorators import login_required
#login_required(login_url='/login/')
def home(request):
return render(request, "home.html")
It's showing like this: http://127.0.0.1:1235/login/?next=/home/
I'm using class based views and I couldn't get any of these solutions to work for what I needed so I'll post my working solution here.
class ManagementPageView(TemplateView):
# Make a dispatch method to handle authentication
def dispatch(self, *args, **kwargs):
# Check if user is authenticated
if not self.request.user.is_authenticated:
# Redirect them to the home page if not
return redirect('home')
# Render the template if they are
return render(self.request, 'management.html')