• CURD插件(仿Django-admin版)


    前言

    如何提升自己的开发效率?

    每个新项目都是自己经做过的项目(经验所致),在项目开发过程中不断总结、封装属于自己的组件,

    例如:每个web项目大部分都涉及增删改查,分页显示,搜素,CRM就是这样的组件,是一件很有必要的事情;

    CURD组件(arya):模仿DjangoAdmin编写增删改查插件;

    组件功能:通过在后台注册表名配置自定制类,操作、显示数据库中的内容;

    组件设计目标:把CRM模块嵌套到不同具有增删改查功能的Django程序上,实现快速开发基于web的后台管理系统;

    组件配置项:

    list_display=[ '字段',‘函数’] : 定制显示页面显示的列名,和内容;

    action                                   : 定制action,批量操作;

    show_add_btn=True            :定制是否显示添加按钮

    model_form=None               :定制model_form 提供编辑、添加、删除页面,通过popup操作关联表数据

    lister_filter                            :组合筛选项

    search_list = ['name', 'qq']     :模糊搜素

    组件说明:

    Site类:注册和生成基础url

    ---方法:

    url(路由调用)

    get_usrl(url方法调用)

    register(注册调用)

    login(登录视图)

    logout(登出视图)

    ---字段:

    namespace(url所需名称空间)

    self. _registry={ }(注册生成

    # {
    # model.表名1,配置对象1
    # model.表名2,配置对象2
    # }

    self.name  app名称

    Config类:生成基础配置,生成增、删、改、查url,处理request请求;

    ---方法

    warpper()  通过装饰器实现每次请求保存request信息

    add_view()   增、删、改、查视图

    changlist_view()

    delete_view()

    changge_view()

    urls() 增、删、改、查url

    extra_urls()url扩展

    类字段:

    list_dispaly=[字段、函数   ]   配置list显示页面需要列 和内容   

    get_listdisplay() 

    actions = [函数]    配置选中chebox可执行的操作

    get_actions()

    add_btn=True  list显示页面是否显示添加按钮

    get_ show_add_btn()

    model_form = None   配置 增、删、改。查操作的model_form

    get_model_form_class()

    list_filter = [字段、函数     ] 配置组合搜素内容

    get_list_filter()

    对象字段

    self.model_class   (models.UserInfo)

    self.request  (每个对象的request请求信息)

    self.site (注册类 )

    ChangList类:封装list显示页面所有数据,通过对象的方式传到前端模板;

    方法:

    add_html(self),gen_list_filter  生成添加按钮  、生成组合搜素 需要的数据比较多不适合在前端模板生成,所以选择后端;

    类字段:

    对象字段:

    self.model_config_obj (因为list显示页面,需要list_display,action,show_add。。。很多配置类的属性,所有干脆把 配置对象封装到ChangList类中  )

    1 class ChangeList(object):  # 由于使用inclusion_tag,需要传很多值,就把这个些值封装到类里,一对象的形式递给前端
    2     def __init__(self, data_list, model_config_obj):
    3         self.list_display = model_config_obj.get_list_play()
    4         self.actions = model_config_obj.get_actions()
    5         self.model_config_obj = model_config_obj
    6         self.list_filter = model_config_obj.get_list_filter()
    View Code

    self.request = request 、self.datalist  用于分页

    -----------------------------------------------------------------------------------------------------------------

    option类:组合搜素 配置项  

    list_filter= [v1.OptionConfig('group',True), v1.OptionConfig('roles',False), ]
    

    方法:

    is_func(self): 判断是否是函数

    name(self): 生成函数名或者字段名

    对象字段

    """
    :param field: 字段名称或函数
    :param is_multi: 是否支持多选
    :param text_func_name: 在Model中定义函数,显示文本名称,默认使用 str(对象)
    :param val_func_name: 在Model中定义函数,显示文本名称,默认使用 对象.pk


    RowItems类:生成组合搜素需要的A标签

    class RowItems():
        def __init__(self, option, data_list, params):
            self.option = option
            self.data_list = data_list
            self.param = copy.deepcopy(params)  # query_dict对象
            print(self.param)
            # print(self.param)  # <QueryDict: {'group': ['1'], 'roles': ['2']}>
            self.param._mutable = True
    
        def __iter__(self):
            if self.option.is_muti: #如果配置了多选
                current_pk_or_list = self.param.getlist(self.option.name)  #[1,2]
                print(current_pk_or_list)
            else:
                current_pk_or_list = self.param.get(self.option.name)  # [1]
            if self.param.get(self.option.name): #如果<QueryDict: {'roles': ['2'], 'group': ['4', '5', '3', '2', '1']}> 能获取到 group  roles
                # 生成全部
                self.param.pop(self.option.name)  # 全部就pop自己
                all_url = self.param.urlencode()
                tmp = '<a href="?%s">全部</a>' % (all_url)
                yield mark_safe(tmp)
            else:
                all_url = self.param.urlencode()  #如果访问的url 没有携带参数
                tmp = '<a class="active" href="?%s">全部</a>' % (all_url)
                yield mark_safe(tmp)
            #生成全部右侧 obj  data_list=[obj,obj,obj ]
            for obj in self.data_list:
                #每个obj 生成 每个 A标签
                pk = str(obj.pk)  #1
                text = str(obj)   #阿斯蒂芬
                if self.option.is_muti:  #如果配置了多选
                    if pk not in current_pk_or_list: #如果obj.pk不在【1.2】
                        tmp=[]
                        tmp.extend(current_pk_or_list)
                        tmp.append(pk)
                        self.param.setlist(self.option.name,tmp) #注意在原来不变的基础上多加1个?group=2&group=3
    
    
    
                else:
                    self.param[self.option.name]=pk
                # print(self.param)  #<QueryDict: {'group': ['2', '3'], 'roles': ['1']}>
                url = self.param.urlencode()
                if not self.option.is_muti: #单选生成A标签
                    if current_pk_or_list == pk:   #url中传来的 get参数
                        tmp = '<a class="active" href="?%s">%s</a>' % (url, text)  # 设置选择标签颜色
                    else:
                        tmp = '<a href="?%s">%s</a>' % (url, text)
                    yield mark_safe(tmp)
                else:
                    if pk in current_pk_or_list:  # url中传来的 get参数
                        tmp = '<a class="active" href="?%s">%s</a>' % (url, text)  # 设置选择标签颜色
                    else:
                        tmp = '<a href="?%s">%s</a>' % (url,text)
                    yield mark_safe(tmp)
    View Code

    一、CURD组件开发

    1、CRM程序入口 apps.App02Config,执行app02下的app.py文件中App02Config类的ready方法;

    CRM要从Django的setings.py配置文件的apps.App02Config类说起,apps.App02Config类在每个APP的apps.py文件中,该类的ready()方法会在ROOT_URLCONF = 'CRM.urls'配置未执行之前执行,实现帮我们把表和自定义配置类注册;

    from django.apps import AppConfig
    
    
    class App01Config(AppConfig):
        name = 'app01'
        def ready(self):
            pass
    View Code

    2、App02Config类中的ready方法,执行 autodiscover_modules('zhanggen'),所以去Django程序的所有app下寻找zhanggen.py并执行;

    autodiscover_modules()模块,在Django程序启动自动发现并执行一个py文件  (启动文件)

    autodiscover_modules(xx.py)模块自动去所有的APP下,寻找xx.py文件并且执行

    from django.apps import AppConfig
    
    
    class App02Config(AppConfig):
        name = 'app02'
        def ready(self):
            # 1.导入autodiscover_modules 程序启动执行autodiscover_modules(参数)
            # 谁导入autodiscover_modules(py文件)并设置了,就会在程序启动前执行那个pywenj
            from django.utils.module_loading import autodiscover_modules
            autodiscover_modules('zhanggen')
    View Code

     3、找到后zhanggen.py from app02.service import v1 

    先写好注册功能,等待调用。。(注意site是用文件实现的单例模式)

    from django.shortcuts import HttpResponse,render
    from django.conf.urls import url
    from types import FunctionType
    from django.urls import reverse
    from django.utils.safestring import mark_safe
    
    class ChangeList(object):  #封装 table 数据传递给前端
        def __init__(self,data_list,list_display,model_config_obj):
            self.data_list = data_list
            self.list_display = list_display
            self.model_config_obj = model_config_obj
    
        def add_html(self):  #封装add 按钮
            app_model = self.model_config_obj.model_class._meta.app_label, self.model_config_obj.model_class._meta.model_name
            add_url = reverse("zg:%s_%s_add" % app_model)
            add_html = mark_safe('<a class="btn btn-primary" href="%s">添加</a>' % (add_url,))
            return add_html
    
    
    class Modelzg(object):    # 注册的时候没有使用配置对象,默认使用的配置对象
        ''''用户基础配置类'''
    
        def __init__(self, model_class, site): #model_class=model.userinfo  , site=Zgsite对象
            self.model_class = model_class
            self.site = site
    
        list_display = []
        show_add_btn = True
    
    
        def get_show_add_btn(self):
            return self.show_add_btn
    
    
        def changelist_view(self, request, *args, **kwargs):
            #由于组册类调用本类的时候传入了表名 self._registry[model]=model_zg(model,self)
            #表名 model_class=models.UserInfo ,所有就可以根据 组册表名获取数据了
            data_list=self.model_class.objects.all()
            self.request = request
            # def headers():
            #     if not self.list_display:#如果用户没有自定义配置就返回表名
            #         yield self.model_class._meta.model_name
            #     else:
            #         for v in self.list_display:
            #         #     if isinstance(v,FunctionType):
            #         #         yield v(True)
            #         #     else:
            #         #         #获取中文 列名称
            #         #         # models.UserInfo._meta.get_field(email/name).verbose_name
            #         #         verbose_name= self.model_class._meta.get_field(v).verbose_name
            #         #         yield verbose_name
            #         #三元运算
            #             yield v(self, is_header=True) if isinstance(v, FunctionType) else self.model_class._meta.get_field(
            #             v).verbose_name
            # def body():
                # for row in data_list:
                    # #row 是数据库中的一行数据,对象
                    # row_data=[]
                    # for name in self.list_display:
                    #     #list_display = ['id', 'name', 'email', xxxxx]
                    #     if isinstance(name,FunctionType):
                    #        row_data.append(name(self,row))
                    #     else:
                    #        row_data.append(getattr(row,name))
                    # yield row_data
                    #三元表达式 列表生成式
            #         if not self.list_display:
            #             yield [str(row), ]
            #         else:
            #             yield [name(self,obj=row) if isinstance(name, FunctionType) else  getattr(row, name) for name in
            #                    self.list_display]
            #
            # context = {'data_list': data_list,
            #            'list_display': self.list_display,
            #            'headers':headers(),
            #            'body':body(),
            #            }
            cl = ChangeList(data_list,self.list_display,self) #传到simp_tag
            context = {
                'cl': cl
            }
    
            return render(request,'nb/changelist.html', context)
    
    
    
        def add_view(self, request, *args, **kwargs):
            return HttpResponse('添加页面')
    
        def delete_view(self, request, *args, **kwargs):
            return HttpResponse('删除页面')
    
        def change_view(self, request, *args, **kwargs):
            return HttpResponse('修改页面')
    
        def get_urls(self):
            app_model_name = self.model_class._meta.app_label, self.model_class._meta.model_name  #(app01,表名)
            '''
            生成url:
            /zg/app01/userinfo/ 显示  
            /zg/app01/userinfo/add 添加
            /zg/app01/userinfo/ 删除
            /zg/app01/userinfo/ 编辑     
            '''
            patterns = [
                url(r'^$', self.changelist_view,name="%s_%s_changelist" %app_model_name),
                url(r'^add/$', self.add_view,name="%s_%s_add" %app_model_name),
                url(r'^(.+)/delete/$', self.delete_view,name="%s_%s_delete" %app_model_name),
                url(r'^(.+)/change/$', self.change_view,name="%s_%s_change" %app_model_name),
            ]
            patterns+=self.extra_urls()
            return patterns
    
        def extra_urls(self):
            ''''自定义扩展url 预留的钩子函数'''
            return []
    
        @property
        def urls(self):
            return self.get_urls(),None,None
    
    
    
    class Zgsite(object):
        def __init__(self):
            self._registry={}
            self.name='zg'
            self.namespace='zg'
    
        def register(self,model,model_zg=None):
            if not model_zg:  #如果在register()注册的时候,没有传(配置类)
                model_zg=Modelzg #就使用Modelzg自己定义的类
            self._registry[model]=model_zg(model,self)   #注意  model:表名   model_zg(model表名,self =Zgsite对象 ):是配置对象
    
        def login(self,request):
            return HttpResponse('登录页面')
    
        def logout(self,request):
            return HttpResponse('注销页面')
    
        def ger_urls(self):
            patterns=[]
            patterns+=[
                url(r'^login/',self.login),
                url(r'^logout/', self.logout),
            ]
            for table_name,config_obj in self._registry.items():
                '''
                table_name._meta.app_label 注册表名所在的app名称  例: app01
                table_name._meta.model_name 注册表名所在的 表名小写 例:userinfo / usergroup
                '''
                patterns += [    #'/zg/app01/userinfo/'
                    # url(r'^app01/userinfo/',config_obj.urls)
                    # url(r'^app01/usergroup/',config_obj.urls继续构造([],'xx','xxx'))
                    # config_obj=Modelzg()   默认对象
                    #config_obj.urls  调用默认对象的 urls,继续构造([],'xx','xxx'))
                    url(r'^%s/%s/'%(table_name._meta.app_label,table_name._meta.model_name,),(config_obj.urls))
                ]
            return patterns
        @property
        def urls(self):
            return self.ger_urls(),self.name,self.namespace  #因为路由系统中的include本质就是返回一个元祖
    
    
    
    
    site=Zgsite()
    View Code

    设计知识点:

    文件实现单例模式:

    文件导入之后该文件中定义的类被实例化成一个对象,1次执行之后,以后再调用这个文件永远得到同一个对象,相当于永远执行1个对象;

    4、zhanggen.py实例化了v1中Zgsite类,得到单例对象site,site单例对象 2次(2张表) 调用了单例象的register()方法,

    v1.site.register(models.UserInfo)
    v1.site.register(models.UserGroup)
    
    #1、导入v1就得到一个单例对象 site
    #2、执行site对象的regist()进行注册
    View Code

    5、site单例对象的register()方法把site对象的self._registry属性,填充成

    {

    models.UserInfo:UserInfoConfig(models.UserInfo,self,) #sel也就是,site对象由于是单例模式userindo的site对象=slef=UserGroup的site对象
    models.UserGroup:UserGroupConfig(models.UserGroup,self,)

         }

    ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

    {

    models.UserInfo,UserInfo配置对象(models.UserInfo,site对象), #配置对象是相互无关系的
    models.UserGroup,UserGrou配置对(models.UserInfo,对象)

     }

    class Zgsite(object):
        def __init__(self):
            self._registry={}
            self.name='zg'
            self.namespace='zg'
    
        def register(self,model,model_zg=None):
            if not model_zg:  #如果在register()注册的时候,没有传(配置类)
                model_zg=Modelzg #就使用Modelzg自己定义的类
            self._registry[model]=model_zg(model,self)   #注意  model:表名   model_zg(model表名,self =Zgsite对象 ):是配置对象
    View Code

    6、Django程序的路由系统开始执行,同样调用了单例对象 v1.py中的单例对象 site,并执行了site单例对象的urls方法;

     url(r'^zg/',v1.site.urls), 

    7、site单例对象的urls方法,调用了site单例对象的ger_urls方法;

        def ger_urls(self):
            patterns=[]
            patterns+=[                       #基本url
                url(r'^login/',self.login),  #/app01/login/
                url(r'^logout/', self.logout),#/app01/logout/
            ]
            for table_name,config_obj in self._registry.items():
                # print(config_obj.model_class)
                '''
                table_name._meta.app_label 注册表名所在的app名称  例: app01
                table_name._meta.model_name 注册表名所在的 表名小写 例:userinfo / usergroup
                '''
                patterns += [    #'/zg/app01/userinfo/'
                    # url(r'^app01/userinfo/',config_obj.urls)
                    # url(r'^app01/usergroup/',config_obj.urls继续构造([],'xx','xxx'))
                    # config_obj=Modelzg()   默认对象
                    #config_obj.urls  调用默认对象的 urls,继续构造([],'xx','xxx'))
             url(r'^%s/%s/'%(table_name._meta.app_label,table_name._meta.model_name,),(config_obj.urls)) #site类注意转接给你了基础配置类
                ]
            return patterns
        @property
        def urls(self):
            return self.ger_urls(),self.name,self.namespace  #因为路由系统中的include本质就是返回一个元祖
    View Code

    8、ger_urls方法,for循环了单例对象的self._registry属性,为每张表生成基础url映射关系;

    ^zg/ ^login/

    ^zg/ ^logout/

    ^zg/ ^app01/userinfo/

    ^zg/ ^app01/usergroup/

     9、单例对象site的ger_urls方法,并每个调用配置对象中的get_urls方法,为所有注册的表生成增删改查路由映射;

        def get_urls(self):
            app_model_name = self.model_class._meta.app_label, self.model_class._meta.model_name  #(app01,表名)
            '''
            生成url:
            /zg/app01/userinfo/ 显示  
            /zg/app01/userinfo/add 添加
            /zg/app01/userinfo/ 删除
            /zg/app01/userinfo/ 编辑     
            '''
            patterns = [
                #每个配置对象执行自己的changelist_view,所有他们执行视图虽然都叫changelist_view()但是不一样
                url(r'^$', self.changelist_view,name="%s_%s_changelist" %app_model_name),
                url(r'^add/$', self.add_view,name="%s_%s_add" %app_model_name),
                url(r'^(.+)/delete/$', self.delete_view,name="%s_%s_delete" %app_model_name),
                url(r'^(.+)/change/$', self.change_view,name="%s_%s_change" %app_model_name),
            ]
            patterns+=self.extra_urls()
            return patterns
    View Code

    涉及知识点:

    1、Django路由系统 includ()路由分发的本质

    本质就是就是return一个元祖,([],app名称,namespace名称空间)或者(py文件,app名称,namespace名称空间)切支持一直往下嵌套;

    urlpatterns = [
        # url(r'^admin/', admin.site.urls),
        url(r'^zg/',v1.site.urls),
        url(r'^app666/', ([
                      url(r'^login/', ( [url(r'^login1/',([url(r'^login2/',index)],'xxx','xxx')) ],'xx','xx')),
                      # url(r'^logout/', self.logout),
                      # url(r'^app01/userinfo/', self.logout),
                      # url(r'^app01/usergroup/', self.logout),
                     ],'nb','nb')),
    
    
    ]
    View Code

    2、namespace 反向生成url

    2.反向生成url
    
    视图
    
    def index(request):
       urls=reverse('tex',args=(8,))
       print(urls)
       return redirect(urls)
    
    路由系统
    url(r'^test/(d+)/',test,name='tex'),
    
    
    视图
    def index(request):
       urls=reverse('tex',kwargs={'a1':9})
       print(urls)
       return redirect(urls)
    
    
    路由系统
     url(r'^test/(?P<a1>d+)/',test,name='tex'),
    
    
    
    
    namespace:区分urlpatterns中同名的path
    
    如果url()中包含namespace,在 reverse(namespace:别名)时一点要加上namesp
    
    def index(request):
       urls=reverse('xx:tex')
       print(urls)
       return redirect(urls)
    
    
    url(r'^app01/',([
                    url(r'^index/',index,name='inde'),
                    url(r'^test/',test,name='tex'),
                                ],'xx','xx')),
    View Code

    3、model.py中类的_meta方法:

      app_name=models.UserInfo._meta.app_label     #获取model中类(表),所在的app
        table_name=models.UserInfo._meta.model_name  #获取model中类(表),表名
        filed_verbose=models.UserInfo._meta.get_field('email').verbose_name #获取列的verbosename 就是描述信息
    View Code

    4、扩展、和重写url

    1、预留  url扩展和重写
    
    zhanggen.py
    #扩展 url
        def extra_urls(self):
            patterns = [
                url(r'test/$',self.test),
            ]
            return patterns
        #重写url
        def get_urls(self):
            patterns = [
                url(r'test/$', self.test),
            ]
            return patterns
    
    
    
    v1.py
       def get_urls(self):
            patterns = [
                url(r'^$', self.changelist_view),
                url(r'^add/$', self.add_view),
                url(r'^(.+)/delete/$', self.delete_view),
                url(r'^(.+)/change/$', self.change_view),
            ]
            patterns+=self.extra_urls()
            return patterns
    
        def extra_urls(self):
            ''''自定义扩展url 预留的钩子函数'''
            return []
    View Code

    CRM基础框架已经搭建完成,每个请求过来都会生成url 视图映射关系,接下来应该设计 视图功能了;(day88内容)

     --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

    1、由每张表的url路由关系,的视图函数设置的都是配置对象的 changelist_view方法;如何区分?别忘了

    {

    models.UserInfo,UserInfo配置对象(models.UserInfo,site对象), #配置对象是相互无关系的
    models.UserGroup,UserGrou配置对(models.UserInfo,对象)

     }

    显示中文标题

    通过获取字段的verbose_name,显示中文标题

        def changelist_view(self, request, *args, **kwargs):
            # 由于在注册的时候,实例化配置对象中传入了 model_class,site,所有可以通过self.model_class区分不同的表
            data_list=self.model_class.objects.all()
            def header():
                if not self.list_display:
                    yield self.model_class._meta.model_name
                else:
                    for i in self.list_display:
                         yield i(self,is_header=True) if isinstance(i,FunctionType) else self.model_class._meta.get_field(i).verbose_name
                        
            context={
                'headers':header(),
                'data_list':data_list
            }
            return render(request,'test.html',context)
    View Code

    涉及知识点:

    三元表达式

    i(self,is_header=True) if isinstance(i,FunctionType) else self.model_class._meta.get_field(i).verbose_name
    View Code

    yield生成器: 在后端制作生成器,在前端for循环生成器,提高性能;

          yield [i(self, is_header=True, row=obj) if isinstance(i, FunctionType) else getattr(obj, i) for i in
                           self.list_display]
    View Code

    yileld应用场景:

    应用1:xrang(100),通过yield做生成器,惰性生成,不要在内存中全部创建;

    应用2:在Django如果后台的数据需要做一些处理之后,再在模板中循环显示出来,就可以在后端使用yield生成;省去了后端的循环;

    显示内容

    涉及知识点:

    列表生成式

      def body():
                for obj in data_list:
                    yield [i(self, is_header=True, row=obj) if isinstance(i, FunctionType) else getattr(obj, i) for i in
                           self.list_display]
    View Code

    在配置类自定义列标题 和自定义列内容

        def xxx(self,is_header=False,row=None):
            if is_header==True:  #如果调用该函数的时候传入的参数是is_header==True,函数执行就返回标题
                return '列名称'
            else:
                return '自定义列'#如果调用该函数的时候传入的参数是is_header==Fase,函数执行就返回内容
    View Code

    2、基础配置对象 changelist_view方法,根据每张表的list_display = []配置,把生成thead 和tbody内容,封装到ChangeList类中cl对象中返回给nb/changelist.html

    class ChangeList(object):  #由于使用inclusion_tag,需要传很多值,就把这个些值封装到类里,一对象的形式递给前端
        def __init__(self,data_list,list_display,model_config_obj):
            self.data_list = data_list
            self.list_display = list_display
            self.model_config_obj = model_config_obj
    
        def add_html(self):  #封装add 按钮
            app_model = self.model_config_obj.model_class._meta.app_label, self.model_config_obj.model_class._meta.model_name
            add_url = reverse("zg:%s_%s_add" % app_model)
            add_html = mark_safe('<a class="btn btn-primary" href="%s">添加</a>' % (add_url,))
            return add_html
    View Code

    涉及知识点·:

    面向对象封装

    def func(a1,a2,a3,a4):  #如果一个函数执行需要很多参数,就可以吧这个函数改造成面向对象的模式
        pass
    
    class Foo():
        def __init__(self,a1,a2,a3,a4): #优势1:一次传入,封装所有值到一个对象;
            self.a1=a1
            self.a2 = a2
            self.a3 = a3
            self.a4 = a4
        def a5(self):               #优势2:还可以通过方法修改封装的值
            pass
    View Code

    3、nb/changelist.html加载 result_list simp_tag,并把后端传来c1对象传到{% show_result_list cl%} include_tag

    4、show_result_list include_tag,把c1对象中的数据,和change_list_table.html进行渲染,后nb/changelist.html显示了后端所有内容

    from django.template.library import Library
    from types import FunctionType
    register=Library()
    
    @register.inclusion_tag('nb/change_list_table.html')
    #inclusion包含_tag自动去找nb/change_list_table.html
    #inclusion_tag返回什么值,就会把这些值,渲染到nb/change_list_table.html
    #其他页面调用show_result_list,就会得到 nb/change_list_table.html模板和show_result_list返回的结果的渲染字符串
    def show_result_list(cl):
        def headers():
            if not cl.list_display:
                yield cl.model_config_obj.model_class._meta.model_name
            else:
                for v in cl.list_display:
                    yield v(cl.model_config_obj, is_header=True) if isinstance(v,
                                                                               FunctionType) else cl.model_config_obj.model_class._meta.get_field(
                        v).verbose_name
        def body():
            for row in cl.data_list:
                if not cl.list_display:
                    yield [str(row), ]
                else:
                    #obj=row,在Django admin定义 def xx(self,obj):  #obj =当前行的对象,方便我们显示 多列数据组合
                    yield [name(cl.model_config_obj, obj=row) if isinstance(name, FunctionType) else  getattr(row, name) for
                           name in cl.list_display]
        return {'headers': headers(),   #
                'body': body(),}
    
    #就是inclusion_tag(模板)类似simple_tag
    View Code

    涉及知识点:

    自定义simp_tag

    当Django模板语言提供的simp_tag无法满足模板渲染需求时,可以利用python代码自定义自己的simp_tag

    include_tag:

    @register.inclusion_tag('nb/change_list_table.html')

    inclusion包含_tag自动去找nb/change_list_table.html

    inclusion_tag返回什么值,就会把这些值,渲染到nb/change_list_table.html

    其他页面调用show_result_list,就会得到 nb/change_list_table.html模板和show_result_list返回的结果的渲染字符串

    方法和函数

    类执行类里的函数需要加self参数,传入一个实例对象。

    对象执行类里的函数叫方法,会自动把自己作为参数传递进去,不需要加self参数。

    #函数和方法
    
    class Foo(object):
        def __init__(self,name):
            self.name=name
        def show(self):
            print('show',self.name)
    
    
    obj=Foo('zhangggen')
    # Foo.show(obj)           #类直接调用 类中的函数,需要加self参数
    
    
    from  types import FunctionType,MethodType
    
    print(isinstance(Foo.show,FunctionType)) #函数
    
    print(isinstance(obj.show,FunctionType)) #方法
    View Code

     5、前端根据权限   get_show_add_btn决定是否显示添加按钮,PermissionConfig()类。权限表钩子

    由于配置类继承了PermissionConfig也继承了基础配置类,权限类优先继承,所有可以重写def get_show_add_btn(self):方法

    class Modelzg(object):    # 注册的时候没有使用配置对象,默认使用的配置对象
        ''''用户基础配置类'''
    
        def __init__(self, model_class, site): #model_class=model.userinfo  , site=Zgsite对象
            self.model_class = model_class
            self.site = site
    
        list_display = []
        show_add_btn = True
    
        def get_show_add_btn(self):   #默认是True,就是显示add按钮,由于广度优先继承PermissionConfig,PermissionConfig类重写了get_show_add_btn
            return self.show_add_btn
    View Code
    class PermissionConfig(object):   #配置类重写 get_show_add_btn,控制 add按钮的显示
        def get_show_add_btn(self):
            print(self.request)
            return False
    View Code

     注释:

    由于配置对象同时继承了PermissionConfig权限配置类,v1.Modelzg默认配置类,配置对象在默认配置类写了self.request = request,而配置对象又被封装到了Cl对象里,所有前端cl.model_config_obj.get_show_add_btn 可以判断是否显示添加权限;

     6、list_display = [ ]配置了字段,就显示 checkbox选择和编辑选项,预留权限钩子

     list_display = []
        show_add_btn = True
        def zhinan(self, obj=None, is_header=False):
            if is_header:
                return '选择'
            else:
                tpl = "<input type='checkbox' value='%s' />" % (obj.pk,)
                return mark_safe(tpl)
    
        def option(self, obj=None, is_header=False):
            if is_header:
                return '选项'
            else:
                # edit_url = reverse('zg:app01_userinfo_change',args=(obj.pk,))
                edit_url = reverse(
                    'zg:%s_%s_change' % (self.model_class._meta.app_label, self.model_class._meta.model_name,),
                    args=(obj.pk,))
                tpl = "<a href='%s'>编辑</a>|<a>删除</a>" % (edit_url,)
                return mark_safe(tpl)
    
        def get_list_play(self):
            res=[]  #不能直接操作self.listplay 因为刷新又会从新添加一次
            if self.list_display:  #如果用户设置显示的列 和数据
                res.extend(self.list_display)
                res.insert(0,Modelzg.zhinan)
                res.append(Modelzg.option)
            return res
    View Code

    预留权限钩子

        def get_list_play(self):
            return super().get_list_play()   # UserInfoConfig对象,先来PermissionConfig,再去v1.Modelzg类找get_list_play方法
    View Code

     7、用户自定制 批量删除和初始化actions

    定义一个actions = []配置项,通过基础配置类的 get_actions(self) 去获取

       actions = []
    
        def get_actions(self):
            res=[]
            res.extend(self.actions)
            res.append(Modelzg.multi_del)
            res.append(Modelzg.init_action)
            return res
    View Code

    把在基础配置类里定义批量删除 和初始化的函数

        def multi_del(self):
           id_list=self.request.POST.getlist('pk')
           # self.model_class.objects.fifter(pk__in=id_list).delete()
    
        multi_del.short_desc='批量删除' #给函数定义一个属性
    
        def init_action(self):
            pass
        init_action.short_desc = '初始化'  # 给函数定义一个属性
    View Code

    把actions = []里定义函数名封装到 cl对象传给前端

      def xxxff(model_config_obj):
                for i in model_config_obj.get_actions():
                    yield (i.__name__,i.short_desc)
            self.actions = xxxff(model_config_obj)
    View Code

    前端把函数名作为option的value 函数的short_desc属性作为内容显示

       {% for action in cl.actions  %}
                    <option value={{ action.0}}>{{ action.1}}</option>
                {% endfor %}
    View Code

    最后把选择的checkbox和选择的函数通过from表单提交到后台基础配置类 chang_list_viwe方法

      self.request = request
            if request.method=='POST':
                func = request.POST.get('action')
                print(func)
                method = getattr(self,func,None)
                # print(isinstance(action_func,FunctionType))
                # 注意从对象中获取的是方法
                if method:
                    method()  # 执行 actions中的函数
            data_list = self.model_class.objects.all()
    View Code

    后端通过反射在后端执获取函数并执行

       def multi_del(self):
           id_list=self.request.POST.getlist('pk')
           # self.model_class.objects.fifter(pk__in=id_list).delete()
    View Code

    8、分页保留原来的搜素条件

    当我们在搜素框输入了搜素条件,点击下一页应当保存搜素条件!

    例如:当前页:http://127.0.0.1:8000/zg/app01/userinfo/?p=1&id=2&page=2/

    点击下一页如和保存p=1&id=2参数

      request_get._mutable=True        #设置可编辑参数
    
    
            page=Pagination(
                #自定制分页器需要传入的参数
                #1、当前访问页码
                current_page=model_config_obj.request.GET.get('page'),
                #2、 数据库总条目数
                total_item_count=data_list.count(),
                #base_url 信息
                base_url=model_config_obj.request.path_info,
                request_params=request_get
    
            )
            #page对象产出 开始也 和结束页
            self.data_list=data_list[page.start:page.end]
            #分页HTML代码
            self.page_html=page.page_html()
    View Code

    修改分页生成的 页码

    """
    使用方法:
    
    from utils.page import Pagination
    def users(request):
        current_page = int(request.GET.get('page',1))
    
        total_item_count = models.UserInfo.objects.all().count()
    
        page_obj = Pagination(current_page,total_item_count,'/users.html')
    
        user_list = models.UserInfo.objects.all()[page_obj.start:page_obj.end]
    
        return render(request,'users.html',{'user_list':user_list,'page_html':page_obj.page_html()})
    
    
    """
    
    
    from django.utils.safestring import mark_safe
    
    class Pagination(object):
    
        def __init__(self,current_page,total_item_count,base_url=None,per_page_count=10,show_pager_count=11,request_params=None):
            """
            :param current_page:  当前页
            :param total_item_count: 数据库数据总条数
            :param base_url: 分页前缀URL
            :param per_page_count:   每页显示数据条数
            :param show_pager_count: 对多显示的页码
            """
            try:
                current_page = int(current_page)
            except Exception as e:
                current_page = 1
            self.current_page = current_page
            self.total_item_count = total_item_count
            self.base_url = base_url
            self.per_page_count = per_page_count
            self.show_pager_count = show_pager_count
            self.request_params=request_params
    
            max_pager_num, b = divmod(total_item_count, per_page_count)
            if b:
                max_pager_num += 1
            self.max_pager_num = max_pager_num
    
        @property
        def start(self):
            """
    
            :return:
            """
            return (self.current_page-1)* self.per_page_count
    
        @property
        def end(self):
            """
    
            :return:
            """
            return self.current_page * self.per_page_count
    
        def page_html(self):
            """
    
            :return:
            """
            page_list = []
    
            if self.current_page == 1:
                prev = ' <li><a href="#">上一页</a></li>'
            else:
                self.request_params['page']=self.current_page-1
                prev = ' <li><a href="%s?%s">上一页</a></li>' % (self.base_url,self.request_params.urlencode(),)
            page_list.append(prev)
    
            half_show_pager_count = int(self.show_pager_count / 2)
    
            # 数据特别少,15条数据=2页
            if self.max_pager_num < self.show_pager_count:
                # 页码小于11
                pager_start = 1
                pager_end = self.max_pager_num + 1
            else:
                if self.current_page <= half_show_pager_count:
                    pager_start = 1
                    pager_end = self.show_pager_count + 1
                else:
                    if self.current_page + half_show_pager_count > self.max_pager_num:
                        pager_start = self.max_pager_num - self.show_pager_count + 1
                        pager_end = self.max_pager_num + 1
                    else:
                        pager_start = self.current_page - half_show_pager_count
                        pager_end = self.current_page + half_show_pager_count + 1
    
            for i in range(pager_start, pager_end):
                self.request_params['page']=i
                if i == self.current_page:
                    tpl = ' <li class="active"><a href="%s?%s">%s</a></li>' % (self.base_url,self.request_params.urlencode(), i,)
                else:
                    tpl = ' <li><a href="%s?%s">%s</a></li>' % (self.base_url,self.request_params.urlencode(), i,)
                page_list.append(tpl)
    
            if self.current_page == self.max_pager_num:
                nex = ' <li><a href="#">下一页</a></li>'
            else:
                self.request_params['page']=self.request_params+1
                nex = ' <li><a href="%s?%s">下一页</a></li>' % (self.base_url,self.request_params.urlencode(),)
            page_list.append(nex)
    
            return mark_safe(''.join(page_list))
    
        def page_html_js(self):
            page_list = []
    
            if self.current_page == 1:
                prev = ' <li><a href="#">上一页</a></li>'
            else:
                prev = ' <li><a onclick="$.changePage(%s)">上一页</a></li>' %(self.current_page-1,)
            page_list.append(prev)
    
            half_show_pager_count = int(self.show_pager_count / 2)
    
            # 数据特别少,15条数据=2页
            if self.max_pager_num < self.show_pager_count:
                # 页码小于11
                pager_start = 1
                pager_end = self.max_pager_num + 1
            else:
                if self.current_page <= half_show_pager_count:
                    pager_start = 1
                    pager_end = self.show_pager_count + 1
                else:
                    if self.current_page + half_show_pager_count > self.max_pager_num:
                        pager_start = self.max_pager_num - self.show_pager_count + 1
                        pager_end = self.max_pager_num + 1
                    else:
                        pager_start = self.current_page - half_show_pager_count
                        pager_end = self.current_page + half_show_pager_count + 1
    
            for i in range(pager_start, pager_end):
                if i == self.current_page:
                    tpl = ' <li class="active"><a onclick="$.changePage(%s)"  >%s</a></li>' % (i,i,)
                else:
                    tpl = ' <li><a onclick="$.changePage(%s)" >%s</a></li>' % (i, i,)
                page_list.append(tpl)
    
            if self.current_page == self.max_pager_num:
                nex = ' <li><a href="#">下一页</a></li>'
            else:
                nex = ' <li><a onclick="$.changePage(%s)" >下一页</a></li>' %(self.current_page+1,)
            page_list.append(nex)
    
            return ''.join(page_list)
    
        def page_html_test(self):
            page_list = []
    
            if self.current_page == 1:
                prev = ' <li><a href="#">上一页</a></li>'
            else:
                prev = ' <li><a num="%s">上一页</a></li>' %(self.current_page-1,)
            page_list.append(prev)
    
            half_show_pager_count = int(self.show_pager_count / 2)
    
            # 数据特别少,15条数据=2页
            if self.max_pager_num < self.show_pager_count:
                # 页码小于11
                pager_start = 1
                pager_end = self.max_pager_num + 1
            else:
                if self.current_page <= half_show_pager_count:
                    pager_start = 1
                    pager_end = self.show_pager_count + 1
                else:
                    if self.current_page + half_show_pager_count > self.max_pager_num:
                        pager_start = self.max_pager_num - self.show_pager_count + 1
                        pager_end = self.max_pager_num + 1
                    else:
                        pager_start = self.current_page - half_show_pager_count
                        pager_end = self.current_page + half_show_pager_count + 1
    
            for i in range(pager_start, pager_end):
                if i == self.current_page:
                    tpl = ' <li class="active"><a num="%s" >%s</a></li>' % (i,i,)
                else:
                    tpl = ' <li><a num="%s" >%s</a></li>' % (i, i,)
                page_list.append(tpl)
    
            if self.current_page == self.max_pager_num:
                nex = ' <li><a href="#">下一页</a></li>'
            else:
                nex = ' <li><a num="%s">下一页</a></li>' %(self.current_page+1,)
            page_list.append(nex)
    
            return ''.join(page_list)
    View Code

    设计知识点:

    from django.http import QueryDict

    当我们向django发送请求时,Django接收request.get()到的数据是query_dict数据类型

    query_dict数据类型默认不可以修改

    request.GET.get()._mutable=True   设置该参数后即可修改

    还原成urlencode类型:request.GET.urlencode()

    9、单表添加功能

    0、设置list显示页面add按钮的跳转的url ,保存url后面的的get参数

     def add_html(self):  #封装add 按钮
            app_model = self.model_config_obj.model_class._meta.app_label, self.model_config_obj.model_class._meta.model_name
            query_dict=QueryDict(mutable=True)
            query_dict['xxoo']=self.model_config_obj.request.GET.urlencode() #xxoo=p%3D1
    View Code

    1、在add_view中定义ModelForm,让配置对象调用该方法时,可以显示html页面

     self.request=request
            chang_list_url_params=request.GET.get('xxoo')
            from django.forms import ModelForm
            class AddModelForm(ModelForm):
                class Meta:
                    model=self.model_class #哪个配置调用自己的add_view方法来,就创建这个表的ModelForm
                    fields='__all__'   #在前端显示的列
            if request.method=='GET':
                form=AddModelForm()
                context={
                    'form':form,
                                     }
                return render(request,'nb/add.html',context)
    View Code

    2、用户提交数据form验证通过,携带list页面的参数,跳转会list页面

       elif request.method=='POST':
                form = AddModelForm(data=request.POST)
                if form.is_valid():
                    form.save()
                    base_url=self.revesrs_chang_list_url()+chang_list_url_params
                    print(base_url)
    
                    return redirect(base_url)
    View Code
        def revesrs_chang_list_url(self):  #为add_viwe生成 跳转会list页面的标签
            list_url=reverse("%s:%s_%s_changelist" % (self.site.namespace,self.app_lable,self.model_name,) )
            return  list_url
    View Code

    3、自定制ModelForm

    通过方法获取默认配置里面的ModelForm如果为空就是使用默认配置,否则使用自定义配置ModelForm

        model_form=None
        def get_model_form_class(self):
            result=self.model_form
            if not result:
                class Defalut_Model_Form(ModelForm):
                    class Meta:
                        model = self.model_class  # 哪个配置调用自己的add_view方法来,就创建这个表的ModelForm
                        fields = '__all__'  # 在前端显示的列
    
                result = Defalut_Model_Form
    
            return result
    View Code
    class UserInforModelFrom(ModelForm):
        www=fields.CharField(widget=widgets.Textarea)
        class Meta:
            model=models.UserInfo
            fields = '__all__'
    
    class UserInfoConfig(PermissionConfig,v1.Modelzg):  #多继承 既继承v1.Modelzg又继承PermissionConfig 优先
        def xxx(self, obj=None, is_header=False):
            if is_header:
                return '列名称'
            return obj.name + obj.email
        # list_display = [zhinan, 'id', 'name', 'nickname', 'email', xxx, option]
        model_form=UserInforModelFrom
    View Code

    涉及知识点:

    1、request.GET.urlencode()

    获取get请求后面携带的参数  

        query_dict['xxoo']=self.model_config_obj.request.GET.urlencode() #xxoo=p%3D1
    View Code

    2、ModelForm的使用

     if request.method=='GET':
                form=AddModelForm()
                context={
                    'form':form,
                                     }
                return render(request,'nb/add.html',context)
            elif request.method=='POST':
                form = AddModelForm(data=request.POST)
                if form.is_valid():
                    form.save()
                    base_url=self.revesrs_chang_list_url()+chang_list_url_params
                    print(base_url)
    
                    return redirect(base_url)
    
                context = {
                    'form': form,
                }
                return render(request,'nb/add.html', context)
    View Code

    在模板中使用

    <form action="" method="post">
        {% csrf_token %}
    {#    {{ form.as_p }}#}
        {% for item in form %}
            <p>{{ item.label }}:{{ item }}{{ item.errors.0 }} </p>
        {% endfor %}
        <input type="submit" value="提交">
    </form>
    View Code

    10、单表编辑/删除功能

    没时间实现

    11、通过popup实现在userinfo表联动添加外键表usergroup的信息

     userinfo添加页面显示

    <!DOCTYPE html>
    {% load result_form %}
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
        <link rel="stylesheet" href="/static/plugins/bootstrap/css/bootstrap.css">
        <style>
            .form-horizontal input[type='text'],input[type='checkbox'],input[type='radio'],input[type='email'], select, textarea {
                display: block;
                width: 100%;
                height: 34px;
                padding: 6px 12px;
                font-size: 14px;
                line-height: 1.42857143;
                color: #555;
                background-color: #fff;
                background-image: none;
                border: 1px solid #ccc;
                border-radius: 4px;
                -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075);
                box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075);
                -webkit-transition: border-color ease-in-out .15s, -webkit-box-shadow ease-in-out .15s;
                -o-transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s;
                transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s;
            }
        </style>
    </head>
    <body>
    <h1>添加页面</h1>
    {% show_form form %}
    <h1></h1>
    </body>
    </html>
    View Code

    执行inclusion_tag,完成对外键表联动添

    from django.template.library import Library
    from types import FunctionType
    from django.forms.models import ModelMultipleChoiceField
    from django.urls import reverse
    from django.forms.models import ModelChoiceField
    from django.db.models.query import QuerySet
    from app02.service.v1 import site
    
    register=Library()
    
    
    def xxxx(model_form_obj):
        for item in model_form_obj :
            tpl={'has_popup':False,'item':item,'popup_url':None}
            # 判断userinfo表的列中是否含外键字段,并且外键的表也得已经注册
            if isinstance(item.field,ModelChoiceField) and item.field.queryset.model in site._registry:
                tpl['has_popup']=True
                flied_class=item.field.queryset.model  #字段外键对应的表名
                app_label=flied_class._meta.app_label #外键表所在的app
                model_name=flied_class._meta.model_name#外键表的表名
                url= reverse('{0}:{1}_{2}_add'.format(site.namespace,app_label,model_name ))
                tpl['popup_url']=url
    
            # print(item.field)  #字段类型
            yield tpl
    
    
    @register.inclusion_tag('nb/change_form.html')
    def show_form(model_form_obj):
        return {'form':xxxx(model_form_obj)}
    
    #inclusion_tag returm什么数据,就在changelist.html里面渲染什么数据
    #最后谁调用 show_form simpletag就包含了 changelist.html渲染完成页面
    View Code

    inclusion_tag使用的页面

    <form class="form-horizontal" method="POST" novalidate>
        {% csrf_token %}
        {% for col in form %}
            <div class="form-group col-sm-6">
                <label class="col-sm-3 control-label">{{ col.item.label }}</label>
                <div class="col-sm-9" style="position: relative">
                    {{ col.item }}
                    {{ col.item.errors.0 }}
                    {% if col.has_popup %}
                        <a onclick="popup('{{ col.popup_url }}')" >添加</a>
                    {% endif %}
                </div>
    
            </div>
        {% endfor %}
    
        <div class="form-group">
            <div class="col-sm-offset-10 col-sm-2">
                <input type="submit" class="btn btn-primary" value="确认添加"/>
            </div>
        </div>
    </form>
    View Code
            elif request.method=='POST':
                form = self.get_model_form_class()(data=request.POST)
                if form.is_valid():
                    obj=form.save()
                    base_url=self.revesrs_chang_list_url()
                    url="%s?%s" %(base_url,chang_list_url_params)
                    tagId = request.GET.get('_popup', None)
                    if tagId:
                        name=str(obj)  #获取 name/title
                        val=obj.pk
                        return render(request,'nb/popuprespose.html',{ 'name':name,'tagId':tagId,'val':val })
                    return redirect(url)
    View Code

    涉及知识点:

    name=str(obj)         执行在model.py中定义的 __str__方法

     

    查看form对象中包含列的相关属性

    def xxxx(model_form_obj):
        for item in model_form_obj :
            tpl={'has_popup':False,'item':item,'popup_url':None}
            # 判断userinfo表的列中是否含外键字段,并且外键的表也得已经注册
            if isinstance(item.field,ModelChoiceField) and item.field.queryset.model in site._registry:
                tpl['has_popup']=True
                flied_class=item.field.queryset.model  #字段外键对应的表名
                app_label=flied_class._meta.app_label #外键表所在的app
                model_name=flied_class._meta.model_name#外键表的表名
                url= reverse('{0}:{1}_{2}_add'.format(site.namespace,app_label,model_name ))
                tpl['popup_url']=url
    
            # print(item.field)  #字段类型
            yield tpl
    View Code

    js之popup弹窗  (3个HTML页面)

    1、p1页面定义1个popup回调函数,p1页面绑定1个popup弹窗时间 到 /p2/

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
    </head>
    <body>
    <h1>p1页面</h1>
    <input type="button" value="按钮" onclick="popfunc();">
    
    </body>
    <script>
        function  popfunc() {
            window.open('/p2/','别名',"status=1, height:500, 600, toolbar=0, resizeable=0");
    {#       点击按钮 出现弹窗 到p2     #}
        }
        function xxx(name) {
            alert(name)
    
        }
    </script>
    </html>
    View Code

    2、/p2/下的视图添加数据库操作,把增加的row对象模板渲染,re1个页面popuprespose.html

    def p2(request):
        if request.method=='GET':
            return render(request,'p2.html')
        elif request.method=='POST':
            from app01 import models
            city=request.POST.get('city')
            obj= models.UserGroup.objects.create(title=city)
            return render(request,'popuprespose.html',{'obj':obj})
    View Code

    3、popuprespose.html页面 自执行函数执行popup发起页面(p1)的回调函数 

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
    </head>
    <body>
    <h1>p1页面</h1>
    <input type="button" value="按钮" onclick="popfunc();">
    
    </body>
    <script>
        function  popfunc() {
            window.open('/p2/','别名',"status=1, height:500, 600, toolbar=0, resizeable=0");
    {#       点击按钮 出现弹窗 到p2     #}
        }
        //回调函数
        function xxx(name) {
            alert(name)
    
        }
    View Code

    12、组合搜素逻辑

    组合搜素这个功能,其实很简单却浪费我大量闹细胞;

     

    1、把所有在显示页面需要的数据封装进ChangeList类里,实例化cl对象传到模板语言通过simple-tag循环、调用生成标签;这是前提;

    显示页面模板中的2层for循环是切入点:

    div class="container">
        <h1>数据列表</h1>
        {#显示组合搜素开始#}
        <div class="row">
            {% if cl.list_filter %}
                <!-- 遍历chang_list的gen_list_filter 方法,也就是Userinfo表中的  query_aet) -->
                {% for row_items in cl.gen_list_filter %}
                    <div class="filter-item">
                        <!-- 遍历自自定义的,具有__iter__方法的个可迭代对象  -->
                        {% for item in row_items %}
                            {{ item }}
                        {% endfor %}
                    </div>
    
                {% endfor %}
                <!-- 遍历自自定义的,具有__iter__方法的个可迭代对象 结束 -->
    
            {% endif %}
            <!-- 遍历chang_list中的gen_list_filter 方法结束 -->
        </div>
        {#显示组合搜素结束#}
    View Code

    1层for循环:  yelid row_items = RowItems(option, data_list, params)

    其中ChangeLis中定义的gen_list_filter方法是个生成器,获取list_filter = [‘group’,'roles ']配置项对应表的数据,for循环gen_list_filter得到一个RowItems对象(也是个生成器);

       def gen_list_filter(self):
            model_class = self.model_config_obj.model_class  # userinfo 类
            params = self.model_config_obj.request.GET  # 从配置对象中获取request参数,传入到 RowItems类
            for option in self.list_filter:  # list_filter=['group对象','name对象','roles对象']
                fild_obj = model_class._meta.get_field(option.name)  # 获取到列对象
                from django.db.models.fields.related import RelatedField
                if isinstance(fild_obj, RelatedField):  # 判断是否为外键字段
                    field_related_class = fild_obj.rel.to  # 获取到列对应的外键表
                    data_list = field_related_class.objects.all()
                    row_items = RowItems(option, data_list, params)  # 实例化一个RowItems对象
                else:  # 判断如果是普通字段直接取值
                    data_list = model_class.objects.all()
                    row_items = RowItems(option, data_list, params)  # 实例化一个RowItems对象
    
                yield row_items
    View Code

    2层for循环: yield mark_safe( tmp = '<a href="?%s">%s</a>' % (url, text))

    遍历data_list,query_set[obj,objobj ],所以data_list中有几个对象就行数据,就得到是1个带有herf链接url的A标签;

    class RowItems():
        def __init__(self, option, data_list, params):
            self.option = option
            self.data_list = data_list
            self.param = copy.deepcopy(params)  # query_dict对象
            print(self.param)  # <QueryDict: {'group': ['1'], 'roles': ['2']}>
            self.param._mutable = True
    
        def __iter__(self):
            current_pk = self.param.get(self.option.name)  # str类型
    
            self.param.pop(self.option.name)
            all_url = self.param.urlencode()
            tmp = '<a href="?%s">全部</a>' % (all_url)
            yield mark_safe(tmp)
            for obj in self.data_list:
                pk = str(obj.pk)
                text = str(obj)
                self.param[self.option.name] = pk
                url = self.param.urlencode()
                if current_pk == pk:
                    tmp = '<a class="active" href="?%s">%s</a>' % (url, text)  # 设置选择标签颜色
                else:
                    tmp = '<a href="?%s">%s</a>' % (url, text)
                yield mark_safe(tmp)
    View Code

     RowItems()的__iter__ 方法,每次根据request.get携带的参数,yeild a标签

    1、先根据get请求,request.getlist(self.optionname) 获取current_pk_or_list参数
    列表 [5,6]

    2、首先self.param.pop(self.option.name)清空group参数自己,留下role参数,生成全部A标签 ;


    3、循环遍历data_list数据,根据query_set[obj,obj,obj]中有几个obj,生成几个A标签;

    3.1先得到每个obj的id 和 str(obj)中文显示内容

    3.2判断是否是多选;

    3.3 判断当前for 循环的obj对象的pk 是否在current_pk_or_list参数列表 [5,6]?

    3.4 如果不在就把当前 obj的的id 添加到current_pk_or_list [1,5,6]

    3.5 重置<QueryDict: {}>,为<QueryDict: {'group': ['1', '5', '6']}>

    3.6 urlendcodeQueryDict,每次赋值A标签的herf属性


    组合搜素 A标签生成href url思想:


    1、用户首次访问:http://127.0.0.1:8000/zg/app01/userinfo/

    reques.getlist(self.option.name) #得到 current_pk_or_list为空 []

    2、RowItems类的__iter__方法遍历   data_list,query_set [ obj ,obj ]

       #生成全部右侧 obj  data_list=[obj,obj,obj ]
            for obj in self.data_list:
                #每个obj 生成 每个 A标签
                pk = str(obj.pk)  #1
                text = str(obj)   #阿斯蒂芬
                if self.option.is_muti:  #如果配置了多选
                    if pk not in current_pk_or_list: #如果obj.pk不在【1.2】
                        tmp=[]
                        tmp.extend(current_pk_or_list)
                        tmp.append(pk)
                        self.param.setlist(self.option.name,tmp) #注意在原来不变的基础上多加1个?group=2&group=3
    View Code

    如果obj.pk不在current_pk_or_list [ ] 中,就把obj.pk添加到tmp,第一次循环进来  tmp=[1 ]

     tmp=[]
     tmp.extend(current_pk_or_list)
     tmp.append(pk)  

    3、修改 query_dict,<QueryDict: { option.name :  [1]  }

    self.param.setlist(self.option.name,tmp) 

    4、url endconde编码      ?group=1

    url = self.param.urlencode()

    5、遍历循环结束,根据obj个数生成A标签的 url

    ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

    1、首次生成url之后,在页面点击搜素项,http://127.0.0.1:8000/zg/app01/userinfo/?group=1

      reques.getlist(self.option.name) #得到 current_pk_or_list为  [1]

     2、RowItems类的__iter__方法遍历   data_list,query_set [ obj ,obj, ]

    如果obj.pk不在current_pk_or_list [ ] 中,就把obj.pk添加到tmp,第一次循环进来  tmp=[1,2 ] / tmp=[1,3 ] / tmp=[1,4] 

    3、url endconde编码,生成A标签的url

    补充 list_filter 配置  函数和‘数据库字段’

    class UserInfoConfig(sites.AryaConfig):
        list_display = ['name', ]
        def func(self,change_list,option):
            #option,change_list,data_list,param_dict=None
            data_list=models.UserInfo.objects.filter(id__gt=2)
            return sites.FilterRow(option,change_list,data_list,self.request.GET)
        list_filter = [                         #文本显示的内容          url参数
            sites.FilterOption('group', True, lambda x: x.title, lambda x: x.id),
            sites.FilterOption('name', False, lambda x: x.name, lambda x: x.name),
            # sites.FilterOption(func, False, lambda x: x.name, lambda x: x.name), #自定制函数
        ]
    View Code

     总结:根据 request.getlist()获取的 [ ],   判断当前循环到的obj.id是否在[],如不不存在 添加到 [  ],修改 query_dict { 'option' : [ ]  },urlencode编码为url格式作为A标签的跳转链接;

    二、CURD组件使用

    经过漫长的开发周期,模仿DjangoAdmin开发出来的CURD组件终于可以使用了,赐名arya;下面是在1个崭新的Django项目中使用arya组件流程;

    1、在settings.py组册arya为本程序app

    INSTALLED_APPS = [
        'django.contrib.admin',
        'django.contrib.auth',
        'django.contrib.contenttypes',
        'django.contrib.sessions',
        'django.contrib.messages',
        'django.contrib.staticfiles',
        'app01.apps.App01Config',
        'arya.apps.AryaConfig',
    
    ]
    View Code

    2、任意APP下创建 arya.py

    3、在arya.py中组册model_class

    from . import models
    from arya.service.sites import site #导入单例模式
    
    site.register(models.Department,)
    View Code

    4、为已经注册的model_class,生成路由映射关系;

    from django.conf.urls import url
    from django.contrib import admin
    from app01 import views
    from arya.service.sites import site
    urlpatterns = [
        # url(r'^admin/', admin.site.urls),
        url(r'^index/',views.index),
        url(r'^arya/',site.urls),
    ]
    View Code

    5、修改母版板样式layout

    diango做模板渲染得时候寻找模板的顺序是,从最外层templates 到app里面的templates

    所以如果想修改网页显示的样式,就可以在最外层templates,定义自己的母版,让其他页面继承;

    6、增、删、改、查基本操作

    from . import models
    from arya.service import sites  #导入单例模式
    
    class DepartmentConfig(sites.AryaConfig):  #注册部门表
        list_display = ['title']
    
    sites.site.register(models.Department,DepartmentConfig)
    
    
    class UserinfoConfig(sites.AryaConfig):  #注册用户表
        list_display = ['name','email']
    
    sites.site.register(models.UserInfo, UserinfoConfig)
    
    
    class CoursetConfig(sites.AryaConfig):  #注册 课程表
        list_display = ['name']
    
    sites.site.register(models.Course, CoursetConfig)
    
    class SchoolConfig(sites.AryaConfig):  #注册部门表
        list_display = ['title']
    
    sites.site.register(models.School,SchoolConfig)
    View Code

    7、插件扩展

    0、脱离插件

    1、扩展URL,extra_url方法,重写URL  get_urls方法,添加RBAC权限

    1.0.添加url

    class UserinfoConfig(sites.AryaConfig):  #注册用户表
        list_display = ['name','email']
        def extra_urls(self):
            app_model_name = self.model_class._meta.app_label, self.model_class._meta.model_name
            patterns = [
                url(r'^(.+)/detail/',self.detail_view, name="%s_%s_changelist" % app_model_name),
            ]
            return patterns
    
        def detail_view(self,request,pk):
            obj=self.model_class.objects.all().filter(pk=pk).first()
            print(obj.name,obj.id)
            return HttpResponse('显示详情')
    View Code
    #工单系统
    
    class Worker_orderConfig(PermissionConfig,v1.AryaConfig):
        def row_graph(self, row=None, is_header=None):
            if is_header:
                return "工单状态"
    
            button_list=["<button class='btn btn-danger btn-sm button' type='button'>未理中</button>",
                         "<button class='btn btn-success btn-sm button' type='button'>处理中</button>",
                         "<button class='btn disabled btn-sm button' type='button'>已关闭</button>"
                         ]
            return mark_safe(button_list[row.status] )
        def details(self, row=None, is_header=None):
            if is_header:
                return "工单流程"
            return mark_safe("<a href="">详细</a>" )
    
        def fenpei(self, row=None, is_header=None):
            if is_header:
                return "分配"
            return mark_safe("<a href='/allocation/?id=%s'>分配</a>"%(row.id) )
    
    
        list_display = ['alarm_time', 'initiator', 'title', 'agent',
                        'desc',row_graph,fenpei,details,]
    
        def extra_url(self):
            app_model_name = self.model_class._meta.app_label, self.model_class._meta.model_name
            print(app_model_name)
            p= [
            url(r'^worker_order_out/$', self.detail_view, name="%s_%s_t" % app_model_name),
                ]
            return p
    
        def detail_view(self, request):
            return HttpResponse('显示详情')
    
    v1.site.register(models.Worker_order,Worker_orderConfig)
    扩展URL

    1.2.arya生成url的规则 

    /arya开头/app名称/model表名/扩展url的名/: 例如:/arya/cmdb/worker_order/worker_order_out/

     

    1.3.在RBAC中为角色增加该URL的访问权限

    在权限管理----》URL权限添加权限标题:测试为标题

    2、自定义添加、显示、删除页面,重写基础配置类中的add_view 、 changelist_view 、delete_view

    注意每一个方法,都要执行self.request=request

    class UserinfoConfig(sites.AryaConfig):  #注册用户表
        list_display = ['name','email']
        def add_view(self,request):
            return render(request,'test.html')
    View Code

    三、RBAC权限录入

    RBAC基于角色的权限管理系统回顾:详见  Django之权限管理插件

    表结构:用户表  Many to many    角色表  Many to many 权限表   foreign key 菜单表 

    中间件:判断当前访问用户是否有访问权限

    登录成功初始化:把当前用户所有权限初始化放到session中

    配置文件: 无需登录就能使用的权限,session的key

    自动生成菜单:css、JavaScript

    1、注册RBAC app

    'rbac.apps.RbacConfig',
    View Code

    2、清空rbac插件的migration记录

     

    3、迁移rbac插件的数据

    python manage.py makemigrations
    
    python manage.py migrate

    4、在rbac创建arya.py注册rbac model_class

    from arya.service import sites
    from . import models
    
    
    
    sites.site.register(models.User)
    sites.site.register(models.Role)
    
    class PermissionConfig(sites.AryaConfig):
            pass
    
    sites.site.register(models.Permission,PermissionConfig)
    sites.site.register(models.Menu)
    View Code

    5、创建权限信息arya/rbac/permission/

    6、完善CURD插件功能;

    1、添加权限自动发现URL;

    (1)方式1

     定制list_play=[]

     重写add_view

    lass PermissionConfig(sites.AryaConfig):
            def dabo(self, obj=None, is_header=False):
                    if is_header:
                            return '其他'
                    return obj.caption+'大波'
            list_display = ['caption','url','menu',dabo]
    
            def add_view(self, request, *args, **kwargs):
                    from pro_crm.urls import urlpatterns
                    all_url_list=get_all_url(urlpatterns,prev='/',is_first=True,)
                    model_form_cls = self.get_model_form_class()
                    popup_id = request.GET.get(self.popup_key)
                    if request.method == 'GET':
                            form = model_form_cls()
                            return render(request, "permission_add_popup.html" if popup_id else "permission_add.html",{'form': form,'url_list':all_url_list})
                    elif request.method == "POST":
                            form = model_form_cls(data=request.POST, files=request.FILES)
                            if form.is_valid():
                                    obj = self.save(form, True)
                                    if obj:
                                            if popup_id:
                                                    context = {'pk': obj.pk, 'value': str(obj), 'popup_id': popup_id}
                                                    return render(request, 'arya/popup_response.html',
                                                                  {"popup_response_data": json.dumps(context)})
                                            else:
                                                    return redirect(self.changelist_url_params)
                            return render(request,"permission_add_popup.html" if popup_id else "permission_add.html", {'form': form,'url_list':all_url_list})
    
    
    
    
    
    sites.site.register(models.Permission,PermissionConfig)
    View Code

    (1)方式2

    修改ModelForm组件

    from arya.service import sites
    from . import models
    from django.shortcuts import render,redirect
    import json
    
    from django.urls.resolvers import RegexURLPattern
    def get_all_url(patterns,prev,is_first=False,result=[],):
        if is_first:
            result.clear()
        for item in patterns:
            v=item._regex.strip('^$')
            if isinstance(item,RegexURLPattern):
                val=prev+v
                result.append((val,val))
            else:
                get_all_url(item.urlconf_name,prev+v)
        return result
    
    
    
    sites.site.register(models.User)
    sites.site.register(models.Role)
    
    from django.forms import ModelForm
    from django.forms import fields
    from django.forms import widgets
    
    #自定义model_form
    
    class PermissionModelForm(ModelForm):
          #ModelForm 可以结合Model把所有数据库字段在页面上生成,也可以增加额外的字段;
        url=fields.ChoiceField()
        class Meta:
            fields = "__all__"
            model = models.Permission  #注意不是models
        def __init__(self,*args,**kwargs):   #重写父类的 __init__方法,每次实例化实时更新 form中的数据
            super(PermissionModelForm,self).__init__(*args,**kwargs)
            from pro_crm.urls import urlpatterns
            self.fields['url'].choices=get_all_url(urlpatterns,'/', True)
    
    
    class PermissionConfig(sites.AryaConfig):
            def dabo(self, obj=None, is_header=False):
                    if is_header:
                            return '其他'
                    return obj.caption+'大波'
            list_display = ['caption','url','menu',dabo]
            model_form = PermissionModelForm
    View Code

     

    2、通过装饰器实现每次请求保存request信息

       def warpper(self,func): #每次获取 self.requesr=request
            @functools.wraps(func) #
            def inner(request,*args,**kwargs):
               self.request=request
               return func(request,*args,**kwargs )
            return inner
    View Code
     patterns = [
                url(r'^$',self.warpper(self.changelist_view), name="%s_%s_changelist" % app_model_name),
                url(r'^add/$',self.warpper(self.add_view), name="%s_%s_add" % app_model_name),
                url(r'^(.+)/delete/$',self.warpper(self.delete_view), name="%s_%s_delete" % app_model_name),
                url(r'^(.+)/change/$',self.warpper(self.change_view), name="%s_%s_change" % app_model_name),
            ]
    View Code

    设计知识点:

    (1)、装饰器:

    @warpper

    def foo()

      pass

    foo=warpprt(foo)=return的结果

    (2)、@functools.wraps(func) 

    装饰器函数里面的文档介绍 完全= 原生函数的文档介绍

    (3)、ModelForm 增加数据库字段之外的field,ModelForm对象实时更新数据

    Django之Form、ModelForm 组件

    ModelForm 可以结合Model把所有数据库字段在页面上生成,也可以增加额外的字段;

    规则:如果增加的字段和数据里的filed重名则覆盖,不重名则新增;

    也可以通过重写__init__ ,每次实例化1个form对象,实时更新数据;

     View Code

    四、Arya整合RBAC整合CRM

    1、创建名为crm的app,并且在setings.py里面注册

    INSTALLED_APPS = [
        'django.contrib.admin',
        'django.contrib.auth',
        'django.contrib.contenttypes',
        'django.contrib.sessions',
        'django.contrib.messages',
        'django.contrib.staticfiles',
        'arya.apps.AryaConfig',
        'rbac.apps.RbacConfig',
        'crm.apps.CrmConfig',
    
    ]
    View Code

    2、使用RBAC,配置RBAC的表;(只能访问自己权限的URL,登录之后获取自己的权限列表放入request.session)

    from arya.service import sites
    from . import models
    from django.shortcuts import render,redirect
    import json
    
    from django.urls.resolvers import RegexURLPattern
    def get_all_url(patterns,prev,is_first=False,result=[],):
        if is_first:
            result.clear()
        for item in patterns:
            v=item._regex.strip('^$')
            if isinstance(item,RegexURLPattern):
                val=prev+v
                result.append((val,val))
            else:
                get_all_url(item.urlconf_name,prev+v)
        return result
    
    
    
    sites.site.register(models.User)
    sites.site.register(models.Role)
    
    from django.forms import ModelForm
    from django.forms import fields
    from django.forms import widgets
    
    
    #-------------------用户表相关配置
    class UserConfig(sites.AryaConfig):
        list_display = ['username','email' ]
    #--------------------角色相关配置
    class RoleConfig(sites.AryaConfig):
        list_display = ['caption',]
    
    #----------------------权限相关配置
    #自定义model_form
    class PermissionModelForm(ModelForm):
          #ModelForm 可以结合Model把所有数据库字段在页面上生成,也可以增加额外的字段;
        url=fields.ChoiceField()
        class Meta:
            fields = "__all__"
            model = models.Permission  #注意不是models
        def __init__(self,*args,**kwargs):   #重写父类的 __init__方法,每次实例化实时更新 form中的数据
            super(PermissionModelForm,self).__init__(*args,**kwargs)
            from pro_crm.urls import urlpatterns
            self.fields['url'].choices=get_all_url(urlpatterns,'/', True)
    class PermissionConfig(sites.AryaConfig):
            def dabo(self, obj=None, is_header=False):
                    if is_header:
                            return '其他'
                    return obj.caption+'大波'
            list_display = ['caption','url','menu',dabo]
            model_form = PermissionModelForm
    
    
    
    sites.site.register(models.User,UserConfig)
    sites.site.register(models.Permission,PermissionConfig)
    sites.site.register(models.Role,RoleConfig)
    sites.site.register(models.Menu)
    View Code

    3、添加crm登录页面  

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>CRM系统登录</title>
    </head>
    <body>
    <form action="" method="post">
        {% csrf_token %}
        <p>用户名:<input type="text" name="username"></p>
        <p>密码:<input type="submit" name="pwd"></p>
        <input type="submit" value="提交">
    </form>
    
    
    </body>
    </html>
    View Code

    4、添加crm首页

    设置母版layout

    <!--- 加载rbac插件中的simp-tag-->
    <!--- 加载rbac插件中的css-->
    //加载rbac插件中的js
    {% load static %}
    {% load rbac %}   <!--- 加载rbac插件中的simp-tag-->
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>{{ title }}</title>
        <link rel="stylesheet" href="{% static 'arya/plugins/bootstrap/css/bootstrap.css' %}"/>
        <link rel="stylesheet" href="{% static 'arya/css/commons.css' %}"/>
        <sytyle>
            <!--- 加载rbac插件中的css-->
            {% rbac_css %}
        </sytyle>
        {% block css %}{% endblock %}
    </head>
    <body>
    <div class="pg-header">
        <div class="logo left" style="text-align: center;background-color: #1c5a9c;">
            <a href="#" style="color: #ffffff;font-size:22px;font-weight: bold;text-decoration: none">
                CRM系统
            </a>
        </div>
    
        <div class="left-menu left">
            <a class="menu-item" href="#">平台首页</a>
            <a class="menu-item" href="#">资产首页</a>
        </div>
    
        <div class="right-menu right clearfix">
    
            <div class="user-info right">
                <a href="#" class="avatar">
                    <img class="img-circle" src="{% static 'arya/img/default_avatar.png' %}">
                </a>
    
                <div class="more-info">
                    <a href="#" class="more-item">个人信息</a>
                    <a href="/logout.html" class="more-item">注销</a>
                </div>
            </div>
    
            <a class="user-menu right">
                消息
                <i class="fa fa-commenting-o" aria-hidden="true"></i>
                <span class="badge bg-success">2</span>
            </a>
    
            <a class="user-menu right">
                通知
                <i class="fa fa-envelope-o" aria-hidden="true"></i>
                <span class="badge bg-success">2</span>
            </a>
    
            <a class="user-menu right">
                任务
                <i class="fa fa-bell-o" aria-hidden="true"></i>
                <span class="badge bg-danger">4</span>
            </a>
        </div>
    
    </div>
    
    <div class="pg-body">
        <div class="menu">
            {% rbac_menu request %}
    
        </div>
        <div class="content">
            {% block breadcrumb %} {% endblock %}
            {% block content %} {% endblock %}
        </div>
    </div>
    
    
    <script src="{% static 'arya/js/jquery-1.12.4.js' %}"></script>
    <script src="{% static 'arya/plugins/bootstrap/js/bootstrap.js' %}"></script>
    
    <script>
        {% rbac_js %}  //加载rbac插件中的js
    </script>
    {% block js %} {% endblock %}
    </body>
    </html>
    View Code

    设置crm首页

        {% extends 'arya/layout.html' %}
    
        {% block content %}
        <h1>欢迎登录</h1>
        {% endblock %}
    View Code

    5、index视图执行RBAC插件逻辑

    1)登录成功之后初始化权限信息

     initial_permission(request,obj)  #初始化用户权限信息

    2)页面显示时生成 菜单HTML

    3)setings.py配置文件 引入rbac中间件、设置rbac使用的key、simple_tag生成多级菜单

    'rbac.middleware.rbac.RbacMiddleware',  #引入rbac中间件
    View Code
    # ############################## RBAC权限相关配置开始 ##############################
    # session中保存权限信息的Key
    RBAC_PERMISSION_URL_SESSION_KEY = "rbac_permission_url_session_key"
    
    # Session中保存菜单和权限信息的Key
    RBAC_MENU_PERMISSION_SESSION_KEY = "rbac_menu_permission_session_key"
    RBAC_MENU_KEY = "rbac_menu_key"
    RBAC_MENU_PERMISSION_KEY = "rbac_menu_permission_key"
    
    # 匹配URL时指定规则
    RBAC_MATCH_PARTTERN = "^{0}$"
    
    # 无需权限控制的URL
    RBAC_NO_AUTH_URL = [
        '/login/',
        "/index/",
    ]
    
    # 无权访问时,页面提示信息
    RBAC_PERMISSION_MSG = "无权限访问"
    
    # 菜单主题
    RBAC_THEME = "default"
    # ############################## RBAC权限相关配置结束 ##############################
    View Code

    6、在权限中间件之前,通过UserAuthMiddleware判断用户是否已经登录,登录之后才有必要设置权限,否则直接返回登录页面;

    权限中间件没有必要,每次每个用户都要执行一次,所有在权限中间件之前新增一个中间件,先判断用户是否已经登录,没有登录直截了当让他先登录;

            if obj:
                request.session['user_info'] = {'nid': obj.id} #用户登录后 设置登录标记
                initial_permission(request,obj)  #初始化用户权限信息
                return redirect('/index/')    #跳转到首页
    class UserAuthMiddleware(MiddlewareMixin):
    
        def process_request(self,request):
            # 如果是/login/可以继续执行
            if request.path_info == '/login/':
                return None
            # 设置权限之前,判断用户是否已经登录?登录之后才判断是否有权限
            user_info = request.session.get('user_info')
            if not user_info:
                return redirect('/login/')
    View Code

    7、CRM的用户表和RBAC的用户表做One to One关联

    from rbac.models import User
    class UserInfo(models.Model):
        """
        员工表
        """
        user = models.OneToOneField(verbose_name='用户账号',to=User)
        name=models.CharField(verbose_name='员工姓名',max_length=16)
        phone=models.CharField(verbose_name='员工电话',max_length=16)
        depart = models.ForeignKey(verbose_name='部门', to="Department")
    View Code

    8、CRM表结构 配置与页面显示

    自定义函数:list_display显示多对多关系字段

    aryaCURD插件: list_display 定义函数,自定义函数返回什么list页面显示什么,外键关系连表查询

    from . import models
    from arya.service import sites
    from django.utils.safestring import mark_safe
    class DepartmentConfig(sites.AryaConfig):
        list_display = ['title']
    
    sites.site.register(models.Department,DepartmentConfig)
    
    class UserInfoConfig(sites.AryaConfig):
        list_display = ['name','phone']
    
    sites.site.register(models.UserInfo,UserInfoConfig)
    
    class CourseConfig(sites.AryaConfig):
        list_display = ['name']
    
    sites.site.register(models.Course,CourseConfig)
    
    class SchoolConfig(sites.AryaConfig):
        list_display = ['title']
    sites.site.register(models.School,SchoolConfig)
    
    class ClassListConfig(sites.AryaConfig):
        def course_dispaly(self,obj=None,is_header=False):
            if is_header:
                return '班级'
            return '%s(%s)期'% (obj.course.name,obj.semester)
        def zhanggen(self,obj=None,is_header=False):
            if is_header:
                return '任课老师'
            teachers=[]
            for obj in obj.teachers.all():
                tpl='<span style="dispaly:inline-block;padding:3px;margin:2px;boder:1px solid #add;" >%s</span>'% (obj.name)
                teachers.append(tpl)
            return mark_safe(''.join(teachers))
        list_display = ['school',course_dispaly,'price','tutor',zhanggen]
    
    sites.site.register(models.ClassList,ClassListConfig)
    View Code

    自定义函数:list_display显示choices字段

    利用obj的get_gender_display()特性,显示choice字段对应的中文名称

    1   def gender_display(self,obj=None,is_header=False):  #显示 choice 字段
    2         if is_header:
    3             return '性别'
    4         return obj.get_gender_display()
    5     list_display = ['qq', 'name', 'consultant',zhanggen,gender_display,]
    6 sites.site.register(models.Customer,CustomerConfig)
    View Code

    小结:

    经过CRM业务和Aaya(增、删、改、查)插件 list_display配置项的结合配置

    就可以 把数据库字段、外键字段、多对多字段、choice字段 都显示出来了;

    9、CRM表结构 配置组合筛选

    (1)、改进list_filter  

    组合搜素显示:

    foreign key

    M2M

    choice

     def __init__(self, option, change_list, data_list, param_dict=None,is_choices=False):
    View Code
      for obj in self.data_list:
                param_dict = copy.deepcopy(self.param_dict)
                if self.is_choices:
                    pk=str(obj[0])
                    text=obj[1]
    View Code

    通过  Q添加筛选条件

    sites.FilterOption('consultant',condtion=Q(depart_id=2)),
    View Code
       def __init__(self, field_or_func, is_multi=False, text_func_name=None, val_func_name=None,condtion=None):
    View Code
      @property
        def get_condtion(self):
            from django.db.models import Q
            if self.condtion:
                return self.condtion
            con=Q()
            return con
    View Code

    9、点击组合筛选,显示筛选数据;(读取URL参数,拼接成字典,去数据库查询)

     @property
        def get_filter_conditon(self):
            fields2 = [obj.name for obj in self.model_class._meta._get_fields()]
            params = self.request.GET
            con = {}
            for k in params:
                if k in fields2:
                    v = self.request.GET.getlist(k)
                    k = '%s__in' % (k)
                    con[k] = v
            return con
    View Code

    涉及知识点:

    1、获取数据库中 外键字段、choice字段(注意不包含多对多字段)

     fields=[ obj.name  for obj in  self.model_class._meta.fields]  #获取数据中 外键字段、choice字段(主语没有多对多)

    2、获取数据库中 多对多字段

    fields1 = [obj.name for obj in self.model_class._meta.many_to_many]  #获取多对多字段

    3、获取数据库中所有字段 (包含反向关联、多对多字段)

    # fields2 = [obj.name for obj in self.model_class._meta._get_fields()]  # 获取反向关联的字段(包含对多多)

    4、查询数据去重 distinct()

    data_list = self.model_class.objects.filter(**self.get_filter_conditon).filter(self.get_search_condtion).distinct() #distinct() 多选去重

    10、CRM客户部 配置模糊搜素功能 search_list = ['name', 'qq'],;

    search_list = ['name', 'qq']

    基础配置类

    search_list = []
        def get_search_list(self):
            search_list = []
            search_list.extend(self.search_list)
            return search_list
    View Code
    @property
        def get_search_condtion(self):
            con=Q()
            con.connector='OR'
            val=self.request.GET.get(self.q)
            if not val:
                return con
            fild_list=self.get_search_list()
            for field in fild_list:
                field='%s__contains'%(field)
                con.children.append((field,val))
            return con
    View Code

    ChangeList类
      self.search_list = model_config.get_search_list()
        def seach_attr(self):
            val=self.model_config.request.GET.get(self.model_config.q)
            print(val)
            return {'value':val,'name':self.model_config.q}
    View Code

    模板

    {% if cl.list_filter %}
                <div class="comb-search">
                    {% for row in cl.gen_list_filter %}
                        <div class="row">
                            {% for col in row %}
                                {{ col }}
                            {% endfor %}
                        </div>
                    {% endfor %}
                </div>
            {% endif %}
            {% if cl.search_list %}
                <div class="row">
                <form method="get">
                    <input value="{{cl.seach_attr.value}}" class="form-control" name="{{cl.seach_attr.name}}" style=" 250px;display: inline-block" type="text" placeholder="请输入关键字">
                    <button class="btn btn-primary">
                        <span class="glyphicon glyphicon-search"></span>
                    </button>
                </form>
            </div>
            {% endif %}
    View Code
  • 相关阅读:
    每日学习
    解决MySQL下把结果导出到文件权限不足问题
    杀死Windows中的进程镜像taskkill /F /IM 镜像名
    大家,中秋节快乐
    学习AutoIt
    为什么你应该(从现在开始就)对自己投资
    验证PE文件数字签名是否有效
    MySQL Cluster集群搭建与测试
    MySQL主从复制与读写分离
    Python与Zabbix API交互配置监控主机
  • 原文地址:https://www.cnblogs.com/sss4/p/7686915.html
Copyright © 2020-2023  润新知