Jak używać dekoratorów Permissions_required w widokach opartych na klasach django

161

Mam trochę problemów ze zrozumieniem, jak działają nowe CBV. Moje pytanie jest takie, muszę wymagać logowania we wszystkich widokach, aw niektórych z nich, określonych uprawnień. W widokach opartych na funkcjach robię to z @permission_required () i atrybutem login_required w widoku, ale nie wiem, jak to zrobić w nowych widokach. Czy jest jakaś sekcja w dokumentacji django wyjaśniająca to? Nic nie znalazłem. Co jest nie tak w moim kodzie?

Próbowałem użyć @method_decorator, ale odpowiada: „ TypeError w /space / prueba / _wrapped_view () przyjmuje co najmniej 1 argument (podano 0)

Oto kod (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
źródło

Odpowiedzi:

211

W dokumentacji CBV wymieniono kilka strategii :

Udekoruj widok dla poszczególnych instancji, urls.pypodczas tworzenia wystąpienia widoku ( dokumenty )

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

Dekorator jest stosowany indywidualnie, więc w urls.pyrazie potrzeby można go dodać lub usunąć w różnych trasach.

Udekoruj swoją klasę, aby każda instancja Twojego widoku została opakowana przez dekoratora ( dokumenty )

Możesz to zrobić na dwa sposoby:

  1. Zastosowanie a method_decoratordo metody wysyłki CBV, np.

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

Jeśli używasz Django <1.9 (którego nie powinieneś, bo nie jest już obsługiwane), nie możesz używać method_decoratorw klasie, więc musisz nadpisać dispatchmetodę:

    class ViewSpaceIndex(TemplateView):

        @method_decorator(login_required)
        def dispatch(self, *args, **kwargs):
            return super(ViewSpaceIndex, self).dispatch(*args, **kwargs)
  1. Powszechną praktyką we współczesnym Django (2.2+) jest używanie miksów dostępu, takich jak django.contrib.auth.mixins.LoginRequiredMixin dostępnych w Django 1.9+ i dobrze opisanych w innych odpowiedziach tutaj:

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

Upewnij się, że umieściłeś Mixin jako pierwszy na liście dziedziczenia (więc kolejność rozwiązywania metod wybiera właściwą rzecz).

Powód, dla którego otrzymujesz, TypeErrorjest wyjaśniony w dokumentach:

Uwaga: method_decorator przekazuje * args i ** kwargs jako parametry do metody dekorowanej w klasie. Jeśli twoja metoda nie akceptuje zgodnego zestawu parametrów, zgłosi wyjątek TypeError.

Lee
źródło
3
Wspomniane tutaj w najnowszej dokumentacji docs.djangoproject.com/en/dev/topics/class-based-views/intro
Bharathwaaj
jak to dołączyć message?
andilabs
Dla tych, którzy nie rozumieli (tak jak ja na początku) - metodę 'dispatch' należy dodać do klasy
ViewSpaceIndex
Czy jest jakiś powód, aby faworyzować jedną z tych metod nad drugą?
Alistair
@Alistair Myślę, że sprowadza się to do osobistych preferencji i utrzymania spójności kodu w Twoim zespole / organizacji. Osobiście skłaniam się jednak ku podejściu mixin, jeśli tworzę widoki oparte na klasach.
Lee
118

Oto moje podejście, tworzę chroniony mixin (jest to przechowywany w mojej bibliotece):

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)

Zawsze, gdy chcesz, aby widok był chroniony, po prostu dodaj odpowiednią mieszankę:

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

Tylko upewnij się, że Twój miks jest pierwszy.

Aktualizacja: opublikowałem to już w 2011 roku, począwszy od wersji 1.9 Django zawiera teraz ten i inne przydatne miksy (AccessMixin, PermissionRequiredMixin, UserPassesTestMixin) w standardzie!

Gert Steyn
źródło
czy można mieć wiele takich miksów? U mnie to nie zadziałało i nie sądzę, żeby miało to sens.
Pykler
Tak, powinno być możliwe posiadanie kilku mikserów, ponieważ każdy mikser dzwoni do super, który wybiera następną klasę zgodnie z MRO
Hobblin
Myślę, że to eleganckie rozwiązanie; Nie podoba mi się mieszanka dekoratorów w moim urls.py i mieszanki w views.py. Jest to sposób na owinięcie dekoratorów, które przeniesie całą tę logikę do widoku.
dhackner
1
django-braces ma to (i więcej) mixinów - bardzo przydatny pakiet do zainstalowania
askvictor
Tylko uwaga dla osób w pełnym trybie opóźnionym, takich jak ja: upewnij się, że nie jesteś zalogowany podczas testowania funkcji login_required ...
Visgean Skeloru
46

Oto alternatywa korzystania z dekoratorów opartych na klasach:

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

Można to następnie użyć po prostu w ten sposób:

@class_view_decorator(login_required)
class MyView(View):
    # this view now decorated
mjtamlyn
źródło
3
Możesz użyć tego do łączenia w łańcuch dekoratorów widoku, ładnie! +1
Pykler
9
To jest tak wielkie, że należy rozważyć włączenie do IMO w górę strumienia.
koniiiik
Uwielbiam to! Zastanawiam się, czy jest w ogóle możliwe przekazywanie argumentów / kwargs w dół z class_view_decorator do function_decorator ??? Byłoby wspaniale, gdyby login_decorator mógł powiedzieć warunkowo dopasuj żądanie.METHOD, więc dotyczy to tylko powiedz postu?
Mike Waites
1
Argumenty / kwargs powinny być łatwo osiągalne przy użyciu class_view_decorator(my_decorator(*args, **kwargs)). Jeśli chodzi o warunkowe dopasowywanie metod - możesz zmodyfikować class_view_decorator, aby zastosować się do View.getlub View.postzamiast View.dispatch.
mjtamlyn
14

