一、权限组件

1、上篇随笔中,我们只是设计好了权限控制的表结构,有三个模型,五张表,两个多对多关系,并且简单实现了对用户的权限控制,我们会发现那样写有一个问题,就是权限控制写死在了项目中,并且没有实现与我们的业务逻辑解耦,当其他项目要使用权限控制时,要再重复写一遍权限控制的代码,因此我们很有必要将权限控制的功能开发成一个组件(可插拔)。

  组件其实就是一个包,将一个与功能相关的代码关联到一起,当其他项目要使用该功能时将组件导入即可,下面我们试着来将权限控制写成一个组件,以客户管理系统为例,利用权限控制组件,实现动态显示权限菜单的功能,目录结构如下:

    luffy_permission/
    ├── db.sqlite3
    ├── luffy_permission
    │ ├── __init__.py
    │ ├── settings.py
    │ ├── urls.py
    │ └── wsgi.py
    ├── manage.py
    ├── rbac # 权限组件,便于以后应用到其他系统
    │ ├── __init__.py
    │ ├── admin.py
    │ ├── apps.py
    │ ├── models.py
    │ ├── tests.py
    │ └── views.py
    ├── templates
    └── web # 客户管理业务
     ├── __init__.py
     ├── admin.py
     ├── apps.py
     ├── models.py
     ├── tests.py
    ├── urls.py
     └── views.py

  (1)rbac/models.py中代码(权限管理的模型类),如下:

from django.db import models

class User(models.Model):
"""
用户表
"""
name = models.CharField(verbose_name='用户名', max_length=32)
password = models.CharField(verbose_name='密码', max_length=32)
roles = models.ManyToManyField(verbose_name='拥有的所有角色', to='Role') def __str__(self):
return self.name

class Role(models.Model):
"""
角色表
"""
title = models.CharField(verbose_name='角色名称', max_length=32)
permissions = models.ManyToManyField(verbose_name='拥有的所有权限', to='Permission')
def __str__(self):
return self.title

class Permission(models.Model):
"""
权限表
"""
url = models.CharField(verbose_name='含正则的URL', max_length=32)
title = models.CharField(verbose_name='标题', max_length=32)
is_menu = models.BooleanField(verbose_name='是否是菜单', default=False)
icon = models.CharField(verbose_name='图标', max_length=32, blank=True)
def __str__(self):
return self.title

  (2)web/models.py(客户管理业务逻辑的模型类),如下:

from django.db import models

class Customer(models.Model):
"""
客户表
"""
name = models.CharField(verbose_name='姓名', max_length=32)
age = models.CharField(verbose_name='年龄', max_length=32)
email = models.EmailField(verbose_name='邮箱', max_length=32)
company = models.CharField(verbose_name='公司', max_length=32)
def __str__(self):
return self.name

class Payment(models.Model):
"""
付费记录表
"""
customer = models.ForeignKey(verbose_name='关联客户', to='Customer',on_delete=models.CASCADE)
money = models.IntegerField(verbose_name='付费金额')
create_time = models.DateTimeField(verbose_name='付费时间', auto_now_add=True)

  (3)以上客户管理系统中的URL和对应的功能有(将url与视图函数对应关系配置在web下的urls.py中,再由全局的urls.py做路由分发):

    客户管理:

      客户列表:/customer/list/

      添加客户:/customer/add/

      删除客户:/customer/del/(\d+)/

      修改客户:/customer/edit/(\d+)/

    账单管理:

      账单列表:/payment/list/

      添加账单:/payment/add/

      删除账单:/payment/del/(\d+)/

      修改账单:/payment/edit/(\d+)/

  (4)在权限组件表中录入相关信息:

    录入权限列表,创建角色(并为角色分配权限),创建用户(并为用户分配角色);

    这样用户登录时,就可以根据当前登录用户找到其所有权限再将权限信息放入session,以后每次访问时候需要先去session检查是否有权访问。

    上篇中我们已经完成这些,接下来将权限和项目解耦并且完成动态显示权限菜单功能。

二、动态显示权限菜单(单级菜单)

  准备工作已经就绪,再按照以下步骤进行:

1、登录页面

  在web目录下的urls.py中新增加一个url与登录视图函数的对应关系:

    登录:/login/

  在web目录下的views.py中创建登录的视图函数,代码如下;

from rbac.service.setsession import initial_session
def login(request):
if request.method == 'POST':
user = request.POST.get('user')
pwd = request.POST.get('pwd')
user_obj = User.objects.filter(name=user, password=pwd).first()
if user_obj:
request.session['user_id'] = user_obj.pk # 用户id注入session
# 将权限列表和权限菜单列表注入session
initial_session(user_obj, request)
return redirect('/customer/list/') return render(request, 'login.html')

  然后在web目录下创建templates文件夹,并在其中创建login.html;

