django笔记|[django项目] 后台权限分组管理
权限分组管理 将多种权限合并到一个组中, 分配时即可一次性分配多种权限, 例如, 售后
具有某几种权限, 当有成员被配置为他时就会拥有着几种权限
I. 权限分组列表 首先咱们来实现权限分组列表, 目的是展示所有的权限组, 同样需要添加一些权限组
1>接口设计
- 接口说明
类目 | 说明 |
---|---|
请求方法 | GET |
url定义 | /admin/groups/ |
参数格式 | 无参数 |
- 返回结果
html
2.1>视图
# 在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})
2.2>路由
# 在myadmin/urls.py中添加如下路由
path('groups/', views.GroupsView.as_view(), name='group_list')
3>前端代码
3.1>html 可以先写一个简单的模板, 然后运行到前端看一下
{% extends 'myadmin/base/content_base.html' %}
{% load static %}
{% load news_template_filters %}{# 分页过滤器 #}
{% block page_header %}
系统设置
{% endblock %}
{% block page_option %}
分组管理
{% endblock %}
首先来到菜单管理创建一个名字为
分组管理
的菜单路由地址是group_list
然后刷新页面即可看到我们的分组管理页面
文章图片
接下来我们来填充它的内容, 修改group_list.html模板
{% extends 'admin/content_base.html' %}
{% load static %}
{% load news_template_filters %} {# 分页过滤器 #}
{% block page_header %}
系统设置
{% endblock %}
{% block page_option %}
权限分组
{% endblock %}
{% block content %}
分组列表
#
组名
菜单
{% for group in groups %}
{{ forloop.counter }}
{{ group.name }}
{% for permis in group.permissions.all %}
{{ permis.name }}/
{% empty %}
暂未分配权限
{% endfor %}
{% endfor %}
{% endblock %}
4>添加数据
前端页面写好了, 但是我们的权限分组仍然只是一个空列表, 我们可以先使用sql语句添加一些数据, 为后续的详情页做铺垫
【django笔记|[django项目] 后台权限分组管理】运行添加分组的语句(例)
INSERT INTO auth_groups(name) VALUES('售后');
运行添加分组权限的语句(例)
INSERT INTO auth_group_permissions(group_id, permission_id) VALUES(1, 70);
对应的权限码你可以在
auth_permission
表中看到, 选择我们在菜单管理中创建的添加完成后的效果
文章图片
II. 权限分组详情页 1>接口设计
- 接口说明:
类目 | 说明 |
---|---|
请求方法 | GET |
url定义 | /admin/group/ |
参数格式 | 路径参数 |
- 参数说明:
参数名 | 类型 | 是否必须 | 描述 |
---|---|---|---|
group_id | 整数 | 是 | 分组id |
- 返回数据
返回html表单
2.1>视图
# 在myadmin/views.py中添加如下视图
class GroupUpdateView(View):
"""
分组更新视图
url: /admin/group//
"""
def get(self, request, group_id):
# 1. 获取被修改的组对象
group = Group.objects.filter(id=group_id).first()
# 2. 判断对象是否存在
if not group:
# 2.1 如果没有就报错
return json_response(errno=Code.NODATA, errmsg='没有此分组!')
# 3. 创建到表单对象
form = GroupModelForm(instance=group)
# 4 返回渲染html
return render(request, 'myadmin/group/group_detail.html')
# 在myadmin/views.py中添加如下视图
class GroupUdateView(View):
"""
分组更新视图
url: /admin/group//
"""
def get(self, request, group_id):
# 1. 获取被修改的组对象
group = Group.objects.filter(id=group_id).first()
# 2. 判断对象是否存在
if not group:
# 2.1 如果没有就报错
return json_response(errno=Code.NODATA, errmsg='没有此分组!')
# 3. 创建到表单对象
form = GroupModelForm(instance=group)
# 4. 拿到所有可用的一级菜单
menus = Menu.objects.only('name', 'permission_id').select_related('permission').filter(is_delete=False,parent=None)
# 5. 拿到当前组的可用权限
permissions = group.permissions.only('id').all()
# 6. 返回渲染html
return render(request, 'myadmin/group/group_detail.html', context={
'form': form,
'menus': menus,
'permissions': permissions
})
2.2>路由
# 在admin/urls.py中添加如下路由
path('group//', views.GroupUdateView.as_view(), name='update_group')
2.3>表单
class GroupModelForm(forms.ModelForm):
#permissions = forms.ModelMultipleChoiceField(queryset=None, required=False, help_text='权限', label='权限')# 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>前端代码
3.1>html
{% extends 'myadmin/base/content_base.html' %}
{% load static %}
{% load admin_customer_tags %}
{% block page_header %}
系统设置
{% endblock %}
{% block page_option %}
权限分组
{% endblock %}{% block content %}
分组详情
{% endblock %}
记得修改一下
group_list.html
中的data_url
属性, 他的目的是让我们可以点击编号跳转到权限详情页
{{ forloop.counter }}
3.2>js
// 创建 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('服务器超时,请重试!')
}
}
);
})
});
});
记得再group_list.html中引用
{% block script %}
src="https://www.it610.com/article/{% static'js/myadmin/group/group_list.js'%}">
{% endblock %}
III. 权限分组修改 1>接口设计
- 接口说明:
类目 | 说明 |
---|---|
请求方法 | PUT |
url定义 | /admin/group/ |
参数格式 | 路径参数+表单参数 |
- 参数说明:
参数名 | 类型 | 是否必须 | 描述 |
---|---|---|---|
group_id | 整数 | 是 | 分组id |
name | 字符串 | 是 | 分组名称 |
permissions | 整数 | 否 | 权限id |
- 返回数据
# 修改正常返回json数据 { "errno": "0", "errmsg": "用户修改成功!" }
如果有错误,返回html表单
2.1>视图
# 在myadmin/views.py的GroupUpdateView视图中添加put方法
class GroupUpdateView(View):
"""
分组更新视图
url:/admin/group//
"""
def put(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='没有此分组!')
# 2. 拿到前端传递的参数
put_data = https://www.it610.com/article/QueryDict(request.body)
# 3. 校验参数
# 3.1 获取表单对象, 通过group作为标识寻找匹配的数据, 再与put_data比较, 并修改不同处
form = GroupModelForm(put_data, instance=group)
# 3.2 表单校验
if form.is_valid():
# 4. 校验成功, 保存数据
form.save()
return json_response(errmsg='分组修改成功!')
else:
# 4. 拿到所有可用的一级菜单
menus = Menu.objects.only('name', 'permission_id').select_related('permission').filter(is_delete=False,parent=None)
# 5. 拿到当前组的可用权限
permissions = group.permissions.only('id').all()
# 6. 返回渲染html
return render(request, 'myadmin/group/group_detail.html', context={
'form': form,
'menus': menus,
'permissions': permissions
})
3>前端代码
3.1>js
# 创建 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('服务器超时,请重试!')
}
}
);
});
// 保存按钮
$('.box-footer button.save').click(function () {
// 将表单中的数据进行格式化
$
.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('服务器超时,请重试!')
}
}
);
} else {
$('#content').html(res)
}
})
.fail((res) => {
message.showError('服务器超时,请重试!')
})
});
// 复选框逻辑
// 点击一级菜单,二级菜单联动
// 注意要在一级菜单中class属性中加上one,二级菜单中加上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)}
})
});
// 点击二级菜单,一级菜单联动
$('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)
}
}
})
});
});
Checkbox跨级联动
前端模板添加两个属性
one
和two
, 分别代表一级和二级菜单{% for menu in menus %}{% for child in menu.children.all %}{% endfor %}{% endfor %}
// 复选框逻辑
// 点击一级菜单,二级菜单联动
// 注意要在一级菜单中class属性中加上one,二级菜单中加上two
// 给带有one属性的所有checkbox加上这个函数
$('div.checkbox.one').each(function () {
let $this = $(this);
$this.find(':checkbox').click(function () {
if ($(this).is(':checked')) {
// 如果点击checkbox是让其被选中,
// 则让其所有的子选项全部选中
$this.siblings('div.checkbox.two').find(':checkbox').prop('checked', true)
} else {
// 否则代表取消选中checkbox,
// 则其所有的子选项也全部取消
$this.siblings('div.checkbox.two').find(':checkbox').prop('checked', false)
}
})
});
// 点击二级菜单,一级菜单联动
// 给带有two属性的所有checkbox加上这个函数
$('div.checkbox.two').each(function () {
let $this = $(this);
$this.find(':checkbox').click(function () {
if ($(this).is(':checked')) {
// 如果点击checkbox是让其被checked,
// 则检查其他子选项是否还有unchecked的
if (!$this.siblings('div.checkbox.two').find(':checkbox').is(':checked')) {
// 如果有,就让父选项变成indeterminate(不确定的)状态
$this.siblings('div.checkbox.one').find(':checkbox').prop('indeterminate', true)
}else{
// 如果全都被checked,就移出父选项的indeterminate状态,
$this.siblings('div.checkbox.one').find(':checkbox').prop('indeterminate', false);
// 然后让父选项被checked
$this.siblings('div.checkbox.one').find(':checkbox').prop('checked', true)
}
} else {
// 否则代表取消选中checkbox,
// 则判断所有的子选项中是否仍有被checked的
if ($this.siblings('div.checkbox.two').find(':checkbox').is(':checked')) {
// 如果有, 就让父选项变成indeterminate(不确定的)状态
$this.siblings('div.checkbox.one').find(':checkbox').prop('indeterminate', true)
}else {
// 如果全都被checked,就移出父选项的indeterminate状态,
$this.siblings('div.checkbox.one').find(':checkbox').prop('indeterminate', false);
// 然后移出父选项的checked状态
$this.siblings('div.checkbox.one').find(':checkbox').prop('checked', false)
}
}
})
});
参考文档:如何实现checkbox的第三种状态?
IIII. 添加分组页面 1>接口设计
1.1>接口说明:
类目 | 说明 |
---|---|
请求方法 | GET |
url定义 | /admin/group/ |
参数格式 | 无参数 |
2>后端代码
2.1>视图
# 在admin/views.py中添加如下视图
class GroupAddView(View):
"""
添加分组视图
url: /admin/group/
"""
def get(self, request):
# 1. 创建表单模型对象
form = GroupModelForm()
# 2. 拿到所有可用的一级菜单
menus = Menu.objects.only('name', 'permission_id').select_related('permission').filter(is_delete=False, parent=None)
# 3. 返回表单渲染的html
return render(request, 'myadmin/group/group_detail.html', context={
'form': form,
'menus': menus
})
2.2>路由
# 在myadmin/urls.py中添加如下路由
path('add_group/', views.GroupAddView.as_view(), name='add_group'),
3>前端代码
3.1>html
3.2>js
// 在myadmin/group/group_list.js 中添加 添加group的按钮的js代码如下
// 添加分组
$('.box-tools button').click(function () {
$('#content').load(
$(this).data('url'),
(response, status, xhr) => {
if (status !== 'success') {
message.showError('服务器超时,请重试!')
}
}
);
});
V. 添加分组 1>接口设计
1.1>接口说明:
类目 | 说明 |
---|---|
请求方法 | POST |
url定义 | /admin/group/ |
参数格式 | 表单参数 |
参数名 | 类型 | 是否必须 | 描述 |
---|---|---|---|
name | 字符串 | 是 | 分组名称 |
permissions | 整数 | 否 | 权限id |
# 修改正常返回json数据
{
"errno": "0",
"errmsg": "添加分组成功!"
}
如果有错误,返回html表单
2>后端代码
2.1>视图
# 在admin/views.py中的GroupAddView视图中添加post方法如下
class GroupAddView(View):
"""
添加分组视图
"""
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 由于添加与修改分组使用的模板和js是同一个, 因此就要分辨到底是做POST还是PUT请求
在前端判断页面的地方添加一个
data-type
, 其分别对应不同的请求方式(put/post), 即可实现代码的复用3.2>js
// 修改 group_detail.js中保存按钮的js代码如下
// 保存按钮
$('.box-footer button.save').click(function () {
// 将表单中的数据进行格式化
$
.ajax({
url: $(this).data('url'),
// 可以拿到当前页面表单的数据,会自动拼接
data: $('form').serialize(),
type: $(this).data('type') // 前端的请求类型
})
.done((res) => {...})
.fail((res) => {...})
});
VI. 权限认证整合 小试牛刀
我们先来拿django内置的权限系统来试一下, 看看有哪些作用
由于我们后台的所有路由几乎都是使用的类视图的形式, 所有这次我们使用继承的方法实现, 官方文档
先拿菜单列表做一下试验:
# 在myadmin/views.py中修改一下代码
from django.contrib.auth.mixins import PermissionRequiredMixinclass MenuUpdateView(PermissionRequiredMixin, View):
# 权限名, 单个可以是字符串, 多个可以用元组
permission_required = 'myadmin.menu_update'
...
- 打开我们的项目页面
- 添加一个
修改菜单
权限, 权限码使用menu_update
- 给一个用户的分组添加上
菜单管理
权限, 但不添加修改菜单
权限 - 登录我们的目标用户(testzh), 打开菜单管理页
- 然后点击
编辑
, 你应该会得到一个403 Forbidden的错误
接下来我们就通过重写的方式, 来完成我们需要的功能: 当没有权限访问时, 返回一个提示界面
1>业务需求
根据django内置的权限模块功能,可以很好的进行权限认证。但是本项目大量使用ajax,在进行权限认证时会遇到麻烦。且本项目的url设计符合RESTFUL api,所以在使用内置权限认证时也会出现问题。因此本项目对权限认证做了二次开发。
2>权限认证Mixin
在
myadmin/views.py
中添加这个类:class MyPermissionRequiredMinxin(PermissionRequiredMixin):
def handle_no_permission(self):
"""
覆盖父类方法,解决ajax返回json数据的问题
:return:
"""
if self.request.is_ajax():
if self.request.user.is_authenticated:
return json_response(errno=Code.ROLEERR, errmsg='您没有权限!' )
else:
return json_response(errno=Code.SESSIONERR, errmsg='您未登录,请登录!', data=https://www.it610.com/article/{'url': reverse(self.get_login_url())})else:
return super().handle_no_permission()def has_permission(self):
"""
覆盖父类方法,解决一个视图类有多个权限对象的问题
"""
perms = self.get_permission_required()
if isinstance(perms, dict):
if self.request.method.lower() in perms:
returnself.request.user.has_perms(perms[self.request.method.lower()])
else:
return self.request.user.has_perms(perms)
注意在settings中设置LOGIN_URL
# 登录url
LOGIN_URL = 'user:login'
3>视图权限认证
使用方法和django提供的权限认证方法一致,新增同一个视图通过请求方式进行权限验证的功能。
#
class MenuUpdateView(MyPermissionRequiredMinxin, View):
"""
菜单管理视图
url:/admin/menu//
"""
# 不同请求,对应不同的权限
permission_required = {
'get': ('myadmin.menu_update',),
'put': ('myadmin.menu_update',),
'delete': ('myadmin.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 = https://www.it610.com/article/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('服务器超时,请重试!')})
});
在咱们的后台上创建响应的菜单即可, 记得在创建时尽量不要用权限表中重复的字段, 具体请参考数据库的
auth_permissions
表格权限分组告一段落, 之后咱么来搞一搞新闻管理页
推荐阅读
- EffectiveObjective-C2.0|EffectiveObjective-C2.0 笔记 - 第二部分
- Android中的AES加密-下
- django-前后端交互
- 【读书笔记】贝叶斯原理
- 【韩语学习】(韩语随堂笔记整理)
- 人性的弱点-笔记
- 读书笔记:博登海默法理学|读书笔记:博登海默法理学 —— 正义的探索(1)
- D034+3组苏曼+《写作这回事》读书笔记
- 《自我的追寻》读书笔记3
- 最有效的时间管理工具(赢效率手册和总结笔记)