14đź‘Ť
Unfortunately, there’s currently no built-in support for this in Django, leaving you at risk of exposing sensitive info when @login_required
is accidentally forgotten.
Here’s a solution from one of my projects:
middleware/security.py
:
def public(function):
"""
Decorator for public views that do not require authentication
"""
orig_func = function
while isinstance(orig_func, partial): # if partial - use original function for authorization
orig_func = orig_func.func
orig_func.is_public_view = True
return function
def is_public(function):
try: # cache is found
return function.is_public_view
except AttributeError: # cache is not found
result = function.__module__.startswith('django.') and not function.__module__.startswith('django.views.generic') # Avoid modifying admin and other built-in views
try: # try to recreate cache
function.is_public_view = result
except AttributeError:
pass
return result
class NonpublicMiddleware(object):
def process_view_check_logged(self, request, view_func, view_args, view_kwargs):
return
def process_view(self, request, view_func, view_args, view_kwargs):
while isinstance(view_func, partial): # if partial - use original function for authorization
view_func = view_func.func
request.public = is_public(view_func)
if not is_public(view_func):
if request.user.is_authenticated(): # only extended checks are needed
return self.process_view_check_logged(request, view_func, view_args, view_kwargs)
return self.redirect_to_login(request.get_full_path()) # => login page
def redirect_to_login(self, original_target, login_url=settings.LOGIN_URL):
return HttpResponseRedirect("%s?%s=%s" % (login_url, REDIRECT_FIELD_NAME, urlquote(original_target)))
settings.py
:
MIDDLEWARE_CLASSES = (
#...
'middleware.security.NonpublicProfilefullMiddleware',
#...
)
and, finally, view code:
from <projname>.middleware import publi
@public
def some_view(request):
#...
# Login required is added automatically
def some_private_view(request):
#...
Also, you may want to look at "Automatically decorating all views of a django project" blog post
7đź‘Ť
As a previous poster mentioned, login not required is the default.
However, sometimes it’s useful to block certain views from logged in users — for instance, it makes no sense for a logged-in user to be able to use the site’s signup page. In that case, you could do something like this, based off the existing login_required decorator
from django.contrib.auth.decorators import user_passes_test
from django.conf import settings
LOGGED_IN_HOME = settings.LOGGED_IN_HOME
def login_forbidden(function=None, redirect_field_name=None, redirect_to=LOGGED_IN_HOME):
"""
Decorator for views that checks that the user is NOT logged in, redirecting
to the homepage if necessary.
"""
actual_decorator = user_passes_test(
lambda u: not u.is_authenticated(),
login_url=redirect_to,
redirect_field_name=redirect_field_name
)
if function:
return actual_decorator(function)
return actual_decorator
- How can I tell Django templates not to parse a block containing code that looks like template tags?
- Django admin inline: select_related
- Assign Value of Named URL to a Variable in Django Templates
- Django makemigrations and migrate on heroku server don't create tables
3đź‘Ť
A bit late, but another simple way to tackle this issue would be to rely on another decorator and add a lambda function:
from django.contrib.auth.decorators import user_passes_test
@user_passes_test(lambda u: u.is_anonymous)
1đź‘Ť
You can use user_passes_test and add a requirement, anonymous_required. This would work like the opposite to login_required and you can use on your register and login page – it is a bit irritating for users to still see the login page, after the are logged in.
This is how you would do it:
from django.contrib.auth.decorators import user_passes_test
#Anonymous required
def anonymous_required(function=None, redirect_url=None):
if not redirect_url:
redirect_url = settings.LOGIN_REDIRECT_URL
actual_decorator = user_passes_test(
lambda u: u.is_anonymous,
login_url=redirect_url
)
if function:
return actual_decorator(function)
return actual_decorator
#Application of the Decorator
@anonymous_required
def register(request):
#Your code goes here
0đź‘Ť
“Login not required” is the default. If you want to annotate that a view should never be login-restricted then you should do so in the docstring.
- Django can't access raw_post_data
- Paypal monthly subscription plan settings for first day of the month and making monthly recurring payment – django python
- When should I use 'path' over 're_path'?
- Moving from direct_to_template to new TemplateView in Django
- Django – How to filter by date with Django Rest Framework?
0đź‘Ť
I use django-decorator-include to use the login_required
decorator to secure an entire app, rather than one view at a time. My app’s main urls.py looks like this:
path('my_secret_app/', decorator_include(login_required, ('my_secret_app.urls', 'my_secret_app'))),
This works great, except for when one of my apps has one view which needs to be public.
To get around this, I wrote my own login_required
and login_not_required
. My login_required
is based on django’s django.contrib.auth.decorators.login_required
, but is slightly modified to actually care when a view is marked as not requiring login.
My project is called mysite
.
My app is called my_secret_app
.
My public view within my_secret_app
is called MyPublicView
.
My entire solution looks like this:
mysite/lib.py
from django.contrib.auth import REDIRECT_FIELD_NAME
from django.contrib.auth.decorators import user_passes_test
# A copy of django.contrib.auth.decorators.login_required that looks for login_not_required attr
def login_required(function=None, redirect_field_name=REDIRECT_FIELD_NAME, login_url=None):
actual_decorator = user_passes_test(
lambda u: u.is_authenticated,
login_url=login_url,
redirect_field_name=redirect_field_name
)
if function:
login_req = getattr(function, "login_required", True)
if login_req:
return actual_decorator(function)
else:
return function
else:
return actual_decorator
# Decorator to mark a view as not requiring login to access
def login_not_required(f):
f.login_required = False
return f
mysite/urls.py
from .lib import login_required
path('my_secret_app/', decorator_include(login_required, ('my_secret_app.urls', 'my_secret_app'))),
my_secret_app/views.py:
from django.utils.decorators import method_decorator
from mysite.lib import login_not_required
class MyPublicView(View):
@method_decorator(login_not_required)
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)
def get(self, request):
...
def post(self, request, *args, **kwargs):
...
You should be able to do the same thing no matter if you’re subclassing View
, ListView
, CreateView
, UpdateView
, TemplateView
, or any of the other ones. It should also work if you’re using a function as your view, though I haven’t tried it:
@login_not_required
def MyPublicView(request):
...
- Does Django have BDD testing tools comparable to Rails' testing tools?
- Using Django Login Required Mixin