如何在基于Django类的视图上使用Permission_required装饰器


161

我在理解新CBV的工作方式时遇到了一些麻烦。我的问题是,我需要在所有视图中登录,并且在某些视图中需要特定的权限。在基于函数的视图中,我使用@permission_required()和视图中的login_required属性来执行此操作,但是我不知道如何在新视图上执行此操作。django文档中是否有某些部分对此进行了解释?我什么都没找到 我的代码有什么问题?

我尝试使用@method_decorator,但它回答“ / spaces / prueba / _wrapped_view()处的TypeError至少接受1个参数(给定0)

这是代码(GPL):

from django.utils.decorators import method_decorator
from django.contrib.auth.decorators import login_required, permission_required

class ViewSpaceIndex(DetailView):

    """
    Show the index page of a space. Get various extra contexts to get the
    information for that space.

    The get_object method searches in the user 'spaces' field if the current
    space is allowed, if not, he is redirected to a 'nor allowed' page. 
    """
    context_object_name = 'get_place'
    template_name = 'spaces/space_index.html'

    @method_decorator(login_required)
    def get_object(self):
        space_name = self.kwargs['space_name']

        for i in self.request.user.profile.spaces.all():
            if i.url == space_name:
                return get_object_or_404(Space, url = space_name)

        self.template_name = 'not_allowed.html'
        return get_object_or_404(Space, url = space_name)

    # Get extra context data
    def get_context_data(self, **kwargs):
        context = super(ViewSpaceIndex, self).get_context_data(**kwargs)
        place = get_object_or_404(Space, url=self.kwargs['space_name'])
        context['entities'] = Entity.objects.filter(space=place.id)
        context['documents'] = Document.objects.filter(space=place.id)
        context['proposals'] = Proposal.objects.filter(space=place.id).order_by('-pub_date')
        context['publication'] = Post.objects.filter(post_space=place.id).order_by('-post_pubdate')
        return context

Answers:


211

CBV文档中列出了一些策略:

