首页 文章

如何在基于django类的视图上使用permission_required装饰器

提问于
浏览
132

我在理解新CBV如何工作方面遇到了一些麻烦 . 我的问题是,我需要在所有视图中登录,在其中一些视图中需要登录特定权限 . 在基于函数的视图中,我使用@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

11 回答

  • 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')
    
  • 14

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

    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)
    
    from django.contrib.auth.mixins import LoginRequiredMixin
    
    class MyView(LoginRequiredMixin, View):
    
        login_url = '/login/'
        redirect_field_name = 'redirect_to'
    

    您收到 TypeError 的原因在文档中有解释:

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

  • 0

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

    Pre 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

    这是我的方法,我创建一个受保护的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'
    

    Just make sure that your mixin is first.

    Update: 我在2011年发布了这个版本,从版本1.9开始,Django现在包括这个和其他有用的mixin(AccessMixin,PermissionRequiredMixin,UserPassesTestMixin)作为标准!

  • 1

    使用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({})
    
  • 3

    我意识到这个线程有点陈旧,但无论如何这里是我的两分钱 .

    使用以下代码:

    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错误 . 通过简单地将补丁函数应用为装饰器,该装饰器也被设置为在基于类的视图中工作

  • 0

    这是非常容易的django> 1.9来支持 PermissionRequiredMixinLoginRequiredMixin

    只需从身份验证中导入即可

    views.py

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

    有关详细信息,请阅读Authorization in django

  • 11

    我根据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
    
  • 113

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

    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)
    
  • 164

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

    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
    
  • 43

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

    因此,要将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 侧,基本视图类必须在 right 侧 . 如果订单不同,您可能会遇到破碎和不可预测的结果 .

相关问题