Search code examples
djangodjango-authentication

What is the opposite of @login_required decorator for Django views?


If I want to make sure that a view is listed as having public access, is there a decorator equivalent to @public_access which would be the opposite of @login_required and make it clear that the view should be publicly accessible always?

One use case I have in mind is to automatically add "@csrf_exempt" to all public views in addition to making it clear in the code that the view should be publicly accessible.


Solution

  • 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