crm-3权限

1.权限基本实现

  rbac: rbac基于角色的权限控制 ,权限本质就是url 

  权限表: url列表  角色表: 一个角色固定访问一些url的地址  用户表: 用户可以绑定角色 ,用户拥有了角色的权限

  生成表数量: url权限表 + 角色表 + 用户表 + 权限角色对多对关系表 + 角色用户多对多关系表

2.rbac组件实现

  基于角色的权限控制创建一个组件实现复用

  1)创建权限表 + 角色表 + 用户表(因为之前项目有用户表 ,此表仅用作抽象类用于子类继承 ,建立映射但不做数据迁移)

####rbac/models
from django.db import models


# Create your models here.
class Permisssion(models.Model):
    url = models.CharField('含正则url', max_length=128)
    title = models.CharField('权限标题', max_length=32, blank=True, null=True)
    is_menu = models.BooleanField('是否为菜单', default=False)


class Role(models.Model):
    name = models.CharField('角色名称', max_length=32)
    permissions = models.ManyToManyField('Permisssion', verbose_name='角色拥有权限')


class rbacuser(models.Model):
    name = models.CharField('用户名', max_length=32)
    password = models.CharField('密码', max_length=32)
    roles = models.ManyToManyField(Role, verbose_name='用户拥有角色')    #这里面不在使用反射获取指定多对多 ,使用类名即可

    # 用户表需要作为基类 让已有的user体系表去继承
    class Meta:
        abstract = True


####crm/models
class User(rbacuser, models.Model):      #直接继承
......


####数据迁移

  2)权限的管理 ,使用admin系统对权限进行直观的管理

####rbac/admin
from django.contrib import admin

# Register your models here.
from rbac import models
from crm.models import User

# 设置配置类对某张表中展示的表字段做页面修改查看
class PermissionAdmin(admin.ModelAdmin):
list_display = ['pk', 'title', 'url', 'is_menu']
list_editable = ['url', 'is_menu']

# 注册时该表引用配置类
admin.site.register(models.Permisssion, PermissionAdmin)
admin.site.register(models.Role)
admin.site.register(User)

  3)权限的流程控制

    用户请求----wsgi----中间件----路由----view----返回数据

    中间件: 增加白名单url(用于获取session)   ,校验session权限 

    返回数据: 权限放入session中 

 

  4)用户登录功能 (白名单成员)

####urls
    url(r'^login/', views.login.as_view(), name='login')


###crm/view
class login(View):
    def get(self, request):
        return render(request, 'login.html')

    def post(self, request):
        name = request.POST.get('username')
        password = request.POST.get('password')
        md5 = hashlib.md5()
        md5.update(password.encode('utf-8'))
        md5_pwd = md5.hexdigest()
        print(md5_pwd, name)
        obj = models.User.objects.filter(name=name, password=md5_pwd)
        print(obj)
        if obj:
            return redirect(reverse('crm:userlist'))
        return HttpResponse('账号密码错误')

####html
{% load static %}
<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <link rel="stylesheet" href="{% static 'plugins/bootstrap/css/bootstrap.css' %} "/>
    <link rel="stylesheet" href="{% static 'plugins/font-awesome/css/font-awesome.css' %} "/>
    <link rel="stylesheet" href="{% static 'css/commons.css' %} "/>
    <link rel="stylesheet" href="{% static 'css/nav.css' %} "/>
    <link rel="stylesheet" href="{% static 'css/menu.css' %} "/>

    <title>login</title>
</head>
<body>
<form class="form-horizontal" method="post">
    {% csrf_token %}
    <div class="form-group">
        <label for="inputEmail3" class="col-sm-2 control-label">用户名</label>
        <div class="col-sm-10">
            <input type="text" class="form-control" id="inputEmail3" name="username">
        </div>
    </div>
    <div class="form-group">
        <label for="inputPassword3" class="col-sm-2 control-label">Password</label>
        <div class="col-sm-10">
            <input type="password" class="form-control" id="inputPassword3" name="password" placeholder="Password">
        </div>
    </div>
    <div class="form-group">
        <div class="col-sm-offset-2 col-sm-10">
            <div class="checkbox">
                <label>
                    <input type="checkbox"> Remember me
                </label>
            </div>
        </div>
    </div>
    <div class="form-group">
        <div class="col-sm-offset-2 col-sm-10">
            <button type="submit" class="btn btn-default">Sign in</button>
        </div>
    </div>
</form>
<script src="{% static 'js/jquery-3.3.1.min.js' %} "></script>
<script src="{% static 'plugins/bootstrap/js/bootstrap.js' %} "></script>
<script src="{% static 'js/menu.js' %} "></script>
</body>
</html>
View Code

  5)白名单功能  

    settings中定义白名单 ,login算入白名单

    rbac应用中创建中间件rbac/middlewares/rbac.py ,使用process_request完成校验白名单放行! 

    settings注册中间件注意顺序

####rbac中间件
from django.utils.deprecation import MiddlewareMixin
from django.shortcuts import HttpResponse
from untitled import settings
import re