2、在rbac目录下创建service文件夹,并在其中创建middlewares.py和setsession.py

  middlewares.py(利用中间件做用户登录和判断权限,中间件要加入全局settings中),代码如下:

from django.utils.deprecation import MiddlewareMixin
from django.shortcuts import redirect, HttpResponse
import re
class PermissionMiddleWare(MiddlewareMixin):
def process_request(self, request):
# 设置白名单放行
for reg in ["/login/", "/admin/*"]:
ret = re.search(reg, request.path)
if ret:
return None # 检验是否登录
user_id = request.session.get('user_id')
if not user_id:
return redirect('/login/') # 检验权限
permission_list = request.session.get('permission_list')
for reg in permission_list:
reg = '^%s$' % reg
ret = re.search(reg, request.path)
if ret:
return None
return HttpResponse('无权访问')

  setsession.py(将当前登录人的所有权限注入session中),代码如下:

def initial_session(user_obj, request):
"""
将当前登录人的所有权限列表和所有菜单权限列表注入session
:param user_obj: 当前登录用户对象
:param request: 请求对象HttpRequest
"""
# 查询当前登录人的所有权限列表
ret = Role.objects.filter(user=user_obj).values('permissions__url',
             'permissions__title',
             'permissions__icon',
                      'permissions__is_menu').distinct()
permission_list = []
permission_menu_list = []
for item in ret:
permission_list.append(item['permissions__url'])
if item['permissions__is_menu']:
permission_menu_list.append({
'url': item['permissions__url'],
'title': item['permissions__title'],
'icon': item['permissions__icon'],
})
print('权限列表', permission_list)
print('菜单权限列表', permission_menu_list)
# 将当前登录人的权限列表注入session中
request.session['permission_list'] = permission_list
# 将当前登录人的菜单权限列表注入session中
request.session['permission_menu_list'] = permission_menu_list

3、客户列表和账单列表属于菜单列表,我们需要渲染到左侧菜单中,根据用户权限判断是否显示,而客户列表和账单列表的左侧菜单是在公共的模板base.html中定义的,我们只看左侧菜单的部分,如下代码:

<div class="menu-body">
<div class="static-menu">
{% for item in permission_menu_list %}
<a href="{{ item.url }}">
<span class="icon-wrap"><i class="fa {{ item.icon }}"></i></span>{{ item.title }}
</a>
{% endfor %}
</div>
</div>

  至此,我们已经实现了动态渲染标签的功能,也就是用户拥有的菜单权限会在页面左侧菜单中显示,没有权限的则不显示。但是还有一个小问题,鼠标滑过相应菜单有一个样式,但是鼠标移走样式消失。如何解决呢?你可能会想到在客户列表和账单列表对应的视图函数中分别加上这样一段逻辑代码:即当前视图函数对应url与用户请求url相同时,给从当前用户session中取出的当前用户菜单全列列表中的字典添加一个键值对(class:active)再传给返回的页面,这种方法是可以满足需求,但同时也存在代码重复的问题,就是在不同的视图函数中重复写了一样的逻辑代码。下面介绍的自定义标签的扩展就可以完美解决。

4、自定义标签扩展功能实现点击菜单增加相应active类名:

  1)保证全局settings.py中的INSTALLED_APPS配置了当前应用rbac;

  2)在rbac目录下创建templatetags模块(文件夹);

  3)在templatetags中创建任意的.py文件(如:my_tags.py),然后就可以在里边写自定义的标签扩展的函数了,如下:

from django import template
register = template.Library()
@register.inclusion_tag("menu.html") # django会自动去templates中寻找
def get_menu_styles(request):
permission_menu_list = request.session.get("permission_menu_list")
for item in permission_menu_list:
if re.search("^{}$".format(item["url"]), request.path):
item["class"] = "active"
return {"permission_menu_list": permission_menu_list} # 返回给menu.html

  4)在rbac目录下创建templates文件夹,并在其中创建任意的.html文件(如:menu.html),该页面存放左侧菜单部分,变量可以由自定义标签函数返回,内容如下:

<div class="static-menu">
{% for item in permission_menu_list %}
<a href="{{ item.url }}" class="{{ item.class }}">
      <span class="icon-wrap"><i class="fa {{ item.icon }}"></i></span> {{ item.title }}
