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

后端 未结 6 2064
耶瑟儿~
耶瑟儿~ 2020-12-29 09:37

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 ma

相关标签:
6条回答
  • 2020-12-29 09:41

    @permission_classes([permissions.AllowAny])

    0 讨论(0)
  • 2020-12-29 09:48

    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

    0 讨论(0)
  • 2020-12-29 09:50

    "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 讨论(0)
  • 2020-12-29 09:51

    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)
    
    0 讨论(0)
  • 2020-12-29 09:53

    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
    
    0 讨论(0)
  • 2020-12-29 10:04

    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):
        ...
    
    0 讨论(0)
提交回复
热议问题