day 01
models文件
用户和角色多对多的原因在于,可能存在临时项目。某个用户除了正常角色,还有可能是安全小组类似的这样的角色。
from django.db import models # Create your models here. class UserInfo(models.Model): name=models.CharField(max_length=32,verbose_name='用户名') pwd=models.CharField(max_length=32,verbose_name='密码') email=models.CharField(max_length=32,verbose_name='邮箱') roles=models.ManyToManyField(to='Role') def __str__(self): return self.name class Role(models.Model): name=models.CharField(max_length=32,verbose_name='角色名') permissions=models.ManyToManyField(to='Permission') def __str__(self): return self.name class Permission(models.Model): name=models.CharField(max_length=32,verbose_name='权限名') url=models.CharField(max_length=200,verbose_name='网址',default=None) def __str__(self): return self.name
PS:考虑到有可能有这种情况。新增了一个role,但是暂时没有权限。so,将上述代码修正如下。 roles=.......,blank=True)
from django.db import models # Create your models here. class UserInfo(models.Model): name=models.CharField(max_length=32,verbose_name='用户名') pwd=models.CharField(max_length=32,verbose_name='密码') email=models.CharField(max_length=32,verbose_name='邮箱') roles=models.ManyToManyField(to='Role') def __str__(self): return self.name class Role(models.Model): name=models.CharField(max_length=32,verbose_name='角色名') permissions=models.ManyToManyField(to='Permission',blank=True) def __str__(self): return self.name class Permission(models.Model): name=models.CharField(max_length=32,verbose_name='权限名') url=models.CharField(max_length=200,verbose_name='网址',default=None) def __str__(self): return self.name
用户 --->角色---->权限,正向查询比价方便。所以,多对多的关系,如上图所示。
views文件
Func1:
用户登录后,后台视图函数通过request.POST.get方法 拿到前端传来的数据,对数据库内的数据进行过滤。
在此之前,把models里的所有模型导入到views文件中。
from rbac.models import * def login(request): if request.method=='POST': username=request.POST.get('username') password=request.POST.get('password') user=UserInfo.objects.filter(name=username,pwd=password).first()
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Func2:
如果验证通过,查询此人的权限。UserInfo -->Role --> Permissions
补充:
返回一个ValuesQuerySet —— QuerySet 的一个子类,迭代时返回字典而不是模型实例对象。
每个字典表示一个对象,键对应于模型对象的属性名称
if user: permission_list1=user.roles.all() for i in permission_list1: print(i,type(i)) permission_list4 = user.roles.all().values() for i in permission_list4: print(i, type(i)) permission_list2 = user.roles.all().values('name') for i in permission_list2: print(i,type(i)) permission_list3=user.roles.all().values('name','permissions__name','permissions__url') print(permission_list1,type(permission_list1)) print(permission_list2,type(permission_list2)) print(permission_list3,type(permission_list3)) print(permission_list4,type(permission_list4))
输出:
材料员 <class 'rbac.models.Role'> {'id': 4, 'name': '材料员'} <class 'dict'> {'name': '材料员'} <class 'dict'> <QuerySet [<Role: 材料员>]> <class 'django.db.models.query.QuerySet'> <QuerySet [{'name': '材料员'}]> <class 'django.db.models.query.QuerySet'> <QuerySet [{'name': '材料员', 'permissions__name': '查询订单', 'permissions__url': '/order/select/'}, {'name': '材料员', 'permissions__name': '查询用户', 'permissions__url': '/user/select/'}]> <class 'django.db.models.query.QuerySet'> <QuerySet [{'id': 4, 'name': '材料员'}]> <class 'django.db.models.query.QuerySet'> {'name': '材料员', 'permissions__name': '查询订单', 'permissions__url': '/order/select/'} {'name': '材料员', 'permissions__name': '查询用户', 'permissions__url': '/user/select/'}
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Django 提供一种强大而又直观的方式来“处理”查询中的关联关系,它在后台自动帮你处理JOIN。 若要跨越关联关系,只需使用关联的模型字段的名称,并使用双下划线分隔,直至你想要的字段.
from rbac.models import * def login(request): if request.method=='POST': username=request.POST.get('username') password=request.POST.get('password') user=UserInfo.objects.filter(name=username,pwd=password).first() # print(user,type(user)) if user: permission_list=user.roles.all().values('name','permissions__name','permissions__url') for item in permission_list: print(item) return HttpResponse('登录成功!') return render(request,'login.html')
输出:
{'name': '材料员', 'permissions__name': '查询订单', 'permissions__url': '/order/select/'} {'name': '材料员', 'permissions__name': '查询用户', 'permissions__url': '/user/select/'}
改进:
可能一个人都多个角色,角色拥有的权限可能有交集。基于这一点考虑,对以上代码进行修正。去掉values()内的name字段。因为这个name字段是roles对象的name,在这里并没有用处,我们需要的是 权限名。
from rbac.models import * def login(request): if request.method=='POST': username=request.POST.get('username') password=request.POST.get('password') user=UserInfo.objects.filter(name=username,pwd=password).first() # print(user,type(user)) if user: permission_list=user.roles.all().values('permissions__name','permissions__url').distinct() for item in permission_list: print(item) return HttpResponse('登录成功!') return render(request,'login.html')
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
实现用户登录成功后,将其权限加入到session中。请求 不同页面,根据用户权限不同,在页面中显示不同的结果。
为什么加入session中,当未登录的用户 想要查看/user/select/,这是肯定不行的。通过session判断当前用户的权限。同时,减轻了数据库的压力,不必一次一次的查询数据库。
若是用户的权限改了呢?很简单,提示用户重新登录。这是普遍的做法。
def userselect(request): ''' 根据有无权限, 显示所有的用户信息 :param request: :return: ''' if request.session.get('url_list'): url_list=request.session.get('url_list') flag = False for url in url_list: if re.match(url,request.path_info): flag=True break if flag: users=UserInfo.objects.all() return render(request,'userselect.html',{'users':users}) else: return HttpResponse('无权访问') else: return redirect('/login/')
其它的也顺理成章喽
def orderselect(request): ''' 根据有无权限, 显示所有的订单信息 :param request: :return: ''' if request.session.get('url_list'): url_list = request.session.get('url_list') flag = False for url in url_list: if re.match(url, request.path_info): flag = True break if flag: orders= Order.objects.all() return render(request, 'orderselect.html', {'orders':orders}) else: return HttpResponse('无权访问') else: return redirect('/login/')
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
上一步有没有优化的地方呢?
感受到了
在rbac这个项目中,所有的视图函数,都需要先判定下当前用户有无访问此页面的权限。也就是说每个视图函数都需要用同一段代码,实现用一个功能。
方法有两个:装饰器,中间件。在这里,中间件是更好的选择。
来吧,写一个中间件。
补充知识:
request是一个HttpRequest 对象。
在Django决定执行哪个视图之前,process_request()会在每个请求上调用。
它应该返回一个None 或一个HttpResponse对象。如果返回None,Django会继续处理这个请求,执行其它process_request()中间件,然后process_view()中间件,最后是对应的视图。如果它返回一个HttpResponse对象,Django 就不用再去调用其它的request、view 或exception 中间件,或对应的视图;它将对HttpResponse 运用响应阶段的中间件,并返回结果。
所以,在中间件中,有问题返回Httpresponse对象,不在调用其他中间件了。如果没问题,则返回None。
同时,在视图函数中,需要对 条件判断的进行修正,将 会出问题的判断放在 if 语句中。
views文件中
def userselect(request): ''' 根据有无权限, 显示所有的用户信息 :param request: :return: ''' url_list=request.session.get('url_list') flag = False for url in url_list: if re.match(url,request.path_info): flag=True break if not flag: return HttpResponse('无权访问') users = UserInfo.objects.all() return render(request, 'userselect.html', {'users': users})
然后将其整体移植到自定义的中间件中。将自定义的 中间件写入设置。
最终如下:
中间件:
from django.utils.deprecation import MiddlewareMixin class RbacMiddleware(MiddlewareMixin): def process_request(self,requst): url_list = request.session.get('url_list') flag = False for url in url_list: if re.match(url, request.path_info): flag = True break if not flag: return HttpResponse('无权访问') return None
视图函数:
def userselect(request): ''' 根据有无权限, 显示所有的用户信息 :param request: :return: ''' users = UserInfo.objects.all() return render(request, 'userselect.html', {'users': users})
settings文件:
MIDDLEWARE = [ 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.common.CommonMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', 'rbac.middlewares.rbac.RbacMiddleware', ]
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
小改动
统一加上了中间件,但是有一点小问题。当登录login页面是,也是返回无权访问。需要经login设为白名单。
在中间件中加一行代码即可。
from django.shortcuts import render,redirect,HttpResponse import re from django.utils.deprecation import MiddlewareMixin class RbacMiddleware(MiddlewareMixin): def process_request(self,request): if request.path_info=='/login/': return None if request.path_info=='/admin/': return None url_list = request.session.get('url_list') flag = False for url in url_list: if re.match(url, request.path_info): flag = True break if not flag: return HttpResponse('无权访问') return None
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
微调使文件更具有可读性
在解决中间件问题后,用户登录成功后,将权限放入其session中,经过这样一个初始化操作。因为有可能开发另外一个系统,也会经历这个初始化操作。所以。选择将其拿出来写作一个函数,写入一个单独的文件中service/init_permission.py。
views文件:
import re # Create your views here. from rbac.models import * from rbac.service.init_permission import init_permission def login(request): if request.method=='POST': username=request.POST.get('username') password=request.POST.get('password') user=UserInfo.objects.filter(name=username,pwd=password).first() # print(user,type(user)) if user: init_permission(user,request) return HttpResponse('登录成功') return render(request,'login.html')
service/init_permission
def init_permission(user,request): # permission_list = user.roles.all().values('permissions__name', 'permissions__url').distinct() permission_list = user.roles.filter(permissions__name__isnull=False).values('permissions__name', 'permissions__url').distinct() url_list = [] for item in permission_list: url_list.append(item.get('permissions__url')) request.session['url_list'] = url_list
补充一个小知识:
值为 True 或 False, 相当于 SQL语句IS NULL和IS NOT NULL.
例:
Entry.objects.filter(pub_date__isnull=True)
SQL等效:
SELECT ... WHERE pub_date IS NULL;
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
代码需要润色的几点
1 白名单可能有很多,将这些白名单写在settings文件中,从settings文件中读取,更合理。
注释的是更正之前的代码。
from django.shortcuts import render,redirect,HttpResponse import re from django.utils.deprecation import MiddlewareMixin from permission import settings class RbacMiddleware(MiddlewareMixin): def process_request(self,request): #1 获取白名单 permission_valid_url=settings.PERMISSION_VALID_URL for url in permission_valid_url: if re.match(url,request.path_info): return None # if request.path_info=='/login/': # return None # if request.path_info=='/admin/': # return None #2 获取权限 url_list = request.session.get('url_list') #3 对用户请求的url进行匹配 flag = False for url in url_list: if re.match(url, request.path_info): flag = True break if not flag: return HttpResponse('无权访问') return None
2 request.session.get('url_list'),可能会在这个项目的很多地方用到,对于这种可能会用到很多次,可以将其写在配置文件中。
在此项目中,两处需要修改。
中间件中:
from django.shortcuts import render,redirect,HttpResponse import re from django.utils.deprecation import MiddlewareMixin from django.conf import settings class RbacMiddleware(MiddlewareMixin): def process_request(self,request): #1 获取白名单 permission_valid_url=settings.PERMISSION_VALID_URL for url in permission_valid_url: if re.match(url,request.path_info): return None # if request.path_info=='/login/': # return None # if request.path_info=='/admin/': # return None #2 获取权限 url_list = request.session.get(settings.PERMISSION_SESSION_KEY) # url_list = request.session.get('url_list') if not url_list: return HttpResponse('未能读取到该用户的信息') #3 对用户请求的url进行匹配 flag = False for url in url_list: if re.match(url, request.path_info): flag = True break if not flag: return HttpResponse('无权访问') return None
init_permission初始化文件中:
from django.conf import settings def init_permission(user,request): # permission_list = user.roles.all().values('permissions__name', 'permissions__url').distinct() permission_list = user.roles.filter(permissions__name__isnull=False).values('permissions__name', 'permissions__url').distinct() url_list = [] for item in permission_list: url_list.append(item.get('permissions__url')) request.session[settings.PERMISSION_SESSION_KEY] = url_list # request.session['url_list'] = url_list
settings文件中添加的两条配置:
PERMISSION_VALID_URL=[ '/login/', '/admin/.*', ] PERMISSION_SESSION_KEY='url_list'
最后:目录结构如下