如何在Django基于类的视图中使用permission_required装饰器

186
我有一些难以理解新的类视图(Class Based Views)是如何工作的。我的问题是,我需要在所有视图中要求登录,在某些视图中还需要特定权限。在基于函数的视图中,我使用@permission_required()和视图中的login_required属性来完成这个操作,但是我不知道如何在新的视图上实现这个功能。Django文档中有关于这方面的介绍吗?我没有找到任何相关内容。我的代码哪里出了问题呢?
我尝试使用@method_decorator,但它回复错误:TypeError at /spaces/prueba/ _wrapped_view() takes at least 1 argument (0 given) 以下是代码(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
13个回答

255

CBV文档中列出了几种策略:

当您在urls.py中实例化视图时,装饰视图(文档)

from django.contrib.auth.decorators import login_required

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

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

装饰您的类,以便每个视图实例都被包装(文档)

有两种方法可以做到这一点:

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

     from django.utils.decorators import method_decorator
     from django.contrib.auth.decorators import login_required
    
     @method_decorator(login_required, name='dispatch')
     class ViewSpaceIndex(TemplateView):
         template_name = 'secret.html'
    
    如果你正在使用 Django < 1.9(不建议这样做,因为它已经不再受支持),那么你无法在类上使用 method_decorator,所以你必须手动覆盖 dispatch 方法。
        from django.contrib.auth.decorators import login_required
    
        class ViewSpaceIndex(TemplateView):
    
            @method_decorator(login_required)
            def dispatch(self, *args, **kwargs):
                return super(ViewSpaceIndex, self).dispatch(*args, **kwargs)
    
    1. 在这里其他答案中很好地概述了,使用类似于django.contrib.auth.mixins.LoginRequiredMixin的mixin:

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

确保在继承列表中将混合类放在第一位(这样Python的方法解析顺序算法就会选择正确的东西)。

如果您的方法不接受兼容的参数集,则会出现TypeError错误,这是由于文档中所解释的method_decorator将*args和**kwargs作为参数传递给类上的装饰方法。


3
最新文档中提到了这里:https://docs.djangoproject.com/en/dev/topics/class-based-views/intro/ - Bharathwaaj
如何将 message 追加到它后面? - andilabs
对于那些不理解的人(就像我一开始一样)-应该将“dispatch”方法添加到ViewSpaceIndex类中。 - o_c
这两种方法之间有任何偏好的原因吗? - Alistair
@Alistair 我认为这归结于个人偏好和在团队/组织中保持代码库一致性。如果我正在构建基于类的视图,我个人倾向于使用mixin方法。 - A Lee

120

我是这样做的,我创建了一个受保护的mixin(这个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现在将其和其他有用的mixin(AccessMixin、PermissionRequiredMixin、UserPassesTestMixin)作为标准功能包含在内!


这种混合类是否可以有多个?对我来说它没有起作用,而且我认为它也没有意义。 - Pykler
是的,应该可以有多个 mixin,因为每个 mixin 都会调用 super,根据方法解析顺序选择下一个类。 - Hobblin
我认为这是一个优雅的解决方案;我不喜欢在我的urls.py中混合装饰器和在views.py中混合mixin。这是一种包装装饰器的方式,可以将所有逻辑移动到视图中。 - dhackner
1
django-braces有这些(以及更多)mixin - 这是一个非常有用的安装包。 - askvictor
只是提醒那些像我一样处于完全迟钝模式的人:在测试 login_required 功能时,请确保您未登录... - Visgean Skeloru
根据您的更新,这是AccessMixin文档的链接:https://docs.djangoproject.com/en/1.9/topics/auth/default/#django.contrib.auth.mixins.AccessMixin - Marshall

51

这里提供一种使用基于类的装饰器的替代方案:

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 - Pykler
9
这太棒了,我认为应该考虑将其纳入上游。 - koniiiik
我喜欢这个!我在想从class_view_decorator传递args/kwargs到function_decorator是否有可能?如果login_decorator可以有条件地匹配request.METHOD,那就太好了,这样它只适用于post请求。 - Mike Waites
1
args/kwargs 可以通过使用 class_view_decorator(my_decorator(*args, **kwargs)) 轻松实现。至于条件方法匹配 - 您可以修改 class_view_decorator,将其应用于 View.getView.post 而不是 View.dispatch - mjtamlyn

22

对于那些使用Django >= 1.9的人,它已经包含在django.contrib.auth.mixins中,作为AccessMixinLoginRequiredMixinPermissionRequiredMixinUserPassesTestMixin

因此,要将 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 Mixin的顺序: Mixins 必须放在 左侧,而 base view 类必须放在右侧。如果顺序不同,可能会导致错误和不可预测的结果。


3
这是2019年最佳答案。此外,在混合顺序方面提出了很好的观点。 - Christian Long

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函数作为装饰器简单应用即可使该装饰器在基于类的视图中正常工作。


5
使用Django Braces。它提供了许多有用的mixin,并且容易获取。文档很好看,可以试试。
你甚至可以创建自定义的mixins。 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
在我的代码中,我编写了这个适配器来调整成员函数为非成员函数:
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')

希望这个功能能够像method_decorator一样内置在Django中,这似乎是一种不错且易读的实现方式。 - MariusSiuram

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/')

4

相隔已久,Django 已经发生了很大的变化。

点击这里查看如何装饰基于类的视图。

https://docs.djangoproject.com/en/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

同样地,如果要在method_decorator中使用permission_required,我们可以这样做:

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

我认为在类视图中通过检查其他权限来实现权限是最简单的方式,而不是使用经典的@login_required...此外,您可以传递多个自定义权限,如下所示:@permission_required(['polls.can_vote', 'polls.change_vote']) - allexiusw

1
这在Django > 1.9中非常容易,因为它支持PermissionRequiredMixinLoginRequiredMixin。只需从auth中导入即可。

views.py

from django.contrib.auth.mixins import LoginRequiredMixin

class YourListView(LoginRequiredMixin, Views):
    pass

要了解更多详细信息,请阅读Django中的授权


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接