Verwendung von erlaubnisberechtigten Dekoratoren in Django-Klassenansichten


161

Ich habe ein bisschen Probleme zu verstehen, wie die neuen CBVs funktionieren. Meine Frage lautet: Ich muss mich in allen Ansichten anmelden und in einigen von ihnen bestimmte Berechtigungen. In funktionsbasierten Ansichten mache ich das mit @permission_required () und dem Attribut login_required in der Ansicht, aber ich weiß nicht, wie ich das in den neuen Ansichten machen soll. Gibt es einen Abschnitt in den Django-Dokumenten, der dies erklärt? Ich habe nichts gefunden Was ist falsch in meinem Code?

Ich habe versucht, den @ method_decorator zu verwenden, aber er antwortet: " TypeError at / space / prueba / _wrapped_view () benötigt mindestens 1 Argument (0 angegeben) ".

Hier ist der 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

Antworten:


211

In den CBV-Dokumenten sind einige Strategien aufgeführt :

Dekorieren Sie die Ansicht auf Instanzbasis, urls.pywenn Sie Ihre Ansicht instanziieren ( Dokumente ).

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

Der Dekorator wird pro Instanz angewendet, sodass Sie ihn urls.pynach Bedarf auf verschiedenen Wegen hinzufügen oder entfernen können .

Dekorieren Sie Ihre Klasse so, dass jede Instanz Ihrer Ansicht vom Dekorateur ( docs ) verpackt wird.

Es gibt zwei Möglichkeiten, wie Sie dies tun können:

  1. Anwenden einer method_decoratorauf Ihre CBV-Versandmethode, z.

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

Wenn Sie Django <1.9 verwenden (was Sie nicht sollten, es wird nicht mehr unterstützt), können Sie es nicht method_decoratorfür die Klasse verwenden, daher müssen Sie die dispatchMethode überschreiben :

    class ViewSpaceIndex(TemplateView):

        @method_decorator(login_required)
        def dispatch(self, *args, **kwargs):
            return super(ViewSpaceIndex, self).dispatch(*args, **kwargs)
  1. In modernen Django (2.2+) ist es üblich, Zugriffsmixine wie django.contrib.auth.mixins.LoginRequiredMixin zu verwenden, die in Django 1.9+ verfügbar sind und in den anderen Antworten hier gut umrissen sind:

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

Stellen Sie sicher, dass Sie das Mixin an die erste Stelle in der Vererbungsliste setzen (damit die Reihenfolge der Methodenauflösung das Richtige auswählt).

Der Grund, warum Sie eine erhalten, TypeErrorwird in den Dokumenten erklärt:

Hinweis: method_decorator übergibt * args und ** kwargs als Parameter an die dekorierte Methode in der Klasse. Wenn Ihre Methode keinen kompatiblen Parametersatz akzeptiert, wird eine TypeError-Ausnahme ausgelöst.



wie anhängen , messageum es?
Andilabs

Für diejenigen, die nicht verstanden haben (wie ich es zuerst getan habe) - die 'dispatch'-Methode sollte der ViewSpaceIndex-Klasse hinzugefügt werden
o_c

Gibt es einen Grund, eine dieser Methoden der anderen vorzuziehen?
Alistair

@Alistair Ich denke, es läuft auf persönliche Vorlieben und die Aufrechterhaltung der Codebasis-Konsistenz in Ihrem Team / Ihrer Organisation hinaus. Ich persönlich tendiere zum Mixin-Ansatz, wenn ich klassenbasierte Ansichten erstelle.
Ein Lee

118

Hier ist mein Ansatz: Ich erstelle ein Mixin, das geschützt ist (dies wird in meiner Mixin-Bibliothek gespeichert):

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)

Wann immer Sie möchten, dass eine Ansicht geschützt wird, fügen Sie einfach das entsprechende Mixin hinzu:

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

Stellen Sie einfach sicher, dass Ihr Mixin an erster Stelle steht.

Update: Ich habe dies bereits 2011 veröffentlicht, beginnend mit Version 1.9. Django enthält jetzt dieses und andere nützliche Mixins (AccessMixin, PermissionRequiredMixin, UserPassesTestMixin) als Standard!


Ist es möglich, mehrere dieser Art von Mixins zu haben? Es hat bei mir nicht funktioniert und ich denke nicht, dass es Sinn macht, es zu haben.
Pykler

Ja, es sollte möglich sein, mehrere Mixins zu haben, da jedes Mixin einen Super-Aufruf
ausführt

Ich denke, dass dies eine elegante Lösung ist; Ich mag es nicht, eine Mischung aus Dekorateuren in meiner urls.py und Mixins in views.py zu haben. Dies ist eine Möglichkeit, Dekorateure zu verpacken, die all diese Logik in die Ansicht verschieben.
Dhackner

1
Django-Klammern hat diese (und mehr) Mixins - ein sehr nützliches Paket zu installieren
Askvictor

Nur ein Hinweis für Leute im Vollverzögerungsmodus wie mich: Stellen Sie sicher, dass Sie nicht angemeldet sind, wenn Sie die Funktionalität von
login_required testen

46

Hier ist eine Alternative mit klassenbasierten Dekorateuren:

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

Dies kann dann einfach so verwendet werden:

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

3
Sie können dies verwenden, um Dekorateure in Kettenansicht zu sehen! +1
Pykler

9
Dies ist so großartig, dass es für die Aufnahme in die vorgelagerte IMO in Betracht gezogen werden sollte.
Koniiiik

