Can login_required by applied to an entire app? - django

Is there a way I can apply the login_required decorator to an entire app? When I say "app" I mean it in the django sense, which is to say a set of urls and views, not an entire project.

Yes, you should use middleware.
Try to look through solutions which have some differences:
http://www.djangosnippets.org/snippets/1179/ - with list of exceptions.
http://www.djangosnippets.org/snippets/1158/ - with list of exceptions.
http://www.djangosnippets.org/snippets/966/ - conversely with list of login required urls.
http://www.djangosnippets.org/snippets/136/ - simplest.

As of Django 3+, you can set login_require() to an entire app by applying a middleware. Do like followings:
Step 1: Create a new file anything.py in your yourapp directory and write the following:
import re
from django.conf import settings
from django.contrib.auth.decorators import login_required
//for registering a class as middleware you at least __init__() and __call__()
//for this case we additionally need process_view() which will be automatically called by Django before rendering a view/template
class ClassName(object):
//need for one time initialization, here response is a function which will be called to get response from view/template
def __init__(self, response):
self.get_response = response
self.required = tuple(re.compile(url) for url in settings.AUTH_URLS)
self.exceptions = tuple(re.compile(url)for url in settings.NO_AUTH_URLS)
def __call__(self, request):
//any code written here will be called before requesting response
response = self.get_response(request)
//any code written here will be called after response
return response
//this is called before requesting response
def process_view(self, request, view_func, view_args, view_kwargs):
//if authenticated return no exception
if request.user.is_authenticated:
return None
//return login_required()
for url in self.required:
if url.match(request.path):
return login_required(view_func)(request, *view_args, **view_kwargs)
//default case, no exception
return None
Step 2: Add this anything.py to Middleware[] in project/settings.py like followings
MIDDLEWARE = [
// your previous middleware
'yourapp.anything.ClassName',
]
Step 3: Also add the following snippet into project/settings.py
AUTH_URLS = (
//disallowing app url, use the url/path that you added on mysite/urls.py (not myapp/urls.py) to include as your app urls
r'/your_app_url(.*)$',
)

I think you are looking for this snippet, containing login-required middleware.

This is an old question. But here goes:
Django Decorator Include
This is a substitute of include in URLConf. Pefect for applying login_required to an entire app.

I clicked all the links in the anwsers, but they were all based on some kind of regular expressions. On Django 3+ you can do the following to restrict for a specific app:
Declare app_name="myapp" in your app's urls.py (https://docs.djangoproject.com/en/3.2/intro/tutorial03/#namespacing-url-names)
(now all these urls should be called with there namespace "myapp:urlname")
Create a middleware.py file in your app with this:
from django.contrib.auth.views import redirect_to_login
from django.core.exceptions import ImproperlyConfigured
from django.urls import resolve
class LoginRequiredAccess:
"""All urls starting with the given prefix require the user to be logged in"""
APP_NAME = 'myapp'
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
if not hasattr(request, 'user'):
raise ImproperlyConfigured(
"Requires the django's authentication middleware"
" to be installed.")
user = request.user
if resolve(request.path).app_name == self.APP_NAME: # match app_name defined in myapp.urls.py
if not user.is_authenticated:
path = request.get_full_path()
return redirect_to_login(path)
return self.get_response(request)
Put "myapp.middleware.LoginRequiredAccess" in your MIDDLEWARE constant from settings.py
Then in your main project urls.py
urlpatterns = [
path('foobar', include('otherapp.urls')), # this will not be redirected
path('whatever', include('myapp.urls')), # all these urls will be redirected to login
]
On of the avantage of this method is it can still works with a root url path, e.g path('', include('myapp.urls')), while the others will do an infinite redirect loop.

I'm wondering if there is any solution to make it works like this:
/app/app.py
class AppConfig(AppConfig):
login_required = True
/project/urls.py
urlpatterns = [
url(r'app/', include('app.urls', namespace='app'))
]
/common/middleare.py
def LogMiddleware(get_response):
def middleware(request):
# solution 1
app = get_app(request)
if app.login_required is True and request.is_authenticated is Fasle:
return HttpResponseRedirect(redirect_url)
# solution 2
url_space = get_url_space(request.get_raw_uri())
if url_space.namespace in ['app', 'admin', 'staff', 'manage'] and \
request.is_authenticated is False:
return HttpResponseRedirect(redirect_url)
I will check if there is any methoded to get the app or url name of a request. I think it looks prettier.

