Cómo usar decoradores con permiso requerido en vistas basadas en clases de django

161

Tengo algunos problemas para entender cómo funcionan los nuevos CBV. Mi pregunta es esta, necesito solicitar el inicio de sesión en todas las vistas, y en algunas de ellas, permisos específicos. En las vistas basadas en funciones, hago eso con @permission_required () y el atributo login_required en la vista, pero no sé cómo hacer esto en las nuevas vistas. ¿Hay alguna sección en los documentos de django que explique esto? No encontré nada. ¿Qué esta mal en mi codigo?

Traté de usar el @method_decorator pero responde " TypeError at / spaces / prueba / _wrapped_view () toma al menos 1 argumento (0 dado) "

Aquí está el código (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
fuente

Respuestas:

211

Hay algunas estrategias enumeradas en los documentos de CBV :

Decora la vista por instancia, en tu urls.pycuando instancias tu vista ( docs )

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

El decorador se aplica por instancia, por lo que puede agregarlo o eliminarlo en diferentes urls.pyrutas según sea necesario.

Decora tu clase para que cada instancia de tu vista sea envuelta por el decorador ( docs )

Hay dos maneras de hacer esto:

  1. Aplicando method_decoratora su método de despacho de CBV, por ejemplo,

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

Si está usando Django <1.9 (que no debería, ya no es compatible) no puede usarlo method_decoratoren la clase, por lo que debe anular el dispatchmétodo:

    class ViewSpaceIndex(TemplateView):

        @method_decorator(login_required)
        def dispatch(self, *args, **kwargs):
            return super(ViewSpaceIndex, self).dispatch(*args, **kwargs)
  1. Una práctica común en Django moderno (2.2+) es usar mixins de acceso como django.contrib.auth.mixins.LoginRequiredMixin disponible en Django 1.9+ y se describe bien en las otras respuestas aquí:

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

Asegúrate de poner el Mixin primero en la lista de herencia (para que la Orden de resolución del método elija lo correcto).

La razón por la que obtienes un mensaje TypeErrorse explica en los documentos:

Nota: method_decorator pasa * args y ** kwargs como parámetros al método decorado en la clase. Si su método no acepta un conjunto de parámetros compatible, generará una excepción TypeError.

Un lee
fuente
3
Mencionado aquí en los últimos documentos docs.djangoproject.com/en/dev/topics/class-based-views/intro
Bharathwaaj
¿Cómo anexarlo message?
andilabs
Para aquellos que no entendieron (como yo lo hice, al principio), el método de 'despacho' debería agregarse a la clase
ViewSpaceIndex
¿Hay alguna razón para favorecer uno de estos métodos sobre el otro?
Alistair
@Alistair Creo que se reduce a preferencias personales y a mantener la coherencia de la base de código dentro de su equipo / organización. Sin embargo, personalmente tiendo al enfoque mixin si estoy construyendo vistas basadas en la clase.
A Lee
118

Aquí está mi enfoque, creo un mixin que está protegido (esto se mantiene en mi biblioteca 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)

Siempre que desee que se proteja una vista, simplemente agregue el mixin apropiado:

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

Solo asegúrate de que tu mezcla sea la primera.

Actualización: publiqué esto en 2011, comenzando con la versión 1.9. Django ahora incluye este y otros mixins útiles (AccessMixin, PermissionRequiredMixin, UserPassesTestMixin) como estándar.

Gert Steyn
fuente
¿Es posible tener múltiples de este tipo de mixins? No funcionó para mí y no creo que tenga sentido que lo hubiera hecho.
Pykler
Sí, debería ser posible tener varios mixins ya que cada mixin hace un llamado a super que elige la próxima clase de acuerdo con el MRO
Hobblin
Creo que esta es una solución elegante; No me gusta tener una mezcla de decoradores en mi urls.py y mixins en views.py. Esta es una forma de envolver decoradores que moverían toda esa lógica a la vista.
dhackner
1
django-braces tiene este (y más) mixins - un paquete muy útil para instalar
askvictor
Solo una nota para las personas en modo de retardo completo como yo: asegúrese de no haber iniciado sesión al probar la funcionalidad login_required ...
Visgean Skeloru
46

Aquí hay una alternativa usando decoradores basados ​​en clases:

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

Esto se puede usar simplemente así:

@class_view_decorator(login_required)
class MyView(View):
    # this view now decorated
mjtamlyn
fuente
3
¡Puedes usar esto para encadenar decoradores de vistas, muy bien! +1
Pykler
9
Esto es tan bueno que debería considerarse para su inclusión aguas arriba de la OMI.
koniiiik
¡Me encanta esto! ¿Me pregunto si es posible pasar args / kwargs desde el class_view_decorator al function_decorator ??? Sería genial si login_decorator pudiera decir condicionalmente request.METHOD, por lo que solo se aplica a dicha publicación.
Mike Waites
1
Los args / kwargs deben ser fácilmente alcanzables mediante el uso class_view_decorator(my_decorator(*args, **kwargs)). En cuanto a la coincidencia de método condicional, puede modificar el class_view_decorator para aplicarlo View.geto en View.postlugar de hacerlo View.dispatch.
mjtamlyn
14

Me doy cuenta de que este hilo está un poco anticuado, pero de todos modos aquí están mis dos centavos.

con el siguiente código:

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

ahora tenemos una forma de parchear un decorador, por lo que será multifuncional. Esto significa efectivamente que cuando se aplica a un decorador de vista normal, así:

login_required = patch_view_decorator(login_required)

este decorador seguirá funcionando cuando se use de la manera original:

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

pero también funcionará correctamente cuando se usa así:

@login_required
class FooView(DetailView):
    model = Foo

Esto parece funcionar bien en varios casos que he encontrado recientemente, incluido este ejemplo del mundo real:

@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 función ajax_view se escribe para modificar una vista (basada en la función), de modo que genera un error 404 cada vez que una llamada no ajax visita esta vista. Simplemente aplicando la función de parche como decorador, este decorador está configurado para funcionar también en vistas basadas en clase

mephisto
fuente
14

Para aquellos de ustedes que usan Django> = 1.9 , ya está incluido en django.contrib.auth.mixinscomo AccessMixin, LoginRequiredMixin, PermissionRequiredMixiny UserPassesTestMixin.

Entonces, para aplicar LoginRequired a CBV (por ejemplo 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'

También es bueno tener en cuenta el orden GCBV Mixin: los mixins deben ir del lado izquierdo y la clase de vista base debe ir del lado derecho . Si el orden es diferente, puede obtener resultados rotos e impredecibles.

vishes_shell
fuente
2
Esta es la mejor respuesta en 2019. Además, un gran punto sobre el orden de mezcla.
Christian Long
5

Usa llaves Django. Proporciona muchos mixins útiles que están fácilmente disponibles. Tiene hermosos documentos. Pruébalo.

Incluso puedes crear tus mixins personalizados.

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

Código de ejemplo:

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
fuente
4

Si es un sitio donde la mayoría de las páginas requieren que el usuario inicie sesión, puede usar un middleware para forzar el inicio de sesión en todas las vistas, excepto en algunas que están especialmente marcadas.

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

Las vistas de terceros que no desea ajustar pueden sobresalir en la configuración:

settings.py:

LOGIN_EXEMPT_URL_PREFIXES = ('/login/', '/reset_password/')
kaleissin
fuente
3

En mi código, he escrito este adaptador para adaptar funciones miembro a una función no miembro:

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

Simplemente puede usarlo así:

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')
conejo.aaron
fuente
Sería bueno que esto estuviera integrado en Django (tal como method_decoratorestá). Parece una forma agradable y legible de lograr esto.
MariusSiuram
1

Esto es súper fácil con django> 1.9 que viene con soporte para PermissionRequiredMixinyLoginRequiredMixin

Solo importa desde la autenticación

views.py

from django.contrib.auth.mixins import LoginRequiredMixin

class YourListView(LoginRequiredMixin, Views):
    pass

Para más detalles lea Autorización en django

Amar
fuente
1

Ha pasado un tiempo y ahora Django ha cambiado mucho.

Consulte aquí cómo decorar una vista basada en clases.

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

La documentación no incluía un ejemplo de "decoradores que toman cualquier argumento". Pero los decoradores que toman argumentos son así:

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

así que si vamos a usar mydec como decorador "normal" sin argumentos, podemos hacer esto:

mydecorator = mydec(10)

@mydecorator
def myfunc():
    return 5

De manera similar, para usar permission_requiredconmethod_decorator

podemos hacer:

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

Si está realizando un proyecto que requiere una variedad de pruebas de permisos, puede heredar esta clase.

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
fuente
0

He hecho esa solución basada en la solución de Josh

class LoginRequiredMixin(object):

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

Uso de la muestra:

class EventsListView(LoginRequiredMixin, ListView):

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

Aquí la solución para el decorador de permiso requerido:

class CustomerDetailView(generics.GenericAPIView):

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