web-dev-qa-db-fra.com

Comment utiliser les décorateurs permission_required sur les vues basées sur des classes Django

J'ai un peu de difficulté à comprendre comment fonctionne le nouveau CBV. Ma question est la suivante: il est nécessaire d’exiger une connexion dans toutes les vues et, dans certaines d’entre elles, des autorisations spécifiques. Dans les vues basées sur les fonctions, je le fais avec @permission_required () et l'attribut login_required dans la vue, mais je ne sais pas comment procéder dans les nouvelles vues. Existe-t-il une section dans la documentation Django expliquant cela? Je n'ai rien trouvé. Quel est le problème dans mon code?

J'ai essayé d'utiliser le @method_decorator mais il répond " TypeError à/spaces/prueba/_wrapped_view () prend au moins 1 argument (0 donné) "

Voici le code (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
136
Oscar Carballal

Quelques stratégies sont énumérées dans les documents CBV :

  1. Ajoutez le décorateur dans votre itinéraire urls.py , par exemple, login_required(ViewSpaceIndex.as_view(..)) 

  2. Décorez la méthode dispatch de votre CBV avec un method_decorator p. Ex.,

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

    Avant Django 1.9, vous ne pouvez pas utiliser method_decorator sur la classe, vous devez donc remplacer la méthode dispatch

    class ViewSpaceIndex(TemplateView):
    
        @method_decorator(login_required)
        def dispatch(self, *args, **kwargs):
            return super(ViewSpaceIndex, self).dispatch(*args, **kwargs)
    
  3. Utilisez les mixins d’accès tels que Django.contrib.auth.mixins.LoginRequiredMixin disponibles dans Django 1.9+ et bien décrits dans les autres réponses ici:

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

La raison pour laquelle vous obtenez une TypeError est expliquée dans la documentation:

Remarque: method_decorator transmet * args et ** kwargs en tant que paramètres à la méthode décorée de la classe. Si votre méthode n'accepte pas de jeu de paramètres compatible, une exception TypeError sera générée.

170
A Lee

Voici mon approche, je crée un mixin protégé (ceci est conservé dans ma bibliothèque 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)

Chaque fois que vous voulez qu'une vue soit protégée, ajoutez simplement le mix approprié:

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

Assurez-vous que votre mixin est le premier.

Mise à jour: J'ai déjà posté cela depuis 2011, à partir de la version 1.9. Django inclut maintenant ce mix et d'autres mixins utiles (AccessMixin, PermissionRequiredMixin, UserPassesTestMixin) en standard!

114
Gert Steyn

Voici une alternative en utilisant des décorateurs de 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

Ceci peut alors être utilisé simplement comme ceci:

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

Je me rends compte que ce fil est un peu démodé, mais voici mes deux centimes de toute façon.

avec le code suivant:

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

nous avons maintenant un moyen de réparer un décorateur, de sorte qu'il deviendra multifonctionnel. Cela signifie effectivement que lorsqu'il est appliqué à un décorateur de vue régulier, comme ceci:

login_required = patch_view_decorator(login_required)

ce décorateur fonctionnera quand même comme il était initialement prévu:

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

mais fonctionnera également correctement lorsqu'il est utilisé de la manière suivante:

@login_required
class FooView(DetailView):
    model = Foo

Cela semble bien fonctionner dans plusieurs cas que j'ai récemment rencontrés, y compris cet exemple concret:

@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

La fonction ajax_view est écrite pour modifier une vue (basée sur une fonction), de sorte qu'elle génère une erreur 404 chaque fois que cette vue est visitée par un appel non ajax. En appliquant simplement la fonction de patch en tant que décorateur, ce décorateur est prêt à travailler dans des vues basées sur la classe.

14
mephisto

Pour ceux qui utilisent Django> = 1.9, il est déjà inclus dans Django.contrib.auth.mixins sous la forme AccessMixin , LoginRequiredMixin , PermissionRequiredMixin et UserPassesTestMixin .

Donc, pour appliquer LoginRequired à CBV (par exemple, 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'

Il est également bon de garder à l’esprit le GCBV Mixin ordre: Mixins doit être placé du côté gauche et la classe vue de base doit être du côté droit. . Si l'ordre est différent, vous pouvez obtenir des résultats brisés et imprévisibles.

13
vishes_shell

Utilisez des bretelles Django. Il fournit beaucoup de mixins utiles et facilement disponibles. Il a de beaux documents. Essaye le. 

Vous pouvez même créer vos mixins personnalisés.

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

Exemple de code:

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
shap4th

S'il s'agit d'un site où la majorité des pages nécessitent la connexion de l'utilisateur, vous pouvez utiliser un middleware pour forcer la connexion sur toutes les vues sauf certaines qui sont particulièrement marquées.

Pré-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

Les vues tierces que vous ne souhaitez pas encapsuler peuvent être déduites des paramètres:

settings.py:

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

Dans mon code, j'ai écrit cet adaptateur pour adapter les fonctions membres à une fonction non membre:

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

Vous pouvez simplement l'utiliser comme ceci:

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')
3
rabbit.aaron

C'est très facile avec Django> 1.9 et le support de PermissionRequiredMixin et LoginRequiredMixin 

Il suffit d'importer de l'auth 

views.py

from Django.contrib.auth.mixins import LoginRequiredMixin

class YourListView(LoginRequiredMixin, Views):
    pass

Pour plus de détails, lire Autorisation dans Django

1
Amar

Si vous effectuez un projet nécessitant une variété de tests d'autorisation, vous pouvez hériter de cette 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

J'ai fait ce correctif basé sur la solution de Josh

class LoginRequiredMixin(object):

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

Exemple d'utilisation:

class EventsListView(LoginRequiredMixin, ListView):

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