Related

Only allow django admin on site id 1

I want to only show the admin for my site with id 1.
In other views I've defined this in the dispatch, but I haven't found a way to do this for the admin.
I tried making a custom admin class, but I didn't see a way to do this either.
Is there a way to define dispatch for admin, or limiting the sites where an admin shows?
The dispatch I'm using:
def dispatch(self, *args, **kwargs):
try:
if get_current_site(self.request).pk != settings.MY_SITE_ID:
response = render(self.request, 'my_app/404.html')
response.status_code = 404
return response
except Site.DoesNotExist:
response = render(self.request, 'my_app/404.html', {'site_doesnotexist': True})
response.status_code = 404
return response
return super(MyView, self).dispatch(*args, **kwargs)
You can specify a different ROOT_URLCONF in the Settings file for the site with admin enabled. This ROOT_URLCONF would include the admin url patterns.
MY_SITE_ID = 1
ROOT_URLCONF = 'my_project.urls_with_admin'
# my_project/my_project/urls_with_admin.py
from django.conf.urls import url
from django.contrib import admin
from urls import urlpatterns
urlpatterns.append(url(r'^admin/', admin.site.urls))
The base urls.py file, used by the other sites, would omit the admin patterns, making the admin inaccessible.

Best way to handle legacy urls in django

I am working on a big news publishing platform. Basically rebuilding everything from ground zero with django. Now as we are almost ready for the launch I need to handle legacy url redirects. What is the best way to do it having in mind that I have to deal with tenths of thousands of legacy urls?
Logic should work like this: If none of existing urls/views where matched run that url thorough legacy Redirect urls patterns/views to see if it can provide some redirect to the new url before returning 404 error.
How do I do that?
You may want to create a fallback view that will try to handle any url not handled by your patterns. I see two options.
Just create a "default" pattern. It's important to this pattern to
be the last within your urlpatterns!
in your urls.py:
urlpatterns = patterns(
'',
# all your patterns
url(r'^.+/', 'app.views.fallback')
)
in your views.py:
from django.http.response import HttpResponseRedirect, Http404
def fallback(request):
if is_legacy(request.path):
return HttpResponseRedirect(convert(request.path))
raise Http404
Create a custom http 404 handler.
in your urls.py:
handler404 = 'app.views.fallback'
in your views.py
from django.http.response import HttpResponseRedirect
from django.views.defaults import page_not_found
def fallback(request):
if is_legacy(request.path):
return HttpResponseRedirect(convert(request.path))
return page_not_found(request)
it may seem to be a nicer solution but it will only work if you set DEBUG setting to False and provide custom 404 template.
Awesome, achieved that by using custom middleware:
from django.http import Http404
from legacy.urls import urlpatterns
class LegacyURLsMiddleware(object):
def process_response(self, request, response):
if response.status_code != 404:
return response
for resolver in urlpatterns:
try:
match = resolver.resolve(request.path[1:])
if match:
return match.func(request, *match.args, **match.kwargs)
except Http404:
pass
return response
Simply add this middleware as a last middleware in MIDDLEWARE_CLASSES list. Then use urls.py file in your legacy app to declare legacy urls and views which will handle permanent redirects. DO NOT include your legacy urls in to main urls structure. This middleware does it for you, but in a bit different way.
Use the Jacobian's django-multiurl. There is a django ticket to address the issue someday, but for now django-multiurl works very good.
Before:
# urls.py
urlpatterns = patterns('',
url('/app/(\w+)/$', app.views.people),
url('/app/(\w+)/$', app.views.place), # <-- Never matches
)
After:
# urls.py
from multiurl import multiurl, ContinueResolving
from django.http import Http404
urlpatterns = patterns('', multiurl(
url('/app/(\w+)/$', app.views.people), # <-- Tried 1st
url('/app/(\w+)/$', app.views.place), # <-- Tried 2nd (only if 1st raised Http404)
catch=(Http404, ContinueResolving)
))

Django: How can I apply the login_required decorator to my entire site (excluding static media)?

