Django:权限介绍 + url 权限 + 一级菜单权限动态显示

权限的介绍

说起权限我们大家都知道,不一样的角色会有不一样的权限。比如就像学生管理系统一样,管理员,老师,学生之间的权限都是不一样的,那么展示的页面也是不一样的。

这里我们介绍的是 web 网站的权限,对于web网站的权限来讲,一个含有正则表达式的 url  就是说一个权限。

这里有两种方案:

方案一:基于人进行分配,这样会造成空间的浪费

         

方案二:基于角色分配(RBAC:role based access control)

                           

 

一般情况下,我们多采用的是第二种方案,将权限分别存于权限表中,其中用户表和角色表是多对多的关系,角色表和权限表也是多对多的关系。

url 权限的简单使用

创建权限的目标是为了创建组件,让其他的项目也能够使用

首先要创建一个Django项目,创建一个app01.

一、表结构的设计

根据方案二中的表结构在models中创建模型,然后迁移数据库:makemigrations    migrate   

# models.py

from django.db import models

# Create your models here.

class User(models.Model):
    name = models.CharField(max_length=32)
    pwd = models.CharField(max_length=32)
    roles = models.ManyToManyField("Role")  
    
    def __str__(self):
        return self.name


class Role(models.Model):
    title = models.CharField(max_length=32)
    permissions = models.ManyToManyField("Permission")
    
    def __str__(self):
        return self.title


class Permission(models.Model):
    title = models.CharField(max_length=32)
    url = models.CharField(max_length=32)
    
    def __str__(self):
        return self.title

 然后进行数据库的迁移

makemigrations

migrate

二、利用Django中的 admin 录入权限数据

要创建超级用户才能登陆 admin

createsuperuser

在app01.admin.py中添加 register,将模型类添加register,在admin中就可以可视化录入数据

from django.contrib import admin

# Register your models here.

from app01.models import User,Role,Permission


admin.site.register(User)  ####

class RoleConfig(admin.ModelAdmin):   # 添加该类可以在admin中改变查看的界面更加方便  类名Config是 固定的
    list_display=["title"]

admin.site.register(Role,RoleConfig)  ###

class PermissionConfig(admin.ModelAdmin):
    list_display=["pk","title","url"]
    ordering = ["pk"]

admin.site.register(Permission,PermissionConfig)  ###

三、权限的注入

权限的注册就是在登录的时候,将登录人的权限加入到session中,登录(获取当前登录用户的权限)

思路:

  • 编写登录
  • 如果用户验证成功就设置session
  • 先查出当前用户的所有的权限
  • 从这些权限中找到所有的url,把这些url放到session中,

# views.py

  这里只是注入了 url 权限,不涉及菜单权限

from django.shortcuts import render,HttpResponse,redirect
from app01.models import User

def login(request):
    
    if request.method=="GET":
        return render(request, 'login.html')
    else:
        # 认证
        user=request.POST.get("user")
        pwd=request.POST.get("pwd")
        user=User.objects.filter(name=user,pwd=pwd).first()   # user是一个对象,一定要first(),否则后面没有 user.pk
        if user:
            # 登录成功

           
            request.session["user_id"]=user.pk    #保存当前登录用户状态信息

            # 查询当前登录人的所有权限列表
            # 查看当前登录人的所有角色
            # ret=Role.objects.filter(user=user)

            permissions=Role.objects.filter(user=user).values("permissions__url").distinct()  # distinct去重,user=user  前者是表明小写,表示记录(对象) 后者user本身就是对象
            print(permissions)

            permission_list=[]
            for item in permissions:
                permission_list.append(item["permissions__url"])   # 双下划线

            # 将当前登录人的权限列表注入session中
            request.session["permission_list"]=permission_list      # 注入的session可以是一个列表

            return HttpResponse("登录成功")

# html  登录的一个form表单  

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<form action="" method="post">
    {% csrf_token %}
    用户名<input type="text" name="user">
    密码<input type="text" name="pwd">
    <input type="submit">
