我在理解新的 CBV 的工作原理时遇到了一些麻烦。我的问题是,我需要在所有视图中登录,其中一些视图需要特定权限。在基于函数的视图中,我使用 @permission_required() 和视图中的 login_required 属性来执行此操作,但我不知道如何在新视图上执行此操作。 django 文档中是否有一些部分对此进行了解释?我什么也没找到。我的代码有什么问题?
我尝试使用@method_decorator,但它回复“TypeError at /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
the CBV docs 中列出了一些策略:
在 urls.py 中实例化视图时装饰视图(文档)
urlpatterns = [
path('view/',login_required(ViewSpaceIndex.as_view(..)),
...
]
装饰器是按实例应用的,因此您可以根据需要在不同的 urls.py
路由中添加或删除它。
装饰您的类,以便包装视图的每个实例(文档)
有两种方法可以做到这一点:
将 method_decorator 应用于您的 CBV 调度方法,例如,从 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
方法:
class ViewSpaceIndex(TemplateView):
@method_decorator(login_required)
def dispatch(self, *args, **kwargs):
return super(ViewSpaceIndex, self).dispatch(*args, **kwargs)
使用像 django.contrib.auth.mixins.LoginRequiredMixin 这样的 mixin 在此处的其他答案中很好地概述: from django.contrib.auth.mixins import LoginRequiredMixin class MyView(LoginRequiredMixin, View): login_url = '/login/' redirect_field_name = 'redirect_to '
确保将 mixin 类放在继承列表的首位(因此 Python 的 Method Resolution Order algorithm 选择了正确的东西)。
文档中解释了您获得 TypeError
的原因:
注意:method_decorator 将 *args 和 **kwargs 作为参数传递给类上的装饰方法。如果您的方法不接受一组兼容的参数,它将引发 TypeError 异常。
这是我的方法,我创建了一个受保护的 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)
每当您想要保护视图时,只需添加适当的 mixin:
class SomeProtectedViewView(LoginRequiredMixin, TemplateView):
template_name = 'index.html'
只要确保你的 mixin 是第一个。
更新:我早在 2011 年就发布了这个,从 1.9 版开始,Django 现在包括这个和其他有用的 mixin(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
class_view_decorator(my_decorator(*args, **kwargs))
轻松实现。至于条件方法匹配 - 您可以修改 class_view_decorator 以将其自身应用于 View.get
或 View.post
而不是 View.dispatch
。
对于那些使用 Django >= 1.9 的人,它已作为 AccessMixin
、LoginRequiredMixin
、PermissionRequiredMixin
和 UserPassesTestMixin
包含在 django.contrib.auth.mixins
中。
因此要将 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 Mixin 的顺序也很好:Mixin 必须放在左侧,而基视图类必须放在右侧。如果顺序不同,您可能会得到破坏和不可预测的结果。
我意识到这个线程有点过时了,但无论如何这是我的两分钱。
使用以下代码:
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 函数用于修改(基于函数的)视图,以便在非 ajax 调用访问该视图时引发 404 错误。通过简单地应用补丁函数作为装饰器,这个装饰器也可以在基于类的视图中工作
使用 Django 大括号。它提供了许多很容易获得的有用的 mixin。它有漂亮的文档。试试看。
你甚至可以创建你的自定义 mixin。
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({})
在我的代码中,我编写了这个适配器来使成员函数适应非成员函数:
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')
method_decorator
一样)。这似乎是实现这一目标的一种很好且可读的方式。
如果它是一个大多数页面要求用户登录的站点,您可以使用中间件强制登录所有视图,除了一些特别标记的视图。
Pre Django 1.10 中间件.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)
视图.py:
def public(request, *args, **kwargs):
...
public.login_required = False
class PublicView(View):
...
public_view = PublicView.as_view()
public_view.login_required = False
您不想包装的第三方视图可以在设置中排除:
设置.py:
LOGIN_EXEMPT_URL_PREFIXES = ('/login/', '/reset_password/')
现在已经有一段时间了,现在 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):
# ...
@login_required
的其他权限来在类基础视图中实现权限的最简单方法......您还可以传递多个自定义权限,如下面的 @permission_required(['polls.can_vote', 'polls.change_vote'])
。
我已经根据 Josh 的解决方案进行了修复
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
这对 django 来说超级简单> 1.9 支持 PermissionRequiredMixin
和 LoginRequiredMixin
只需从身份验证导入
视图.py
from django.contrib.auth.mixins import LoginRequiredMixin
class YourListView(LoginRequiredMixin, Views):
pass
有关详细信息,请阅读 Authorization in django
如果你正在做一个需要各种权限测试的项目,你可以继承这个类。
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)
这里是 permission_required 装饰器的解决方案:
class CustomerDetailView(generics.GenericAPIView):
@method_decorator(permission_required('app_name.permission_codename', raise_exception=True))
def post(self, request):
# code...
return True
不定期副业成功案例分享
message
附加到它?