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

目标:生成一个独立的组件,到哪都能用

一、先创建一个 项目,建一个app01和rbac的应用

二、表结构设计

1、先看配置文件合适不,给创建的rbac在配置文件里面设置一下

找到INSTALLED_APPS=['rbac']


配置静态文件


2、设计表结构

models中创建类:五个类,七张表

角色表:

用户表:

权限表:

权限组表:

菜单表:

角色表和权限表是多对多的关系(一个角色可以有多个权限,一个权限可以对应多个角色)

用户表和角色表是多对多的关系(一个用户可以有多个角色,一个角色有多个用户)

所以有会多生成两张关系表

一个菜单下面有多个组

一个组下面有多个菜单

一个菜单下面有多个权限



from django.db import models

# Create your models here.

class Role(models.Model):

title = models.CharField(max_length=32,verbose_name="角色")

permissions = models.ManyToManyField(to="Permission",verbose_name="拥有权限的角色",blank=True) #权限和角色是多对多的关系

def __str__(self):

return self.title

class Meta:

verbose_name_plural = "角色表"

class Permission(models.Model):

title = models.CharField(max_length=32,verbose_name="权限名")

url = models.CharField(max_length=32,verbose_name="带正则的url")

codes = models.CharField(max_length=32,verbose_name="代码")

group = models.ForeignKey(to="Group",verbose_name="所属组",blank=True) #组和权限是一对多的关系,一个组有多个权限

menu_gp = models.ForeignKey(to='Permission',related_name='aaa',null=True,blank=True,verbose_name="组内菜单")

def __str__(self):

return self.title

class Meta:

verbose_name_plural = "权限表"

class UserInfo(models.Model):

name = models.CharField(max_length=32,verbose_name="姓名")

password = models.CharField(max_length=64,verbose_name="密码")

email = models.CharField(max_length=32,verbose_name="邮箱")

roles = models.ManyToManyField(to="Role",blank=True) #用户和角色是多对多的关系

def __str__(self):

return self.name

class Meta:

verbose_name_plural = "用户表"

class Group(models.Model):

title = models.CharField(max_length=32,verbose_name="组名称")

menu = models.ForeignKey(to="Menu",verbose_name="组内菜单",blank=True) #一个组下有多个菜单

def __str__(self):

return self.title

class Meta:

verbose_name_plural = "权限组"

class Menu(models.Model):

caption = models.CharField(max_length=32,verbose_name="菜单")

def __str__(self):

return self.caption

class Meta:

verbose_name_plural = "菜单表"


具体分析为什么要多加个code列和权限组表呢?

1、我们一般是先看到的是列表页面,在这个页面上是否显示添加,是否显示编辑,是否显示删除,都是需要判断的

有无添加权限,有无删除权限,有无编辑权限,我们可以给每一个url一个代号


dict = {

1:{                 代号

/userinfo/     list

/userinfo/add/       add

/userinfo/del(\d+)/    del

/userinfo/edit(\d+)/    edit

}

}


不仅在列表页面需要知道他有那些权限,在其他页面也知道他有那些权限

所以上面的方案还是有点不好,那么我们采取下面的方案。将代号取出来放在一个列表里面


dict = {

1:{

"codes":["list","add","del","edit"]

urls:[

"/userinfo/",

"/userinfo/add"/,

"/userinfo/del(\d+)/ ",

"/userinfo/edit(\d+)/ ",

]

}

2:{

"codes":{"list","add","del","edit"}

urls:[

"/order",

"/order/add"/,

"/order/del(\d+)/ ",

"/order/edit(\d+)/ ",

]

}

}


把这个字典存到session中

当你访问页面的时候我就知道你有什么权限

一个url对应一个code

多个url对应一个组

注意:

关联字段 null = True        数据库用的时候可以为空

关联字段 blank = True     admin用的时候可以为空

当出现这个错误的时候


解决办法

python manage.py migrate --fake 废弃

三、通过django-admin录入权限数据


- 先创建一个超级用户 python3 manage.py createsuperuser

- 用户名 root

- 密码 zhy123456

- 在admin.py 中

from rbac import models

admin.site.register(models.Permission)

admin.site.register(models.Role)

admin.site.register(models.UserInfo)

这样的话上去的是英文的,如果你想让中文显示就在类中加一个类

class Meta:

verbose_name_plural = "权限表"

- 当你给关联字段录入数据的时候会有错误提示,那么在类中你的那个关联字段在加一个属性blank = True 可以为空