</form>

</body>
</html>
View Code

注意:

  • session中可以保存很多的东西,并不一定是登录信息,比如之前学的验证码的保存,也用到了session
  • permissions中获取的类型和内容:动态参数是 \d 

    <QuerySet [{'permissions__url': 'customers/edit/(\\d+)/'}, {'permissions__url': 'customers/delete/(\\d+)/'}, {'permissions__url': 'orders/edit/(\\d+)/'}, {'permissions__url': 'orders/delete/(\\d+)/'}]>

四、权限的校验

利用中间件进行校验

权限加入到session中,在每次访问请求 url 的时候,都要校验该 url 请求是否在session中,但是如果在每个视图函数中都要校验的话,会有代码的重复,因此我们可以在中间件中进行权限的校验。

 # app01  ===>   middlewares.py

from django.utils.deprecation import MiddlewareMixin
from django.shortcuts import HttpResponse,redirect
import re


class PermissionMiddleWare(MiddlewareMixin):   #必须继承该类

    def process_request(self,request):

        current_path = request.path    # 当前路径

        # 设置白名单,即使不在权限中,也会放行,注意必须用循环,因为 "/admin/*" 含有正则
        # 如果直接用in的话,"/admin/*" 会被当做字符串
        for path in ["/login/","/admin/*"]:   # 将其作为规则符合当前路径,那么就放行
            ret = re.search(path,current_path)
            if ret:
                return None     # 返回空则放行


        # 先校验是否登录        
        user_id = request.session.get("user_id")
        if not user_id:
            return redirect("/login/")
        
        # 再校验权限
        # 同样的由于url中含有 \d 正则,因此需要使用循环,而不是in 
        permission_list = request.session.get("permission_list")
        
        for item in permission_list:
            item="^%s$"%(item)            #将规则加开头结尾,否则只要含有权限url中内容就会放行,
            ret = re.search(item,current_path)
            if ret:
                return None
        return HttpResponse("无权访问!")

# setting.py  添加中间件

MIDDLEWARE = [
    'app01.middlewares.PermissionMiddleWare'
]

注意:

  • 在设置白名单或者校验的时候,如果路径中含有正则表达式的,不能直接用 in ,要用循环,循环白名单或校验的内容,将其作为规则,然后去校验当前路径,是否符合规则。
  • 另外在校验权限的时候,要注意,规则一定要加开头和结尾,否则当前路径中,只要含有规则中内容,还有多余的内容,也会通过放行,例如:/customers/(规则)/customers/add/(当前路径)也会放行

五、权限的优化 ♥

写权限的目的是为了让其可以再其他项目中也能使用,否则每个项目都要写一套权限,因此可以把权限写成组件的形式,方便项目的调用。

组件实质上也是一个包,将写的权限都放入一个包内,供其他的项目调用。

1、创建一个关于权限的app(rbac):

python manage.py startapp rbac      # 也可以在 tool  run manage

2、在rbac中创建包 service, 将所有与权限相关的内容都放入该包中

  • 与权限相关的表(模型类)放入rbac 的 models.py 中,利用 admin 进行数据的录入
  • 与权限相关的注入session内容,放入rbac 中 service 包中的 rbac.py 中,封入一个函数之内  def  initial_sesson(user,request) ,注意传值,user和request,以后项目中写登录的时候,登录成功即调用该方法,然后再进行登录成功后的重定向。
  • 与权限相关的中间件校验内容,放入rbac 中 service 包中的 middlewares.py 中,不要忘记在setting中进行设置路径

# rbac ==> models.py

from django.db import models


