Как использовать allow_required декораторы в представлениях на основе классов django
У меня возникли проблемы с пониманием того, как работают новые CBV. Мой вопрос заключается в следующем: мне нужно требовать входа во все представления, а в некоторых из них - определенные разрешения. В функциональных представлениях я делаю это с помощью @permission_required() и атрибута login_required в представлении, но я не знаю, как это сделать в новых представлениях. Есть ли в документации по Django какой-то раздел, объясняющий это? Я ничего не нашел. Что не так в моем коде?
Я пытался использовать @method_decorator, но он отвечает: "Ошибка TypeError в /space /prueba/ _wrapped_view() принимает как минимум 1 аргумент (0 задано)"
Вот код (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
11 ответов
Есть несколько стратегий, перечисленных в документах CBV:
Добавьте декоратор в свой
urls.py
маршрут, например,login_required(ViewSpaceIndex.as_view(..))
Украсьте свой CBV
dispatch
метод сmethod_decorator
например,from django.utils.decorators import method_decorator @method_decorator(login_required, name='dispatch') class ViewSpaceIndex(TemplateView): template_name = 'secret.html'
До Django 1.9 вы не можете использовать
method_decorator
в классе, так что вы должны переопределитьdispatch
метод:class ViewSpaceIndex(TemplateView): @method_decorator(login_required) def dispatch(self, *args, **kwargs): return super(ViewSpaceIndex, self).dispatch(*args, **kwargs)
Используйте миксины доступа, такие как django.contrib.auth.mixins.LoginRequiredMixin, доступные в Django 1.9+ и хорошо изложенные в других ответах здесь:
from django.contrib.auth.mixins import LoginRequiredMixin class MyView(LoginRequiredMixin, View): login_url = '/login/' redirect_field_name = 'redirect_to'
Причина, по которой вы получаете TypeError
объясняется в документах:
Примечание: method_decorator передает *args и **kwargs в качестве параметров оформленному методу класса. Если ваш метод не принимает совместимый набор параметров, он вызовет исключение TypeError.
Вот мой подход: я создаю миксины, которые защищены (они хранятся в моей библиотеке миксов):
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)
Когда вы хотите, чтобы представление было защищено, просто добавьте соответствующий миксин:
class SomeProtectedViewView(LoginRequiredMixin, TemplateView):
template_name = 'index.html'
Просто убедитесь, что ваш миксин первый.
Обновление: я опубликовал это в далеком 2011 году, начиная с версии 1.9 Django теперь включает этот и другие полезные миксины (AccessMixin, PermissionRequiredMixin, UserPassesTestMixin) в стандартной комплектации!
Вот альтернатива с использованием декораторов на основе классов:
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
Это может быть использовано просто так:
@class_view_decorator(login_required)
class MyView(View):
# this view now decorated
Для тех из вас, кто использует Django> = 1.9, он уже включен в django.contrib.auth.mixins
как AccessMixin
, LoginRequiredMixin
, PermissionRequiredMixin
а также UserPassesTestMixin
,
Таким образом, чтобы применить LoginRequired к CBV(например, 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'
Также хорошо иметь в виду порядок микширования GCBV: миксины должны идти слева, а класс базового вида - справа. Если порядок отличается, вы можете получить сломанные и непредсказуемые результаты.
Я понимаю, что эта тема немного устарела, но в любом случае вот мои два цента.
со следующим кодом:
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
Теперь у нас есть способ исправления декоратора, чтобы он стал многофункциональным. Это фактически означает, что при применении к обычному представлению декоратора, например так:
login_required = patch_view_decorator(login_required)
этот декоратор все равно будет работать, если он использовался так, как он был изначально предназначен:
@login_required
def foo(request):
return HttpResponse('bar')
но также будет работать правильно, когда используется так:
@login_required
class FooView(DetailView):
model = Foo
Похоже, что это хорошо работает в нескольких случаях, с которыми я недавно сталкивался, включая пример из реальной жизни:
@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
Функция ajax_view написана для изменения представления (на основе функций), так что оно вызывает ошибку 404 всякий раз, когда это представление посещается неадресным вызовом. Просто применяя функцию patch в качестве декоратора, этот декоратор также может работать в представлениях на основе классов.
Use Django Braces. It provides a lot of useful mixins that is easily available. It has beautiful docs. Попробуйте это.
You can even create your custom mixins.
http://django-braces.readthedocs.org/en/v1.4.0/
Example 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({})
Если это сайт, на котором большинство страниц требует, чтобы пользователь вошел в систему, вы можете использовать промежуточное ПО для принудительного входа в систему для всех представлений, кроме тех, которые особо отмечены.
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
Сторонние представления, которые вы не хотите переносить, могут быть исключены в настройках:
settings.py:
LOGIN_EXEMPT_URL_PREFIXES = ('/login/', '/reset_password/')
В моем коде я написал этот адаптер для адаптации функций-членов к функции, не являющейся членом:
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
Вы можете просто использовать это так:
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')
Прошло некоторое время, и теперь Django так сильно изменился.
Узнайте здесь, как украсить представление на основе классов.
https://docs.djangoproject.com/en/2.2/topics/class-based-views/intro/
В документации не было примеров "декораторов, принимающих любой аргумент". Но декораторы, принимающие аргументы, выглядят так:
def mydec(arg1):
def decorator(func):
def decorated(*args, **kwargs):
return func(*args, **kwargs) + arg1
return decorated
return deocrator
поэтому, если мы хотим использовать mydec как "обычный" декоратор без аргументов, мы можем сделать это:
mydecorator = mydec(10)
@mydecorator
def myfunc():
return 5
Аналогично, чтобы использовать permission_required
с method_decorator
Мы сможем:
@method_decorator(permission_required("polls.can_vote"), name="dispatch")
class MyView:
def get(self, request):
# ...
Это очень легко с django > 1.9 и поддержкой PermissionRequiredMixin
а также LoginRequiredMixin
Просто импортируй из аутентификации
views.py
from django.contrib.auth.mixins import LoginRequiredMixin
class YourListView(LoginRequiredMixin, Views):
pass
Для более подробной информации читайте Авторизация в Django
Я сделал это на основе решения Джоша
class LoginRequiredMixin(object):
@method_decorator(login_required)
def dispatch(self, *args, **kwargs):
return super(LoginRequiredMixin, self).dispatch(*args, **kwargs)
Пример использования:
class EventsListView(LoginRequiredMixin, ListView):
template_name = "events/list_events.html"
model = Event
Вот решение для декоратора permission_required:
class CustomerDetailView(generics.GenericAPIView):
@method_decorator(permission_required('app_name.permission_codename', raise_exception=True))
def post(self, request):
# code...
return True
Если вы делаете проект, который требует различных тестов разрешений, вы можете унаследовать этот класс.
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)