Django之权限(起步)
一. 权限概述
1. 认识权限
为什么要有权限? 因为权限让不同的用户拥有不同的功能. 权限可以对功能进行划分.
生活中处处有权限. 比如, 腾讯视频会员才有观看某个最新电影的权限, 你有房间钥匙就有了进入这个房间的权限. 同样, 程序开发过程中也有权限, 我们今天所说的权限指的是web网站权限, 对于不同用户访问web服务时应该有不同的功能. 例如: 一个公司有CEO, 销售主管, 销售等等, 不同的用户能访问的服务也不是完全相同的. 处于这样的需求下, 我们就需要权限控制了.
2. 为什么要开发权限组件?
组件可以减少代码的重复, 能提高我们的开发效率--开发一次组件, 那么在以后的项目中可以一直使用.
3. web开发中权限是指什么?
首先我们要认识到, web程序是通过 url 的切换来查看不同的页面(功能)的, 所以权限指的其实就是一个含有正则表达式的URL, 对url控制就是对权限的控制.
结论: 一个人有多少权限就取决于他有多少个URL的访问权限.
二. 权限表结构设计
1.版本一
按照生活中的实际情况来看, 一个用户有多个权限, 一个权限也可以分配给多个用户, 所以我们可以设计出下面的三张表:
分析: 设计完该表结构之后, 我们又考虑到这种情况: 如果我们再新增10个用户, 每个用户都有权限表中的2个权限, 这意味着我们就要在"权限和用户关联表"中新增40条记录. 如果再拓展到我们的实际生活中, 又会出现怎样的情况呢? 生活中一个web网站肯定不止2个url, 这就意味着每新增一个客户, 我们就要在某些表中增加若干条记录. 这样做不仅加重的程序员工作任务, 而且造成了数据库空间的严重浪费. 很显然, 此表有待改进!
2. 版本二
观察生活中的实际情况, 我们发现, 某些网站有普通用户和VIP用户, 部分网站的VIP用户也分了等级. 此时我们思考: 这样的做法实际上是给用户分了类, 把我们的用户分为了不同的角色. 是的, 此时我们再进一步联想: 我们是否也可以对用户进行角色划分, 然后再对不同的角色进行权限的分配呢?
- 分析:
- 一个人可以有多个角色
- 一个角色可以有多个人
- 一个角色可以有多个权限
- 一个权限可以分配给多个角色
以下是我们的表结构设计:
调整以后的表结构, 由原来的基于用户的权限控制转换成基于角色的权限控制(RBAC), 以后再进行权限分配时只需要给指定角色分配一次权限, 给众多用户分配指定角色即可.
三. Django项目中进行权限设置
步骤概述:
- 1.数据库配置(使用Django自带数据库即可)
- 2.models.py文件: 创建三张表, 分别是用户表, 角色表, 权限表
- 3.使用Django自带的admin来对各个表进行数据添加
- 4.路由匹配(urls.py文件)
- 5.视图函数(views.py文件)
- 6.自定义中间件
Django生命周期的回顾
在进行Django权限设置之前, 我们首先要回顾一下Django的生命周期:
1. models.py文件配置
- 创建项目
MyLuffyPermission
, app名称为rbac
. rbac/models.py
: 我们进行权限表结构设计时, 共有5张表, 其中2张是关系表, 所以models.py文件中只有3个model, 分别是权限表, 角色表和用户表.- 数据库迁移指令
python manage.py makemigrations
python manage.py migrate
2. 使用Django自带的admin对表进行数据添加
(1) 创建超级用户
(2) 对rabc/admin.py
文件进行配置
from django.contrib import admin
from rbac import models
class PermissionModelAdmin(admin.ModelAdmin):
list_display = ['title', 'url'] # 展示的字段
list_editable = ['url'] # 编辑的字段
admin.site.register(models.Permission, PermissionModelAdmin)
admin.site.register(models.Role)
admin.site.register(models.User)
(3)启动Django项目, 浏览器输入127.0.0.1:8000/admin/
, 回车. 对数据库进行相关信息的添加与修改.
3. rbac组件的开发
在rabc应用中创建一个middlewares的文件夹, 在该文件夹中新建rabc.py
, 它就是我们自定义的一个中间件, 作用是权限校验.
rabc/middlewares/rabc.py
: 该中间件的编写分三步走:- (1)获取当前访问的url
- (2)获取当前用户权限信息
- (3)权限校验
- 具体代码如下所示:
from django.utils.deprecation import MiddlewareMixin
from django.conf import settings
from django.shortcuts import HttpResponse
import re
class RabcMiddleware(MiddlewareMixin):
def process_request(self, request):
# 1.获取当前访问的URL
url = request.path_info
print(111111)
# 如果当前的url是白名单中的内容,return None,即允许该url通过校验
for i in settings.WHITE_LIST:
if re.match(i, url): # 如果匹配成功
return # 通过校验
# 2.获取当前用户的权限信息 注意session中的键值对是在登录的时候(web/views/account)设置的
permission_list = request.session.get(settings.PERMISSION_SESSION_KEY)
print(222222)
# 3.权限的校验
for i in permission_list:
# URL权限 匹配成功
if re.match(r"^{}$".format(i['permissions__url']), url):
print(333333)
return
# 校验失败,拒绝访问
return HttpResponse('没有访问权限')
4. 路由匹配, 视图函数
(1) MyluffyPermission/urls.py
(2) 创建一个名叫web
的app应用, 并在该应用中创建urls.py
文件
# 创建app
python manage.py startapp app的名称
# 注册app
找到MyluffyPermission/settings.py文件下的INSTALLED_APPS项,然后注册app
(3) 在web/urls.py文件中写我们的路由匹配
(4) 在web应用下创建一个名为views
的python包
, 对视图函数进行分类, 每个角色的功能单独用一个.py
文件来写.
5. 登录功能
在成功配置完我们的rbac组件之后(主要包括rbac/middlewares/rbac.py
和rbac/migrations/middles.py
), 我们现在开始进行对登录功能的编写.
(1)路由匹配: web/urls.py
from django.conf.urls import url
from web.views import account
urlpatterns = [
# 用户登录
url(r'^login/$', account.login, name='login'),
]
(2)视图函数: web/views/account.py
from django.shortcuts import HttpResponse, redirect, render, reverse
from rbac import models
from django.conf import settings
def login(request):
if request.method == 'POST':
# 获取提交的数据
user = request.POST.get('user')
pwd = request.POST.get('pwd')
# 数据库匹配管理对象
obj = models.User.objects.filter(name=user, password=pwd).first()
if not obj: # 错误优先
return render(request, 'login.html', {'error_msg': '用户名或密码错误'})
# 登录成功,保存用户权限信息:
ret = obj.roles.all().filter(permissions__url__isnull=False).values(
'permissions__url',
'permissions__title',).distinct()
request.session[settings.PERMISSION_SESSION_KEY] = list(ret)
return HttpResponse("登录成功")
return render(request, 'login.html')
对account.py
文件中其中两行代码的解释:
ret = obj.roles.all().filter(permissions__url__isnull=False).values(
'permissions__url',
'permissions__title',).distinct()
"""
obj -- User对象
.roles.all() -- 通过User对象拿到该用户的所有角色对象(roles)
.filter(permissions__url__isnull=False)
-- permissions__url ==> 角色对象(roles)跨表查询Permission表的 URL权限(url)信息
-- isnull=False ==> isnull表示空,那么isnull=False表示不为空,也就是说 整个ORM语句筛选出了"URL权限不为空的角色对象"
.values('permissions__url','permissions__title')
-- 拿到角色的 URL权限和权限标题 组成的字典
.distinct() -- 去重
"""
request.session[settings.PERMISSION_SESSION_KEY] = list(ret)
# request.session[key] = value表示设置session数据
# 由ret=xxx.values(xxx)可知:ret是一个QuerySet对象.
# 由于request.session设置session时,会把数据转换为json格式,而ret却是一个QuerySet,因此我们把ret转换为列表后,就可以设置session了
# PERMISSION_SESSION_KEY是我们自定义的一个配置信息,我们把它写在settings.py文件里. 配置它的目的是:今后我们再次设置session时,它的key就是一个动态的可变的,如果需要修改key,我们只需要在settings.py文件里重新配置即可,而不再需要找到对应的函数,重复修改.从而达到简化我们代码修改的工作量.
(3)模板文件: web/templates/login.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form action="" method="post">
{% csrf_token %}
<p>
用户名:<input type="text" name="user">
</p>
<p>
密码:<input type="password" name="pwd">
</p>
<p style="color: red">{{ error_msg }}</p>
<button>登录</button>
</form>
</body>
</html>