權(quán)限分組管理
一专酗、權(quán)限分組列表
1. 接口設(shè)計(jì)
- 接口說(shuō)明
類(lèi)目 | 說(shuō)明 |
---|---|
請(qǐng)求方法 | GET |
url定義 | /admin/groups/ |
參數(shù)格式 | 無(wú)參數(shù) |
-
返回結(jié)果
html
2. 后端代碼
-
視圖
# 在myadmin/views.py中定義如下視圖 class GroupListView(View): """ 分組列表視圖 url:/admin/groups/ """ def get(self, request): groups = Group.objects.only('name').all() return render(request, 'myadmin/group/group_list.html', context={'groups': groups})
-
路由
# 在myadmin/urls.py中添加如下路由 path('groups/', views.GroupsView.as_view(), name='group_list')
3. 前端代碼
-
html
<!-- 新建myadmin/group/group_list.html模板 --> {% extends 'admin/content_base.html' %} {% load static %} {% load news_template_filters %} {% block page_header %} 系統(tǒng)設(shè)置 {% endblock %} {% block page_option %} 權(quán)限分組 {% endblock %} {% block content %} <div class="box"> <div class="box-header with-border"> <h3 class="box-title">分組列表</h3> <div class="box-tools"> </div> </div> <!-- /.box-header --> <div class="box-body"> <div style="margin-bottom: 10px"> </div> <table class="table table-bordered"> <tbody> <tr> <th>#</th> <th>組名</th> <th>菜單</th> </tr> {% for group in groups %} <tr> <td style="width: 40px" data-url="{% url 'admin:update_group' group.id %}"><a href="#">{{ forloop.counter }}</a></td> <td>{{ group.name }}</td> <td> {% for permis in group.permissions.all %} {{ permis.name }}/ {% empty %} 暫未分配權(quán)限 {% endfor %} </td> </tr> {% endfor %} </tbody> </table> </div> </div> {% endblock %}
二择葡、權(quán)限分組詳情頁(yè)
1.接口設(shè)計(jì)
- 接口說(shuō)明:
類(lèi)目 | 說(shuō)明 |
---|---|
請(qǐng)求方法 | GET |
url定義 | /admin/group/<int:group_id>/ |
參數(shù)格式 | 路徑參數(shù) |
- 參數(shù)說(shuō)明:
參數(shù)名 | 類(lèi)型 | 是否必須 | 描述 |
---|---|---|---|
group_id | 整數(shù) | 是 | 分組id |
-
返回?cái)?shù)據(jù)
返回html表單
2.后端代碼
-
視圖
# 在myadmin/views.py中添加如下視圖 class GroupUdateView(View): """ 分組更新視圖 url: /admin/group/<int:group_id>/ """ def get(self, request, group_id): group = Group.objects.filter(id=group_id).first() if group: form = GroupModeForm(instance=group) permissions = group.permissions.only('id').all() else: form = GroupModeForm() permissions = [] menus = models.Menu.objects.only('name', 'permission_id').select_related('permission').filter(is_delete=False, parent=None) return render(request, 'myadmin/group/group_detail.html', context={ 'form': form, 'menus': menus, 'permissions': permissions })
-
路由
# 在admin/urls.py中添加如下路由 path('group/<int:group_id>/', views.GroupUdateView.as_view(), name='update_group')
-
表單
# 在 admin/forms.py中添加如下表單 class GroupModeForm(forms.ModelForm): permissions = forms.ModelMultipleChoiceField(queryset=None, required=False, help_text='權(quán)限', label='權(quán)限') def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.fields['permissions'].queryset = Permission.objects.filter(menu__is_delete=False) class Meta: model = Group fields = ['name', 'permissions']
3.前端代碼
-
html
<!-- 創(chuàng)建模板myadmin/group/group_detail.html --> {% extends 'myadmin/base/content_base.html' %} {% load static %} {% load admin_customer_tags %} {% block page_header %} 系統(tǒng)設(shè)置 {% endblock %} {% block page_option %} 權(quán)限分組 {% endblock %} {% block content %} <div class="box box-primary"> <div class="box-header with-border"> <h3 class="box-title">分組詳情</h3> </div> <!-- /.box-header --> <!-- form start --> <div class="box-body"> <div class="row"> <div class="col-md-3"></div> <div class="col-md-6"> <form class="form-horizontal"> {% csrf_token %} {% for field in form %} {% if field.name == 'permissions' %} <div class="form-group {% if field.errors %}has-error{% endif %}"> <label for="{{ field.id_for_label }}" class="col-sm-2 control-label">{{ field.label }}</label> <div class="col-sm-10"> {% for error in field.errors %} <label class="control-label" for="{{ field.id_for_label }}">{{ error }}</label> {% endfor %} {% for menu in menus %} <div class="row" style="margin: 0"> <div class="checkbox"> <label for="menu_{{ menu.permission.id }}"> <input {% if menu.permission in permissions %}checked{% endif %} type="checkbox" name="permissions" id="menu_{{ menu.permission.id }}" value="{{ menu.permission.id }}">{{ menu.name }} </label> </div> {% for child in menu.children.all %} <div class="checkbox col-sm-offset-1"> <label for="menu_{{ child.permission.id }}"> <input type="checkbox" {% if child.permission in permissions %}checked{% endif %} name="permissions" id="menu_{{ child.permission.id }}" value="{{ child.permission.id }}">{{ child.name }} </label> </div> {% endfor %} </div> {% endfor %} </div> </div> {% else %} <div class="form-group {% if field.errors %}has-error{% endif %}"> <label for="{{ field.id_for_label }}" class="col-sm-2 control-label">{{ field.label }}</label> <div class="col-sm-10"> {% for error in field.errors %} <label class="control-label" for="{{ field.id_for_label }}">{{ error }}</label> {% endfor %} {% add_class field 'form-control' %} </div> </div> {% endif %} {% endfor %} </form> </div> <div class="col-md-3"></div> </div> </div> <div class="box-footer"> <button type="button" class="btn btn-default back">返回</button> <button type="button" data-url="{% url 'myadmin:group_update' form.instance.id %}" class="btn btn-primary pull-right save">保存 </button> </div> </div> {% endblock %}
-
js
// 創(chuàng)建 js/myadmin/group/group_list.js $(()=>{ // 分組詳情 $('tr').each(function () { $(this).children('td:first').click(function () { $('#content').load( $(this).data('url'), (response, status, xhr) => { if (status !== 'success') { message.showError('服務(wù)器超時(shí)烘跺,請(qǐng)重試铣揉!') } } ); }) }); });
記得再group_list.html中引用
三旗闽、權(quán)限分組修改
1.接口設(shè)計(jì)
- 接口說(shuō)明:
類(lèi)目 | 說(shuō)明 |
---|---|
請(qǐng)求方法 | PUT |
url定義 | /admin/group/<int:group_id>/ |
參數(shù)格式 | 路徑參數(shù)+表單參數(shù) |
- 參數(shù)說(shuō)明:
參數(shù)名 | 類(lèi)型 | 是否必須 | 描述 |
---|---|---|---|
group_id | 整數(shù) | 是 | 分組id |
name | 字符串 | 是 | 分組名稱(chēng) |
permissions | 整數(shù) | 否 | 權(quán)限id |
-
返回?cái)?shù)據(jù)
# 修改正常返回json數(shù)據(jù) { "errno": "0", "errmsg": "用戶修改成功神汹!" }
如果有錯(cuò)誤曹仗,返回html表單
2.后端代碼
-
視圖
# 在myadmin/views.py的GroupUpdateView視圖中添加put方法 class GroupUpdateView(View): """ 分組更新視圖 url:/admin/group/<int:group_id>/ """ def get(self, request, group_id): # 1. 拿到要修改的分組 group = Group.objects.filter(id=group_id).first() # 1.1 判斷是否存不存在 if not group: return json_response(errno=Code.NODATA, errmsg='沒(méi)有此分組被冒!') # 2.創(chuàng)建表單 form = GroupModeForm(instance=group) # 3.拿到所有的可用一集菜單 menus = Menu.objects.only('name', 'permission_id').select_related('permission').filter(is_delete=False,parent=None) # 4.拿到當(dāng)前組的可用權(quán)限 permissions = group.permissions.only('id').all() # 3.返回渲染html return render(request, 'myadmin/group/group_detail.html', context={ 'form': form, 'menus': menus, 'permissions': permissions }) def put(self, request, group_id): # 1. 拿到要修改的分組 group = Group.objects.filter(id=group_id).first() # 1.1 判斷是否存不存在 # 1.1 判斷是否存不存在 if not group: return json_response(errno=Code.NODATA, errmsg='沒(méi)有此分組军掂!') # 2. 拿到前端傳遞的參數(shù) put_data = QueryDict(request.body) # 3. 校驗(yàn)參數(shù) # 3.1 創(chuàng)建表單對(duì)象 form = GroupModeForm(put_data, instance=group) if form.is_valid(): # 4. 如果成功,保存數(shù)據(jù) form.save() return json_response(errmsg='修改分組成功昨悼!') else: # 5. 如果失敗 menus = Menu.objects.only('name', 'permission_id').select_related('permission').filter(is_delete=False, parent=None) # 4.拿到當(dāng)前組的可用權(quán)限 permissions = group.permissions.only('id').all() return render(request, 'myadmin/group/group_detail.html', context={ 'form': form, 'menus': menus, 'permissions': permissions })
3.前端代碼
-
js
# 創(chuàng)建 js/admin/group/group_detail.js $(() => { // 返回按鈕 $('.box-footer button.back').click(() => { $('#content').load( $('.sidebar-menu li.active a').data('url'), (response, status, xhr) => { if (status !== 'success') { message.showError('服務(wù)器超時(shí)蝗锥,請(qǐng)重試!') } } ); }); // 保存按鈕 $('.box-footer button.save').click(function () { // 將表單中的數(shù)據(jù)進(jìn)行格式化 $ .ajax({ url: $(this).data('url'), data: $('form').serialize(), type: 'PUT' }) .done((res) => { if (res.errno === '0') { message.showSuccess('修改分組成功率触!'); $('#content').load( $('.sidebar-menu li.active a').data('url'), (response, status, xhr) => { if (status !== 'success') { message.showError('服務(wù)器超時(shí)终议,請(qǐng)重試!') } } ); } else { $('#content').html(res) } }) .fail((res) => { message.showError('服務(wù)器超時(shí)穴张,請(qǐng)重試!') }) }); // 復(fù)選框邏輯 // 點(diǎn)擊一級(jí)菜單,二級(jí)菜單聯(lián)動(dòng) // 注意要在一級(jí)菜單中class屬性中加上one渐夸,二級(jí)菜單中加上two $('div.checkbox.one').each(function () { let $this = $(this); $this.find(':checkbox').click(function () { if($(this).is(':checked')){ $this.siblings('div.checkbox.two').find(':checkbox').prop('checked', true) }else{ $this.siblings('div.checkbox.two').find(':checkbox').prop('checked', false) } }) }); // 點(diǎn)擊二級(jí)菜單,一級(jí)菜單聯(lián)動(dòng) $('div.checkbox.two').each(function () { let $this = $(this); $this.find(':checkbox').click(function () { if($(this).is(':checked')){ $this.siblings('div.checkbox.one').find(':checkbox').prop('checked', true) }else { if(!$this.siblings('div.checkbox.two').find(':checkbox').is(':checked')){ $this.siblings('div.checkbox.one').find(':checkbox').prop('checked', false) } } }) }); });
四桃纯、添加分組頁(yè)面
1.接口設(shè)計(jì)
1.1接口說(shuō)明:
類(lèi)目 | 說(shuō)明 |
---|---|
請(qǐng)求方法 | GET |
url定義 | /admin/group/ |
參數(shù)格式 | 無(wú)參數(shù) |
1.2返回?cái)?shù)據(jù)
返回html表單
2.后端代碼
2.1 視圖
# 在admin/views.py中添加如下視圖
class GroupAddView(View):
"""
添加分組視圖
"""
def get(self, request):
form = GroupModeForm()
menus = models.Menu.objects.only('name', 'permission_id').select_related('permission').filter(is_delete=False,
parent=None)
return render(request, 'admin/group/group_detail.html', context={'form': form, 'menus': menus})
2.2路由
# 在admin/urls.py中添加如下路由
path('group/', views.GroupAddView.as_view(), name='add_group'),
3.前端代碼
3.1html
<!-- 在admin/group/group_list.html 中添加 添加group的按鈕-->
<div class="box-tools">
<button type="button" class="btn btn-primary btn-sm"
data-url="{% url 'admin:add_group' %}">添加分組
</button>
</div>
3.2js
<!-- 在admin/group/group.js 中添加 添加group的按鈕的js代碼如下 -->
// 添加分組
$('.box-tools button').click(function () {
$('#content').load(
$(this).data('url'),
(response, status, xhr) => {
if (status !== 'success') {
message.showError('服務(wù)器超時(shí)酷誓,請(qǐng)重試!')
}
}
);
});
五态坦、添加分組
1.接口設(shè)計(jì)
1.1接口說(shuō)明:
類(lèi)目 | 說(shuō)明 |
---|---|
請(qǐng)求方法 | POST |
url定義 | /admin/group/ |
參數(shù)格式 | 表單參數(shù) |
1.2參數(shù)說(shuō)明:
參數(shù)名 | 類(lèi)型 | 是否必須 | 描述 |
---|---|---|---|
name | 字符串 | 是 | 分組名稱(chēng) |
permissions | 整數(shù) | 否 | 權(quán)限id |
1.3返回?cái)?shù)據(jù)
# 修改正常返回json數(shù)據(jù)
{
"errno": "0",
"errmsg": "添加分組成功!"
}
如果有錯(cuò)誤伞梯,返回html表單
2.后端代碼
2.1 視圖
# 在admin/views.py中的GroupAddView視圖中添加post方法如下
class GroupAddView(View):
"""
添加分組視圖
"""
def get(self, request):
form = GroupModeForm()
menus = models.Menu.objects.only('name', 'permission_id').select_related('permission').filter(is_delete=False,
parent=None)
return render(request, 'admin/group/group_detail.html', context={'form': form, 'menus': menus})
def post(self, request):
form = GroupModeForm(request.POST)
if form.is_valid():
form.save()
return json_response(errmsg='添加分組成功!')
else:
menus = models.Menu.objects.only('name', 'permission_id').select_related('permission').filter(
is_delete=False,
parent=None)
return render(request, 'admin/group/group_detail.html', context={'form': form, 'menus': menus})
3.前端代碼
3.1 html
<!-- 修改group_detail.html中的保存按鈕代碼 -->
<button type="button" {% if form.instance.id %}
data-url="{% url 'admin:update_group' form.instance.id %}"
data-type="PUT"
{% else %}
data-url="{% url 'admin:add_group' %}"
data-type="POST"
{% endif %}
class="btn btn-primary pull-right save">保存
</button>
3.2 js
// 修改 group_detail.js中保存按鈕的js代碼如下
// 保存按鈕
$('.box-footer button.save').click(function () {
// 將表單中的數(shù)據(jù)進(jìn)行格式化
$
.ajax({
url: $(this).data('url'),
data: $('form').serialize(),
type: $(this).data('type')
})
.done((res) => {
if (res.errno === '0') {
message.showSuccess('修改分組成功!');
$('#content').load(
$('.sidebar-menu li.active a').data('url'),
(response, status, xhr) => {
if (status !== 'success') {
message.showError('服務(wù)器超時(shí),請(qǐng)重試涮拗!')
}
}
);
} else {
$('#content').html(res)
}
})
.fail((res) => {
message.showError('服務(wù)器超時(shí)惯退,請(qǐng)重試!')
})
});
六、權(quán)限認(rèn)證整合
1.業(yè)務(wù)需求
根據(jù)django內(nèi)置的權(quán)限模塊功能妒貌,可以很好的進(jìn)行權(quán)限認(rèn)證通危。但是本項(xiàng)目大量使用ajax,在進(jìn)行權(quán)限認(rèn)證時(shí)會(huì)遇到麻煩灌曙。且本項(xiàng)目的url設(shè)計(jì)符合RESTFUL api,所以在使用內(nèi)置權(quán)限認(rèn)證時(shí)也會(huì)出現(xiàn)問(wèn)題。因此本項(xiàng)目對(duì)權(quán)限認(rèn)證做了二次開(kāi)發(fā)颖杏。
2.權(quán)限認(rèn)證Mixin
- Mixin類(lèi)
class MyPermissionRequiredMinxin(PermissionRequiredMixin):
def has_permission(self):
"""
覆蓋父類(lèi)方法留储,解決不同請(qǐng)求翼抠,權(quán)限不同的問(wèn)題欲鹏。
"""
perms = self.get_permission_required()
if isinstance(perms, dict):
if self.request.method.lower() in perms:
return self.request.user.has_perms(perms[self.request.method.lower()])
else:
return self.request.user.has_perms(perms)
def handle_no_permission(self):
"""
覆蓋父類(lèi)方法膘盖,解決ajax返回json數(shù)據(jù)的問(wèn)題
:return:
"""
if self.request.is_ajax():
if self.request.user.is_authenticated:
return json_response(errno=Code.ROLEERR, errmsg='您沒(méi)有權(quán)限侠畔!' )
else:
return json_response(errno=Code.SESSIONERR, errmsg=
'您未登錄,請(qǐng)登錄损晤!', data={'url': reverse(self.get_login_url())})
else:
return super().handle_no_permission()
3.視圖權(quán)限認(rèn)證
使用方法和django提供的權(quán)限認(rèn)證方法一致软棺,新增同一個(gè)視圖通過(guò)請(qǐng)求方式進(jìn)行權(quán)限驗(yàn)證的功能。
#
class MenuUpdateView(MyPermissionRequiredMinxin, View):
"""
菜單管理視圖
url:/admin/menu/<int:menu_id>/
"""
# 不同請(qǐng)求尤勋,對(duì)應(yīng)不同的權(quán)限
permission_required = {
'get': ('admin.menu_update',),
'put': ('admin.menu_update',),
'delete': ('admin.menu_delete',),
}
...
4.ajax接收處理
// 編輯菜單
$editBtns.click(function () {
let $this = $(this);
$currentMenu = $this.parent().parent();
menuId = $this.parent().data('id');
$
.ajax({
url: '/admin/menu/' + menuId + '/',
type: 'get'
})
.done((res) => {
if (res.errno === '4101') {
message.showError(res.errmsg);
setTimeout(() => {
window.location.href = res.data.url
}, 1500)
} else if (res.errno === '4105') {
message.showError(res.errmsg)
} else {
$('#modal-update .modal-content').html(res);
$('#modal-update').modal('show')
}
})
.fail(() => {
message.showError('服務(wù)器超時(shí)喘落,請(qǐng)重試!')
})
});