urls.py实例化视图时,按实例装饰视图(docs

urlpatterns = [
    path('view/',login_required(ViewSpaceIndex.as_view(..)),
    ...
]

装饰器是按实例应用的,因此您可以urls.py根据需要在不同的路径中添加或删除它。

装饰您的类,以便每个视图实例都由装饰器包装(docs

有两种方法可以执行此操作:

  1. 将a method_decorator应用于您的CBV调度方法,例如,

    from django.utils.decorators import method_decorator
    
    @method_decorator(login_required, name='dispatch')
    class ViewSpaceIndex(TemplateView):
        template_name = 'secret.html'

如果您使用的是Django <1.9(不应该支持,不再支持),则不能method_decorator在该类上使用它,因此您必须重写该dispatch方法:

    class ViewSpaceIndex(TemplateView):

        @method_decorator(login_required)
        def dispatch(self, *args, **kwargs):
            return super(ViewSpaceIndex, self).dispatch(*args, **kwargs)
  1. 现代Django(2.2+)中的常见做法是使用Django 1.9+中可用的访问混合器(例如django.contrib.auth.mixins.LoginRequiredMixin),并在此处列出了其他答案:

    from django.contrib.auth.mixins import LoginRequiredMixin
    
    class MyView(LoginRequiredMixin, View):
    
        login_url = '/login/'
        redirect_field_name = 'redirect_to'

确保将Mixin首先放在继承列表中(以便方法解析顺序选择正确的事物)。

TypeError文档中解释了您获得a的原因:

注意:method_decorator将* args和** kwargs作为参数传递给类中经过修饰的方法。如果您的方法不接受一组兼容的参数,它将引发TypeError异常。



如何追加message呢?
andilabs 2014年

对于那些不了解的人(像我一开始一样)-应该将“ dispatch”方法添加到ViewSpaceIndex类中
o_c

有没有理由偏爱其中一种方法?
阿利斯泰尔

@Alistair我认为这归结为个人喜好并保持团队/组织内代码库的一致性。如果我要构建基于类的视图,我个人倾向于混合方法。
李李

118

这是我的方法,我创建了一个受保护的mixin(保存在我的mixin库中):

from django.contrib.auth.decorators import login_required
from django.utils.decorators import method_decorator

class LoginRequiredMixin(object):
    @method_decorator(login_required)
    def dispatch(self, request, *args, **kwargs):
        return super(LoginRequiredMixin, self).dispatch(request, *args, **kwargs)

每当您希望保护视图时,只需添加适当的mixin:

class SomeProtectedViewView(LoginRequiredMixin, TemplateView):
    template_name = 'index.html'

只要确保您的mixin在第一位即可。

更新:我早在2011年就以1.9版本发布了该版本,Django现在已标准包含此版本和其他有用的混合包(AccessMixin,PermissionRequiredMixin,UserPassesTestMixin)!


是否可以有多个这种混合器?它对我不起作用,我认为它没有道理。
皮克勒2012年

是的,应该有多个混合对象,因为每个混合对象都会根据MRO调用super来选择下一类
Hobblin 2012年

我认为这是一个很好的解决方案。我不喜欢在urls.py中混合使用装饰器,而在views.py中混合使用mixins。这是包装装饰器的方法,该装饰器会将所有逻辑移到视图。
dhackner 2013年

1
django-braces拥有此(和更多)
mixins-

只是对像我这样处于全速模式的人的一个提示:在测试login_required功能时,请确保您未登录...
Visgean Skeloru 2015年

46

这是使用基于类的装饰器的替代方法:

from django.utils.decorators import method_decorator

def class_view_decorator(function_decorator):
    """Convert a function based decorator into a class based decorator usable
    on class based Views.

    Can't subclass the `View` as it breaks inheritance (super in particular),
    so we monkey-patch instead.
    """

    def simple_decorator(View):
        View.dispatch = method_decorator(function_decorator)(View.dispatch)
        return View

    return simple_decorator

然后可以像这样简单地使用它:

@class_view_decorator(login_required)
class MyView(View):
    # this view now decorated

3
您可以使用它很好地链接视图装饰器!+1
皮克勒2012年

9
太好了,应该考虑将其纳入上游IMO。
koniiiik 2012年

我喜欢这个!我想知道是否有可能将args / kwargs从class_view_decorator向下传递给function_decorator?如果login_decorator可以说有条件地匹配request.METHOD,那就太好了,所以它仅适用于说发表?
Mike Waites 2013年

1
args / kwargs应该可以使用轻松实现class_view_decorator(my_decorator(*args, **kwargs))。至于条件方法匹配-您可以修改class_view_decorator以将其应用于View.getView.post代替View.dispatch
mjtamlyn

14

我知道这个线程有些陈旧,但是无论如何这是我的两分钱。

使用以下代码:

from django.utils.decorators import method_decorator
from inspect import isfunction

class _cbv_decorate(object):
    def __init__(self, dec):
        self.dec = method_decorator(dec)

    def __call__(self, obj):
        obj.dispatch = self.dec(obj.dispatch)
        return obj

def patch_view_decorator(dec):
    def _conditional(view):
        if isfunction(view):
            return dec(view)

        return _cbv_decorate(dec)(view)

    return _conditional

我们现在有一种修补装饰器的方法,因此它将变得多功能。这实际上意味着,当将其应用于常规视图装饰器时,如下所示:

login_required = patch_view_decorator(login_required)

按原本的方式使用时,此装饰器仍然可以使用:

@login_required
def foo(request):
    return HttpResponse('bar')

但在像这样使用时也可以正常工作:

@login_required
class FooView(DetailView):
    model = Foo

这在我最近遇到的几种情况下似乎都可以正常工作,包括以下实际示例:

@patch_view_decorator
def ajax_view(view):
    def _inner(request, *args, **kwargs):
        if request.is_ajax():
            return view(request, *args, **kwargs)
        else:
            raise Http404

    return _inner

编写ajax_view函数是为了修改(基于函数的)视图,以便每当非ajax调用访问该视图时,都会引发404错误。通过简单地将patch函数用作装饰器,该装饰器也可以在基于类的视图中使用


14

对于那些你谁使用的Django> = 1.9,它已经列入django.contrib.auth.mixinsAccessMixinLoginRequiredMixinPermissionRequiredMixinUserPassesTestMixin

因此要将LoginRequired应用于CBV(例如DetailView):

from django.contrib.auth.mixins import LoginRequiredMixin
from django.views.generic.detail import DetailView


class ViewSpaceIndex(LoginRequiredMixin, DetailView):
    model = Space
    template_name = 'spaces/space_index.html'
    login_url = '/login/'
    redirect_field_name = 'redirect_to'

这也很好地记住GCBV密新秩序:混入必须走在左边一侧,基本视图类必须在走右边侧。如果顺序不同,则可能会导致无法预测的结果。


2
这是2019年的最佳答案。此外,关于混入顺序的要点。
克里斯蒂安·朗

5

使用Django大括号。它提供了许多容易获得的有用的mixin。它具有漂亮的文档。试试看。

您甚至可以创建自定义mixin。

http://django-braces.readthedocs.org/en/v1.4.0/

示例代码:

from django.views.generic import TemplateView

from braces.views import LoginRequiredMixin


class SomeSecretView(LoginRequiredMixin, TemplateView):
    template_name = "path/to/template.html"

    #optional
    login_url = "/signup/"
    redirect_field_name = "hollaback"
    raise_exception = True

    def get(self, request):
        return self.render_to_response({})

4

如果这是一个大多数页面都要求用户登录的站点,则可以使用中间件强制在所有视图上登录,除了其中有特别标记的。

Django 1.10之前的middleware.py:

from django.contrib.auth.decorators import login_required
from django.conf import settings

EXEMPT_URL_PREFIXES = getattr(settings, 'LOGIN_EXEMPT_URL_PREFIXES', ())

class LoginRequiredMiddleware(object):
    def process_view(self, request, view_func, view_args, view_kwargs):
        path = request.path
        for exempt_url_prefix in EXEMPT_URL_PREFIXES:
            if path.startswith(exempt_url_prefix):
                return None
        is_login_required = getattr(view_func, 'login_required', True)
        if not is_login_required:
            return None
        return login_required(view_func)(request, *view_args, **view_kwargs) 

views.py:

def public(request, *args, **kwargs):
    ...
public.login_required = False

class PublicView(View):
    ...
public_view = PublicView.as_view()
public_view.login_required = False

您可以在设置中免除您不想包装的第三方视图:

settings.py:

LOGIN_EXEMPT_URL_PREFIXES = ('/login/', '/reset_password/')

3

在我的代码中,我编写了此适配器以使成员函数适应非成员函数:

from functools import wraps


def method_decorator_adaptor(adapt_to, *decorator_args, **decorator_kwargs):
    def decorator_outer(func):
        @wraps(func)
        def decorator(self, *args, **kwargs):
            @adapt_to(*decorator_args, **decorator_kwargs)
            def adaptor(*args, **kwargs):
                return func(self, *args, **kwargs)
            return adaptor(*args, **kwargs)
        return decorator
    return decorator_outer

您可以像这样简单地使用它:

from django.http import HttpResponse
from django.views.generic import View
from django.contrib.auth.decorators import permission_required
from some.where import method_decorator_adaptor


class MyView(View):
    @method_decorator_adaptor(permission_required, 'someapp.somepermission')
    def get(self, request):
        # <view logic>
        return HttpResponse('result')

很好,这是Django的内置功能(就像method_decorator是)。似乎是实现此目的的一种很好且可读的方法。
MariusSiuram 2015年

1

django> 1.9带有对PermissionRequiredMixinand的支持,这非常容易LoginRequiredMixin

只需从auth导入

views.py

from django.contrib.auth.mixins import LoginRequiredMixin

class YourListView(LoginRequiredMixin, Views):
    pass

有关更多详细信息,请参阅Django中的授权。


1

过去已经有一段时间了,现在Django发生了很大变化。

在此处查看如何装饰基于类的视图。

https://docs.djangoproject.com/zh-CN/2.2/topics/class-based-views/intro/#decorating-the-class

该文档未包含“带有任何参数的装饰器”的示例。但是带有参数的装饰器是这样的:

def mydec(arg1):
    def decorator(func):
         def decorated(*args, **kwargs):
             return func(*args, **kwargs) + arg1
         return decorated
    return deocrator

因此,如果要将mydec用作不带参数的“常规”装饰器,则可以执行以下操作:

mydecorator = mydec(10)

@mydecorator
def myfunc():
    return 5

所以同样,使用permission_requiredmethod_decorator

我们可以做的:

@method_decorator(permission_required("polls.can_vote"), name="dispatch")
class MyView:
    def get(self, request):
        # ...

0

如果您正在执行需要各种权限测试的项目,则可以继承此类。

from django.contrib.auth.decorators import login_required
from django.contrib.auth.decorators import user_passes_test
from django.views.generic import View
from django.utils.decorators import method_decorator



class UserPassesTest(View):

    '''
    Abstract base class for all views which require permission check.
    '''


    requires_login = True
    requires_superuser = False
    login_url = '/login/'

    permission_checker = None
    # Pass your custom decorator to the 'permission_checker'
    # If you have a custom permission test


    @method_decorator(self.get_permission())
    def dispatch(self, *args, **kwargs):
        return super(UserPassesTest, self).dispatch(*args, **kwargs)


    def get_permission(self):

        '''
        Returns the decorator for permission check
        '''

        if self.permission_checker:
            return self.permission_checker

        if requires_superuser and not self.requires_login:
            raise RuntimeError((
                'You have assigned requires_login as False'
                'and requires_superuser as True.'
                "  Don't do that!"
            ))

        elif requires_login and not requires_superuser:
            return login_required(login_url=self.login_url)

        elif requires_superuser:
            return user_passes_test(lambda u:u.is_superuser,
                                    login_url=self.login_url)

        else:
            return user_passes_test(lambda u:True)

0

我已经根据乔希的解决方案进行了修复

class LoginRequiredMixin(object):

    @method_decorator(login_required)
    def dispatch(self, *args, **kwargs):
        return super(LoginRequiredMixin, self).dispatch(*args, **kwargs)

用法示例:

class EventsListView(LoginRequiredMixin, ListView):

    template_name = "events/list_events.html"
    model = Event

0

这是Permission_required装饰器的解决方案:

class CustomerDetailView(generics.GenericAPIView):

@method_decorator(permission_required('app_name.permission_codename', raise_exception=True))
    def post(self, request):
        # code...
        return True
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.