一、什么是权限
权限:
在一个网站中会涉及到哪些用户能访问什么样的网页,那些网页一般用户是不能访问的,这就是权限,关于后台的东西客户是不能进去的,只能是内部人员才能进行管理,而内部的后台还得按级别进行授权,只有最高权限才有访问和编辑全部页面的权力,说到底权限就是权力。
权限的表述:
要想控制权限我们西能从url下手,因为url是访问任何网页都不可缺少的,可以让每个url代表一种权限,有权限就能访问此url,没有的话就访问不了,这样就形成了权限管理,所以说权限就是url
二、权限管理组件(rbac)
1、先创建一个django项目:
创建django项目,里边暂时创建一个可以用的应用(app01为例)
2、创建应用rbac
在上面的项目中再创建一个应用:rbac,在setting中注册rbac应用
3、在rbac中的models文件中建表
因为这里的表示用来管理权限的所以要在rbac应用中建,首先我们用户表、角色表、权限表
from django.db import models # Create your models here. class UserInfo(models.Model): ''' 用户表 字段roles是用户表与角色表是多对多的关系可以身兼数职 ''' name=models.CharField(max_length=32) pwd=models.CharField(max_length=32,default=123) email=models.EmailField() roles=models.ManyToManyField(to="Role") def __str__(self): return self.name class Role(models.Model): ''' 角色表 字段permissions是角色表和权限表多对多的关系 ''' title=models.CharField(max_length=32) permissions=models.ManyToManyField(to="Permission") def __str__(self): return self.title class Permission(models.Model): ''' 权限表 因为权限表中没有关系字段所以在录入数据时可以先给权限表录入数据 ''' url=models.CharField(max_length=32) title=models.CharField(max_length=32) def __str__(self): return self.title
4、用admin给各表录入数据
from django.contrib import admin from .models import * #from rbac import models admin.site.register(UserInfo) admin.site.register(Role) class PermissinConfig(admin.ModelAdmin): ''' 可以说这是定义了一个表格在admin中的显示方式 ''' list_display=["id","title","url"] #这句是在admin中显示的列也就是字段名想让哪个字段在admin中显示就加到列表中 ordering=["id"] #在admin中记录的排序方式 admin.site.register(Permission,PermissionConfig) #与前面两张表一样注册只不过多了一个参数,前面定义的一个类
利用admin给权限表录入数据:
id | url | title |
1 | /users/ | 查看用户列表 |
2 | /user/add/ | 添加用户 |
3 | /user/delete/(\d+) | 删除用户 |
4 | /user/edit/(\d+) | 编辑用户 |
5 | /orders/ | 查看订单 |
6 | /order/add/ | 添加订单 |
7 | /order/delete/(\d+) | 删除订单 |
8 | /order/edit/(\d+) | 编辑订单 |
利用admin给角色表中录入数据:
id | title |
1 | CEO |
2 | 销售经理 |
3 | 销售员 |
利用admin给用户表录入数据:
id | name | pwd | |
1 | kxl | kxl@qq.com | 123456 |
2 | zhaoyan | yan@qq.com | 123456 |
3 | jingjing | jing@qq.com | 123456 |
给角色绑定权限给用户绑定角色:
因为建表时已经绑定了多对多关系,所以关系表示自动生成的,在录入数据是将用户kxl绑定CEO角色,zhaoyan绑定销售经理角色,jingjing绑定销售员工角色,给CEO角色绑定所有权限,给销售经理绑定查看用户列表、查看订单列表、增加订单、修改订单、删除订单,给销售员绑定查看订单信息,添加订单
三、在应用app01中创建视图,项目urls中创建对应关系
1.首先在urls中创建对应关系
from django.conf.url import url from django.contrib import admin from app01 import views#这儿一定是app01中的views而不是rbac urlpattern=[ url(r'^admin/',admin.site.urls), url(r'^login/',views.login), url(r'^users/',views.users), url(r'^orders/',views.orders), ]
2.在app01的views中创建login视图函数:
from django.shortcuts import import render,redirect,HttpResponse from rbac.models import * def login(rquest): if request.method=="GET": return render(request,"login.html") else: user=request.POST.get("user") pwd=request.POST.get("pwd") user=UserInfo.objects.filter(name=user,pwd=pwd).first() if user: request.session["user_id"]=user.pk permission_info=user.roles.all().values("permissions__url","permissi on__title").distinct()#获取当前登录用户的所有权限 temp=[] for i in permission_info: temp.append(i["permission__url"]) request.seesion["permission_list"]=temp return HttpResponse("登录成功!") else: return redirect("/login/") def users(request): return HttpRespones("用户列表") def orders(request): return HttpRespones("订单列表")
这样就完成了登录获取登录用户权限以及访问用户列表,订单列表,但是还存在问题就是这样的话权限还没用上,每个登录的人不管是谁都能访问用户列表和订单列表,这样与不设权限不是没区别吗。
四、添加简单的权限限制
1.先从views下手:
首先在views中的users函数和orders函数中各自加上权限限制:
#因为url中有类似edit/(\d+)的,首先导入正则模块 import re current_path=request.path_info#获取当前访问路径 flag=False#设置一个标识符默认为False permission_list=request.session.get("permission_list") if not permission_list:#如果session中没有键值对证明没登录让它跳转到login return redirect("/login/") for permission_url in permission_list: ret=re.match(permission_url,current_path) if ret: flag=True break if not flag: return HttpRespones("没有权限")
这样的话如果登录的用户有查看用户列表权限或有查看订单列表权限才能够访问这两个url及对应的视图函数,这样写虽然解决了上面没有限制的问题,但是两个视图函数里边写了一模一样的代码,重用太多了又得解决这个问题。
2.利用中间件能够过滤请求来添加限制
创建中间件文件,将加在两个视图函数的用来限制访问的代码写到新创建的中间件文件中:
在rbac文件夹创建一个文件夹用来存此应用需要的自定义文件,比如自定义中间件:(permissionMiddleware.py)
from django.utils.deprecation import MiddlewareMixin from django.shortcuts import redirect,render,HttpResponse import re class M1(MiddlewareMixin): def process_request(self,request): pass current_path=request.path_info valid_url_menu=["/login/","/reg/","/admin/.*"] for valid_url in valid_url_menu: ret=re.match(valid_url,current_path) if ret: return None permission_list=request.session.get("permission_list") if not permission_list: return redirect("/login/") flag=False for permission_url in permission_list: ret=re.match(permission_url,current_path) if ret: flag=True break if not flag: return HttpResponse("没有权限")
注意里面的白名单一定要加,不然次中间件将login请求也会返回“没有权限”
五、将权限粒度到按钮
权限应该粒度到按钮,比如在订单列表中,如果有添加订单的权限就把添加添加按钮显示出来,否则将不会显示出按钮,还有删除编辑等按钮都要搞成这样。
创建一个订单列表的html页面:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>Title</title> <link rel="stylesheet" href="https://cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous"> </head> <body> <div> <h3>订单列表</h3> <p><a href="/orders/add/"><button class="col-md-offset-4 btn btn-success">添加订单</button></a></p> {# 此处暂时写成死的,应该是依据权限中有没有添加订单的权限来决定这个按钮显不显示#} <div class="row"> <div class="col-md-6"> <table class="table table-stripped"> <tr> <th>订单编号</th> <th>订单标题</th> <th>订单日期</th> <th>操作</th> </tr> <tr> <td>asdf</td> <td>asdf</td> <td>asdf</td> <td> <a href="/orders/edit/"><button class="edit btn btn-info">编辑</button></a> <a href=/orders/delete/"><button class="delete btn btn-danger">删除</button></a> </td> {# 这儿的两个操作功能也是暂时写在这儿的,也是根据权限来改变它的显示的,必须根据用户有没有删除和编辑功能#} </tr> </table> </div> </div> </div> </body> </html>
此HTML文件中拥有两个地方本应该不能写死的,但是怎样改呢,这就需要给数据表中添加一点数据了,首先多创建一张分组表,它的共能主要是为了将权限进行分组,按照类型分组比如分为,用户组和订单组,一切有关users的添加编辑,删除权限都属于用户组,订单组类似,给permission添加外键关联分组表,再给permission添加code字段为了方便在前端表示url:
封装方法:
我们将给session中写权限的代码封装成一个函数写在单独的文件中(与自定义中间件在同一文件夹创建initial.py):
def initial_session(request,user): permission_info=user.role.all().values("permission__url", "permission__title", "permission__group_id", "permission__code").distinct() print(permission_info) permission_dict={} for permission in permission_info: gid = permission["permission__group_id"] if gid in permission_dict: permission_dict[gid]["urls"].append(permission["permission__url"]) permission_dict[gid]["codes"].append(permission["permission__code"]) else: permission_dict[gid]={ "urls":[permission["permission__url"],], "codes":[permission["permission__code"]] } request.session["permission_dict"]=permission_dict ''' { 1:{ urls:[] code:[] } } '''
这个文件是将含有已登录用户相关的权限表中的数据取出来,且格式化成:
{
1:{
urls:[]
code:[]
}
}
这样的形式写入session,这样在权限中间件中获取和返回也要紧随这改变:
from django.utils.deprecation import MiddlewareMixin from django.shortcuts import redirect,render,HttpResponse import re#由于可能 class M1(MiddlewareMixin): def process_request(self,request): pass current_path=request.path_info valid_url_menu=["/login/","/reg/","/admin/.*","/$"]#此处为添加的白名单 for valid_url in valid_url_menu: ret=re.match(valid_url,current_path) if ret: return None user_id=request.session.get("user_id") if not user_id: return redirect("/login/") permission_dict = request.session.get("permission_dict") for item in permission_dict.values(): urls=item["urls"] codes=item["codes"] for url in urls: url="^%s$"%url ret=re.match(url,current_path) if ret: request.permission_codes=codes return None return HttpResponse("没有权限") # flag=False # for permission_url in permission_list: # ret=re.match(permission_url,current_path) # if ret: # flag=True # break # if not flag: # return HttpResponse("没有权限")
这样在orders的视图函数中就可以取出用户所用有权下的codes列表,然后返回给前端页面进行判断按钮功能是否在codes列表中,如果在就显示按钮如果不在则不显示按钮为了前端查询方便我们在自定义文件夹中创建一个文件,里边创建一个类,将判断是否在codes列表中的方法封装起来在视图函数实例化类,发送给前端:
lass Permission(object): def __init__(self,code_list): self.code_list=code_list def list(self): return "list" in self.code_list def add(self): return "add" in self.code_list def delete(self): return "delete" in self.code_list def edit(self): return "edit" in self.code_list
def orders(request): ''' 订单信息列表 :param request: :return: ''' permission_codes=request.permission_codes per=Permission(permission_codes) return render(request,"orders.html",locals())
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>Title</title> <link rel="stylesheet" href="https://cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous"> </head> <body> <div> <h3>订单列表</h3> {% if per.add %} <p><a href="/orders/add/"><button class="col-md-offset-4 btn btn-success">添加订单</button></a></p> {% endif %} <div class="row"> <div class="col-md-6"> <table class="table table-stripped"> <tr> <th>订单编号</th> <th>订单标题</th> <th>订单日期</th> <th>操作</th> </tr> <tr> <td>asdf</td> <td>asdf</td> <td>asdf</td> <td> {% if per.edit %} <a href="/orders/edit/"><button class="edit btn btn-info">编辑</button></a> {% endif %} {% if per.delete %} <a href=/orders/delete/"><button class="delete btn btn-danger">删除</button></a> {% endif %} </td> </tr> </table> </div> </div> </div> </body> </html>
就这样再给users添加这样的限制就行了。
六、动态菜单
更改页面:
动态菜单就是说不用js效果,实现菜单的展示和隐藏,这该如何实现呢,与以前做的js完全搭不上边啊,首先我们总得自己下一个菜单出来在想想怎样实现它的展示和隐藏吧,所以首先给订单列表页面用css摆出一个左侧菜单
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>Title</title> <link rel="stylesheet" href="https://cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous"> <style> body { background-color: #eeeeee; } .heade { width: 100%; height: 49px; background-color: #336699; margin-top: 0; line-height: 49px; font-size: 20px; } .menu{ background-color: beige; padding-top: 10px; height: 600px; } .menu .title{ margin: 10px 10px; font-size: 14px; } .menu .con{ margin-left: 30px; } .content_r{ margin-top: 10px; } .hide{ display: none; } </style> </head> <body> <div class="heade"> 订单列表 </div> <div class="row content"> {# 此处暂时写成死的,应该是依据权限中有没有添加订单的权限来决定这个按钮显不显示#} <div class=" col-md-2"> <div class="menu"> <div class="title"> 用户管理 </div> <div class="con hide"> <p>1111111111111</p> <p>1111111111111</p> <p>1111111111111</p> </div> <div class="title"> 订单管理 </div> <div class="con hide"> <p>1111111111111</p> <p>1111111111111</p> <p>1111111111111</p> </div> </div> </div> <div class="col-md-8 content_r"> {% if per.add %} <p><a href="/orders/add/"> <button class="col-md-offset-8 btn btn-success">添加订单</button> </a></p> {% endif %} <table class="table table-stripped"> <tr> <th>订单编号</th> <th>订单标题</th> <th>订单日期</th> <th>操作</th> </tr> <tr> <td>asdf</td> <td>asdf</td> <td>asdf</td> <td> {% if per.edit %} <a href="/orders/edit/"> <button class="edit btn btn-info">编辑</button> </a> {% endif %} {% if per.delete %} <a href=/orders/delete/"> <button class="delete btn btn-danger">删除</button> </a> {% endif %} </td> {# 这儿的两个操作功能也是暂时写在这儿的,也是根据权限来改变它的显示的,必须根据用户有没有删除和编辑功能#} </tr> </table> </div> </div> </body> </html>
更改数据表:
这个左侧菜单不能写死,我们得让它变成动态的所以我们的表还得加一张,那就是加一张Menu表来表示来存储菜单信息且给权限表中刚添加一个字段,添加一个自关联字段,parent用来表示因为一般删除,添加,编辑都基于查出来的列表上进行的,我们可以将查作为父,而删除,编辑,添加作为子,在菜单中只显示父而不显示子,因为添加,删除,编辑按钮都是在列表页面中显示的,所以菜单中不需要显示。
未完待续........