The example provides a snippet for an application level view, but what if I have lots of different (and some non-application) entries in my "urls.py" file, including templates? How can I apply this login_required decorator to each of them?
(r'^foo/(?P<slug>[-\w]+)/$', 'bugs.views.bug_detail'),
(r'^$', 'django.views.generic.simple.direct_to_template', {'template':'homepage.html'}),
Dropped this into a middleware.py file in my project root (taken from http://onecreativeblog.com/post/59051248/django-login-required-middleware)
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)
Then appended projectname.middleware.LoginRequiredMiddleware to my MIDDLEWARE_CLASSES in settings.py.
For those who have come by later to this, you might find that django-stronghold fits your usecase well. You whitelist any urls you want to be public, the rest are login required.
https://github.com/mgrouchy/django-stronghold
Here's a slightly shorter middleware.
from django.contrib.auth.decorators import login_required
class LoginRequiredMiddleware(object):
def process_view(self, request, view_func, view_args, view_kwargs):
if not getattr(view_func, 'login_required', True):
return None
return login_required(view_func)(request, *view_args, **view_kwargs)
You'll have to set "login_required" to False on each view you don't need to be logged in to see:
Function-views:
def someview(request, *args, **kwargs):
# body of view
someview.login_required = False
Class-based views:
class SomeView(View):
login_required = False
# body of view
#or
class SomeView(View):
# body of view
someview = SomeView.as_view()
someview.login_required = False
This means you'll have to do something about the login-views, but I always end up writing my own auth-backend anyway.
Some of the previous answers are either outdated (older version of Django), or introduce poor programming practices (hardcoding URLs, not using routes). Here's my take that is more DRY and sustainable/maintainable (adapted from Mehmet's answer above).
To highlight the improvements here, this relies on giving URLs route names (which are much more reliable than using hard-coded URLs/URIs that change and have trailing/leading slashes).
from django.utils.deprecation import MiddlewareMixin
from django.urls import resolve, reverse
from django.http import HttpResponseRedirect
from my_project import settings
class LoginRequiredMiddleware(MiddlewareMixin):
"""
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 by setting a tuple of routes to ignore
"""
def process_request(self, request):
assert hasattr(request, 'user'), """
The Login Required middleware needs to be after AuthenticationMiddleware.
Also make sure to include the template context_processor:
'django.contrib.auth.context_processors.auth'."""
if not request.user.is_authenticated:
current_route_name = resolve(request.path_info).url_name
if not current_route_name in settings.AUTH_EXEMPT_ROUTES:
return HttpResponseRedirect(reverse(settings.AUTH_LOGIN_ROUTE))
And in the settings.py file, you can define the following:
AUTH_EXEMPT_ROUTES = ('register', 'login', 'forgot-password')
AUTH_LOGIN_ROUTE = 'register'
Here is the classical LoginRequiredMiddleware for Django 1.10+:
from django.utils.deprecation import MiddlewareMixin
class LoginRequiredMiddleware(MiddlewareMixin):
"""
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).
"""
def process_request(self, request):
assert hasattr(request, 'user'), """
The Login Required middleware needs to be after AuthenticationMiddleware.
Also make sure to include the template context_processor:
'django.contrib.auth.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)
Noteworthy differences:
path.to.LoginRequiredMiddleware should be included in MIDDLEWARE not MIDDLEWARE_CLASSES in settings.py.
is_authenticated is a bool not a method.
see the docs for more info (although some parts are not very clear).
Use middleware.
http://www.djangobook.com/en/2.0/chapter17/
and
http://docs.djangoproject.com/en/1.2/topics/http/middleware/#topics-http-middleware
I'm assuming this didn't change a whole lot in 1.2
Middleware allows you to create a class with methods who will process every request at various times/conditions, as you define.
for example process_request(request) would fire before your view, and you can force authentication and authorization at this point.
In addition to meder omuraliev answer if you want exempt url like this (with regexp):
url(r'^my/url/(?P<pk>[0-9]+)/$', views.my_view, name='my_url')
add it to EXEMPT_URLS list like this:
LOGIN_EXEMPT_URLS = [r'^my/url/([0-9]+)/$']
r'..' in the beginning of the string necessary.
Django Login Required Middleware
Put this code in middleware.py :
from django.http import HttpResponseRedirect
from django.conf import settings
from django.utils.deprecation import MiddlewareMixin
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(MiddlewareMixin):
def process_request(self, request):
assert hasattr(request, 'user')
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)
And, in settings.py :
LOGIN_URL = '/app_name/login'
LOGIN_EXEMPT_URLS=(
r'/app_name/login/',
)
MIDDLEWARE_CLASSES = (
# ...
'python.path.to.LoginRequiredMiddleware',
)
Like this :
'app_name.middleware.LoginRequiredMiddleware'
If you have lots of views and you do not want to touch any one you can just use Middleware for this issue. Try code below:
import traceback
from django.contrib.auth.decorators import login_required
class RejectAnonymousUsersMiddleware(object):
def process_view(self, request, view_func, view_args, view_kwargs):
current_route_name = resolve(request.path_info).url_name
if current_route_name in settings.AUTH_EXEMPT_ROUTES:
return
if request.user.is_authenticated:
return
return login_required(view_func)(request, *view_args, **view_kwargs)
Cautions:
You must add this middleware to the bottommost of middleware section
of settings.py
You should put this variable in settings.py
AUTH_EXEMPT_ROUTES = ('register', 'login', 'forgot-password')
Thanks from #Ehsan Ahmadi
In the newer version of Django, the middleware should be written like this( my Djang version = 4.1.1) :
middleware.py
from django.contrib.auth.decorators import login_required
from django.urls import resolve
from django.utils.deprecation import MiddlewareMixin
AUTH_EXEMPT_ROUTES = ('captcha-image', 'login', 'captcha')
class RejectAnonymousUsersMiddleware(MiddlewareMixin):
def process_view(self, request, view_func, view_args, view_kwargs):
current_route_name = resolve(request.path_info).url_name
if request.user.is_authenticated:
return
if current_route_name in AUTH_EXEMPT_ROUTES:
return
return login_required(view_func)(request, *view_args, **view_kwargs)
settings.py
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
'myproject.middleware.RejectAnonymousUsersMiddleware',
]
Here's an example for new-style middleware in Django 1.10+:
from django.contrib.auth.decorators import login_required
from django.urls import reverse
def login_required_middleware(get_response):
"""
Require user to be logged in for all views.
"""
exceptions = {'/admin/login/'}
def middleware(request):
if request.path in exceptions:
return get_response(request)
return login_required(get_response, login_url=reverse('admin:login'))(request)
return middleware
This example exempts the admin login form to avoid redirect loop, and uses that form as the login url.

How to test 500.html error page in django development env?

I am using Django for a project and is already in production.
In the production environment 500.html is rendered whenever a server error occurs.
How do I test the rendering of 500.html in dev environment? Or how do I render 500.html in dev, if I turn-off debug I still get the errors and not 500.html
background: I include some page elements based on a page and some are missing when 500.html is called and want to debug it in dev environment.
I prefer not to turn DEBUG off. Instead I put the following snippet in the urls.py:
if settings.DEBUG:
urlpatterns += patterns('',
(r'^500/$', 'your_custom_view_if_you_wrote_one'),
(r'^404/$', 'django.views.generic.simple.direct_to_template', {'template': '404.html'}),
)
In the snippet above, the error page uses a custom view, you can easily replace it with Django's direct_to_template view though.
Now you can test 500 and 404 pages by calling their urls: http://example.com/500 and http://example.com/404
In Django 1.6 django.views.generic.simple.direct_to_template does not exists anymore, these are my settings for special views:
# urls.py
from django.views.generic import TemplateView
from django.views.defaults import page_not_found, server_error
urlpatterns += [
url(r'^400/$', TemplateView.as_view(template_name='400.html')),
url(r'^403/$', TemplateView.as_view(template_name='403.html')),
url(r'^404/$', page_not_found),
url(r'^500/$', server_error),
]
And if you want to use the default Django 500 view instead of your custom view:
if settings.DEBUG:
urlpatterns += patterns('',
(r'^500/$', 'django.views.defaults.server_error'),
(r'^404/$', 'django.views.generic.simple.direct_to_template', {'template': '404.html'}),
)
Continuing shanyu's answer, in Django 1.3+ use:
if settings.DEBUG:
urlpatterns += patterns('',
(r'^500/$', 'django.views.defaults.server_error'),
(r'^404/$', 'django.views.defaults.page_not_found'),
)
For Django > 3.0, just set the raise_request_exception value to False.
from django.test import TestCase
class ViewTestClass(TestCase):
def test_error_page(self):
self.client.raise_request_exception = False
response = self.client.get(reverse('error-page'))
self.assertEqual(response.status_code, 500)
self.assertTrue(
'some text from the custom 500 page'
in response.content.decode('utf8'))
Documentation: https://docs.djangoproject.com/en/3.2/topics/testing/tools/
NOTE: if the error page raises an exception, that will show up as an ERROR in the test log. You can turn the test logging up to CRITICAL by default to suppress that error.
Are both debug settings false?
settings.DEBUG = False
settings.TEMPLATE_DEBUG = False
How i do and test custom error handlers
Define custom View based on TemplateView
# views.py
from django.views.generic import TemplateView
class ErrorHandler(TemplateView):
""" Render error template """
error_code = 404
template_name = 'index/error.html'
def dispatch(self, request, *args, **kwargs):
""" For error on any methods return just GET """
return self.get(request, *args, **kwargs)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['error_code'] = self.error_code
return context
def render_to_response(self, context, **response_kwargs):
""" Return correct status code """
response_kwargs = response_kwargs or {}
response_kwargs.update(status=self.error_code)
return super().render_to_response(context, **response_kwargs)
Tell django to use custom error handlers
# urls.py
from index.views import ErrorHandler
# error handing handlers - fly binding
for code in (400, 403, 404, 500):
vars()['handler{}'.format(code)] = ErrorHandler.as_view(error_code=code)
Testcase for custom error handlers
# tests.py
from unittest import mock
from django.test import TestCase
from django.core.exceptions import SuspiciousOperation, PermissionDenied
from django.http import Http404
from index import views
class ErrorHandlersTestCase(TestCase):
""" Check is correct error handlers work """
def raise_(exception):
def wrapped(*args, **kwargs):
raise exception('Test exception')
return wrapped
def test_index_page(self):
""" Should check is 200 on index page """
response = self.client.get('/')
self.assertEqual(response.status_code, 200)
self.assertTemplateUsed(response, 'index/index.html')
#mock.patch('index.views.IndexView.get', raise_(Http404))
def test_404_page(self):
""" Should check is 404 page correct """
response = self.client.get('/')
self.assertEqual(response.status_code, 404)
self.assertTemplateUsed(response, 'index/error.html')
self.assertIn('404 Page not found', response.content.decode('utf-8'))
#mock.patch('index.views.IndexView.get', views.ErrorHandler.as_view(error_code=500))
def test_500_page(self):
""" Should check is 500 page correct """
response = self.client.get('/')
self.assertEqual(response.status_code, 500)
self.assertTemplateUsed(response, 'index/error.html')
self.assertIn('500 Server Error', response.content.decode('utf-8'))
#mock.patch('index.views.IndexView.get', raise_(SuspiciousOperation))
def test_400_page(self):
""" Should check is 400 page correct """
response = self.client.get('/')
self.assertEqual(response.status_code, 400)
self.assertTemplateUsed(response, 'index/error.html')
self.assertIn('400 Bad request', response.content.decode('utf-8'))
#mock.patch('index.views.IndexView.get', raise_(PermissionDenied))
def test_403_page(self):
""" Should check is 403 page correct """
response = self.client.get('/')
self.assertEqual(response.status_code, 403)
self.assertTemplateUsed(response, 'index/error.html')
self.assertIn('403 Permission Denied', response.content.decode('utf-8'))
urls.py
handler500 = 'project.apps.core.views.handler500'
handler404 = 'project.apps.core.views.handler404'
views.py
from django.template.loader import get_template
from django.template import Context
from django.http import HttpResponseServerError, HttpResponseNotFound
def handler500(request, template_name='500.html'):
t = get_template(template_name)
ctx = Context({})
return HttpResponseServerError(t.render(ctx))
def handler404(request, template_name='404.html'):
t = get_template(template_name)
ctx = Context({})
return HttpResponseNotFound(t.render(ctx))
tests.py
from django.test import TestCase
from django.test.client import RequestFactory
from project import urls
from ..views import handler404, handler500
class TestErrorPages(TestCase):
def test_error_handlers(self):
self.assertTrue(urls.handler404.endswith('.handler404'))
self.assertTrue(urls.handler500.endswith('.handler500'))
factory = RequestFactory()
request = factory.get('/')
response = handler404(request)
self.assertEqual(response.status_code, 404)
self.assertIn('404 Not Found!!', unicode(response))
response = handler500(request)
self.assertEqual(response.status_code, 500)
self.assertIn('500 Internal Server Error', unicode(response))
Update for Django > 1.6 and without getting
page_not_found() missing 1 required positional argument: 'exception'
Inspired by this answer:
# urls.py
from django.views.defaults import page_not_found, server_error, permission_denied, bad_request
[...]
if settings.DEBUG:
# This allows the error pages to be debugged during development, just visit
# these url in browser to see how these error pages look like.
urlpatterns += [
path('400/', bad_request, kwargs={'exception': Exception('Bad Request!')}),
path('403/', permission_denied, kwargs={'exception': Exception('Permission Denied')}),
path('404/', page_not_found, kwargs={'exception': Exception('Page not Found')}),
path('500/', server_error),
You can simply define the handler404 and handler500 for errors in your main views.py file as detailed in this answer:
https://stackoverflow.com/a/18009660/1913888
This will return the error that you desire when Django routes to that handler. No custom URL configuration is needed to route to a different URL name.
In Django versions < 3.0, you should do as follows:
client.py
from django.core.signals import got_request_exception
from django.template import TemplateDoesNotExist
from django.test import signals
from django.test.client import Client as DjangoClient, store_rendered_templates
from django.urls import resolve
from django.utils import six
from django.utils.functional import SimpleLazyObject, curry
class Client(DjangoClient):
"""Test client that does not raise Exceptions if requested."""
def __init__(self,
enforce_csrf_checks=False,
raise_request_exception=True, **defaults):
super(Client, self).__init__(enforce_csrf_checks=enforce_csrf_checks,
**defaults)
self.raise_request_exception = raise_request_exception
def request(self, **request):
"""
The master request method. Composes the environment dictionary
and passes to the handler, returning the result of the handler.
Assumes defaults for the query environment, which can be overridden
using the arguments to the request.
"""
environ = self._base_environ(**request)
# Curry a data dictionary into an instance of the template renderer
# callback function.
data = {}
on_template_render = curry(store_rendered_templates, data)
signal_uid = "template-render-%s" % id(request)
signals.template_rendered.connect(on_template_render,
dispatch_uid=signal_uid)
# Capture exceptions created by the handler.
exception_uid = "request-exception-%s" % id(request)
got_request_exception.connect(self.store_exc_info,
dispatch_uid=exception_uid)
try:
try:
response = self.handler(environ)
except TemplateDoesNotExist as e:
# If the view raises an exception, Django will attempt to show
# the 500.html template. If that template is not available,
# we should ignore the error in favor of re-raising the
# underlying exception that caused the 500 error. Any other
# template found to be missing during view error handling
# should be reported as-is.
if e.args != ('500.html',):
raise
# Look for a signalled exception, clear the current context
# exception data, then re-raise the signalled exception.
# Also make sure that the signalled exception is cleared from
# the local cache!
response.exc_info = self.exc_info # Patch exception handling
if self.exc_info:
exc_info = self.exc_info
self.exc_info = None
if self.raise_request_exception: # Patch exception handling
six.reraise(*exc_info)
# Save the client and request that stimulated the response.
response.client = self
response.request = request
# Add any rendered template detail to the response.
response.templates = data.get("templates", [])
response.context = data.get("context")
response.json = curry(self._parse_json, response)
# Attach the ResolverMatch instance to the response
response.resolver_match = SimpleLazyObject(
lambda: resolve(request['PATH_INFO'])
)
# Flatten a single context. Not really necessary anymore thanks to
# the __getattr__ flattening in ContextList, but has some edge-case
# backwards-compatibility implications.
if response.context and len(response.context) == 1:
response.context = response.context[0]
# Update persistent cookie data.
if response.cookies:
self.cookies.update(response.cookies)
return response
finally:
signals.template_rendered.disconnect(dispatch_uid=signal_uid)
got_request_exception.disconnect(dispatch_uid=exception_uid)
tests.py
from unittest import mock
from django.contrib.auth import get_user_model
from django.core.urlresolvers import reverse
from django.test import TestCase, override_settings
from .client import Client # Important, we use our own Client here!
class TestErrors(TestCase):
"""Test errors."""
#classmethod
def setUpClass(cls):
super(TestErrors, cls).setUpClass()
cls.username = 'admin'
cls.email = 'admin#localhost'
cls.password = 'test1234test1234'
cls.not_found_url = '/i-do-not-exist/'
cls.internal_server_error_url = reverse('password_reset')
def setUp(self):
super(TestErrors, self).setUp()
User = get_user_model()
User.objects.create_user(
self.username,
self.email,
self.password,
is_staff=True,
is_active=True
)
self.client = Client(raise_request_exception=False)
# Mock in order to trigger Exception and resulting Internal server error
#mock.patch('django.contrib.auth.views.PasswordResetView.form_class', None)
#override_settings(DEBUG=False)
def test_errors(self):
self.client.login(username=self.username, password=self.password)
with self.subTest("Not found (404)"):
response = self.client.get(self.not_found_url, follow=True)
self.assertNotIn('^admin/', str(response.content))
with self.subTest("Internal server error (500)"):
response = self.client.get(self.internal_server_error_url,
follow=True)
self.assertNotIn('TypeError', str(response.content))
Starting from Django 3.0 you could skip the custom Client definition and just use the code from tests.py.

Django: Redirect logged in users from login page

I want to set up my site so that if a user hits the /login page and they are already logged in, it will redirect them to the homepage. If they are not logged in then it will display normally. How can I do this since the login code is built into Django?
I'm assuming you're currently using the built-in login view, with
(r'^accounts/login/$', 'django.contrib.auth.views.login'),
or something similar in your urls.
You can write your own login view that wraps the default one. It will check if the user is already logged in (through is_authenticated attribute official documentation) and redirect if he is, and use the default view otherwise.
something like:
from django.contrib.auth.views import login
def custom_login(request):
if request.user.is_authenticated:
return HttpResponseRedirect(...)
else:
return login(request)
and of course change your urls accordingly:
(r'^accounts/login/$', custom_login),
The Django 1.10 way
For Django 1.10, released in August 2016, a new parameter named redirect_authenticated_user was added to the login() function based view present in django.contrib.auth [1].
Example
Suppose we have a Django application with a file named views.py and another file named urls.py. The urls.py file will contain some Python code like this:
#
# Django 1.10 way
#
from django.contrib.auth import views as auth_views
from . import views as app_views
urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'^login/', auth_views.login, name='login',
kwargs={'redirect_authenticated_user': True}),
url(r'^dashboard/', app_views.Dashboard.as_view(), name='dashboard'),
url(r'^$', TemplateView.as_view(template_name='index.html'), name='index'),
]
From that file, the relevant part within the urlpatterns variable definition is the following, which uses the already mentioned redirect_authenticated_user parameter with a True value:
url(r'^login/', auth_views.login, name='login',
kwargs={'redirect_authenticated_user': True}),
Take note that the default value of the redirect_authenticated_user parameter is False.
The Django 1.11 way
For Django 1.11, released in April 2017, the LoginView class based view superseded the login() function based view [2], which gives you two options to choose from:
Use the same Django 1.10 way just described before, which is a positive thing because your current code will continue working fine. If you tell Python interpreter to display warnings, by for example running in a console terminal the command python -Wd manage.py runserver in your Django project directory and then going with a web browser to your login page, you would see in that same console terminal a warning message like this:
/usr/local/lib/python3.6/site-packages/django/contrib/auth/views.py:54:
RemovedInDjango21Warning: The login() view is superseded by the
class-based LoginView().
Use the new Django 1.11 way, which will make your code more modern and compatible with future Django releases. With this option, the example given before will now look like the following one:
Example
We again suppose that we have a Django application with a file named views.py and another file named urls.py. The urls.py file will contain some Python code like this:
#
# Django 1.11 way
#
from django.contrib.auth import views as auth_views
from . import views as app_views
urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'^login/',
auth_views.LoginView.as_view(redirect_authenticated_user=True),
name='login'),
url(r'^dashboard/', app_views.Dashboard.as_view(), name='dashboard'),
url(r'^$', TemplateView.as_view(template_name='index.html'), name='index'),
]
From that file, the relevant part within the urlpatterns variable definition is the following, which again uses the already mentioned redirect_authenticated_user parameter with a True value, but passing it as an argument to the as_view method of the LoginView class:
url(r'^login/',
auth_views.LoginView.as_view(redirect_authenticated_user=False),
name='login'),
Take note that here the default value of the redirect_authenticated_user parameter is also False.
References
[1] Relevant section in Django 1.10 release notes at https://docs.djangoproject.com/en/dev/releases/1.10/#django-contrib-auth
[2] Relevant section in Django 1.11 release notes at https://docs.djangoproject.com/en/1.11/releases/1.11/#django-contrib-auth
anonymous_required decorator
For class based views
Code:
from django.shortcuts import redirect
def anonymous_required(func):
def as_view(request, *args, **kwargs):
redirect_to = kwargs.get('next', settings.LOGIN_REDIRECT_URL )
if request.user.is_authenticated():
return redirect(redirect_to)
response = func(request, *args, **kwargs)
return response
return as_view
Usage:
url(r'^/?$',
anonymous_required(auth_views.login),
),
url(r'^register/?$',
anonymous_required(RegistrationView.as_view()),
name='auth.views.register'
),
# Could be used to decorate the dispatch function of the view instead of the url
For view functions
From http://blog.motane.lu/2010/01/06/django-anonymous_required-decorator/
Code:
from django.http import HttpResponseRedirect
def anonymous_required( view_function, redirect_to = None ):
return AnonymousRequired( view_function, redirect_to )
class AnonymousRequired( object ):
def __init__( self, view_function, redirect_to ):
if redirect_to is None:
from django.conf import settings
redirect_to = settings.LOGIN_REDIRECT_URL
self.view_function = view_function
self.redirect_to = redirect_to
def __call__( self, request, *args, **kwargs ):
if request.user is not None and request.user.is_authenticated():
return HttpResponseRedirect( self.redirect_to )
return self.view_function( request, *args, **kwargs )
Usage:
#anonymous_required
def my_view( request ):
return render_to_response( 'my-view.html' )
For Django 2.x, in your urls.py:
from django.contrib.auth import views as auth_views
from django.urls import path
urlpatterns = [
path('login/', auth_views.LoginView.as_view(redirect_authenticated_user=True), name='login'),
]
Add this decorator above your login view to redirect to /home if a user is already logged in
#user_passes_test(lambda user: not user.username, login_url='/home', redirect_field_name=None)
and don't forget to import the decorator
from django.contrib.auth.decorators import user_passes_test
Since class based views (CBVs) is on the rise. This approach will help you redirect to another url when accessing view for non authenticated users only.
In my example the sign-up page overriding the dispatch() method.
class Signup(CreateView):
template_name = 'sign-up.html'
def dispatch(self, *args, **kwargs):
if self.request.user.is_authenticated:
return redirect('path/to/desired/url')
return super().dispatch(*args, **kwargs)
Cheers!
https://docs.djangoproject.com/en/3.1/topics/auth/default/#all-authentication-views
Add the redirect route in settings
LOGIN_URL = 'login'
And in the URLs add redirect_authenticated_user=True to LoginView
path('login/', auth_views.LoginView.as_view(template_name='users/login.html',redirect_authenticated_user=True), name='login')
I know this is a pretty old question, but I'll add my technique in case anyone else needs it:
myproject/myapp/views/misc.py
from django.contrib.auth.views import login as contrib_login, logout as contrib_logout
from django.shortcuts import redirect
from django.conf import settings
def login(request, **kwargs):
if request.user.is_authenticated():
return redirect(settings.LOGIN_REDIRECT_URL)
else:
return contrib_login(request, **kwargs)
logout = contrib_logout
myproject/myapp/urls.py
from django.conf.urls import patterns, url
urlpatterns = patterns('myapp.views.misc',
url(r'^login/$', 'login', {'template_name': 'myapp/login.html'}, name='login'),
url(r'^logout/$', 'logout', {'template_name': 'myapp/logout.html'}, name='logout'),
)
...
Assuming that you are done setting up built-in Django user authentication (and using decorators), add this in your settings.py:
LOGIN_REDIRECT_URL = '/welcome/'
NOTE: '/welcome/' here is the URL of the homepage. It is up to you what to replace it with.
All you have to do is set the "root" url to the homepage view. Since the homepage view is already restricted for logged on users, it'll automatically redirect anonymous users to the login page.
Kepp the url as it is.
And add something like:
(r'^$', 'my_project.my_app.views.homepage'),