</a>
{% endfor %}
</div>

  5)在模板页面base.html中显示菜单的位置先导入之前创建的my_tags.py文件,再调用自定义标签函数,如下:

  <div class="menu-body">
    {% load my_tags %}
    {% get_menu_styles request %} # 依次写函数名和参数,空格隔开
  </div>

  分析:当调用自定义标签函数get_menu_styles时,就会执行my_tags.py中相应函数并将返回值返回给函数对应装饰器中定义的menu.html页面,渲染成html页面后再将渲染结果返回到调用get_menu_styles的页面,这样做的好处是,当多个视图函数需要返回给浏览器同一个模板页面且都需要给这个模板页面的相同地方传递变量且该变量是由相同的业务逻辑产生,这个时候我们可以利用自定义标签函数,变量统一由自定义标签函数返回给一个页面(这个页面是将模板页面中共同使用这个变量的代码块提取出来所构成,如例中的menu.html),再在模板页面中调用该函数,避免了在视图函数中重复写相同的业务逻辑代码。这也是过滤器(包括django已有的和自定义的过滤器)和自定义标签的存在意义,即提高代码的复用性。

三、补充知识点

1、admin补充 - list_editable和search_fields

  我们之前了解过Django提供的admin,其实admin的功能相当强大,我们目前了解的仅仅是九牛一毛,上篇中我们学习了如何在admin中自定义显示样式,今天我们再学习两个:list_editable和search_fields。还是以权限控制中的权限表为例:

  上篇中我们为Permission表创建了三个字段(id、url、title),今天又加了两个(is_menu、icon),当我们在admin为permission表按如下定义时:

# 自定义类,类名自己定,但必须继承ModelAdmin
class PermissionConfig(admin.ModelAdmin):
  list_display = ['pk', 'title', 'url', 'is_menu', 'icon']
  list_editable = ['url', 'is_menu', 'icon']
  search_fields = ['title']
  ordering = ['pk'] # 按照主键从低到高 admin.site.register(Permission, PermissionConfig)

  效果如图:

  总结:

    1)list_editable   字段在展示的同时可以编辑;

    2)search_fields   显示按某字段搜索的功能;

2、自定义标签和过滤器

  (1)首先保证settings.py中的INSTALLED_APPS配置了当前app,否则django无法找到自定义的标签和过滤器;

  (2)在app中创建templatetags模块(也就是包,包的名字只能是templatetags);

  (3)在templatetags中创建任意的.py文件(如:my_tags.py),然后就可以在里边写自定义的标签和过滤器了,如下:

from django.utils.safestring import mark_safe
from django import template
register = template.Library() # register的名字是固定的,不可改变 @register.filter # 自定义过滤器(filter)
def filter_multi(v1, v2):
  return v1 * v2 @register.simple_tag # 自定义标签(simple_tag)
def simple.tag_multi(v1, v2):
  return v1 * v2 @register.simple_tag # 自定义标签(simple_tag)
def my_input(id, arg):
  result = "<input type='text' id='%s' class='%s' />" % (id, arg)
  return mark_safe(result)

  (4)在使用自定义simple_tag和filter的html文件中先导入之前创建的my_tags.py,再使用,如下:

{% load my_tags %}   # 导入

# num=12
{{ num|filter_multi:2 }} # {{ num|filter_multi:"[22,333,4444]" }} {% simple_tag_multi 2 5 %} # 参数不限,但不能放在if、for语句中
{% simple_tag_multi num 5 %}

  总结:

    1)自定义过滤器的函数对参数有限制,只能是一个或者两个,当你想传超过两个的参数时,只能自己想办法,比如可以把参数放到列表中再传入,而自定义标签的函数参数可以有任意多个;

    2)django在查找自定义标签和过滤器文件时,会依次查找INSTALLED_APPS中已配置的app下的templatetags模块中与load后同名的py文件,若两个app中的templatetags都有相同的py文件且文件中定义的同名的过滤器或者标签函数,那么后者会覆盖前者,因此尽量避免py文件同名。

    3)自定义simple_tag不可以放在if、for语句中,而filter可以用在if等语句后,如下:

{% if num|filter_multi:30 > 100 %}
{{ num|filter_multi:30 }}
{% endif %}

3、Font Awesome - 一套绝佳的图标字体库和CSS框架

官网:http://fontawesome.dashgame.com/