class Permission(models.Model):
    """
    权限表
    """
    title = models.CharField(max_length=32, verbose_name='标题')
    url = models.CharField(max_length=32, verbose_name='权限')
    
    is_menu = models.BooleanField(default=False, verbose_name='是否是菜单')                  # 用于存放是否属于菜单权限
    icon = models.CharField(max_length=32, verbose_name='图标', null=True, blank=True)      # 用于存放菜单权限的图标
    
    class Meta:
        verbose_name_plural = '权限表'
        verbose_name = '权限表'
    
    def __str__(self):
        return self.title


class Role(models.Model):
    name = models.CharField(max_length=32, verbose_name='角色名称')
    permissions = models.ManyToManyField(to='Permission', verbose_name='角色所拥有的权限', blank=True)
    
    def __str__(self):
        return self.name


class User(models.Model):
    """
    用户表
    """
    name = models.CharField(max_length=32, verbose_name='用户名')
    password = models.CharField(max_length=32, verbose_name='密码')
    roles = models.ManyToManyField(to='Role', verbose_name='用户所拥有的角色', blank=True)
    
    def __str__(self):
        return self.name

# rbac ==>  service ==> rbac.py

  注意传值,user  和  request,登录成功后,要调用该方法 ♥

from rbac.models import Role

def initial_sesson(user,request):
    """
    功能:将当前登录人的所有权限录入session中
    :param user: 当前登录人
    """
    # 查询当前登录人的所有权限列表
    # 查看当前登录人的所有角色
    # ret=Role.objects.filter(user=user)
    permissions = Role.objects.filter(user=user).values("permissions__url",
                                                        "permissions__is_menu",
                                                        "permissions__title",
                                                        "permissions__icon",
                                                        ).distinct()

    permission_list = []
    permission_menu_list = []

    for item in permissions:
        # 构建权限列表
        permission_list.append(item["permissions__url"])

        # 构建菜单权限列表  这里后面会有详细的介绍。
        if item["permissions__is_menu"]:
            permission_menu_list.append({
                "title":item["permissions__title"],
                "icon":item["permissions__icon"],
                "url":item["permissions__url"],
            })

    # 将当前登录人的权限列表注入session中
    request.session["permission_list"] = permission_list
    # 将当前登录人的菜单权限列表注入session中
    print("permission_menu_list",permission_menu_list)
    request.session["permission_menu_list"] = permission_menu_list

# rbac ==>  service ==> middlewares.py

  每次发送请求,都会通过中间件

from django.utils.deprecation import MiddlewareMixin
from django.shortcuts import HttpResponse,redirect
import re

class PermissionMiddleWare(MiddlewareMixin):

    def process_request(self,request):
        print("permission_list",request.session.get("permission_list"))
        current_path = request.path

        # 设置白名单放行
        for reg in  ["/login/","/admin/*"]:
            ret=re.search(reg,current_path)
            if ret:
                return None
        # /customers/edit/1

        # 校验是否登录

        user_id=request.session.get("user_id")
        if not user_id:
            return redirect("/login/")

        # 校验权限  涉及到正则要用循环
        permission_list=request.session.get("permission_list")

        for reg in permission_list:
             reg="^%s$"%reg
             ret=re.search(reg,current_path)
             if ret:
                 return None

        return HttpResponse("无访问权限!")

   中间件要在 setting 中进行设置

MIDDLEWARE = ['rbac.service.middlewares.PermissionMiddleWare',    #根据中间件所在的目录位置,来进行添加中间件,注意一定要写对,否则会报错。
]

一级菜单权限

前面详细介绍了什么是权限,以及url 权限,并且将每个人的权限通过角色分配,在表结构中,进行了设计 ,但是这种情况下,我们只能通过地址栏输入url 来查看该用户是否具有访问权限,这种不方便用户的操作,我们希望用户能够登录成功之后,能够再左侧的菜单栏只显示自己又权限的功能,没有权限的则不显示,但是并不是所有的权限都要放入左侧的菜单栏中,比如添加、变价、删除,本身就是基于查看页面中的 a 标签发送请求的,因此,没有必要放在左侧的菜单栏处。