Zdaję sobie sprawę, że ten wątek jest trochę przestarzały, ale i tak oto moje dwa centy.

z następującym kodem:

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

mamy teraz sposób na łatanie dekoratora, aby stał się wielofunkcyjny. W praktyce oznacza to, że po zastosowaniu do zwykłego dekoratora widoku, na przykład:

login_required = patch_view_decorator(login_required)

ten dekorator będzie nadal działał, gdy będzie używany zgodnie z pierwotnym przeznaczeniem:

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

ale będzie również działać poprawnie, gdy jest używany w taki sposób:

@login_required
class FooView(DetailView):
    model = Foo

Wydaje się, że działa to dobrze w kilku przypadkach, z którymi ostatnio się spotkałem, w tym na tym przykładzie z prawdziwego świata:

@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

Funkcja Ajax_view została napisana w celu zmodyfikowania widoku (opartego na funkcji), tak aby wywoływał błąd 404 za każdym razem, gdy ten widok jest odwiedzany przez wywołanie inne niż ajax. Po prostu stosując funkcję łatki jako dekorator, ten dekorator jest gotowy do pracy również w widokach opartych na klasach

mephisto
źródło
14

Dla tych z Was, którzy używają Django> = 1,9 , to już uwzględnione w django.contrib.auth.mixinsjak AccessMixin, LoginRequiredMixin, PermissionRequiredMixini UserPassesTestMixin.

Aby zastosować LoginRequired do CBV (np. 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'

Warto również pamiętać o kolejności GCBV Mixin: Mixiny muszą znajdować się po lewej stronie, a klasa widoku bazowego musi znajdować się po prawej stronie. Jeśli kolejność jest inna, możesz uzyskać zepsute i nieprzewidywalne wyniki.

vishes_shell
źródło
2
To najlepsza odpowiedź w 2019 roku. Świetna uwaga na temat kolejności miksowania.
Christian Long
5

Użyj nawiasów klamrowych Django. Zapewnia wiele użytecznych, łatwo dostępnych miksów. Ma piękne dokumenty. Wypróbuj to.

Możesz nawet tworzyć własne składanki.

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

Przykładowy kod:

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
źródło
4

Jeśli jest to witryna, w której większość stron wymaga zalogowania się użytkownika, możesz użyć oprogramowania pośredniego, aby wymusić logowanie we wszystkich widokach, z wyjątkiem tych, które są specjalnie oznaczone.

Oprogramowanie pośrednie w wersji Pre Django 1.10.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

Wyświetlenia stron trzecich, których nie chcesz zawijać, można wyłączyć w ustawieniach:

settings.py:

LOGIN_EXEMPT_URL_PREFIXES = ('/login/', '/reset_password/')
kaleissin
źródło
3

W moim kodzie napisałem ten adapter, aby dostosować funkcje składowe do funkcji niebędącej składową:

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

Możesz go po prostu użyć w ten sposób:

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')
królik.aaron
źródło
Byłoby fajnie, gdyby to było wbudowane w Django (tak jak method_decoratorjest). Wydaje się, że jest to miły i czytelny sposób na osiągnięcie tego.
MariusSiuram
1

Jest to bardzo łatwe z django> 1.9, które obsługuje PermissionRequiredMixiniLoginRequiredMixin

Po prostu zaimportuj z pliku auth

views.py

from django.contrib.auth.mixins import LoginRequiredMixin

class YourListView(LoginRequiredMixin, Views):
    pass

Aby uzyskać więcej informacji, przeczytaj Autoryzacja w django

Amar
źródło
1

Minęło trochę czasu, a teraz Django bardzo się zmieniło.

Sprawdź tutaj, jak ozdobić widok oparty na klasach.

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

Dokumentacja nie zawierała przykładu „dekoratorów, którzy przyjmują jakikolwiek argument”. Ale dekoratorzy, którzy przyjmują argumenty, wyglądają następująco:

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

więc jeśli mamy używać mydec jako „normalnego” dekoratora bez argumentów, możemy to zrobić:

mydecorator = mydec(10)

@mydecorator
def myfunc():
    return 5

Więc podobnie, aby użyć permission_required zmethod_decorator

możemy zrobić:

@method_decorator(permission_required("polls.can_vote"), name="dispatch")
class MyView:
    def get(self, request):
        # ...
królik.aaron
źródło
0

Jeśli robisz projekt, który wymaga różnych testów uprawnień, możesz dziedziczyć tę klasę.

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
źródło
0

Zrobiłem tę poprawkę w oparciu o rozwiązanie Josha

class LoginRequiredMixin(object):

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

Przykładowe użycie:

class EventsListView(LoginRequiredMixin, ListView):

    template_name = "events/list_events.html"
    model = Event
Ramast
źródło
0

Tutaj rozwiązanie dla Permissions_required decorator:

class CustomerDetailView(generics.GenericAPIView):

@method_decorator(permission_required('app_name.permission_codename', raise_exception=True))
    def post(self, request):
        # code...
        return True
Ashish Sondagar
źródło