class RbacMiddleWare(MiddlewareMixin):
    def process_request(self, request):
        url = request.path_info
        for i in settings.VALID_LIST:
            if re.match(i, url):
                return
        return HttpResponse('不在白名单中呢!!')

####settings配置中间件
    'rbac.middlewares.rbac.RbacMiddleWare',

  6)权限实现访问控制

   

  login功能关键: 找到该用户的角色 ,并取出所有的权限去重, 放入session中

###crm/view 的login功能
from django.shortcuts import render, redirect, reverse, HttpResponse
from django.views import View
from crm import models
import hashlib


# Create your views here.
class login(View):
    def get(self, request):
        return render(request, 'login.html')

    def post(self, request):
        name = request.POST.get('username')
        password = request.POST.get('password')
        md5 = hashlib.md5()
        md5.update(password.encode('utf-8'))
        md5_pwd = md5.hexdigest()
        print(md5_pwd, name)
        obj = models.User.objects.filter(name=name, password=md5_pwd).first()
        if not obj:
            return HttpResponse('账号密码错误')

        # 如果用户登录成功!!此时取出该用户的权限放入session
        # 该用户的(角色名 权限名 权限url)放入permisssion__query中去重(角色定义url重叠)
        else:
            permission_query = obj.roles.all().filter(permissions__url__isnull=False).values(
                # 'name',
                'permissions__title',
                'permissions__url',
            ).distinct()
            print(list(permission_query))

        # 将对象列表转换为列表 ! 并将用户权限存入session
        request.session['permissions'] = list(permission_query)

        # 此刻该用户登录成功
        request.session['is_login'] = True

        return redirect(reverse('crm:userlist'))

  中间件关键 : 判断白名单 --- 判断登录状态 --- 登录后不需权限访问的url --- 登录后需要权限访问的url(从login给的session中取出对应的权限校验一下)

####rbac/middlerware/rbac   中间件校验
from django.utils.deprecation import MiddlewareMixin
from django.shortcuts import HttpResponse
from untitled import settings
import re


class RbacMiddleWare(MiddlewareMixin):
    def process_request(self, request):
        url = request.path_info
        # 白名单校验
        for i in settings.VALID_LIST:
            if re.match(i, url):
                return
        # 登录状态校验
        is_login = request.session.get('is_login', None)
        if not is_login:
            return HttpResponse('未登录')
        # 已登录 判断url是否不需要权限访问
        for i in settings.NO_PERMISSION_LIST:
            if re.match(i, url):
                return
        # 已登录 不在白名单 需要权限访问的url ,查看用户的权限
        permission_list = request.session['permissions']
        for i in permission_list:
            re_url = i['permissions__url']
            print(re_url)
            if re.match(re_url, url):
                return

        return HttpResponse('无权访问!!')

 

3.后端模板加工三种方法

  filter        #自定义管道过滤器 ,通过后端对变量做二次加工

  simple_tag     #展示执行函数的返回值

  inclusion_tag    #生成一小段代码 ,对比include而言能使用传参

  使用方法: 三种方法的使用流程基本一致 ,仅装饰器不同

    1)app下创建templatetags的py包

    2)包中创建py文件

    3)python文件写入

      from django import template    

      register = template.Library()

  filter 自定义加工变量(简单 ,但是参数唯一)

##rbac/templatetags/filter_test.py
from django import template

register = template.Library()

# 因为前端使用只有一个参数(value|add_str:arg)
@register.filter
def add_str(value, arg):
    return '{}={}'.format(value, arg)

##html中引用  注意字符串需要加引号 ,也可以是后端内容
    {% load filter_test %}
    {{ 'ppp'|add_str:'lolo' }}

  simple_tag 显示执行函数的返回值 (简单 ,还可以传入任意数量参数)

##rbac/templatetags/filter_test.py
from django import template

register = template.Library()

@register.simple_tag
def add_test(*args, **kwargs):
    return '位置参数{} 关键字参数{}'.format('-'.join(args), '-'.join(kwargs.values()))


##html中应用 ,参数随便写
    {% load my_define %}
    {% add_test 'a' 'b' age='18' name='屈冠文' %}

  inclusion_tag 生成代码段 ,需要一个html文件存放代码段 ,在定义函数中引用 ,返回值放入html代码段 ,并在模板中引用函数执行(复杂 ,可以生成标签功能较好)

    1)准备html代码段 2)准备函数返回值给到代码段处理 3)模板中执行函数返回代码段内容

##准备代码片段 rbac/templates/menu.html
{% for foo in num %}
    <li>foo</li>
{% endfor %}


##返回值给到代码片段 rbac/templatetags/filter_test.py
from django import template

register = template.Library()

@register.inclusion_tag('menu.html')
def show_range(num):
    return {'num': range(num)}


##html中load引用执行函数展示返回值
{% load my_define %}
{% block menu_list %}
    {% show_range 10 %}
{% endblock %}

 

