我的Django项目的权限机制
本文主要介绍我在Django博客使用的权限设置,我的博客中几乎所有的权限设置都是我自己写的。这么做的原因是我在写Flask博客的时候,学到了一些这方面的知识,感觉很不错。因此就借鉴里面的逻辑,自己写了这方面的代码。
1、关于文章的访问权限
我就从Model和View两层来说。
首先我在Model中定义一个字段,用来指示是否公开,是否允许别人可见: 源代码如下:
ACCESS = { 100:u'公开', 200:u'私人可见' } class Article(models.Model): title = models.CharField(max_length=150,unique=True,verbose_name=u'标题') alias = models.CharField(max_length=150,verbose_name=u'英文标题') .......... .......... access = models.IntegerField(default=100,choices=ACCESS.items(),verbose_name=u'文章权限,公开或者私人可见')
access字段指定该字段是否公开。如果设为公开那么所有人可以访问;如果设为私密,那么就不允许一些用户访问。那此时问题来了,该如何设置限制访问逻辑。我的代码如下,该方法也定义在Model中:
def can_access(self,user): if self.access == 100: return True if self.access == 200: if user is None: return False else: return self.author.id == user.id or user.is_staff
上面的代码很简单,如果是私密的,只有文章作者或者管理员可以访问。
在View中的代码:
class ArticleDetailView(BaseMixin,DetailView): queryset = Article.objects.filter(status=0) slug_field = 'alias' context_object_name = 'article' template_name = 'article.html' object = None def get(self, request, *args, **kwargs): alias = self.kwargs.get('slug') try: self.object = self.queryset.get(alias=alias) except Article.DoesNotExist: logger.error('article does not exsists') #I should rewrite the 404 html later return HttpResponseNotFound('Page not Found') # add permission,if the article has set permission,the web will raise one exveption if not self.object.can_access(request.user): raise PermissionDenied
看这段代码最后面,如果can_acees方法返回False,就抛出一个禁止Django内置的禁止访问的异常,即返回403页面。
2、自定义视图权限装饰器
首先自己定义一个装饰器函数,用来修饰要设置权限的视图函数或类方法。 装饰器函数源代码:
from functools import wraps from django.http import HttpResponse,HttpResponseRedirect,HttpResponseNotFound def permission_forbidden(http_exception=403,next_url='/account/login/'): """ Usage: @permission_forbidden(403) def test(request): return HttpResposne('hello world') when decorated by permission_forbidden,if the user is not staff, it will raise one PerissionDenied exception :param http_exception: :return:the return value of decorated function """ def decorator(func): @wraps(func) def wrapper(request,**kwargs): if http_exception == 403: if request.user.is_staff: rv = func(request,**kwargs) return rv else: raise PermissionDenied elif http_exception == 401: if not request.user.is_authenticated(): return HttpResponseRedirect(next_url) rv = func(request,**kwargs) return rv return wrapper return decorator
这是自己写的一个三层装饰器函数,代码很简单。如果http_exception为403,当不是管理员权限就抛出禁止访问异常;当http_exception为401,当用户没有登录就跳转到登录页面。
在View中:
@permission_forbidden(http_exception=401) def TestForm(request): if 'start' in request.GET and request.GET['start']: return render_to_response('cost.html',{'current':'haibo','start':request.GET['start']}) else: return render_to_response('cost.html',{'current':''})
这是一个用于测试的视图函数。
上面讲解了如何使用装饰器函数装饰视图函数,那么该如何装饰基于类的视图呢?
在Django中的官网上给出了答案。
最简单的方法是在URLconf 中装饰,装饰类的as_view方法:
如现在我在View中定义了一个基于类的视图:
class AuthorView(BaseMixin,ListView): template_name = 'author_information.html' context_object_name = 'categories' def get_queryset(self): categories = Category.available_categories() return categories def get_context_data(self,*args,**kwargs): return super(AuthorView,self).get_context_data(**kwargs)
直接在URl中配置: url(r’^author/$’, permission_forbidden(http_exception=403)(AuthorView.as_view()),name=’blog_author’),
这样就可实现上面对视图函数相同的装饰。这个方法在每个实例的基础上运用装饰器。如果想让视图的每个实例都被装饰,你需要一种不同的方法。
下面是摘抄自官方文档:
类的方法和独立的函数不完全相同,所以你不可以直接将函数装饰器运用到方法上 —— 你首先需要将它转换成一个方法装饰器。
method_decorator 装饰器将函数装饰器转换成方法装饰器,这样它就可以用于实例方法上。例如:
from django.contrib.auth.decorators import login_required from django.utils.decorators import method_decorator from django.views.generic import TemplateView class ProtectedView(TemplateView): template_name = 'secret.html' @method_decorator(login_required) def dispatch(self, *args, **kwargs): return super(ProtectedView, self).dispatch(*args, **kwargs)
在这个例子中,ProtectedView 的每个实例都将有登录保护。
不过有一点要说明:我用这个方法测试没有成功!!!
上面说了这么多我自定义的权限设置,这里也说一下Django内置的权限系统。
详细的内容请参考下面的两个链接,一个是个人博客,一个是Django官方文档:
- http://www.cnblogs.com/esperyong/archive/2012/12/20/2826690.html
- http://python.usyiyi.cn/django/topics/auth/default.html
我这里只说两点:
一是分配权限的问题。
默认情况下,Django会为每一个Model自动生成一add,delete,change的权限,并保存在auth_permission数据表中。
我们要想把权限分配给用户,就要用User的user_permissions属性。
def user_gains_perms(request, user_id): user = get_object_or_404(User, pk=user_id) # any permission check will cache the current set of permissions user.has_perm('myapp.change_bar') permission = Permission.objects.get(codename='change_bar') user.user_permissions.add(permission) # Checking the cached permission set user.has_perm('myapp.change_bar') # False # Request new instance of User # Be aware that user.refresh_from_db() won't clear the cache. user = get_object_or_404(User, pk=user_id) # Permission cache is repopulated from the database user.has_perm('myapp.change_bar')
对于超级用户来讲,他拥有所有的权限,并不需要再做分配。
2、模板中的权限变量
在setting.py中,我们定义了:
TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', 'DIRS': [os.path.join(BASE_DIR,'templates')], 'APP_DIRS': True, 'OPTIONS': { 'context_processors': [ 'django.template.context_processors.debug', 'django.template.context_processors.request', 'django.contrib.auth.context_processors.auth', 'django.contrib.messages.context_processors.messages', ], }, }, ]
context_processor会自动添加一些上下文,当然,其中包括perms.源代码:
def auth(request): """ Returns context variables required by apps that use Django's authentication system. If there is no 'user' attribute in the request, uses AnonymousUser (from django.contrib.auth). """ if hasattr(request, 'user'): user = request.user else: from django.contrib.auth.models import AnonymousUser user = AnonymousUser() return { 'user': user, 'perms': PermWrapper(user), }
它添加了,user和perms两个变量,perms指的是当前用户的所有权限。 这样,在模板中,就可以使用perms变量了。