我的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('<h1>Page not Found</h1>')
        # 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变量了。

--------EOF---------
微信分享/微信扫码阅读