Django实战教程: 开发企业级应用智能文档管理系统smartdoc(2)之权限管理

版权声明:本文系大江狗原创,请勿直接copy应用于你的出版物或任何公众平台。 https://blog.csdn.net/weixin_42134789/article/details/82184551

最近有点小忙,更新本公众号的时间都要靠挤了。但权限这东西非常重要,而且有点抽象, 所以小编我只能慢慢写了,希望能用通俗易懂的语言和实例帮大家了解如何在Django中控制和管理用户权限。在Django实战教程: 开发企业级应用智能文档管理系统smartdoc(1)里,我们利用Django开发了一个叫smartdoc的app,已经搭好了基础框架,并具体实现了以下几个功能性页面(标黄部分已实现)。

  • 产品列表,产品详情,产品创建和产品修改 - 4个页面

  • 类别列表,类别详情,类别创建和类别修改 - 4个页面

  • 文档列表,文档详情,文档创建和文件修改 - 4个页面

  • 文档一般搜索和Ajax搜索 - 1个页面

本文是smartdoc教程的第2部分,重点讲解用户权限控制与管理。关于文档搜索和Ajax搜索功能我们会放在最后一部分教程。

权限管理需求分析

扫描二维码关注公众号,回复: 3739452 查看本文章

权限机制能够约束用户行为,控制页面的显示内容,也能让我们的app更灵活功能更强大。一个完整的用户权限应该包含3个要素: 用户,对象和权限,即什么用户对什么对象有什么样的权限。

以smartdoc文档系统为例,我们的对象有3类: 产品,类别和文档。我们的权限主要包括查看(view), 创建(add), 更改(change)和删除(delete)。我们的用户大致可以分为以下几类。根据用户分类的不同,我们需要设置不同的权限。

  • 所有用户(销售人员,匿名用户) - 无需注册登录,可查看产品,类别和文档。

  • 一般用户(一般员工) - 登录后,可以创建,更改产品和类别,但是不能上传和更改文档。

  • 高级用户(高级员工) - 登录后,可以创建,更改产品,类别和文档。

  • 超级用户(管理员) - 登录后,可以创建,更改和删除产品,类别和文档。

另外为了加大难度,我们还需要在权限管理上再加上一条。每个用户只能更改自己创建的对象(产品,类别和文档)信息。我们下面就来看看如何实现咱们客户的权限管理需求。

看不见不等于做不了

有很多网站是这样的,用户登录前看到的是一个页面,登录后看到的是另一个页面,通常登录后页面含有更多内容和信息。初学者可能会说,这不就是权限管理吗?答案不是,我们以实际案例告诉你来告诉你,看不见不等于做不了。

我们先来看产品列表的案例。在模板里我们通过request.user.is_authenticated来判断用户是否已登录,如果用户已登录,列表下面会出现添加产品的链接。

{% if request.user.is_authenticated %}
<p>
<a href="{% url 'smartdoc:product_create' %}">添加产品</a>
</p>
{% else %}
<p>请先<a href="{% url 'account_login' %}?next=
{% url 'smartdoc:product_create' %}">登录</a>添加产。</p>
{% endif %}

匿名用户看到的页面是这样子的。

用户登录后,可以看到+ 添加产品这个按钮。

那么问题来了,匿名用户看不见+ 添加产品这个按钮,但是如果用户直接访问产品创建页面/smartdoc/product/create/会怎么样了? 我们直接上图吧(见下图)。惊不惊喜,意不意外?不是只有登录用户才能创建产品吗? 原因是我们只是在前端根据用户是否登录显示了不同的内容,并没有在后端视图里对用户是否登录或着有着何种权限进行判断和执行。对于权限控制,后端显然比前端更重要,因为判断用户是否有权限,可以进行什么操作显然是逻辑问题,而不是前端显示问题。

要真正实现用户登录后才能创建产品,我们必需要在后端对视图做出修改,真正执行我们设置的权限。最简单的方法就是使用@login_required这个装饰器。因为我们使用的是基于类的视图(Class Based View), 而不是函数视图,所以我们还需要使用@method_decorator这个装饰器,先将类伪装成函数,再使用@login_required这个装饰器。

from django.contrib.auth.decorators import login_required
from django.utils.decorators import method_decorator

@method_decorator(login_required, name='dispatch')
class ProductCreate(CreateView):
    model = Product
    template_name = 'smartdoc/form.html'
    form_class = ProductForm

    # Associate form.instance.user with self.request.user
    def form_valid(self, form):
        form.instance.author = self.request.user
        return super().form_valid(form)

@login_required这个装饰器的作用是当用户试图访问某个页面时会要求用户先登录,用户登录后自动转向该页面。此时匿名用户如果再访问创建页面/smartdoc/product/create/,他们会被直接转到登录页面,登录后才会跳转到产品创建页面(如下图所示)。

恭喜你,你已经可以实现最简单的权限管理了。记住,真正的权限管理在后台哦。

John和Chris的不同

是否登录只能区别匿名用户和注册用户,但实际应用中不同注册用户也有不同的权限。对于本例,假设我们有John和Chris两个注册用户,他们的权限分别如下。我们该如何控制实现呢?

  • John(一般员工) - 登录后,可以创建,更改产品和类别,但是不能上传和更改文档。

  • Chris(高级员工) - 登录后,可以创建,更改产品,类别和文档。