permissions = models.ManyToManyField(to="Permission",verbose_name="具有的所有权限", blank=True)


四、编写登录

1.编写登录

2.如果用户验证成功就设置session

3.先查出当前用户的所有的权限

4.从这些权限中找到所有的url,吧这些url放到session中

这些都是在rbac里面的操作,如果我们做一些复杂的操作,可能会有好多的代码

我们写rbac的目的是做成一个公共的组件,为了让别人省事

我们在创建一个server的文件夹,里面创建一个init_permission的py文件。

结构化数据:方便以后做操作。。。


dict = {

1:{

"codes":["list","add","del","edit"]

urls:[

"/userinfo/",

"/userinfo/add"/,

"/userinfo/del(\d+)/ ",

"/userinfo/edit(\d+)/ ",

]

}

2:{

"codes":{"list","add","del","edit"}

urls:[

"/order",

"/order/add"/,

"/order/del(\d+)/ ",

"/order/edit(\d+)/ ",

]

}

}


5.拿到用户请求的url去session里面做验证

获取当前请求的url

获取session中保存当前用户的权限

然后开始验证

如果匹配成功就有权访问

如果匹配不成功就无权访问

用re去匹配的时候,re.match(/userinfo/,/userinfo/add) #都能匹配到

那么要记得在匹配正则的时候加个起始符和终止符regex = "^{0}$".format(url)

def login(request):

.....

设置session

def index(request):

....

获取session

def userinfo(request):

获取session

这样如果有好多个函数,就的重复好多代码,我们可以用中间件来处理

中间件和装饰器的区别:

中间件用来做批量处理

如果函数不多的话可以用加装饰器的方法

五、中间件:获取session,并且当用户匹配成功的时候,先把code保存在request中,方便以后判断

1、记得要配置白名单


2、必须继承MiddlewareMixin这个类


class MiddlewareMixin(object):

def __init__(self, get_response=None):

self.get_response = get_response

super(MiddlewareMixin, self).__init__()

def __call__(self, request):

response = None

if hasattr(self, 'process_request'):

response = self.process_request(request)

if not response:

response = self.get_response(request)

if hasattr(self, 'process_response'):

response = self.process_response(request, response)

return response


六、设计权限管理-----问题:在访问列表页面时,是否需要判断有无添加权限、有无删除权限、有无编辑权限。

views


def userinfo(request):

# 方式一

# Page_permission = request.permission_code_list

# 方式二:实例化

page_permission = BasePagePermission(request.permission_code_list)

print("page_permission",request.permission_code_list)

data_list = [

{"id":1,"name":"xxx1"},

{"id":2,"name":"xxx2"},

{"id":3,"name":"xxx3"},

{"id":4,"name":"xxx4"},

{"id":5,"name":"xxx5"},

]

return render(request,"userinfo.html",{"data_list":data_list,"page_permission":page_permission})


在模板userinfo.html中:两种使用方式

方式一:


{% if "add" in Page_permission %}

添加

{% endif %}

{% for row in data_list %}

{{ row.id }}{{ row.name }}


{% if "edit" in Page_permission %}


编辑


{% endif %}

{% if "del" in Page_permission %}

