Comment utiliser permission_required décorateurs sur django la classe de base des points de vue
Question
Je vais avoir un peu de mal à comprendre comment le nouveau CBVs travail.Ma question est la suivante, j'ai besoin d'exiger de connexion dans tous les points de vue, et dans certains d'entre eux, des autorisations spécifiques.En fonction des vues basées sur les je le fais avec @permission_required() et le login_required attribut de la vue, mais je ne sais pas comment le faire sur le nouveau point de vue.Est-il un article dans le django docs expliquer cela?Je n'ai pas trouvé quoi que ce soit.Quel est le problème dans mon code?
J'ai essayé d'utiliser le @method_decorator mais elle rétorque "TypeError à /espaces/prueba/ _wrapped_view() prend au moins 1 argument (0)"
Voici le 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
La solution
Il ya quelques stratégies énumérées dans la CBV docs:
Décorer la vue par exemple, dans votre urls.py
lorsque vous instanciez votre point de vue (docs)
urlpatterns = [
path('view/',login_required(ViewSpaceIndex.as_view(..)),
...
]
Le décorateur est appliquée par exemple de base, de sorte que vous pouvez ajouter ou supprimer des différentes urls.py
les itinéraires en tant que de besoin.
Décorer votre classe, de sorte que chaque instance de votre point de vue sera enveloppé par le décorateur (docs)
Il y a deux façons dont vous pouvez faire ceci:
L'application d'un
method_decorator
pour votre CBV méthode dispatch par exemple,from django.utils.decorators import method_decorator @method_decorator(login_required, name='dispatch') class ViewSpaceIndex(TemplateView): template_name = 'secret.html'
Si vous êtes à l'aide de Django < 1.9 (qui ne convient pas, il n'est plus pris en charge), vous ne pouvez pas utiliser method_decorator
sur la classe, de sorte que vous devez remplacer la dispatch
méthode:
class ViewSpaceIndex(TemplateView):
@method_decorator(login_required)
def dispatch(self, *args, **kwargs):
return super(ViewSpaceIndex, self).dispatch(*args, **kwargs)
Une pratique courante dans moderne Django (2.2+) est d'utiliser l'accès mixin comme django.contrib.auth.mixin.LoginRequiredMixin disponible dans Django de 1,9+ et décrit bien dans les autres réponses ici:
from django.contrib.auth.mixins import LoginRequiredMixin class MyView(LoginRequiredMixin, View): login_url = '/login/' redirect_field_name = 'redirect_to'
Assurez-vous de mettre le Mixin en premier dans la liste d'héritage (donc la Méthode de Résolution de l'Ordre choisit la bonne Chose).
La raison pour laquelle vous êtes l'obtention d'un TypeError
est expliqué dans la doc:
Note:method_decorator passe *args et **kwargs comme paramètres à la décorées méthode de la classe.Si votre méthode n'accepte pas compatible d'un ensemble de paramètres qu'il va lever une exception TypeError.
Autres conseils
Voici mon approche, je crée un mixin protégé (ceci est conservé dans ma bibliothèque 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)
Chaque fois que vous voulez que la vue soit protégée, vous devez simplement ajouter le mixin approprié:
class SomeProtectedViewView(LoginRequiredMixin, TemplateView):
template_name = 'index.html'
Assurez-vous que votre mixin est le premier.
Mise à jour: J'ai posté cette question de retour en 2011, en commençant par la version 1.9 Django inclut maintenant ceci et d'autres mélanges utiles (AccessMixin, PermissionRequiredMixin, userPassestestMixin) en standard!
Voici une alternative utilisant des décorateurs de 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
Ceci peut ensuite être utilisé simplement comme ceci:
@class_view_decorator(login_required)
class MyView(View):
# this view now decorated
Je réalise que ce fil est un peu daté, mais voici mes deux cents de toute façon.
avec le code suivant:
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
Nous avons maintenant un moyen de corriger un décorateur, il deviendra donc multifonctionnel.Cela signifie effectivement que lorsqu'il est appliqué à un décorateur de vue régulier, comme:
login_required = patch_view_decorator(login_required)
Ce décorateur fonctionnera toujours lorsqu'il sera utilisé comme il était initialement destiné à:
@login_required
def foo(request):
return HttpResponse('bar')
mais fonctionnera également correctement lorsqu'il est utilisé comme:
@login_required
class FooView(DetailView):
model = Foo
Cela semble fonctionner bien dans plusieurs cas que j'ai récemment rencontré, y compris cet exemple du monde réel:
@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 fonction AJAX_View est écrite pour modifier une vue (fonction basée sur la fonction), de sorte qu'elle augmente une erreur 404 chaque fois que cette vue est visitée par un appel non Ajax.En appliquant simplement la fonction de correctif sous forme de décorateur, ce décorateur est entièrement défini pour fonctionner en vues basées sur la classe aussi bien
Pour ceux d'entre vous qui les utilisent Django >= 1.9, il est déjà inclus dans django.contrib.auth.mixins
comme AccessMixin
, LoginRequiredMixin
, PermissionRequiredMixin
et UserPassesTestMixin
.
Donc, pour appliquer LoginRequired de CBV(par ex. 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'
Il est également bon de garder à l'esprit GCBV Mixin ordre: Mixin faut aller sur le de gauche côté, et de l' vue de la base la classe doit aller dans le droit côté.Si l'ordre est différent, vous pouvez obtenir cassé et des résultats imprévisibles.
Utilisez des accolades Django.Il fournit de nombreux mélanges utiles facilement disponibles. Il a de belles documents.Essaye le.
Vous pouvez même créer vos mixines personnalisées.
http://django-braces.readthedocs.org/fr/v1.4.0/
Exemple de code:
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({})
Si c'est un site où la majorité des pages nécessite l'enregistrement de l'utilisateur, vous pouvez utiliser un middleware pour forcer la connexion sur toutes les vues sauf les certains qui sont particulièrement marqués.
Pré-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)
vues.py:
def public(request, *args, **kwargs):
...
public.login_required = False
class PublicView(View):
...
public_view = PublicView.as_view()
public_view.login_required = False
Vues tierces que vous ne voulez pas envelopper peut être fabriquée dans les réglages:
paramètres.py:
LOGIN_EXEMPT_URL_PREFIXES = ('/login/', '/reset_password/')
Dans mon code, j'ai écrit cet adaptateur pour adapter les fonctions des membres à une fonction non membre:
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
Vous pouvez simplement l'utiliser comme ceci:
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')
C'est super facile avec django > 1.9 à venir, avec le soutien de PermissionRequiredMixin
et LoginRequiredMixin
Il suffit d'importer de l'auth
views.py
from django.contrib.auth.mixins import LoginRequiredMixin
class YourListView(LoginRequiredMixin, Views):
pass
Pour plus de détails, lire Autorisation dans django
Il a été un certain temps maintenant, et maintenant, Django a beaucoup changé.
Vérifiez ici pour savoir comment décorer une classe de base point de vue.
https://docs.djangoproject.com/en/2.2/topics/class-based-views/intro/#decorating-the-class
La documentation ne soit pas un exemple de "décorateurs qui prend un argument".Mais décorateurs prendre des arguments sont comme ceci:
def mydec(arg1):
def decorator(func):
def decorated(*args, **kwargs):
return func(*args, **kwargs) + arg1
return decorated
return deocrator
donc, si nous sommes à l'utilisation mydec comme "normale" décorateur, sans arguments, nous pouvons faire ceci:
mydecorator = mydec(10)
@mydecorator
def myfunc():
return 5
Donc de la même façon, à utiliser permission_required
avec method_decorator
nous pouvons faire:
@method_decorator(permission_required("polls.can_vote"), name="dispatch")
class MyView:
def get(self, request):
# ...
Si vous faites un projet qui nécessite une variété de tests d'autorisation, vous pouvez hériter de cette 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)
J'ai fait ce correctif basé sur la solution de Josh
class LoginRequiredMixin(object):
@method_decorator(login_required)
def dispatch(self, *args, **kwargs):
return super(LoginRequiredMixin, self).dispatch(*args, **kwargs)
Utilisation des échantillons:
class EventsListView(LoginRequiredMixin, ListView):
template_name = "events/list_events.html"
model = Event
Ici la solution pour la permission_Required décorateur:
class CustomerDetailView(generics.GenericAPIView):
@method_decorator(permission_required('app_name.permission_codename', raise_exception=True))
def post(self, request):
# code...
return True