Django - 权限(2)- 动态显示单级权限菜单的更多相关文章

  1. SNF快速开发平台2019-权限管理模型-记录级-字段级权限实践

    1.1.1  字段级权限 字段级权限适用于对不同人的能否查看或录入不同表不同字段的权限控制. 是否启用字段级权限配置 不启用字段级权限后,[用户权限管理]程序[字段级权限]按钮会隐藏,导致无法给管理其 ...

  2. 巨蟒django之权限7:动态生成一级&&二级菜单

    内容回顾: . 权限的控制 . 表结构设计 存权限的信息 用户表 - name 用户名 - pwd 密码 - roles 多对多 角色表 - name - permissions 多对多 权限表 - ...

  3. python 全栈开发,Day110(django ModelForm,客户管理之 编辑权限(一))

    昨日内容回顾 1. 简述权限管理的实现原理. 粒度控制到按钮级别的权限控制 - 用户登陆成功之后,将权限和菜单信息放入session - 每次请求时,在中间件中做权限校验 - inclusion_ta ...

  4. WinForm/MIS项目开发之中按钮级权限实践

    一.前言 AgileEAS.NET SOA 中间件平台是一款基于基于敏捷并行开发思想和Microsoft .Net构件(组件)开发技术而构建的一个快速开发应用平台.用于帮助中小型软件企业建立一条适合市 ...

  5. Django-xadmin+rule对象级权限的实现

    原文:https://blog.csdn.net/zcyuefan/article/details/77743380 1. 需求vs现状 1.1 需求要求做一个ERP后台辅助管理的程序,有以下几项基本 ...

  6. 手摸手,带你用vue实现后台管理权限系统及顶栏三级菜单显示

    手摸手,带你用vue实现后台管理权限系统及顶栏三级菜单显示 效果演示地址 项目demo展示 重要功能总结 权限功能的实现 权限路由思路: 根据用户登录的roles信息与路由中配置的roles信息进行比 ...

  7. [Django]用户权限学习系列之User权限基本操作指令

    针对Django 后台自带的用户管理系统,虽说感觉还可以,但是为了方便用户一些操作,特别设计自定义的用户权限管理系统. 在制作权限页面前,首先需要了解权限和用户配置权限的指令,上章讲到权限的添加,删除 ...

  8. [Django]用户权限学习系列之Permission权限基本操作指令

    若需建立py文件进行测试,则在文件开始加入以下代码即可 #coding:utf-8 import os os.environ.setdefault("DJANGO_SETTINGS_MODU ...

  9. 设置SharePoint2010列表的项目级权限

    转:http://www.cfanz.cn/?c=article&a=read&id=24096 在SharePoint2010中我们经常会用到这样的权限设置,在一个列表中可以存储多个 ...

随机推荐

  1. matplotlib极坐标系应用之雷达图

    #!/usr/bin/env python3 #-*- coding:utf-8 -*- ############################ #File Name: test.py #Autho ...

  2. FileZilla Server-Can’t access file错误解决方法

    在某服务器上用FileZilla Server搭建了一个FTP服务器.开始使用没有发现任何问题,后来在向服务器传送大文件的时候,发现总是传输到固定的百分比的时候出现 ”550 can’t access ...

  3. linux学习笔记11---命令more

    more命令,功能类似 cat ,cat命令是整个文件的内容从上到下显示在屏幕上. more会以一页一页的显示方便使用者逐页阅读,而最基本的指令就是按空白键(space)就往下一页显示,按 b 键就会 ...

  4. linux学习笔记8--命令touch

    linux的touch命令不常用,一般在使用make的时候可能会用到,用来修改文件时间戳,或者新建一个不存在的文件. touch命令有两个功能:一是用于把已存在文件的时间标签更新为系统当前的时间(默认 ...

  5. HDU 1075 What Are You Talking About (Trie)

    What Are You Talking About Time Limit: 10000/5000 MS (Java/Others)    Memory Limit: 102400/204800 K ...

  6. makefile的选项LDFLAGS和LIBS的区别

    LDFLAGS是选项,LIBS是要链接的库.都是喂给ld的,只不过一个是告诉ld怎么吃,一个是告诉ld要吃什么. 网上不难搜索到上面这段话.不过“告诉ld怎么吃”是什么意思呢? 看看如下选项: LDF ...

  7. python3颜色输出

    遇到一个项目,需求是在python3中,处理结果显示高亮加颜色,然后资料整理如下 ### 格式: \033[显示方式;前景色;背景色m 这里的格式是规定了m后面的输出字符颜色样式 说明: 前景色 背景 ...

  8. [复习] JAVA 遍历目录 (递归调用和非递归)

    JAVA 遍历文件夹下的所有文件(递归调用和非递归调用) 1.不使用递归的方法调用. public void traverseFolder1(String path) { int fileNum = ...

  9. linux之挂载硬盘

    sudo gedit /etc/fstab中添加 UUID=190534e2-d8ae-4928-94b7-0f4d4209a3ab     /data     ext4     defaults   ...

  10. Sql server 打不开了,无法识别的配置节 system.serviceModel 解决方案

    异常描述: System.Configuration.ConfigurationErrorsException: 配置系统未能初始化 ---> System.Configuration.Conf ...