{

删除


{% endif %}

{% endfor %}


如果不想像上面一样每个都判断,那么还有第二种方法,

方式二:


吧permission_code_list处理一下

在views中定义一个类

class BasePagePermission(object):

def __init__(self,code_list):

self.code_list = code_list

def has_add(self):

if "add" in self.code_list:

return True

def has_del(self):

if "del" in self.code_list:

return True

def has_edit(self):

if "edit" in self.code_list:

return True

实例化:page_permission = BasePagePermission(request.permission_code_list)

在模板中

{% if page_permission.has_add %}

添加

{% endif %}

{% for row in data_list %}

{{ row.id }}{{ row.name }}


{% if page_permission.has_edit %}


编辑


{% endif %}

{% if page_permission.has_del %}

{

删除


{% endif %}

{% endfor %}


七、设计菜单管理-----问题:1、如何生成菜单

2、怎么让这些菜单分级显示并且如果当前访问的url权限默认展开如果是组内菜单就加粗或者变红

3、非菜单url,默认选中原菜单。(如果你是点击用户列表进来的,那么你看到页面了,如果你点击添加的时候,你的那个用户列看不见了,这就不好了。所以要设计当你点击添加按钮的时候,那个用户列表被默认选中)

菜单管理

菜单一

用户管理

权限管理

菜单二

订单管理

角色管理

分级做了菜单。这些菜单该显示什么菜单?是当前用户登录之后从数据库拿到这个用户拥有的权限,然后把权限搞成菜单

在表里面设计了一个组内菜单(自关联 ),当menu_gp_id为NULL就代表可以作为菜单


1、在初始化的时候,初始化权限信息,获取权限信息并放置到session中


menu_list = []

for item in permission_list:

tpl = {

"id":item["permissions__id"],

"title":item["permissions__title"],

"url":item["permissions__url"],

"menu_gp_id":item["permissions__menu_gp_id"],

"menu_id":item["permissions__group__menu_id"],

"menu_title":item["permissions__group__menu__caption"]

}

menu_list.append(tpl)

request.session[settings.PERMISSION_MENU_KEY] = menu_list


因为是要在页面上渲染,一般我们会在视图函数的render里面加{"":变量}这样渲染,

但是还有个更好用的方法:用自定义的标签

具体操作:

1、找到app创建一个templatetags的文件夹

2、然后在里面随便创建一个文件

3、导入form django.template import Library

register = Library()

方式一:

@register.simple_tag

def menu():

return "菜单"     这里返回啥页面上就显示啥

然后在母版里面导入mnue.html

{% load rbac %}

方式二:

@register.includsion_tag("xxx.html") #这里存放的是html文件,,,@register.includsion_tag("xxx.html")   自动会读这个文件并且把返回值拿到在页面上渲染

def menu():

return "菜单"     这里返回啥页面上就显示啥

“在母版中:{%menu_html  request%}     request是参数,记得要加上{% load rbac %}

4、注意:

如果有两个文件夹同名,避免发生冲突:就再创建一个文件夹包起来

2、去Session中获取菜单相关信息,匹配当前URL,生成菜单

①先把和菜单相关的所有字段取出来


menu_list = [

{'id': 1, 'title': '用户列表', 'url': '/userinfo/', 'menu_gp_id': None, 'menu_id': 2, 'menu_title': '菜单二'},

{'id': 2, 'title': '添加用户', 'url': '/userinfo/add/', 'menu_gp_id': 1, 'menu_id': 2, 'menu_title': '菜单二'},

{'id': 3, 'title': '删除用户', 'url': '/userinfo/del/(\\d+)/', 'menu_gp_id': 1, 'menu_id': 2, 'menu_title': '菜单二'},

{'id': 4, 'title': '编辑用户', 'url': '/userinfo/edit/(\\d+)/', 'menu_gp_id': 1, 'menu_id': 2, 'menu_title': '菜单二'},

{'id': 5, 'title': '订单列表','url': '/order/', 'menu_gp_id': None, 'menu_id': 1, 'menu_title': '菜单一'},

{'id': 6, 'title': '添加订单', 'url': '/order/add/', 'menu_gp_id': 2, 'menu_id': 1, 'menu_title': '菜单一'},

{'id': 7, 'title': '删除订单', 'url': '/order/del/(\\d+)/', 'menu_gp_id': 2, 'menu_id': 1, 'menu_title': '菜单一'},

{'id': 8, 'title': '编辑订单', 'url': '/order/edit/(\\d+)/', 'menu_gp_id': 2, 'menu_id': 1, 'menu_title': '菜单一'}

]


②然后循环列表找出可以作为菜单的权限

{

1: {'id': 1, 'title': '用户列表', 'url': '/userinfo/', 'menu_gp_id': None, 'menu_id': 2, 'menu_title': '菜单二'},

5: {'id': 5, 'title': '订单列表', 'url': '/order/', 'menu_gp_id': None, 'menu_id': 1, 'menu_title': '菜单一'}

}

③再次循环列表向上边的字典中添加active

{

1: {'id': 1, 'title': '用户列表', 'url': '/userinfo/', 'menu_gp_id': None, 'menu_id': 2, 'menu_title': '菜单二', 'active': True},

5: {'id': 5, 'title': '订单列表', 'url': '/order/', 'menu_gp_id': None, 'menu_id': 1, 'menu_title': '菜单一'}

}

④结构化数据(吧上面得到的数据化成下面这样格式的,方便以后使用)


{

1: {

'menu_id': 1,

'menu_title': '菜单一',

'active': None,

'children': [

{'title': '订单列表', 'url': '/order/', 'active': None}

]

}

2: {

'menu_id': 2,

'menu_title': '菜单二',

'active': True,

'children': [

{'title': '用户列表', 'url': '/userinfo/', 'active': True}

]

},

}


八、具体代码详见下一篇。。。。。。。。