Come usare i decoratori richiesti daorizzazione_delle viste basate sulla classe di django


161

Ho un po 'di problemi a capire come funzionano i nuovi CBV. La mia domanda è questa, ho bisogno di richiedere l'accesso in tutte le viste e in alcune di esse, autorizzazioni specifiche. Nelle viste basate su funzioni lo faccio con @permission_required () e l'attributo login_required nella vista, ma non so come farlo nelle nuove viste. C'è qualche sezione nei documenti di Django che spiega questo? Non ho trovato niente Cosa c'è di sbagliato nel mio codice?

Ho provato a usare @method_decorator ma risponde " TypeError at /pace / prueba / _wrapped_view () richiede almeno 1 argomento (0 dato) "

Ecco il codice (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

Risposte:


211

Ci sono alcune strategie elencate nei documenti CBV :

Decora la vista in base all'istanza, nel tuo urls.pyquando crei un'istanza della vista ( documenti )

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

Il decoratore viene applicato su una base per istanza, quindi puoi aggiungerlo o rimuoverlo in urls.pypercorsi diversi secondo necessità.

Decora la tua classe in modo che ogni istanza del tuo punto di vista sia racchiusa dal decoratore ( documenti )

Ci sono due modi per farlo:

  1. Applicando un method_decoratormetodo di spedizione CBV, ad es.

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

Se stai usando Django <1.9 (cosa che non dovresti, non è più supportato) non puoi usarlo method_decoratorsulla classe, quindi devi sovrascrivere il dispatchmetodo:

    class ViewSpaceIndex(TemplateView):

        @method_decorator(login_required)
        def dispatch(self, *args, **kwargs):
            return super(ViewSpaceIndex, self).dispatch(*args, **kwargs)
  1. Una pratica comune nel moderno Django (2.2+) è usare i mixin di accesso come django.contrib.auth.mixins.LoginRequiredMixin disponibile in Django 1.9+ e delineato bene nelle altre risposte qui:

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

Assicurati di inserire prima il Mixin nell'elenco delle ereditarietà (in modo che l'Ordine di risoluzione del metodo scelga la cosa giusta).

Il motivo per cui stai ricevendo un TypeErrorè spiegato nei documenti:

Nota: method_decorator passa * args e ** kwargs come parametri al metodo decorato sulla classe. Se il tuo metodo non accetta un set di parametri compatibile genererà un'eccezione TypeError.



come aggiungerlo message?
andilabs

