Day-27 CRM权限和增删改查组件(CURD)-arya(一)

CRM权限

bug修改

  1. 预准备
# 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
  1. 删除编辑,菜单无法显示
# 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 %}
  1. 控制页面是否显示‘添加’,‘编辑’,‘删除’
# 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)

  1. 参考博文:
    http://www.cnblogs.com/wupeiqi/articles/7444717.html
  2. 注册
    只要注册一个类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

知识点

  1. 启动文件的执行顺序
    admin.py
    urls.py
    1. 制作启动文件在admin.py和自定义启动文件同时使用
      1. 创建一个app(python manage.py startapp arya)
      2. 在该app下找到apps.py文件在AryaConfig(AppConfig)类中加入:
      def ready(self):
          from django.utils.module_loading import autodiscover_modules
          autodiscover_modules('arya')
      
    2. settings.py注册
    INSTALLED_APPS = [
        'arya.apps.AryaConfig',
    ]
    
    1. 注册完成后,该程序会在所有的启动文件中找叫arya.py文件默认先执行
  2. 单例模式
    基于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'}
  1. 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) ),
# ]
  1. 动态创建路由关系
    制作启动文件,每个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()
  1. 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>
  1. 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('删除')

posted on 2017-11-29 15:16  运维小学生  阅读(118)  评论(0)    收藏  举报

导航