Cómo usar el permiso de decoradores requeridos en vistas basadas en clases de django

Me está costando un poco entender cómo funcionan los nuevos CBV. Mi pregunta es la siguiente: necesito solicitar el inicio de sesión en todas las vistas y, en algunas, permisos específicos. En las vistas basadas en funciones, lo hago con @permission_required () y el atributo login_required en la vista, pero no sé cómo hacerlo 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 

Hay algunas estrategias enumeradas en los documentos CBV :

  1. Agregue el decorador en su ruta urls.py , por ejemplo, login_required(ViewSpaceIndex.as_view(..))

  2. Decore el método de dispatch su CBV con un method_decorator , por ejemplo,

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

    Antes de Django 1.9 no puedes usar method_decorator en la clase, por lo que debes anular el método de dispatch :

     class ViewSpaceIndex(TemplateView): @method_decorator(login_required) def dispatch(self, *args, **kwargs): return super(ViewSpaceIndex, self).dispatch(*args, **kwargs) 
  3. Use los mixins de acceso como django.contrib.auth.mixins.LoginRequiredMixin disponibles en Django 1.9+ y bien descritos en las otras respuestas aquí:

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

El motivo por el que obtiene un TypeError se 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.

Aquí está mi enfoque, creo un mixin que está protegido (esto se guarda en mi biblioteca de 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 proteger una vista, simplemente agregue la mezcla adecuada:

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

Solo asegúrate de que tu mezcla esté primero.

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

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 puede usarse simplemente así:

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

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

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 manera de parchar un decorador, por lo que se convertirá en multifuncional. Esto efectivamente significa que cuando se aplica a un decorador de vista normal, de esta manera:

 login_required = patch_view_decorator(login_required) 

este decorador seguirá funcionando cuando se use de la manera en que fue originalmente diseñado:

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

pero también funcionará correctamente cuando se use 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 está escrita para modificar una vista (basada en función), de modo que genera un error 404 siempre que esta visita sea visitada por una llamada que no sea ajax. Simplemente aplicando la función de parche como decorador, este decorador está listo para funcionar en vistas basadas en clase también

Para aquellos de ustedes que usan Django> = 1.9 , ya está incluido en django.contrib.auth.mixins como AccessMixin , LoginRequiredMixin , PermissionRequiredMixin y 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 Mixin de GCBV: Mixins debe ir del lado izquierdo , y la clase de vista base debe ir en el lado derecho . Si el orden es diferente, puede obtener resultados rotos e impredecibles.

Si se trata de un sitio en el que 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 especialmente marcadas.

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 desee envolver se pueden exceder en la configuración:

settings.py:

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

Usa los frenos de Django. Proporciona muchas mezclas ú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({}) 

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): #  return HttpResponse('result') 

Esto es muy fácil con django> 1.9 que viene con soporte para PermissionRequiredMixin y LoginRequiredMixin

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

Si está realizando un proyecto que requiere una variedad de pruebas de permiso, 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) 

He hecho esa correcció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 muestra:

 class EventsListView(LoginRequiredMixin, ListView): template_name = "events/list_events.html" model = Event 
Intereting Posts