RABC許可權控制(頁面操作角色,許可權和進行分配)
阿新 • • 發佈:2019-07-10
上一節主要說的是如何通過url進行許可權控制,這一節就說一下如何開發一個許可權控制的介面,這樣我們就能很方便的建立角色,並分配給使用者不同角色和不同許可權.
1.編寫角色管理頁面
這個編寫較為簡單,主要是通過modelform快速實現的,下面程式碼比較簡單,我就不多說了
效果圖如下:
程式碼如下:
def role_list(request): """角色列表""" roles_list = Role.objects.all() # 分頁 current_page_num = request.GET.get('page') pagination = MyPagination(current_page_num, roles_list.count(), request) roles_list = roles_list[pagination.start:pagination.end] return render(request, 'rbac/role_list.html', {'roles_list': roles_list, 'pagination':pagination}) def role_operate(request, edit_id=None): """角色操作""" role_obj = Role.objects.filter(pk=edit_id).first() if request.method == "POST": role_form = RoleModelForm(request.POST, instance=role_obj) if role_form.is_valid(): role_form.save() return redirect(reverse('rbac:role_list')) return render(request, 'rbac/role_operate.html', {'role_form': role_form}) role_form = RoleModelForm(instance=role_obj) return render(request, 'rbac/role_operate.html', {'role_form': role_form, 'role_obj': role_obj}) def role_del(request, del_id): """刪除角色""" Role.objects.filter(pk=del_id).delete() return redirect(reverse('rbac:role_list'))
RoleModelForm,這裡就只有一個name欄位,不給角色欄位是建立完使用者後到時再分配
class RoleModelForm(forms.ModelForm): """角色的modelform""" class Meta: model = Role fields = ['name'] error_messages = { 'name': {'required': '角色名稱不能為空'} } widgets = { 'name': wid.TextInput(attrs={'class': 'form-control'}) }
2.編寫選單許可權管理頁面
這個也不是特別難,因為主要就是兩張表在頁面渲染的事情,效果圖如下:
程式碼如下:
def menu_list(request): """選單許可權列表""" # 獲取所有的選單 menu_list = Menu.objects.all() # 選單管理目前選擇的選單名稱id mid = request.GET.get('mid') # 如果mid有值則通過二級選單中選單id是一級選單的和二級選單下子許可權id屬於一級選單的全部找出來顯示,沒有則顯示全部選單 if mid: permission_list = Permission.objects.filter(Q(parent__menu__id=mid) | Q(menu_id=mid)) else: permission_list = Permission.objects.all() # 查詢出許可權表中的所有欄位 all_permission_list = permission_list.values('id', 'url', 'title', 'url_name', 'menu_id', 'parent_id', 'menu__title') # 把所有選單以字典形式儲存在字典中 all_permission_dict = {} # 第一次for迴圈將二級選單加入字典中 for permission in all_permission_list: menu_id = permission.get('menu_id') # 有menu_id就證明是二級選單,加入字典 if menu_id: permission['children'] = [] all_permission_dict[permission['id']] = permission # 第二次for迴圈將三級選單(子許可權)加入到二級選單的children中 for permission in all_permission_list: parent_id = permission.get('parent_id') if parent_id: all_permission_dict[parent_id]['children'].append(permission) return render(request, 'rbac/menu_list.html', {'menu_list': menu_list, 'all_permission_dict': all_permission_dict, 'mid': mid}) def menu_operate(request, edit_id=None): """選單管理操作""" menu_obj = Menu.objects.filter(pk=edit_id).first() if request.method == "POST": form_obj = MenuModelForm(request.POST, instance=menu_obj) if form_obj.is_valid(): form_obj.save() return redirect(reverse('rbac:menu_list')) return render(request, 'rbac/menu_operate.html', {'form_obj': form_obj}) form_obj = MenuModelForm(instance=menu_obj) return render(request, 'rbac/menu_operate.html', {'form_obj': form_obj, 'menu_obj': menu_obj}) def menu_del(request, del_id): """選單管理刪除""" Menu.objects.filter(pk=del_id).delete() return redirect(reverse('rbac:menu_list')) def permission_operate(request, edit_id=None): """許可權管理操作""" permission_obj = Permission.objects.filter(pk=edit_id).first() if request.method == "POST": form_obj = PermissionModelForm(request.POST, instance=permission_obj) if form_obj.is_valid(): form_obj.save() return redirect(reverse('rbac:menu_list')) return render(request, 'rbac/permission_operate.html', {'form_obj': form_obj}) form_obj = PermissionModelForm(instance=permission_obj) return render(request, 'rbac/permission_operate.html', {'form_obj': form_obj, 'permission_obj': permission_obj}) def permission_del(request, del_id): """許可權管理刪除""" Permission.objects.filter(pk=del_id).delete() return redirect(reverse('rbac:menu_list'))
頁面對上面兩個資料的for迴圈展示(這也是最主要的資料展示部分)
{% for p_permission in all_permission_dict.values %} <tr class="parent" id="{{ p_permission.id }}"> <td class="title"><i class="fa fa-caret-down"></i>{{ p_permission.title }}</td> <td>{{ p_permission.url }}</td> <td>{{ p_permission.url_name }}</td> <td>是</td> <td>{{ p_permission.menu__title }}</td> <td> <a href="{% url 'rbac:permission_edit' p_permission.id %}"><i class="fa fa-edit"></i></a> <a style="margin-left: 10px" href="{% url 'rbac:permission_del' p_permission.id %}"><i class="fa fa-trash-o text-danger"></i></a> </td> </tr> {% for c_permission in p_permission.children %} <tr pid="{{ c_permission.parent_id }}"> <td style="padding-left: 20px">{{ c_permission.title }}</td> <td>{{ c_permission.url }}</td> <td>{{ c_permission.url_name }}</td> <td></td> <td></td> <td> <a href="{% url 'rbac:permission_edit' c_permission.id %}"><i class="fa fa-edit"></i></a> <a style="margin-left: 10px" href="{% url 'rbac:permission_del' c_permission.id %}"><i class="fa fa-trash-o text-danger"></i></a> </td> </tr> {% endfor %} {% endfor %}
點選二級選單顯示和隱藏,這裡巧妙的用到了二級選單的id和子許可權的parent_id相等去顯示和隱藏,下面是顯示隱藏的jquery程式碼
<script> $('.permisson-area').on('click', 'tr .title', function () { var caret = $(this).find('i'); var id = $(this).parent().attr('id'); if (caret.hasClass('fa-caret-right')){ caret.removeClass('fa-caret-right').addClass('fa-caret-down'); $(this).parent().nextAll('tr[pid="' + id + '"]').removeClass('hide'); }else{ caret.removeClass('fa-caret-down').addClass('fa-caret-right'); $(this).parent().nextAll('tr[pid=' + id + ']').addClass('hide'); } }) </script>
3.分配許可權管理頁面編寫(這個比較麻煩,主要是資料結構比較複雜,巢狀太多層,注意這還是二級選單)
實現的效果圖如下:
程式碼如下,備註在程式碼中寫了,詳細請看程式碼:
def distribute_permissions(request): """分配許可權""" # uid是前端提交的使用者id,rid是前端提交的角色id uid = request.GET.get('uid') rid = request.GET.get('rid') # 使用者新增角色,由於有多個from表單所以給每個from表單一個postType if request.method == 'POST' and request.POST.get('postType') == 'role' and uid: user = User.objects.filter(id=uid).first() if not user: return HttpResponse('使用者不存在') # 因為是多對多的關係,所以用set就可以直接更新資料了,記得set裡面必須是可迭代物件,所以getlist user.roles.set(request.POST.getlist('roles')) # 角色新增許可權 if request.method == 'POST' and request.POST.get('postType') == 'permission' and rid: role = Role.objects.filter(id=rid).first() if not role: return HttpResponse('角色不存在') role.permissions.set(request.POST.getlist('permissions')) # 所有使用者,介面使用者展示 user_list = User.objects.all() # 取得當前使用者的所有角色 user_has_roles = User.objects.filter(id=uid).values('id', 'roles') # 獲取使用者擁有的角色id,資料結構是{角色id: None},這種資料結構推薦,到時直接in就能判斷了,效率高 user_has_roles_dict = {item['roles']: None for item in user_has_roles} # 角色列表(所有角色),介面使用者展示 role_list = Role.objects.all() # 如過選中了角色,那麼就根據角色id拿到所有的許可權 if rid: role_has_permissions = Role.objects.filter(id=rid).values('id', 'permissions') # 如果只選中了使用者沒有選擇角色,那麼就通過使用者的角色去拿對應的所有許可權 elif uid and not rid: user = User.objects.filter(id=uid).first() if not user: return HttpResponse('使用者不存在') role_has_permissions = user.roles.values('id', 'permissions') else: # 都沒選中,就是初始化狀態,介面不勾選任何許可權選單 role_has_permissions = [] # 獲取角色擁有的許可權id,資料結構是{許可權id: None} role_has_permissions_dict = {item['permissions']: None for item in role_has_permissions} # 以列表形式存放所有的選單資訊 all_menu_list = [] # 查詢出所有選單 menu_queryset = Menu.objects.values('id', 'title') # 以字典形式存放所有的選單資訊 menu_dict = {} # 這個for迴圈的作用是將一級選單資訊分別放入了menu_dict字典和all_menu_list列表中 for item in menu_queryset: item['children'] = [] # 存放二級選單(父許可權) menu_dict[item['id']] = item # 注意這裡是將item物件賦值給了item['id'],所以menu_dict和all_menu_list是一起變化的 all_menu_list.append(item) """ 下面是這兩個的資料結構,字典套字典,然後children欄位子選單就是列表,然後反覆這樣巢狀 menu_dict = {'menu_id': {'id':1, 'title': 'xxx', 'children': [ {'id', 'title', 'menu_id', 'children': [ {'id', 'title', 'parent_id'} ]}, ]}, None: {'id': None, 'title': '其他', 'children': [{'id', 'title', 'parent_id'}]} } all_menu_list = [ {'id':1, 'title': 'xxx', 'children': [ {'id', 'title', 'menu_id', 'children': [ {'id', 'title', 'parent_id'} ]}, ]}, {'id': None, 'title': '其他', 'children': [{'id', 'title', 'parent_id'}]} ] """ # 像首頁這些不屬於任何一級選單,所以可以歸屬於other下面 other = {'id': None, 'title': '其他', 'children': []} # 兩個資料結構分別加入other all_menu_list.append(other) menu_dict[None] = other # 查詢二級選單的許可權資訊 parent_permission = Permission.objects.filter(menu__isnull=False).values('id', 'title', 'menu_id') # 二級選單資訊字典 parent_permission_dict = {} """ parent_permission_dict = {父許可權id: {'id', 'title', 'menu_id', 'children': [ {'id', 'title', 'parent_id'} ]} } """ for per in parent_permission: per['children'] = [] # 存放子許可權 nid = per['id'] menu_id = per['menu_id'] # 以二級選單id為鍵,二級選單資訊為值加入到二級選單字典中 parent_permission_dict[nid] = per # 一級選單字典將二級選單加入到children下,注意一級選單列表資料結構也會跟著增加(py記憶體使用導致) menu_dict[menu_id]['children'].append(per) # 類似上面的操作,將不是二級選單的許可權全部找出來,包括子許可權和other node_permission = Permission.objects.filter(menu__isnull=True).values('id', 'title', 'parent_id') for per in node_permission: pid = per['parent_id'] # 如果不是子許可權,就將資訊加入到other的children下 if not pid: menu_dict[None]['children'].append(per) continue # 是子許可權就加入到二級選單的children下,因為menu_dict存放的是二級選單的物件,所以此時menu_dict就有了各個層級的資料 parent_permission_dict[pid]['children'].append(per) return render(request, 'rbac/distribute_permissions.html', { 'user_list': user_list, 'role_list': role_list, 'user_has_roles_dict': user_has_roles_dict, 'role_has_permissions_dict': role_has_permissions_dict, 'all_menu_list': all_menu_list, 'uid': uid, 'rid': rid, })
前端程式碼:
{% extends 'layout.html' %} {% block css %} <style> .user-area ul { padding-left: 20px; } .user-area li { cursor: pointer; padding: 2px 0; } .user-area li a { display: block; } .user-area li.active { font-weight: bold; color: red; } .user-area li.active a { color: red; } .role-area tr td a { display: block; } .role-area tr.active { background-color: #f1f7fd; border-left: 3px solid #fdc00f; } .permission-area tr.root { background-color: #f1f7fd; cursor: pointer; } .permission-area tr.root td i { margin: 3px; } .permission-area .node { } .permission-area .node input[type='checkbox'] { margin: 0 5px; } .permission-area .node .parent { padding: 5px 0; } .permission-area .node label { font-weight: normal; margin-bottom: 0; font-size: 12px; } .permission-area .node .children { padding: 0 0 0 20px; } .permission-area .node .children .child { display: inline-block; margin: 2px 5px; } table { font-size: 12px; } .panel-body { font-size: 12px; } .panel-body .form-control { font-size: 12px; } </style> {% endblock %} {% block content %} <div class="container-fluid" style="margin-top: 20px"> <div class="col-sm-3 user-area"> <div class="panel panel-default"> <div class="panel-heading"><i class="fa fa-user"></i> 使用者資訊</div> <div class="panel-body"> <ul> {% for user in user_list %} <li class={% if user.id|safe == uid %}"active"{% endif %}> <a href="?uid={{ user.id }}">{{ user.name }}</a> </li> {% endfor %} </ul> </div> </div> </div> <div class="col-sm-3 role-area"> <form action="" method="post"> {% csrf_token %} <input type="hidden" name="postType" value="role"> <div class="panel panel-default"> <div class="panel-heading"><i class="fa fa-book"></i> 角色 {% if uid %} <button type="submit" style="padding: 2px 6px;position: relative;top: -3px;" class="btn btn-success pull-right"><i style="margin-right: 2px" class="fa fa-save"></i> 儲存 </button> {% endif %} </div> <div class="panel-body"> <span style="color: darkgray">提示:點選使用者後才能為其分配角色</span> </div> <table class="table table-hover"> <thead> <tr> <th>角色</th> <th>選擇</th> </tr> </thead> <tbody> {% load my_tag %} {% for role in role_list %} <tr {% if role.id|safe == rid %}class="active"{% endif %}> <td><a href="?{% get_role_url request role.id %}">{{ role.name }}</a></td> <td> {% if role.id in user_has_roles_dict %} <input type="checkbox" name="roles" value="{{ role.id }}" checked> {% else %} <input type="checkbox" name="roles" value="{{ role.id }}"> {% endif %} </td> </tr> {% endfor %} </tbody> </table> </div> </form> </div> <div class="col-sm-6 permission-area"> <form action="" method="post"> {% csrf_token %} <input type="hidden" name="postType" value="permission"> <div class="panel panel-default"> <div class="panel-heading"><i class="fa fa-reddit"></i> 許可權分配</div> {% if rid %} <button type="submit" style="padding: 2px 6px;position: relative;top: -32px; margin-right: 10px;" class="btn btn-success pull-right"><i style="margin-right: 2px" class="fa fa-save"></i> 儲存 </button> {% endif %} <div class="panel-body"> <span style="color: darkgray">提示:點選角色後,才能為其分配許可權</span> </div> <table class="table"> <tbody class="permission-tbody"> {% for item in all_menu_list %} <tr class="root"> <td><i class="fa fa-caret-down"></i>{{ item.title }}</td> </tr> <tr class="node"> <td> {% for node in item.children %} <div class="parent"> {% if node.id in role_has_permissions_dict %} <input id="permission_{{ node.id }}" name="permissions" value="{{ node.id }}" type="checkbox" checked> {% else %} <input id="permission_{{ node.id }}" name="permissions" value="{{ node.id }}" type="checkbox"> {% endif %} <label for="permission_{{ node.id }}">{{ node.title }}</label> </div> <div class="children"> {% for child in node.children %} <div class="child"> {% if child.id in role_has_permissions_dict %} <input id="permission_{{ child.id }}" name="permissions" value="{{ child.id }}" type="checkbox" checked> {% else %} <input id="permission_{{ child.id }}" name="permissions" value="{{ child.id }}" type="checkbox"> {% endif %} <label for="permission_{{ child.id }}">{{ child.title }}</label> </div> {% endfor %} </div> {% endfor %} </td> </tr> {% endfor %} </tbody> </table> </div> </form> </div> </div> {% endblock %} {% block js %} <script> $('.permission-tbody').on('click', '.root', function () { var caret = $(this).find('i'); if (caret.hasClass('fa-caret-right')) { caret.removeClass('fa-caret-right').addClass('fa-caret-down'); $(this).next('.node').removeClass('hide'); } else { caret.removeClass('fa-caret-down').addClass('fa-caret-right'); $(this).next('.node').addClass('hide'); } }) </script> {% endblock %}
至此,頁面大致開發完成,單獨開發完成rbac之後,還得嵌入專案中去,大致也說一下遷移的過程:
rbac應用於其他專案流程 1.拷貝rbac到新專案 2.在settings中註冊rabc app 3.資料庫遷移 首先先刪除原有migrations下的檔案,再執行資料庫遷移命令 python manage.py makemigrations python manage.py migrate 4.在根目錄下的urls.py中新增rbac相關的url re_path(r'^rbac/', include('rbac.urls', namespace='rbac')) 5.layout.html的建立和編寫,因為rbac中的模板都繼承了這個 6.錄入許可權資訊 角色管理 許可權管理 7.分配許可權 先使用者關聯,原系統使用者表一對一關聯rbac使用者表 from rbac.models import User user = models.OneToOneField(User, null=True, blank=True, on_delete=models.CASCADE) 給使用者分角色和許可權 8.登入應用許可權 登入成功後獲取rbac的user_obj,然後初始化使用者資訊 應用許可權校驗中介軟體 9.注意模板layout內容和名稱 10.應用麵包屑導航欄,中間會遇到很多樣式js等不同,慢慢除錯吧 11.許可權控制到按鈕級別
&n