Day-27 CRM权限和增删改查组件(CURD)-arya(一)
CRM权限
bug修改
- 预准备
# rbac/service/init_permission.py
from django.conf import settings
# 获取权限信息,放入session,inclusion_tag
def init_permission(user,request):
"""
获取权限在放到session中
:param obj:
:param request:
:return:
"""
permission_list = user.roles.filter(permissions__id__isnull=False).values( # 角色如果没有权限permissions__id有可能为空,permissions__id__isnull=False,permissions__id不可为空
'permissions__id', # 双下划线跨表查询
'permissions__title',
'permissions__url',
'permissions__code',
'permissions__menu_gp_id',
'permissions__group_id', # 组内菜单ID,Null表示菜单
'permissions__group__menu_id', # 当前权限所在组的菜单ID
'permissions__group__menu__title', # 当前权限所在组的菜单名称
).distinct() # distinct() 去重 for 权限
menu_permission_list = []
for item in permission_list:
# is_menu = item['permissions__is_menu']
# if not is_menu:
# continue
tpl = {
'id':item['permissions__id'],
'title': item['permissions__title'],
'url': item['permissions__url'],
'menu_gp_id':item['permissions__menu_gp_id'],
'menu_id': item['permissions__group__menu_id'],
'menu_title': item['permissions__group__menu__title'],
# 'parent_id':item['permissions__group__parent_id'],
}
menu_permission_list.append(tpl) # 生成菜单列表
#
request.session[settings.PERMISSION_MENU_KEY] = menu_permission_list
# 获取权限信息,放入session,中间件
"""
{
1:{
codes:[list,add,edit,del],
urls:[/userinfo/,/userinfo/add/,/userinfo/change/,...], # 根据当前用户拥有的权限
}
2:{
codes:[list,add,edit,del],
urls:[/userinfo/,/userinfo/add/],
}
3:{
codes:[list,add,edit,del],
urls:[/userinfo/],
}
}
"""
result = {}
for item in permission_list:
group_id = item['permissions__group_id']
code = item['permissions__code']
url = item['permissions__url']
if group_id in result:
result[group_id]['codes'].append(code)
result[group_id]['urls'].append(url)
else:
result[group_id] = {"codes": [code, ], "urls": [url, ]}
"""
{
1:{'codes':['list','add'],urls:['/userinfo/','/userinfo/add/']},
2:{'codes':['list','add'],urls:['/order/','/order/add/']},
}
"""
request.session[settings.PERMISSION_URL_DICT_KEY] = result
- 删除编辑,菜单无法显示
# rbac/templatetags/rbac.py
from django.conf import settings
from django.template import Library
import re
register = Library()
@register.inclusion_tag("menu.html")
def menu_html(request):
"""
去Session中获取菜单相关信息,匹配当前URL,生成菜单
:param request:
:return:
"""
menu_list = request.session[settings.PERMISSION_MENU_KEY]
current_url = request.path_info
#
# 循环menu_list,找到menu_gp_id为空的字典,加入到menu_dict中
menu_dict = {}
for item in menu_list:
if not item['menu_gp_id']:
menu_dict[item['id']] = item
# 匹配item的url,将menu_gp_id为真的对应的menu_dict[menu_gp_id]中的,active置为真
# 将menu_gp_id为空的menu_dict[item['id']]的active置为真
for item in menu_list:
regex = "^{0}$".format(item['url'])
if re.match(regex,current_url):
menu_gp_id = item['menu_gp_id']
# 如果有menu_gp_id的找到menu_gp_id对应的纪录,在该记录加上active为True
# 如果没有menu_gp_id,找到自己的id,添加active为True
if menu_gp_id:
menu_dict[menu_gp_id]['active'] = True
else:
menu_dict[item['id']]['active'] = True
result = {}
for item in menu_dict.values():
active = item.get('active')
menu_id = item['menu_id']
if menu_id in result:
result[menu_id]['children'].append({'title':item['title'],'url':item['url'],'active':active})
if active:
result[menu_id]['active'] = True
else:
result[menu_id] = {
'menu_id':item['menu_id'],
'menu_title':item['menu_title'],
'active':active,
'children':[
{'title':item['title'],'url':item['url'],'active':active}
]
}
return {'menu_dict':result}
'''
result
{
1: {
'menu_id': 1,
'menu_title': '菜单一',
'active': True,
'children': [
{
'title': '用户列表',
'url': '/userinfo/',
'active': True
},
{
'title': '添加用户',
'url': '/userinfo/add/',
'active': None
},
{
'title': '订单列表',
'url': '/order/',
'active': None
},
{
'title': '添加订单',
'url': '/order/add/',
'active': None
}
]
},
2: {
'menu_id': 2,
'menu_title': '菜单二',
'active': None,
'children': [
{
'title': '账目列表',
'url': '/account/',
'active': None
},
{
'title': '添加账目',
'url': '/account/add/',
'active': None
}
]
}
}
'''
#
# menu.html
{% for k,item in menu_dict.items %}
<div class="item">
<div class="item-title">{{ item.menu_title }}</div>
{% if item.active %}
<div class="item-permission">
{% else %}
<div class="item-permission hide">
{% endif %}
{% for v in item.children %}
{% if v.active %}
<a href="{{ v.url }}" class="active">{{ v.title }}</a>
{% else %}
<a href="{{ v.url }}">{{ v.title }}</a>
{% endif %}
{% endfor %}
</div>
</div>
{% endfor %}
- 控制页面是否显示‘添加’,‘编辑’,‘删除’
# rbac/middleware/rbac.py
from django.shortcuts import redirect,HttpResponse
from django.conf import settings
import re
class MiddlewareMixin(object):
def __init__(self,get_response=None):
self.get_response = get_response
super(MiddlewareMixin,self).__init__()
def __call__(self, request):
response = None
if hasattr(self,'process_request'):
response = self.process_request(request)
if not response:
response = self.get_response(request)
if hasattr(self,'process_response'):
response = self.process_response(request,response)
return response
#
class RbacMiddleware(MiddlewareMixin):
#
def process_request(self,request):
# 获取当前请求的URL
# request.path_info
# 获取session中的保存当前用户的权限
# request.session.get("permission_url_list")
current_url = request.path_info
#
# 当前请求不需要执行权限验证
for url in settings.VALID_URL:
if re.match(url,current_url):
return None
permission_dict = request.session.get(settings.PERMISSION_URL_DICT_KEY)
"""
{
1:{
codes:[list,add,edit,del],
urls:[/userinfo/,/userinfo/add/,/userinfo/change/,...], # 根据当前用户拥有的权限
}
2:{
codes:[list,add,edit,del],
urls:[/userinfo/,/userinfo/add/],
}
3:{
codes:[list,add,edit,del],
urls:[/userinfo/],
}
}
"""
if not permission_dict:
return redirect('/login/')
flag = False
for group_id,code_url in permission_dict.items():
for db_url in code_url['urls']:
regax = "^{0}$".format(db_url)
if re.match(regax,current_url):
# 获取当前用户对当前组内的所有code,并赋值给request
x request.permission_code_list = code_url['codes'] ※
# request.xxx可以自定义,其他地方可调用
flag = True
break
if flag:
break
if not flag:
return HttpResponse("无权访问")
# views.py
from django.shortcuts import render,redirect,HttpResponse
from rbac import models
from rbac.service.init_permission import init_permission
def login(request):
if request.method == "GET":
return render(request,'login.html')
user = request.POST.get('user')
pwd = request.POST.get('pwd')
#
obj = models.User.objects.filter(username=user,password=pwd).first()
if obj:
# 登录成功
# 获取当前用户权限信息
# 获取权限信息
# 获取菜单信息
init_permission(obj,request) # 将用户信息和包含session信息的request传给初始化模块
return redirect('/userinfo/')
return render(request,'login.html')
#
def userinfo(request):
return render(request,'userinfo.html')
#
# userinfo.html
{% extends "layout.html" %}
{% block content %}
<h1>用户列表</h1>
{% if 'add' in request.permission_code_list %}
<a href="/userinfo/add">添加</a>
{% endif %}
<table>
<tr>
<td>wbd</td>
<td>xxx</td>
{% if 'edit' in request.permission_code_list %}
<td>
<a href="">编辑</a>
</td>
{% endif %}
{% if 'del' in request.permission_code_list %}
<td>
<a href="">删除</a>
</td>
{% endif %}
</tr>
</table>
{% endblock %}
自定义CURD插件
django admin,解决基本的增删改查(CURD)
- 参考博文:
http://www.cnblogs.com/wupeiqi/articles/7444717.html - 注册
只要注册一个类admin就会多至少4个url
相当于admin内部做了urls.py和views.py中的事
admin.site.register(models.UserInfo)
URL:
/admin/app01/userinfo/
/admin/app01/userinfo/add/
/admin/app01/userinfo/1/change/
/admin/app01/userinfo/1/delete/
admin.site.register(models.UserType)
URL:
/admin/app01/usertype/
/admin/app01/usertype/add/
/admin/app01/usertype/1/change/
/admin/app01/usertype/1/delete/
# app01/admin.py
from django.contrib import admin
from . import models
admin.site.register(models.UserInfo)
admin.site.register(models.UserType)
# urls.py
urlpatterns = [
url(r'^admin/', admin.site.urls), # 找到admin中注册的类为每个类生成4个URL
]
结论:
先注册类
循环注册的所有的类,每个类创建4个url
/admin/app01/userinfo/ obj1.changelist_view
/admin/app01/userinfo/add/ obj1.add_view
/admin/app01/userinfo/(\d+)/delete/ obj1.delete_view
/admin/app01/userinfo/(\d+)/change/ obj1.change_view
self.model=models.UserInfo
/admin/app01/usertype/ obj2.changelist_view
/admin/app01/usertype/add/ obj2.add_view
/admin/app01/usertype/(\d+)/delete/ obj2.delete_view
/admin/app01/usertype/(\d+)/change/ obj2.change_view
self.model=models.UserType
Admin自带的自定义功能:
根据发配置问价的修改,完成增删改查(CURD)的定制任务
from django.contrib import admin
from . import models
from django.contrib.admin import ModelAdmin
from django.forms import ModelForm
from django.forms import fields
from django.forms import widgets
#
class UserModelForm(ModelForm): # 用于form
others = fields.CharField() # 用于增加一个自定义个的用户可填充的列信息
class Meta:
models = models.UserInfo
fields = '__all__'
error_messages = {
'name':{'required':'用户名不能为空'},
}
#
class UserInfoModelAdmin(ModelAdmin):
list_display = ['name','pwd','xxx'] # 定义列表页面显示哪些列数据
def xxx(self,obj):
return obj.name + obj.pwd
xxx.empty_value_display = '显示默认为空的值'
list_display_links = ['pwd'] # 定义列表页面,哪列可以进行点击进入编辑页面
list_filter = ['ut'] # 定义列表页面,做快速搜索功能
# list_per_page = 2 # 定义列表页面,分页,一页显示几行
list_editable = ['name'] # 定义列表页面,可用输入框编辑列,似乎只能单列
search_fields = ['name','pwd'] # 定义列表页面,可多了做模糊搜索
save_on_top = True # 定义列表页面,在页面上方在显示一个保存按钮
def func(self,request,queryset): # 定义列表页面,自定义批量操作的动作
print(self,request,queryset)
print(request.POST.getlist('_selected_action')) # 拿到了用户的ID:['2', '1']
id_list = request.POST.getlist('_selected_action')
# models.UserInfo.objects.filter(id__in=id_list).delete()
func.short_description = '批量初始化'
actions = [func,]
# 定义HTML模板
# add_form_template = None
# change_form_template = None
# change_list_template = ['xxx.html'] # 对应templates下的html文件
# delete_confirmation_template = None
# delete_selected_confirmation_template = None
# object_history_template = None
# raw_id_fields = ['ut',] # 定义列表页面,详细页面,针对FK和M2M字段变成以Input框形式
# fields = ['name'] # 定义列表页面,只显定义的字段
# exclude = ['name'] # 定义列表页面,只显示定义以外的字段
# readonly_fields = ['name'] # 定义列表页面,定义字段只读不可更改
# fieldsets = ( # 定义列表页面,设置基本数据,其他数据需要点击扩展显示
# ('基本数据', {
# 'fields': ('user', )
# }),
# ('其他', {
# 'classes': ('collapse', 'wide', 'extrapretty'), # 'collapse','wide', 'extrapretty'
# 'fields': ('pwd', 'ut'),
# }),
# )
# filter_vertical = ['roles',] # 定义列表页面,针对M2M字段,页面已选和未选上下显示方式
filter_horizontal = ['roles',] # 定义列表页面,针对M2M字段,页面已选和未选水平显示方式
# ordering = [' id'] # 定义列表页面,排序,以定义字段为基准排序,-id从大到小
# prepopulated_fields = {'email':('name','pwd',)} # 定义列表页面,emial字段自动结合name和pwd写入
form = UserModelForm
#
admin.site.register(models.UserInfo,UserInfoModelAdmin)
#
class UserTypeModelAdmin(ModelAdmin):
list_display = ['title',]
admin.site.register(models.UserType,UserTypeModelAdmin)
#
admin.site.register(models.Role)
自定义Admin
知识点
- 启动文件的执行顺序
admin.py
urls.py- 制作启动文件在admin.py和自定义启动文件同时使用
- 创建一个app(python manage.py startapp arya)
- 在该app下找到apps.py文件在AryaConfig(AppConfig)类中加入:
def ready(self): from django.utils.module_loading import autodiscover_modules autodiscover_modules('arya')
- settings.py注册
INSTALLED_APPS = [ 'arya.apps.AryaConfig', ]
- 注册完成后,该程序会在所有的启动文件中找叫arya.py文件默认先执行
- 制作启动文件在admin.py和自定义启动文件同时使用
- 单例模式
基于Python文件导入特性的单例模式,实例被多次导入也会永远用一个实例
清空app01/admin.py内容
# arya/service/v1.py
class AryaSite(object):
def __init__(self):
self._registry = {}
#
def register(self,class_name,config_class):
self._registry[class_name] = config_class
site = AryaSite()
#
# app01/arya.py
from arya.service import v1
#
v1.site.register('k1','v1') # 值均加入到了_registry中
v1.site.register('k2','v2')
# urls.py
from arya.service import v1
print(v1.site._registry) # {'k2': 'v2', 'k1': 'v1'}
- urls.py中的include的路由分发
include本质上是一个元祖,格式为([],None,None)
可以根据定制逐级路由分发,在列表里还可以无限的加下一级路由
只要符合([],None,None)的格式
from django.conf.urls import url,include
from django.contrib import admin
from arya.service import v1
#
# from django.shortcuts import HttpResponse
# def login(request):
# return HttpResponse('login')
#
# def logout(request):
# return HttpResponse('logout')
urlpatterns = [
url(r'^arya/', (v1.site.urls,None,None)),
]
# url(r'login/', self.login),
# url(r'logout/', self.logout),
# url(r'/app01/userinfo/', ([
# url(r'/', self.change_view),
# url(r'add/', self.add_view),
# url(r'(\d+)/change/', self.change_view),
# url(r'(\d+)/delete/', self.delete_view),
#
# ],None,None) ),
# url(r'/app01/usertype/', ([
# url(r'/', self.change_view),
# url(r'add/', self.add_view),
# url(r'(\d+)/change/', self.change_view),
# url(r'(\d+)/delete/', self.delete_view),
# ],None,None) ),
# url(r'^admin/', admin.site.urls), # 找到admin中注册类,为每一个类生成四个URL
# url(r'^crm/', include("app01.urls")), # 找到admin中注册类,为每一个类生成四个URL
# url(r'^crm/', ([
# url(r'login/', login),
# url(r'logout/', logout),
# url(r'app01/userinfo/', ([
# url(r'login/', login),
# url(r'logout/', logout),
# url(r'logout/', logout),
# url(r'logout/', logout),
# ],None,None)),
# url(r'app01/usertype/', include([
# url(r'login/', login),
# url(r'login/', login),
# url(r'login/', login),
# url(r'logout/', logout),
# ],None,None))
# ],None,None) ),
# ]
- 动态创建路由关系
制作启动文件,每个app的arya.py
site._registry = {
app01.UserInfo: UserInfoConfig对象1(UserInfo) =>[self.changelist_view(self,),self.add_view,self.change_view,self.delete_view]
app01.UserType: AryaConfig对象2(UserType) =>[self.changelist_view(self,),self.add_view,self.change_view,self.delete_view]
app02.Server: AryaConfig对象3(Server) =>[self.changelist_view(self,),self.add_view,self.change_view,self.delete_view]
}
以后,让UserInfo的所有操作交给AryaConfig对象来处理
urls.py
url(r'^arya/', (v1.site.urls,None,None)),
for model_class,ary_config_obj in self._registry.items():
UserInfo ary_config_obj.url
- 列表 ary_config_obj.changelist_view
- 添加 ary_config_obj.add_view
- 删除 ary_config_obj.delete_view
- 修改 ary_config_obj.change_view
# urls.py
from django.conf.urls import url,include
from django.contrib import admin
from arya.service import v1
#
urlpatterns = [
url(r'^arya/', v1.site.urls),
]
#
# app01/arya.py
from arya.service import v1
from . import models
#
v1.site.register(models.UserInfo,v1.AryaConfig)
v1.site.register(models.UserType,v1.AryaConfig)
v1.site.register(models.Role,v1.AryaConfig)
#
# arya/service/v1.py
from django.conf.urls import url
from django.shortcuts import HttpResponse,render
class AryaConfig(object):
def __init__(self,model_class,site):
self.model_class = model_class
self.site = site
@property
def urls(self):
partterns = [
url(r'^$',self.list_view),
url(r'^add/',self.add_view),
url(r'^(\d+)/change/',self.change_view),
url(r'^(\d+)/delete/',self.delete_view),
]
return partterns
def list_view(self,request):
data_list = self.model_class.objects.all()
return render(request,'list.html',{'data_list':data_list})
def add_view(self,request):
return HttpResponse('添加')
def change_view(self,request,nid):
return HttpResponse('编辑')
def delete_view(self,request,nid):
return HttpResponse('删除')
#
'''
将models.UserInfo作为key,v1.AryaConfig作为value保存在_registry的字典里
路由调用v1.site.urls分发路由,parttens作为url的列表预定义了login和logout的路由
之后循环_registry中的key和value,格式化为路径,路由到AryaConfig的urls方法
urls方法中的partterns列表中预定义的list,add,change和delete的路由对应各自的方法
每个项目中如app01的每个表可以直接使用增删改查的方法
'''
class AryaSite(object):
def __init__(self):
self._registry = {}
def register(self,class_name,config_class): # class_name = UserInfo,config_class = AryaConfig
self._registry[class_name] = config_class(class_name,self)
@property
def urls(self):
partterns = [
url(r'^login/',self.login),
url(r'^logout/',self.logout),
]
for model_class,arya_config_obj in self._registry.items():
print(model_class,arya_config_obj,model_class._meta.app_label,model_class._meta.model_name,)
app_name = model_class._meta.app_label # _meta.app_label获取UserInfo的app名称
table_name = model_class._meta.model_name # _meta.model_name获取UserInfo表名的小写
app_model_name = r'^{0}/{1}/'.format(app_name,table_name)
pt = url(app_model_name,(arya_config_obj.urls,None,None))
partterns.append(pt)
return partterns,None,None
def login(self,request):
return HttpResponse('Login')
def logout(self,request):
return HttpResponse('logout')
#
site = AryaSite()
- arya组件自定制list_display
# app01/arya.py
from arya.service import v1
from . import models
#
class UserInfoConfig(v1.AryaConfig):
list_display = ['name','pwd','email']
v1.site.register(models.UserInfo,UserInfoConfig)
#
class UserTypeConfig(v1.AryaConfig):
list_display = ['title']
v1.site.register(models.UserType,UserTypeConfig)
#
class RoleConfig(UserTypeConfig):
list_display = ['caption']
v1.site.register(models.Role,RoleConfig)
#
# arya/service/v1.py
class AryaConfig(object):
list_display = []
def __init__(self,model_class,site):
self.model_class = model_class
self.site = site
@property
def urls(self):
partterns = [
url(r'^$',self.list_view),
url(r'^add/',self.add_view),
url(r'^(\d+)/change/',self.change_view),
url(r'^(\d+)/delete/',self.delete_view),
]
return partterns
def list_view(self,request):
data_list = []
queryset = self.model_class.objects.all() # 获取表对象
for row in queryset: # 循环每一行的内容
if not self.list_display: # 如果list_display为空
data_list.append([row,]) # 将整行加入data_list
else:
tmp = []
for n in self.list_display: # 如果list_display有值,循环如果list_display有值
tmp.append(getattr(row,n)) # 将每个定义的字符串如name,字段反射到数据库拿值
data_list.append(tmp) # 将tmp列表加入到data_list展示到前端
return render(request,'list.html',{'data_list':data_list})
def add_view(self,request):
return HttpResponse('添加')
def change_view(self,request,nid):
return HttpResponse('编辑')
def delete_view(self,request,nid):
return HttpResponse('删除')
#
# list.html
<body>
<table border="1">
{% for item in data_list %}
<tr>
{% for col in item %}
<td>{{ col }}</td>
{% endfor %}
</tr>
{% endfor %}
</table>
</body>
- arya组件自定制list_display加入函数控制
可以加入自定义的方法,如在页面加入增删改查的a标签
# app01\arya.py
from arya.service import v1
from . import models
from django.utils.safestring import mark_safe
#
class UserInfoConfig(v1.AryaConfig):
def func(self,row):
return mark_safe("<a href='/delete/{0}'>删除</a>".format(row.id))
def checkbox(self,row):
return mark_safe("<input type='checkbox' value='{0}'>".format(row.id))
list_display = [checkbox,'name','pwd','email',func] # 将自定义方法加入到list_display
v1.site.register(models.UserInfo,UserInfoConfig)
# arya\service\v1.py
class AryaConfig(object):
list_display = []
def __init__(self,model_class,site):
self.model_class = model_class
self.site = site
@property
def urls(self):
partterns = [
url(r'^$',self.list_view),
url(r'^add/',self.add_view),
url(r'^(\d+)/change/',self.change_view),
url(r'^(\d+)/delete/',self.delete_view),
]
return partterns
def list_view(self,request):
data_list = []
queryset = self.model_class.objects.all()
for row in queryset:
if not self.list_display:
data_list.append([row,])
else:
tmp = []
for str_func in self.list_display:
if isinstance(str_func,str): # 判断是否为字符串
tmp.append(getattr(row,str_func)) # 字符串,字段反射到数据库拿值
else:
tmp.append(str_func(self,row)) # 如果为方法,将自己和row对象作为参数传入方法
data_list.append(tmp)
return render(request,'list.html',{'data_list':data_list})
def add_view(self,request):
return HttpResponse('添加')
def change_view(self,request,nid):
return HttpResponse('编辑')
def delete_view(self,request,nid):
return HttpResponse('删除')