Como usar decoradores permission_required em visualizações baseadas em classe do django


161

Estou com problemas para entender como os novos CBVs funcionam. Minha pergunta é esta: preciso solicitar o login em todas as visualizações e, em algumas delas, em permissões específicas. Nas visualizações baseadas em funções, faço isso com @permission_required () e o atributo login_required na visualização, mas não sei como fazer isso nas novas visualizações. Existe alguma seção nos documentos do django explicando isso? Eu não encontrei nada. O que há de errado no meu código?

Tentei usar o @method_decorator, mas ele respondeu " TypeError em / spaces / prueba / _wrapped_view () leva pelo menos 1 argumento (0 dado) "

Aqui está o código (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

Respostas:


211

Existem algumas estratégias listadas nos documentos da CBV :

Decore a exibição por instância, urls.pyquando você instanciar sua exibição ( docs )

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

O decorador é aplicado por instância, para que você possa adicioná-lo ou removê-lo em diferentes urls.pyrotas, conforme necessário.

Decore sua turma para que todas as instâncias de sua visualização sejam agrupadas pelo decorador ( documentos )

Há duas maneiras de você fazer isto:

  1. Aplicando a method_decoratorao seu método de envio CBV, por exemplo,

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

Se você estiver usando o Django <1.9 (o que você não deveria, não é mais suportado), você não pode usar method_decoratorna classe, então você deve substituir o dispatchmétodo:

    class ViewSpaceIndex(TemplateView):

        @method_decorator(login_required)
        def dispatch(self, *args, **kwargs):
            return super(ViewSpaceIndex, self).dispatch(*args, **kwargs)
  1. Uma prática comum no Django moderno (2.2+) é usar mixins de acesso como django.contrib.auth.mixins.LoginRequiredMixin disponível no Django 1.9+ e descrito bem nas outras respostas aqui:

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

Certifique-se de colocar o Mixin primeiro na lista de herança (para que a Ordem de resolução do método escolha a coisa certa).

O motivo pelo qual você recebe um TypeErroré explicado nos documentos:

Nota: method_decorator passa * args e ** kwargs como parâmetros para o método decorado na classe. Se o seu método não aceitar um conjunto compatível de parâmetros, será gerada uma exceção TypeError.



como anexar messagea ele?
9789 Andrett

Para quem não entendeu (como eu entendi a princípio) - o método 'dispatch' deve ser adicionado à classe
ViewSpaceIndex

Existe alguma razão para favorecer um desses métodos em detrimento do outro?
Alistair

@Alistair Acho que tudo se resume a preferências pessoais e a manutenção da consistência da base de código na sua equipe / organização. Eu, pessoalmente, tendem a adotar a abordagem mixin, se estiver criando visões baseadas em classe.
A Lee

118

Aqui está minha abordagem, eu crio um mixin que está protegido (isso é mantido na minha biblioteca de 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)

Sempre que você deseja proteger uma visualização, basta adicionar a combinação apropriada:

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

Apenas certifique-se de que seu mixin seja o primeiro.

Atualização: Eu publiquei isso no caminho de volta em 2011, começando com a versão 1.9. O Django agora inclui este e outros mixins úteis (AccessMixin, PermissionRequiredMixin, UserPassesTestMixin) como padrão!


é possível ter vários desse tipo de mixins? Não funcionou para mim e não acho que faça sentido.
Pykler

Sim, deve ser possível ter vários mixins uma vez que cada mixin faz uma chamada para super-que pega a próxima aula, de acordo com a MRO
Hobblin

Eu acho que essa é uma solução elegante; Eu não gosto de ter uma mistura de decoradores em meus urls.py e mixins em views.py. Essa é uma maneira de agrupar decoradores que moveriam toda essa lógica para a exibição.
dhackner

1
django-chaves tem isso (e mais) mixins - Um pacote muito útil para instalar
askvictor

Apenas uma nota para as pessoas em modo de atraso completo como eu: certifique-se de que você não está logado funcionalidade ao testar login_required ...
Visgean Skeloru

46

Aqui está uma alternativa usando decoradores baseados em classe:

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

Isso pode ser usado da seguinte maneira:

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

3
Você pode usar isso para encadear decoradores de vista, agradavelmente! 1
Pykler,

9
Isso é tão bom que deve ser considerado para inclusão na IMO a montante.
26712 koniiiik

Eu amo isto! Estou querendo saber se é possível passar args / kwargs para baixo do class_view_decorator para o function_decorator ?? Seria ótimo se o login_decorator pudesse dizer que condicionalmente corresponde a request.METHOD, portanto, isso só se aplica a say post?
Mike Waites

1
Os argumentos / kwargs devem ser facilmente alcançáveis ​​usando class_view_decorator(my_decorator(*args, **kwargs)). Quanto à correspondência condicional do método - você pode modificar o class_view_decorator para aplicar-se a ele View.getou ao View.postinvés dele View.dispatch.
Mjtamlyn # 1 de

14

Sei que esse tópico é um pouco datado, mas aqui estão meus dois centavos de qualquer maneira.

com o seguinte código:

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

agora temos uma maneira de corrigir um decorador, para que ele se torne multifuncional. Isso significa efetivamente que, quando aplicado a um decorador de vista comum, é assim:

login_required = patch_view_decorator(login_required)

esse decorador ainda funcionará quando usado da maneira como foi originalmente planejado:

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

mas também funcionará corretamente quando usado da seguinte maneira:

@login_required
class FooView(DetailView):
    model = Foo

Isso parece funcionar bem em vários casos que encontrei recentemente, incluindo este exemplo do mundo real:

@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

A função ajax_view é gravada para modificar uma visão (baseada em função), de modo que gera um erro 404 sempre que essa visão é visitada por uma chamada que não seja ajax. Simplesmente aplicando a função de patch como um decorador, este decorador está pronto para funcionar também em visualizações baseadas em classe


14

Para aqueles de vocês que usam Django> = 1.9 , que já está incluído no django.contrib.auth.mixinscomo AccessMixin, LoginRequiredMixin, PermissionRequiredMixine UserPassesTestMixin.

Portanto, para aplicar o LoginRequired ao CBV (por exemplo 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'

Também é bom ter em mente a ordem do GCBV Mixin: os mixins devem ir no lado esquerdo e a classe de vista base deve ir no lado direito . Se a ordem for diferente, você poderá obter resultados quebrados e imprevisíveis.


2
Esta é a melhor resposta em 2019. Além disso, um ótimo ponto sobre a ordem do mixin.
Christian Long

5

Use chaves do Django. Ele fornece muitos mixins úteis que estão facilmente disponíveis. Tem documentos bonitos. Experimente.

Você pode até criar seus mixins personalizados.

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

Código de exemplo:

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

Se é um site em que a maioria das páginas exige que o usuário esteja logado, você pode usar um middleware para forçar o login em todas as visualizações, exceto em algumas que são especialmente marcadas.

Antes do 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

As visualizações de terceiros que você não deseja agrupar podem ser excluídas nas configurações:

settings.py:

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

3

No meu código, escrevi este adaptador para adaptar funções-membro a uma função não-membro:

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

Você pode simplesmente usá-lo assim:

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

Seria bom que este fosse um built-in no Django (assim como method_decoratoré). Parece uma maneira agradável e legível de conseguir isso.
MariusSiuram

1

Isso é super fácil com o django> 1.9 vindo com suporte PermissionRequiredMixineLoginRequiredMixin

Basta importar da autenticação

views.py

from django.contrib.auth.mixins import LoginRequiredMixin

class YourListView(LoginRequiredMixin, Views):
    pass

Para mais detalhes, leia Autorização no django


1

Já faz um tempo agora e agora o Django mudou muito.

Confira aqui como decorar uma exibição baseada em classe.

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

A documentação não incluiu um exemplo de "decoradores que aceitam qualquer argumento". Mas os decoradores que aceitam argumentos são assim:

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

portanto, se quisermos usar o mydec como um decorador "normal" sem argumentos, podemos fazer isso:

mydecorator = mydec(10)

@mydecorator
def myfunc():
    return 5

Da mesma forma, para usar permission_requiredcommethod_decorator

nós podemos fazer:

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

0

Se você estiver executando um projeto que requer vários testes de permissão, poderá herdar esta classe.

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

Fiz essa correção com base na solução de Josh

class LoginRequiredMixin(object):

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

Uso da amostra:

class EventsListView(LoginRequiredMixin, ListView):

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

0

Aqui a solução para o decorador permission_required:

class CustomerDetailView(generics.GenericAPIView):

@method_decorator(permission_required('app_name.permission_codename', raise_exception=True))
    def post(self, request):
        # code...
        return True
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.