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

161

J'ai un peu de mal à comprendre comment fonctionnent les nouveaux CBV. Ma question est la suivante, je dois 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 fais cela avec @permission_required () et l'attribut login_required dans la vue, mais je ne sais pas comment faire cela sur les nouvelles vues. Y a-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 at / 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
Oscar Carballal
la source

Réponses:

211

Il existe quelques stratégies répertoriées dans la documentation CBV :

Décorez la vue sur une base par instance, dans votre urls.pylorsque vous instanciez votre vue ( docs )

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

Le décorateur est appliqué sur une base par instance, vous pouvez donc l'ajouter ou le supprimer dans différents urls.pyitinéraires selon vos besoins.

Décorez votre classe pour que chaque instance de votre vue soit enveloppée par le décorateur ( docs )

Vous pouvez procéder de deux manières:

  1. Appliquer un method_decoratorà votre méthode d'expédition CBV, par exemple,

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

Si vous utilisez Django <1.9 (ce que vous ne devriez pas, ce n'est plus pris en charge), vous ne pouvez pas utiliser method_decoratorsur la classe, vous devez donc remplacer la dispatchméthode:

    class ViewSpaceIndex(TemplateView):

        @method_decorator(login_required)
        def dispatch(self, *args, **kwargs):
            return super(ViewSpaceIndex, self).dispatch(*args, **kwargs)
  1. Une pratique courante dans Django moderne (2.2+) consiste à utiliser des mixins d'accès comme django.contrib.auth.mixins.LoginRequiredMixin disponible dans Django 1.9+ et bien décrit 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'
    

Assurez-vous de placer le Mixin en premier dans la liste d'héritage (afin que l'ordre de résolution des méthodes choisisse la bonne chose).

La raison pour laquelle vous obtenez un TypeErrorest expliquée dans la documentation:

Remarque: method_decorator transmet * args et ** kwargs comme paramètres à la méthode décorée sur la classe. Si votre méthode n'accepte pas un ensemble de paramètres compatible, elle lèvera une exception TypeError.

Un Lee
la source
3
Mentionné ici dans les derniers documents docs.djangoproject.com/en/dev/topics/class-based-views/intro
Bharathwaaj
comment y ajouter message?
andilabs
Pour ceux qui n'ont pas compris (comme je l'ai fait, au début) - la méthode 'dispatch' devrait être ajoutée à la classe
ViewSpaceIndex
Y a-t-il une raison de privilégier l'une de ces méthodes par rapport à l'autre?
Alistair
@Alistair Je pense que cela se résume à des préférences personnelles et au maintien de la cohérence de la base de code au sein de votre équipe / organisation. Personnellement, j'ai tendance à adopter l'approche mixin si je construis des vues basées sur les classes.
A Lee
118

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

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

Assurez-vous simplement que votre mixin est le premier.

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

Gert Steyn
la source
est-il possible d'avoir plusieurs mixins de ce genre? Cela n'a pas fonctionné pour moi et je ne pense pas que cela ait du sens.
Pykler
Oui, il devrait être possible d'avoir plusieurs mixins puisque chaque mixin fait un appel au super qui choisit la classe suivante conformément au MRO
Hobblin
Je pense que c'est une solution élégante; Je n'aime pas avoir un mélange de décorateurs dans mes urls.py et de mixins dans views.py. C'est un moyen d'envelopper les décorateurs qui déplaceraient toute cette logique vers la vue.
dhackner
1
django-braces a ce (et plus) mixins - un paquet très utile à installer
askvictor
Juste une note pour les personnes en mode retard complet comme moi: assurez-vous de ne pas être connecté lors du test de la fonctionnalité login_required ...
Visgean Skeloru
46

Voici une alternative utilisant des décorateurs basés sur les classes:

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

Cela peut ensuite être utilisé simplement comme ceci:

@class_view_decorator(login_required)
class MyView(View):
    # this view now decorated