一、什么是菜单权限?

  左侧菜单栏,有标题,图标,点击请求的路径

  

二、权限表,添加字段(is_menu    icon)

  因此在涉及权限表的时候,可以多涉及两个字段 is_menu 存储bool值,和 icon 存储图标,根据 is_menu 来判断是否是菜单权限来决定当前登录的用户菜单栏显示的内容。

  具体的添加方式,查看上面优化中的 models.py 中已经添加。

三、在注入权限的时候,session中需要注入 权限列表 菜单权限

  • 权限列表:8条所有权限的 url
  • 菜单权限列表:可以当做左侧菜单栏的权限,包括菜单权限的 url、title、icon  (因为左侧菜单栏,有标题,图标,点击请求的路径,渲染的时候需要用到)

  详细的添加方式,查看上面优化中的 session 注入。

  注意:session 中注入的是键值对,可以添加多个键值对

四、菜单权限的显示  

  由于左侧的菜单栏在每次发送请求的时候都会显示在页面,因此在每个请求的视图函数中都要传送 菜单权限列表 给前端数据来渲染,由于render 的时候是将 html 解析成一大堆的字符串,传递给前端浏览器,浏览器根据 html 的字符串渲染出页面,这就造成了 模板html 和 数据 是分离的,要求每个视图函数中都要传送数据,这样就造成了代码的重复。

  如何解决以上问题?

  我们可以使用模板语法中提供的自定义过滤标签传值的思想。

 

1、自定义的过滤器标签要默认放入 templatetags  包中,因为是根权限相关的,所以整个包可以放入 rbac (app) 中,

# rbac(app)  /  templatetags  /   web.py

  其中,templatetags 是必须命名, web.py 是可以随便起名字的,rbac(app) 整个app项目也是可以随便起名字的,这里因为是跟全年相关的额,所以起名为 rbac

  注意:添加 templatetags 以后需要重新启动项目

from django.template import Library
import re
register =Library()   # 必须命名为 register


# 该自定义过滤器标签不仅给menu.html中传值permission_menu_list,
# 而且还根据列表中的url是否为当前路径,给其添加键值对class=acitve,给菜单列表添加样式
@register.inclusion_tag("rbac/menu.html") # 默认会找 templates 文件夹下,因此不需要写 templates 路径 def get_menu_styles(request):      # 注意该方法需要传值 permission_menu_list = request.session.get("permission_menu_list") for item in permission_menu_list: if re.search("^{}$".format(item["url"]), request.path): item["class"] = "active" return {"permission_menu_list":permission_menu_list} # 会传值给上面的 rbac/menu.html中,此处类似于render的传值

 

2、编写,自定义过滤标签中的传入数据的  rbac.menu.html

  # rabc(app)  /  templates  /  rbac  /  menu.html

    根据 传入的 permission_menu_list 渲染菜单权限列表的标签, 用于需要的模板,通过load  和 调用该方法。

    注意:模板变量有值则渲染,没值不渲染,不会对页面有任何的影响

<div class="static-menu">

    {% for item in permission_menu_list %}
        <a href="{{ item.url }}" class="{{ item.class }}">
            <span class="icon-wrap"><i class="fa {{ item.icon }}"></i></span> {{ item.title }}
        </a>
    {% endfor %}

</div>

 

3、在母版中 html 中渲染以上的 菜单标签 语法

  找到要渲染该菜单标签的位置

  load 会直接去找 templatetags 中的 web.py 文件

  get_menu_styles 是过滤标签的方法,request 是该方法的传的参数,由于request 是全局变量,因此在 html 中是可直接使用的。

 

    <div class="left-menu">
        <div class="menu-body">

               {% load web %}
               {% get_menu_styles request %}

        </div>
    </div> 

 

关于权限的app(rbac)中目录解析:

   

 

posted @ 2018-11-13 20:56  葡萄想柠檬  Views(2363)  Comments(1)    收藏  举报
目录代码