Ich liebe es! Ich frage mich, ob es überhaupt möglich ist, args / kwargs vom class_view_decorator an den function_decorator weiterzugeben. Es wäre großartig, wenn der login_decorator sagen könnte, dass die Anforderung bedingt übereinstimmt. METHOD, also gilt dies nur für den Beitrag?
Mike Waites

1
Die args / kwargs sollten mit leicht erreichbar sein class_view_decorator(my_decorator(*args, **kwargs)). Was den bedingten Methodenabgleich betrifft, können Sie den class_view_decorator so ändern, dass er sich auf View.getoder View.postanstelle von anwendet View.dispatch.
Mjtamlyn

14

Mir ist klar, dass dieser Thread etwas veraltet ist, aber hier sind trotzdem meine zwei Cent.

mit folgendem Code:

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

Wir haben jetzt die Möglichkeit, einen Dekorateur zu patchen, damit er multifunktional wird. Dies bedeutet effektiv, dass bei Anwendung auf einen normalen Ansichtsdekorateur wie folgt:

login_required = patch_view_decorator(login_required)

Dieser Dekorateur funktioniert weiterhin, wenn er wie ursprünglich vorgesehen verwendet wird:

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

funktioniert aber auch richtig, wenn es so verwendet wird:

@login_required
class FooView(DetailView):
    model = Foo

Dies scheint in einigen Fällen, auf die ich kürzlich gestoßen bin, gut zu funktionieren, einschließlich dieses realen Beispiels:

@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

Die Funktion ajax_view wurde geschrieben, um eine (funktionsbasierte) Ansicht so zu ändern, dass sie einen 404-Fehler auslöst, wenn diese Ansicht von einem Nicht-Ajax-Aufruf aufgerufen wird. Durch einfaches Anwenden der Patch-Funktion als Dekorateur kann dieser Dekorateur auch in klassenbasierten Ansichten verwendet werden


14

Für diejenigen , die verwenden Django> = 1,9 , ist es bereits in enthalten django.contrib.auth.mixinswie AccessMixin, LoginRequiredMixin, PermissionRequiredMixinund UserPassesTestMixin.

So wenden Sie LoginRequired auf CBV an (z. B. 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'

Beachten Sie auch die GCBV-Mixin-Reihenfolge: Mixins müssen auf der linken Seite und die Basisansichtsklasse auf der rechten Seite angezeigt werden . Wenn die Reihenfolge anders ist, können fehlerhafte und unvorhersehbare Ergebnisse erzielt werden.


2
Dies ist die beste Antwort im Jahr 2019. Auch ein guter Punkt zur Mixin-Reihenfolge.
Christian Long

5

Verwenden Sie Django-Zahnspangen. Es bietet viele nützliche Mixins, die leicht verfügbar sind. Es hat schöne Dokumente. Versuch es.

Sie können sogar Ihre benutzerdefinierten Mixins erstellen.

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

Beispielcode:

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

Wenn es sich um eine Site handelt, auf der für die meisten Seiten der Benutzer angemeldet sein muss, können Sie eine Middleware verwenden, um die Anmeldung für alle Ansichten zu erzwingen Ausnahme einiger besonders markierter .

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

Ansichten von Drittanbietern, die Sie nicht umbrechen möchten, können in den folgenden Einstellungen ausgeschlossen werden:

settings.py:

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

3

In meinem Code habe ich diesen Adapter geschrieben, um Elementfunktionen an eine Nichtmitgliedsfunktion anzupassen:

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

Sie können es einfach so verwenden:

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

Es wäre schön, wenn dies ein eingebauter Django wäre (genau wie er method_decoratorist). Es scheint eine gute und lesbare Möglichkeit zu sein, dies zu erreichen.
MariusSiuram

1

Dies ist super einfach, da Django> 1.9 mit Unterstützung für PermissionRequiredMixinund geliefert wirdLoginRequiredMixin

Einfach aus der Authentifizierung importieren

views.py

from django.contrib.auth.mixins import LoginRequiredMixin

class YourListView(LoginRequiredMixin, Views):
    pass

Weitere Informationen finden Sie unter Autorisierung in Django


1

Es ist schon eine Weile her und jetzt hat sich Django so sehr verändert.

Hier erfahren Sie, wie Sie eine klassenbasierte Ansicht dekorieren.

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

Die Dokumentation enthielt kein Beispiel für "Dekorateure, die irgendwelche Argumente annehmen". Aber Dekorateure, die Argumente annehmen, sind wie folgt:

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

Wenn wir also mydec als "normalen" Dekorateur ohne Argumente verwenden wollen, können wir dies tun:

mydecorator = mydec(10)

@mydecorator
def myfunc():
    return 5

Also ähnlich zu verwenden permission_requiredmitmethod_decorator

wir können tun:

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

0

Wenn Sie ein Projekt ausführen, für das verschiedene Berechtigungstests erforderlich sind, können Sie diese Klasse erben.

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

Ich habe dieses Update basierend auf Joshs Lösung vorgenommen

class LoginRequiredMixin(object):

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

Beispielnutzung:

class EventsListView(LoginRequiredMixin, ListView):

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

0

Hier die Lösung für den erlaubten Dekorateur:

class CustomerDetailView(generics.GenericAPIView):

@method_decorator(permission_required('app_name.permission_codename', raise_exception=True))
    def post(self, request):
        # code...
        return True
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.