#django #django-views #django-authentication #django-class-based-views
#django #django-views #django-аутентификация #представления на основе классов django
Вопрос:
У меня возникли небольшие проблемы с пониманием того, как работают новые CBV. Мой вопрос заключается в следующем: мне нужно требовать входа во все представления, а в некоторых из них — определенных разрешений. В представлениях на основе функций я делаю это с помощью @permission_required() и атрибута login_required в представлении, но я не знаю, как это сделать в новых представлениях. Есть ли какой-нибудь раздел в документах django, объясняющий это? Я ничего не нашел. Что не так в моем коде?
Я попытался использовать @method_decorator, но он отвечает «Ошибка типа в /spaces/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
Ответ №1:
Есть несколько стратегий, перечисленных в документах CBV:
Украсьте представление, когда вы создаете его экземпляр в своих urls.py
(документах)
from django.contrib.auth.decorators import login_required
urlpatterns = [
path('view/',login_required(ViewSpaceIndex.as_view(..)),
...
]
Декоратор применяется для каждого экземпляра, поэтому вы можете добавлять его или удалять разными urls.py
маршрутами по мере необходимости.
Украсьте свой класс так, чтобы каждый экземпляр вашего представления был обернут (docs)
Это можно сделать двумя способами:
-
Примените
method_decorator
к вашему методу отправки CBV, например,from django.utils.decorators import method_decorator from django.contrib.auth.decorators import login_required @method_decorator(login_required, name='dispatch') class ViewSpaceIndex(TemplateView): template_name = 'secret.html'
Если вы используете Django < 1.9 (чего делать не следует, он больше не поддерживается), вы не можете использовать method_decorator
в классе, поэтому вам придется переопределить dispatch
метод вручную:
from django.contrib.auth.decorators import login_required
class ViewSpaceIndex(TemplateView):
@method_decorator(login_required)
def dispatch(self, *args, **kwargs):
return super(ViewSpaceIndex, self).dispatch(*args, **kwargs)
-
Используйте смесь, подобную django.contrib.auth.mixins.LoginRequiredMixin хорошо описан в других ответах здесь:
from django.contrib.auth.mixins import LoginRequiredMixin class MyView(LoginRequiredMixin, View): login_url = '/login/' redirect_field_name = 'redirect_to'
Убедитесь, что вы разместили класс mixin первым в списке наследования (чтобы алгоритм порядка разрешенияметода Python выбрал правильную вещь).
Причина, по которой вы получаете TypeError
, объясняется в документах:
Примечание: method_decorator передает *args и ** kwargs в качестве параметров к оформленному методу в классе. Если ваш метод не принимает совместимый набор параметров, это вызовет исключение TypeError.
Комментарии:
1. Упоминается здесь в последних документах docs.djangoproject.com/en/dev/topics/class-based-views/intro
2. как добавить
message
к нему?3. Для тех, кто не понял (как и я сначала) — метод ‘dispatch’ должен быть добавлен в класс ViewSpaceIndex
4. Есть ли какая-либо причина отдавать предпочтение одному из этих методов перед другим?
5. @Alistair Я думаю, что это сводится к личным предпочтениям и поддержанию согласованности кодовой базы в вашей команде / организации. Я лично склоняюсь к смешанному подходу, если я создаю представления на основе классов.
Ответ №2:
Вот мой подход, я создаю защищенный mixin (он хранится в моей библиотеке 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)
Всякий раз, когда вы хотите, чтобы представление было защищено, вы просто добавляете соответствующий микс:
class SomeProtectedViewView(LoginRequiredMixin, TemplateView):
template_name = 'index.html'
Просто убедитесь, что ваш микс является первым.
Обновление: Я опубликовал это в way еще в 2011 году, начиная с версии 1.9, теперь Django включает этот и другие полезные микшины (AccessMixin, PermissionRequiredMixin, UserPassesTestMixin) в стандартную комплектацию!
Комментарии:
1. возможно ли иметь несколько таких миксинов? У меня это не сработало, и я не думаю, что это имеет смысл.
2. Да, должно быть возможно иметь несколько миксинов, поскольку каждый миксин выполняет вызов super, который выбирает следующий класс в соответствии с MRO
3. Я думаю, что это элегантное решение; Мне не нравится иметь смесь декораторов в моем urls.py и микширует в views.py. Это способ обернуть декораторы, которые перенесут всю эту логику в представление.
4. в django-braces есть этот (и не только) mixins — очень полезный пакет для установки
5. Просто замечание для людей в режиме полной задержки, таких как я: убедитесь, что вы не вошли в систему при тестировании функциональности login_required…
Ответ №3:
Вот альтернатива использованию декораторов на основе классов:
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
Комментарии:
1. Вы можете использовать это для создания цепочки декораторов представлений, красиво! 1
2. Это настолько здорово, что его следует рассмотреть для включения в восходящий IMO.
3. Мне это нравится! Мне интересно, возможно ли вообще передать аргументы / kwargs из class_view_decorator в function_decorator??? Было бы здорово, если бы login_decorator мог указать запрос на условное соответствие. МЕТОД, поэтому он применяется только, скажем, к post?
4. Аргументы / kwargs должны быть легко достижимы с помощью
class_view_decorator(my_decorator(*args, **kwargs))
. Что касается сопоставления условных методов — вы могли бы изменить class_view_decorator, чтобы он применялся кView.get
илиView.post
вместоView.dispatch
.
Ответ №4:
Для тех из вас, кто использует Django >= 1.9, это уже включено в django.contrib.auth.mixins
as 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: микшины должны располагаться с левой стороны, а класс базового представления должен располагаться с правой стороны. Если порядок отличается, вы можете получить сломанные и непредсказуемые результаты.
Комментарии:
1. Это лучший ответ в 2019 году. Кроме того, отличный момент о порядке смешивания.
Ответ №5:
Я понимаю, что эта тема немного устарела, но в любом случае вот мои два цента.
со следующим кодом:
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 всякий раз, когда это представление посещается не ajax-вызовом. Простым применением функции patch в качестве декоратора этот декоратор полностью настроен для работы и в представлениях на основе классов
Ответ №6:
Используйте фигурные скобки Django. Он предоставляет множество полезных миксинов, которые легко доступны. В нем есть прекрасные документы. Попробуйте.
Вы даже можете создавать свои собственные микшины.
http://django-braces.readthedocs.org/en/v1.4.0/
Пример кода:
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({})
Ответ №7:
В моем коде я написал этот адаптер для адаптации функций-членов к функции, не являющейся членом:
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')
Комментарии:
1. Было бы неплохо, чтобы это было встроено в Django (точно так же, как
method_decorator
есть). Это кажется приятным и читаемым способом достижения этого.
Ответ №8:
Если это сайт, на большинстве страниц которого требуется, чтобы пользователь вошел в систему, вы можете использовать промежуточное программное обеспечение для принудительного входа во все представления, за исключением тех, которые отмечены особо.
До 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/')
Ответ №9:
Прошло уже некоторое время, и теперь Django так сильно изменился.
Посмотрите здесь, как оформить представление на основе классов.
https://docs.djangoproject.com/en/2.2/topics/class-based-views/intro/#decorating-the-class
В документации не было примера «декораторов, которые принимают любой аргумент». Но декораторы, которые принимают аргументы, выглядят следующим образом:
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):
# ...
Комментарии:
1. Я думаю, что это самый простой способ получить разрешения в базовых представлениях класса, проверяя другие разрешения, которые являются классическими
@login_required
… Также вы можете передать более одного пользовательского разрешения следующим образом@permission_required(['polls.can_vote', 'polls.change_vote'])
.
Ответ №10:
Я внес это исправление на основе решения Джоша
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
Ответ №11:
Это очень просто, поскольку django > 1.9 поставляется с поддержкой PermissionRequiredMixin
и LoginRequiredMixin
Просто импортируйте из auth
views.py
from django.contrib.auth.mixins import LoginRequiredMixin
class YourListView(LoginRequiredMixin, Views):
pass
Для получения более подробной информации прочитайте Авторизация в django
Ответ №12:
Если вы выполняете проект, для которого требуется множество тестов разрешений, вы можете наследовать этот класс.
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)
Ответ №13:
Вот решение для декоратора permission_required:
class CustomerDetailView(generics.GenericAPIView):
@method_decorator(permission_required('app_name.permission_codename', raise_exception=True))
def post(self, request):
# code...
return True