最好的方法就是使用Django自带的permission权限管理。Permission的Django自带的模型,与User是多对多的关系,有其对应的数据表。Django中每个model被创建后后,Django默认都会添加该model的add, change和delete三个permission(Django 2.1下还有view这个permission)到permission数据表中。例如,定义一个名为Document的模型后,Django会自动创建相应的三个permission:add_document, change_document和delete_document。我们后续会系统性介绍Django的权限管理Permission和Group。

以smartdoc系统为例,Django的permission系统已经为各个对象(文档,类别和产品)生成了的三种权限。但此时数据表里的权限尚未与用户建立联系,只是一些字段。我们需要在登录admin后,找到Chris和John,并添加相应的权限。Chris比John多两个权限: add文档和change文档。注意: 添加完后别忘了点save保存哦。

此时如果让John登录,你会发现他仍然可以上传文档(如下图所示)。难道Django的Permission系统不工作了? 当然不是。使用Django自带Permission权限管理系统的要诀分2两步: 第一步是给用户设置相应的权限,第二步是在模板和视图里判断用户是否有相应的权限。我们只完成了第一步,怎么会工作呢?

在模板中判断用户是否有权限

在模板中可以使用全局变量perms来判断当前用户的所有权限。如下例中,我们在product_detail.html模板中用perms来检测当前用户是否有添加文档(add_document)和添加产品(add_product)的权限,有的话才显示相应按钮。

{% if request.user.is_authenticated %}

<p>
   {% if perms.smartdoc.add_document %}
    <a href="{% url 'smartdoc:document_create' product.id %}">上传文档</a> |
    {% endif %}

    {% if perms.smartdoc.add_product %}
  <a href="{% url 'smartdoc:product_create' %}">添加产品</a>
    {% endif %}
</p>
{% else %}
<p>请先<a href="{% url 'account_login' %}?next={% url 'smartdoc:product_detail' product.id %}">登录</a>添加产品,编辑产品或上传文档。</p>
{% endif %}

由于John只有添加产品的权限,没有上传文档的权限,所以我们理应看到添加产品的按钮,而不应该看到上传文档的按钮。我们来看看最后效果,是不是这样子的?

如果Chris访问同一页面,他一定可以在前端多看到一个按钮。恭喜你,我们实现了注册用户间的差异化。事情到此就结束了吗?显然不是。让我们重温那段话。对于权限控制,后端显然比前端更重要,因为判断用户是否有权限,可以进行什么操作显然是逻辑问题,而不是前端显示问题。

在视图中判断用户是否有权限

在视图中可以使用user.has_perm()方法来判断一个用户是不是有相应的权限, 当然最快捷的方式是使用@permission_required这个装饰器,这样能够分离权限验证和核心的业务逻辑,使代码更简洁,逻辑更清晰。在下例中,只有已登录且有添加产品权限的用户才能创建产品。

from django.contrib.auth.decorators import login_required, permission_required
from django.utils.decorators import method_decorator

@method_decorator(login_required, name='dispatch')
@method_decorator(permission_required('smartdoc.add_product', raise_exception=True), name='dispatch')
class ProductCreate(CreateView):
    model = Product
    template_name = 'smartdoc/form.html'
    form_class = ProductForm

    # Associate form.instance.user with self.request.user
    def form_valid(self, form):
        form.instance.author = self.request.user
        return super().form_valid(form)

强行访问无权限的页面只会出现403错误,如下图所示。

Django Permission系统的缺陷

Django自带的permission系统只对模型有用,而不是针对某个具体的object。一旦用户获得编辑文档的权限,那么该用户将获得编辑所有文档的权限。在本例中,我们不是说过要加点难度吗?每个用户只能编辑自己创建的对象(产品,类别或文档)。这时我们可以通过get_object方法来实现,参加如何使用Django通用视图的get_queryset, get_context_data和get_object等方法。如果用户尝试访问不是自己创建的object,将会出现404的错误。

from django.contrib.auth.decorators import login_required, permission_required
from django.utils.decorators import method_decorator

@method_decorator(login_required, name='dispatch')
@method_decorator(permission_required('smartdoc.change_product', raise_exception=True), name='dispatch')
class ProductUpdate(UpdateView):
    model = Product
    template_name = 'smartdoc/form.html'
    form_class = ProductForm

    def get_object(self, queryset=None):
        obj = super().get_object(queryset=queryset)
        if obj.author != self.request.user:
            raise Http404()
        return obj

小结

本文以smartdoc智能文档系统为例,详细介绍了Django在前端和后端如何检查和管理用户的权限,实现我们项目对于用户权限管理方面的需求。本教程中我们通过admin后台一个用户一个用户添加权限,当用户很多时这显然不现实。两个更好的方式是(1)在创建用户时直接分配权限(2)创建权限组group。除此以外403和404页面也非常简陋。小编我会后续专文介绍Django的Permission和Group,并介绍403和404页面的定制,欢迎关注我的微信公众号。

大江狗

2018.8.22

猜你喜欢

转载自blog.csdn.net/weixin_42134789/article/details/82184551