Per coloro che non capivano (come ho fatto all'inizio) - il metodo 'dispatch' dovrebbe essere aggiunto alla classe
ViewSpaceIndex

C'è qualche motivo per favorire uno di questi metodi rispetto all'altro?
Alistair,

@Alistair Penso che si riduca alle preferenze personali e al mantenimento della coerenza della base di codice all'interno del tuo team / organizzazione. Personalmente tendo verso l'approccio mixin se sto costruendo viste basate sulla classe però.
A Lee il

118

Ecco il mio approccio, creo un mixin che è protetto (questo è conservato nella mia libreria di 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)

Ogni volta che si desidera proteggere una vista, è sufficiente aggiungere il mixin appropriato:

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

Assicurati solo che il tuo mixin sia il primo.

Aggiornamento: l' ho pubblicato nel lontano 2011, a partire dalla versione 1.9 Django ora include questo e altri utili mixin (AccessMixin, PermissionRequiredMixin, UserPassesTestMixin) come standard!


è possibile avere multipli di questo tipo di mixin? Non ha funzionato per me e non credo abbia senso che avrebbe.
Pykler,

Sì, dovrebbe essere possibile avere diversi mixin poiché ogni mixin fa una chiamata a super che sceglie la classe successiva in conformità con l'MRO
Hobblin,

Penso che questa sia una soluzione elegante; Non mi piace avere un mix di decoratori nel mio urls.py e mixin in views.py. Questo è un modo per avvolgere i decoratori che sposterebbero tutta quella logica alla vista.
Dhackner,

1
django-braces ha questo (e altro) mixin - un pacchetto molto utile da installare
askvictor

Solo una nota per le persone in modalità di ritardo completo come me: assicurati di non aver effettuato l'accesso durante il test della funzionalità login_required ...
Visgean Skeloru

46

Ecco un'alternativa usando decoratori di 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

Questo può quindi essere usato semplicemente in questo modo:

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

3
Puoi usarlo per decorare le decorazioni a catena, bene! +1
Pykler,

9
Questo è così grande che dovrebbe essere considerato per l'inclusione a monte dell'IMO.
Koniiiik,

Amo questo! Mi chiedo se è possibile passare args / kwargs dal class_view_decorator al function_decorator ??? Sarebbe bello se il login_decorator potesse dire condizionalmente corrispondere richiesta.METHOD quindi si applica solo per dire post?
Mike Waites,

1
Gli args / kwargs dovrebbero essere facilmente raggiungibili usando class_view_decorator(my_decorator(*args, **kwargs)). Per quanto riguarda la corrispondenza del metodo condizionale, è possibile modificare class_view_decorator per applicarsi View.getao View.postanziché a View.dispatch.
mjtamlyn,

14

Mi rendo conto che questa discussione è un po 'datata, ma ecco comunque i miei due centesimi.

con il seguente codice:

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

ora abbiamo un modo per rattoppare un decoratore, quindi diventerà multifunzionale. Ciò significa effettivamente che, se applicato a un decoratore di viste regolari, in questo modo:

login_required = patch_view_decorator(login_required)

questo decoratore funzionerà ancora quando usato nel modo in cui era originariamente previsto:

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

ma funzionerà anche correttamente se usato in questo modo:

@login_required
class FooView(DetailView):
    model = Foo

Questo sembra funzionare bene in diversi casi che mi sono imbattuto di recente, incluso questo esempio del mondo reale:

@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 funzione ajax_view è scritta per modificare una vista (basata sulla funzione), in modo che generi un errore 404 ogni volta che questa vista viene visitata da una chiamata non ajax. Applicando semplicemente la funzione patch come decoratore, questo decoratore è pronto per funzionare anche in viste basate su classe


14

Per quelli di voi che usano Django> = 1.9 , è già incluso in django.contrib.auth.mixinscome AccessMixin, LoginRequiredMixin, PermissionRequiredMixine UserPassesTestMixin.

Quindi per applicare LoginRequired to CBV (es. 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'

È anche bene tenere presente l'ordine dei mixin GCBV: i mixin devono andare sul lato sinistro e la classe della vista di base deve andare sul lato destro . Se l'ordine è diverso, puoi ottenere risultati non prevedibili e rotti.


2
Questa è la migliore risposta nel 2019. Inoltre, ottimo punto sull'ordine dei mixin.
Christian Long,

5

Usa le parentesi graffe Django. Fornisce molti utili mixin che sono facilmente disponibili. Ha bellissimi documenti. Provalo.

Puoi persino creare i tuoi mixin personalizzati.

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

Codice di esempio:

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 si tratta di un sito in cui la maggior parte delle pagine richiede che l'utente abbia effettuato l'accesso, è possibile utilizzare un middleware per forzare l'accesso su tutte le visualizzazioni ad eccezione di alcune particolarmente contrassegnate.

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

Le viste di terze parti che non vuoi avvolgere possono essere fatte eccezione nelle impostazioni:

settings.py:

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

3

Nel mio codice ho scritto questo adattatore per adattare le funzioni membro a una funzione non 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

Puoi semplicemente usarlo in questo modo:

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

Sarebbe bello che questo fosse un built-in su Django (proprio come lo method_decoratorè). Sembra un modo piacevole e leggibile per raggiungere questo obiettivo.
MariusSiuram,

1

Questo è super facile con django> 1.9 in arrivo con supporto per PermissionRequiredMixineLoginRequiredMixin

Importa solo dall'autorizzazione

views.py

from django.contrib.auth.mixins import LoginRequiredMixin

class YourListView(LoginRequiredMixin, Views):
    pass

Per maggiori dettagli leggi Autorizzazione in django


1

È passato un po 'di tempo e ora Django è cambiato molto.

Controlla qui per come decorare una vista di classe.

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

La documentazione non includeva un esempio di "decoratori che accetta qualsiasi argomento". Ma i decoratori che prendono argomenti sono così:

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

quindi se vogliamo usare mydec come decoratore "normale" senza argomenti, possiamo farlo:

mydecorator = mydec(10)

@mydecorator
def myfunc():
    return 5

Allo stesso modo, da usare permission_requiredconmethod_decorator

possiamo fare:

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

0

Se stai facendo un progetto che richiede una varietà di test di autorizzazione, puoi ereditare questa 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

Ho fatto quella correzione sulla base della soluzione di Josh

class LoginRequiredMixin(object):

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

Esempio di utilizzo:

class EventsListView(LoginRequiredMixin, ListView):

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

0

Ecco la soluzione per decoratore permesso_preceduto:

class CustomerDetailView(generics.GenericAPIView):

@method_decorator(permission_required('app_name.permission_codename', raise_exception=True))
    def post(self, request):
        # code...
        return True
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.