[Fixed]-What is the opposite of @login_required decorator for Django views?

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
👤LS55321

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
👤matshidis

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.

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):
    ...
👤John

-1đź‘Ť

@permission_classes([permissions.AllowAny])

👤korku02

Leave a comment