mjtamlyn
la source
3
Vous pouvez l'utiliser pour enchaîner les décorateurs, bien! +1
Pykler
9
C'est tellement génial qu'il devrait être envisagé d'inclure l'OMI en amont.
koniiiik
J'aime cela! Je me demande s'il est possible de passer args / kwargs du class_view_decorator au function_decorator ??? Ce serait génial si le login_decorator pouvait dire conditionnellement match request.METHOD donc cela ne s'applique que pour le post?
Mike Waites
1
Les args / kwargs devraient être facilement réalisables en utilisant class_view_decorator(my_decorator(*args, **kwargs)). En ce qui concerne la correspondance de méthode conditionnelle, vous pouvez modifier le class_view_decorator pour qu'il s'applique à View.getou à la View.postplace de View.dispatch.
mjtamlyn
14

Je me rends compte que ce fil est un peu daté, mais voici mes deux cents quand même.

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 patcher un décorateur, il deviendra donc multifonctionnel. Cela signifie effectivement que lorsqu'il est appliqué à un décorateur de vue standard, comme ceci:

login_required = patch_view_decorator(login_required)

ce décorateur fonctionnera toujours lorsqu'il est utilisé comme il était initialement prévu:

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

mais fonctionnera également correctement lorsqu'il est utilisé comme ceci:

@login_required
class FooView(DetailView):
    model = Foo

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

@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 déclenche 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 également prêt à fonctionner dans des vues basées sur les classes.

Méphisto
la source
14

Pour ceux d' entre vous qui utilisent Django> = 1.9 , il est déjà inclus dans django.contrib.auth.mixinscomme AccessMixin, LoginRequiredMixin, PermissionRequiredMixinet 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 l'ordre GCBV Mixin: les mixins doivent aller du côté gauche et la classe de vue de base doit aller du côté droit . Si l'ordre est différent, vous pouvez obtenir des résultats cassés et imprévisibles.

vishes_shell
la source
2
C'est la meilleure réponse en 2019. Aussi, excellent point à propos de mixin order.
Christian Long
5

Utilisez les accolades Django. Il fournit de nombreux mixins utiles 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({})
shap4th
la source
4

S'il s'agit d'un site où la majorité des pages nécessite que l'utilisateur soit connecté, vous pouvez utiliser un middleware pour forcer la connexion sur toutes les vues sauf certaines qui sont spécialement marquées.

Middleware.py pré Django 1.10:

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 supprimées dans les paramètres:

settings.py:

LOGIN_EXEMPT_URL_PREFIXES = ('/login/', '/reset_password/')
kaleissin
la source
3

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')
lapin.aaron
la source
Ce serait bien que ce soit un intégré sur Django (tout comme l' method_decoratorest). Cela semble être une manière agréable et lisible d'y parvenir.
MariusSiuram
1

C'est super facile avec django> 1.9 avec support pour PermissionRequiredMixinetLoginRequiredMixin

Importez simplement depuis l'authentification

views.py

from django.contrib.auth.mixins import LoginRequiredMixin

class YourListView(LoginRequiredMixin, Views):
    pass

Pour plus de détails, lisez Autorisation dans django

Amar
la source
1

Cela fait un moment maintenant et maintenant Django a tellement changé.

Vérifiez ici comment décorer une vue basée sur les classes.

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

La documentation n'incluait pas d'exemple de "décorateurs qui acceptent n'importe quel argument". Mais les décorateurs qui acceptent des arguments sont comme ceci:

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

donc si nous devons utiliser mydec comme décorateur "normal" sans arguments, nous pouvons le faire:

mydecorator = mydec(10)

@mydecorator
def myfunc():
    return 5

De même, à utiliser permission_requiredavecmethod_decorator

nous pouvons faire:

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

Si vous effectuez un projet qui nécessite divers 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)
Vinayak Kaniyarakkal
la source
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
Ramast
la source
0

Voici la solution pour le décorateur permission_required:

class CustomerDetailView(generics.GenericAPIView):

@method_decorator(permission_required('app_name.permission_codename', raise_exception=True))
    def post(self, request):
        # code...
        return True
Ashish Sondagar
la source