【问题标题】:How to use permission_required decorators on django class-based views如何在基于 django 类的视图上使用 permission_required 装饰器
【发布时间】:2011-08-29 11:17:45
【问题描述】:

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

我尝试使用@method_decorator,但它回复“TypeError at /spaces/prueba/ _wrapped_view() 需要至少 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

【问题讨论】:

    标签: django django-views django-authentication django-class-based-views


    【解决方案1】:

    the CBV docs中列出了一些策略:

    urls.py (docs) 中实例化视图时装饰视图

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

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

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

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

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

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

    如果你使用的是 Django method_decorator,所以你必须手动覆盖 dispatch 方法:

        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'
      

    确保将 mixin 类放在继承列表的首位(这样 Python 的 Method Resolution Order algorithm 会选择正确的东西)。

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

    注意: method_decorator 将 *args 和 **kwargs 作为参数传递给类上的装饰方法。如果您的方法不接受兼容的参数集,则会引发 TypeError 异常。

    【讨论】:

    • 如何添加message
    • 对于那些不明白的人(就像我一开始那样) - 'dispatch' 方法应该添加到 ViewSpaceIndex 类中
    • 是否有任何理由偏爱其中一种方法而不是另一种?
    • @Alistair 我认为这归结为个人偏好和在您的团队/组织内保持代码库的一致性。如果我正在构建基于类的视图,我个人倾向于使用 mixin 方法。
    【解决方案2】:

    这是我的方法,我创建了一个受保护的 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)作为标准!

    【讨论】:

    • 这种mixin可以有多个吗?它对我不起作用,我认为它没有意义。
    • 是的,应该可以有多个 mixin,因为每个 mixin 都会调用 super 根据 MRO 选择下一个类
    • 我认为这是一个优雅的解决方案;我不喜欢在我的 urls.py 中混合使用装饰器,在 views.py 中混合使用。这是一种包装装饰器的方法,可以将所有逻辑移动到视图中。
    • django-braces 有这个(以及更多)mixins - 一个非常有用的安装包
    • 对像我这样处于完全延迟模式的人的注意事项:确保在测试 login_required 功能时您没有登录...
    【解决方案3】:

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

    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
    

    【讨论】:

    • 你可以用它来链接视图装饰器,很好! +1
    • 这太棒了,应该考虑纳入上游 IMO。
    • 我喜欢这个!我想知道是否有可能将 args/kwargs 从 class_view_decorator 传递到 function_decorator?如果 login_decorator 可以说有条件地匹配 request.METHOD 那就太好了,所以它只适用于说 post?
    • args/kwargs 应该可以通过使用class_view_decorator(my_decorator(*args, **kwargs)) 轻松实现。至于条件方法匹配 - 您可以修改 class_view_decorator 以将其自身应用于View.getView.post 而不是View.dispatch
    【解决方案4】:

    对于那些使用 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 必须放在 left 一侧,base view 类必须放在在右侧。如果顺序不同,您可能会得到破坏和不可预测的结果。

    【讨论】:

    • 这是 2019 年的最佳答案。另外,关于 mixin 顺序的要点。
    【解决方案5】:

    我意识到这个帖子有点过时了,但无论如何,这是我的两分钱。

    使用以下代码:

    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 错误。通过简单地将补丁函数用作装饰器,这个装饰器也可以在基于类的视图中工作

    【讨论】:

      【解决方案6】:

      使用 Django 大括号。它提供了许多很容易获得的有用的 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({})
      

      【讨论】:

        【解决方案7】:

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

        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 一样)。这似乎是实现这一目标的一种很好且可读的方式。
        【解决方案8】:

        如果它是一个大多数页面要求用户登录的网站,您可以使用中间件强制登录所有视图除了一些特别标记的视图。

        Django 1.10 前中间件.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/')
        

        【讨论】:

          【解决方案9】:

          现在已经有一段时间了,现在 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
          

          同样,将permission_requiredmethod_decorator 一起使用

          我们可以做到:

          @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']) .
          【解决方案10】:

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

          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
          

          【讨论】:

            【解决方案11】:

            使用 django > 1.9 支持 PermissionRequiredMixinLoginRequiredMixin 非常简单

            只需从身份验证导入

            views.py

            from django.contrib.auth.mixins import LoginRequiredMixin
            
            class YourListView(LoginRequiredMixin, Views):
                pass
            

            更多详情请阅读Authorization in django

            【讨论】:

              【解决方案12】:

              如果你正在做一个需要各种权限测试的项目,你可以继承这个类。

              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)
              

              【讨论】:

                【解决方案13】:

                permission_required 装饰器的解决方案如下:

                class CustomerDetailView(generics.GenericAPIView):
                
                @method_decorator(permission_required('app_name.permission_codename', raise_exception=True))
                    def post(self, request):
                        # code...
                        return True
                

                【讨论】:

                  猜你喜欢
                  • 2012-06-11
                  • 1970-01-01
                  • 2020-07-07
                  • 2017-03-08
                  • 2015-05-29
                  • 2015-06-23
                  • 2014-10-20
                  • 2023-02-07
                  相关资源
                  最近更新 更多