4. 根据权限实现一级菜单

  1)动态生成一级菜单

    login的登录函数 :

      登陆成功后从数据库拿出用户的所有权限 ,并将权限放入permission_dict字典 ,再将字典中权限是菜单的放入menu_list列表中

      将权限字典与菜单列表放入session中保存给用户

class login(View):
    def get(self, request):
        return render(request, 'login.html')

    def post(self, request):
        name = request.POST.get('username')
        password = request.POST.get('password')
        md5 = hashlib.md5()
        md5.update(password.encode('utf-8'))
        md5_pwd = md5.hexdigest()
        print(md5_pwd, name)
        obj = models.User.objects.filter(name=name, password=md5_pwd).first()
        if not obj:
            return HttpResponse('账号密码错误')

        # 如果用户登录成功!!此时取出该用户的权限放入session
        # 该用户的(角色名 权限名 权限url)放入permisssion__query中去重(角色定义url重叠)
        else:
            permission_query = obj.roles.all().filter(permissions__url__isnull=False).values(
                # 'name',
                'permissions__title',
                'permissions__url',
                'permissions__is_menu',
                'permissions__name',
            ).distinct()
            # print(permission_query)

        permission_dict = {}
        menu_list = []

        for i in permission_query:
            permission_dict[i['permissions__name']] = {'url': i['permissions__url']}
            if i['permissions__is_menu']:
                menu_list.append({'title': i['permissions__title'], 'url': i['permissions__url']})

        # 将对象列表转换为列表 ! 并将用户权限存入session
        request.session[settings.PERMISSION_SESSION_KEY] = permission_dict
        print(permission_dict)
        request.session[settings.MENU_SESSION_KEY] = menu_list
        # 此刻该用户登录成功
        request.session['is_login'] = True

        return redirect(reverse('crm:deplist'))
View Code

    rbac中间件:

      遇到需要权限才能访问的url ,去session中将权限字典的value取出匹配 ,匹配成功就返回None表示通过

from django.utils.deprecation import MiddlewareMixin
from django.shortcuts import HttpResponse
from untitled import settings
import re


class RbacMiddleWare(MiddlewareMixin):
    def process_request(self, request):
        url = request.path_info
        # 白名单校验
        for i in settings.VALID_LIST:
            if re.match(i, url):
                return
        # 登录状态校验
        is_login = request.session.get('is_login', None)
        if not is_login:
            return HttpResponse('未登录')
        # 已登录 判断url是否不需要权限访问
        for i in settings.NO_PERMISSION_LIST:
            if re.match(i, url):
                return
        # 已登录 不在白名单 需要权限访问的url ,查看用户的权限
        permission_dict = request.session[settings.PERMISSION_SESSION_KEY]
        for i in permission_dict.values():
            re_url = '^'.format(i['url'])
            if re.match(re_url, url):
                return

        return HttpResponse('无权访问!!')
View Code

    /rbac/templatetags/my_define自定义代码段后台:

      使用inclusion_tag制作菜单代码段 ,函数接收模板中传过的request参数 ,取出request中的session的菜单列表, 对菜单列表中匹配的当前访问菜单添加active标记 ,将菜单列表返回给menu代码段

@register.inclusion_tag('menu.html')
def menu(request):
    url_now = request.path_info
    menu_list = request.session[settings.MENU_SESSION_KEY]
    for i in menu_list:
        # 重点标记当前访问的菜单
        if re.match('^{}'.format(i['url']), url_now):
            i['class'] = 'active'
    return {'menu_list': menu_list}
View Code

    /rbac/template/menu.html自定义代码段前端:

      代码段接收到my_define的返回值进行生成代码段 ...

{% for menu in menu_list %}
   <div class="static-menu">
    <a class="{{ menu.class }}" href="{{ menu.url }}"><span class="icon-wrap"><i class="fa fa-map-o" aria-hidden="true"></i></span>{{ menu.title }}</a>
    </div>
{% endfor %}
View Code

    /layout.html模板

      菜单是通用内容放在了 ,所以放在了母板中 ,母板中执行my_define ,并展示menu.html

            {% load my_define %}
            {% menu request %}
View Code

  2)限制到按钮级别 ,展示部门中有新增的一个按钮 ,但是必须拥有add权限的用户才能显示使用 (后续edit也需要修改)

    原理: 通过模板中传过来的request对象与参数(url别名), 在permission_dict字典中判断如果有url别名返回true ,其中url别名是个关键点 ,url别名是专用去匹配前端的参数的 (前面构建数据格式已经将别名转为字典的key值)

####校验某个按钮权限my_define.py 
 @register.filter def has_permission(request, name): if name in request.session[settings.PERMISSION_SESSION_KEY]: return True

####母版文件中判断是否给该用户展示layout.html
{% load my_define %}
{% if request|has_permission:'dep_add' %}
<a class="btn btn-sm btn-primary" href="{% url 'crm:depadd' %}"><span class="icon-wrap"><i
class="fa fa-map-o"
aria-hidden="true"></i></span>新增
</a>
{% endif %}

 

    

 

 

 

 

 

         

 

posted @ 2019-09-29 14:40  屈冠文  阅读(163)  评论(0编辑  收藏  举报