zoukankan      html  css  js  c++  java
  • 可快速生成增删curd改查功能的插件

    仿造Django中的admin自己实现增删改查、模糊搜索、批量操作、条件筛选、popup功能的插件

    1.创建组件

      首先创建一个app,这里取名为stark,在settings.py中将其进行注册

    INSTALLED_APPS = [
        'django.contrib.admin',
        'django.contrib.auth',
        'django.contrib.contenttypes',
        'django.contrib.sessions',
        'django.contrib.messages',
        'django.contrib.staticfiles',
        'stark.apps.StarkConfig',
    ]
    settings.py

    2.启动项

      在stark的apps中写入启动项

    #在apps.py中:
    
    from django.apps import AppConfig
    
    #必须写的内容,是Django启动的时候会自动加载
    class StarkConfig(AppConfig):
        name = 'stark'
        def ready(self):
            from django.utils.module_loading import autodiscover_modules
            autodiscover_modules('stark')
    apps.py

    3.创建插件文件

      在stark下创建一个叫service的包,包中创建一个文件,这里取名为v1.py。在v1中创建类似于AdminSite ModelAdmin的类,用于自己封装组件

    class StarkConfig(object):
        """
            用于为每个类(即每张表)生成url对应关系,并处理用户请求的基类
            日后会封装很多功能
        """
        def __init__(self,model_class,site):
            self.model_class = model_class
            self.site = site
    
    
    class StarkSite(object):
        '''
        单例模式创建的对象的类,是一个容器,用于放置处理请求对应关系
        {model.UserInfo:StarkConfig(model.UserInfo,self)}
        '''
    
        def __init__(self):
            self._registey = {}
    
        def register(self,model_class,stark_config_class=None):
            if not stark_config_class:
    #stark_config_class,没写派生类时默认给予StarkConfig
                stark_config_class = StarkConfig
    
            self._registey[model_class] = stark_config_class(model_class,self)
    #字典{表名:stark_config_class(表名,self)}
    
    site = StarkSite()#单例模式
    StarkConfig和 StarkSite

    4.自动生成url

      在全局的ulrs.py 中

    from django.conf.urls import url
    from stark.service import v1
    
    urlpatterns = [
        url(r'^stark/', v1.site.urls),#自己创建的类似admin的功能,需在app里有相应的注册
    
    ]

      在v1.py中,为每行需要操作的表生成增删改查4个url

    class StarkConfig(object):
    
            def __init__(self,model_class,site):
                self.model_class = model_class
                self.site = site
            
    #装饰器,为了传参数request
            def wrap(self,view_func):
                def inner(request,*args,**kwargs):
                    self.request=request
                    return view_func(request,*args,**kwargs)
                return inner
    
            def get_urls(self):#第五步
                app_model_name=(self.model_class._meta.app_label,self.model_class._meta.model_name,)#元祖(app名,表名)
                url_patterns=[
                url(r'^$',self.wrap(self.changelist_view),name='%s_%s_changelist'%app_model_name),
                url(r'^add/$',self.wrap(self.add_view),name='%s_%s_add'%app_model_name),
                url(r'^(d+)/delete/$',self.wrap(self.delete_view),name='%s_%s_delete'%app_model_name),
                url(r'^(d+)/change/$',self.wrap(self.change_view),name='%s_%s_change'%app_model_name),
            ]
                url_patterns.extend(self.extra_url())#除增删改查外,想要自定义的新增的url
                return url_patterns#最后就得到了需要用到的一堆url
            def extra_url(self):
                return []
    ########################################
            @property
            def urls(self):#第四步
                return self.get_urls()
    
            def urls(self):
                return self.get_urls()
    
    
    
    #########反向生成url#####################
            def get_change_url(self, nid):
                name = 'stark:%s_%s_change' % (self.model_class._meta.app_label, self.model_class._meta.model_name)
                edit_url = reverse(name, args=(nid,))
                return edit_url
    
            def get_list_url(self):
                name = 'stark:%s_%s_changelist' % (self.model_class._meta.app_label, self.model_class._meta.model_name)
                edit_url = reverse(name)
                return edit_url
    
            def get_add_url(self):
                name = 'stark:%s_%s_add' % (self.model_class._meta.app_label, self.model_class._meta.model_name)
                edit_url = reverse(name)
                return edit_url
    
            def get_delete_url(self, nid):
                name = 'stark:%s_%s_delete' % (self.model_class._meta.app_label, self.model_class._meta.model_name)
                edit_url = reverse(name, args=(nid,))
                return edit_url
    
    
    ######视图函数(之后会具体扩展)####
            def changelist_view(self,request,*args,**kwargs):
                return HttpResponse('列表')
    
            def add_view(self,request,*args,**kwargs):
                return HttpResponse('添加')
    
            def delete_view(self,request,nid,*args,**kwargs):
                return HttpResponse('删除')
    
            def change_view(self,request,nid,*args,**kwargs):
                return HttpResponse('修改')
    
    class StarkSite(object):
        '''
        单例模式创建的对象的类,是一个容器,用于放置处理请求对应关系
        {model.UserInfo:StarkConfig(model.UserInfo,self)}
        '''
        def __init__(self):
            self._registry = {}
    
        def register(self,model_class,stark_config_class=None):
            if not stark_config_class:
                #stark_config_class即29,没写那个派生类的时候默认给予StarkConfig
                stark_config_class=StarkConfig
            self._registry[model_class]=stark_config_class(model_class,self)
            #表名:stark_config_class(表名,self)
    
        def get_urls(self):#第三步,给url
            url_pattern=[]
            for model_class,stark_config_obj in self._registry.items():#去字典里取值
                app_name=model_class._meta.app_label#app名
                model_name=model_class._meta.model_name#表名
                curd_url=url(r'^%s/%s/'%(app_name,model_name),(stark_config_obj.urls,None,None))
                #拼接生成url,需执行stark_config_obj.urls———第四步
                url_pattern.append(curd_url)
            return url_pattern
    
        @property
        def urls(self):#第二步,要url
            return (self.get_urls(),None,'stark')
    
    
    
    site=StarkSite()#第一步,单例模式
    对 StarkConfig 和 StarkSite 进行扩展

    5.定制功能

      定制每一个列表页面(查)都会有的功能,若不具备该功能可在派生类中修改。

      这里用了分页功能,可在全局中创建文件夹utils,将自己写的分页器page.py放入其中,之后在v1.py中导入即可使用。

    class StarkConfig(object):
        """
            用于为每个类(即每张表)生成url对应关系,并处理用户请求的基类
        """
        def __init__(self,model_class,site):
            self.model_class=model_class
            self.site=site
            self.request=None
    
    
    ############定制功能####################
    #########1 默认每个tr都会拥有的td
        def checkbox(self,obj=None,is_header=False):
            if is_header:
                return '选择'
            return mark_safe('<input type="checkbox" name="pk" value="%s" >' %(obj.id,))
        def edit(self,obj=None,is_header=False):
            if is_header:
                return '编辑操作'
            #url地址栏的搜索条件
            query_str=self.request.GET.urlencode()
            if query_str:
                #重新构造<button class="btn btn-primary"></button>
                params=QueryDict(mutable=True)
                params[self._query_param_key]=query_str
                return mark_safe('<button class="btn btn-primary"><a href="%s?%s" style="color:white;">编辑</a></button>' %(self.get_change_url(obj.id),params.urlencode(),))
            return mark_safe('<button class="btn btn-primary"><a href="%s" style="color:white;">编辑</a></button>' %(self.get_change_url(obj.id),))
        def delete(self,obj=None,is_header=False):
            if is_header:
                return '删除操作'
            query_str = self.request.GET.urlencode()
            if query_str:
                # 重新构造
                params = QueryDict(mutable=True)
                params[self._query_param_key] = query_str
                return mark_safe('<button class="btn btn-danger"><a href="%s?%s" style="color:white;">删除</a></button>' % (self.get_delete_url(obj.id), params.urlencode(),))
    
            return mark_safe('<button class="btn btn-danger"><a href="%s" style="color:white;">删除</a></button>'%(self.get_delete_url(obj.id),) )
    
        list_display=[]
        #得到派生类中自定义的list_display
        def get_list_display(self):
            data=[]
            if self.list_display:#派生类中定义的要显示的字段
                data.extend(self.list_display)#加入到data中
                data.append(StarkConfig.edit)#加入编辑td
                data.append(StarkConfig.delete)#加入删除td
                data.insert(0,StarkConfig.checkbox)#在最前面插一个td
            return data
    
        edit_link=[]
        def get_edit_link(self):
            result=[]
            if self.edit_link:
                result.extend(self.edit_link)
            return result
    
    
    
    ######### 2是否显示add按钮
        show_add_btn = True  # 默认显示
        def get_show_add_btn(self):
            return self.show_add_btn
    
    #########3 关键字搜索
        show_search_form = False#默认不显示
        def get_show_search_form(self):
            return self.show_search_form
        search_fields = []#关键字默认为空
        def get_search_fields(self):
            result = []
            if self.search_fields:
                result.extend(self.search_fields)#派生类中自定义的关键字
            return result
    
        def get_search_condition(self):
            key_word = self.request.GET.get(self.search_key)#'_q'
            search_fields = self.get_search_fields()#关键字
            condition = Q()#创建Q对象用于与或
            condition.connector = 'or'#搜索条件之间用或连接
            if key_word and self.get_show_search_form():
                for field_name in search_fields:
                    condition.children.append((field_name, key_word))
            return condition
    #############4 actions,批量功能,需拥有权限才可拥有此功能
        show_actions = False#默认不显示
        def get_show_actions(self):
            return self.show_actions
    
        actions = []#默认批量操作内容为空
        def get_actions(self):
            result = []
            if self.actions:
                result.extend(self.actions)#加入派生类中自定制的批量操作
            return result
    
    
    #############5 组合搜索
        show_comb_filter = False
        def get_show_comb_filter(self):
            return self.show_comb_filter
    
        comb_filter=[]#默认为空
        def get_comb_filter(self):
            result=[]
            if self.comb_filter:
                result.extend(self.comb_filter)#得到派生类中的条件删选
            return result
    
    #############6排序
        order_by = []
        def get_order_by(self):
            result = []
            result.extend(self.order_by)
            return result
    StarkConfig类中扩展

      

    6.ChangeList

      因为列表展示页面(查)需要后端向前端传很多的参数,所以我们这里讲所有需要传的参数封装到一个额外的类中,这样我们在视图函数中只需要将这个类实例化,只用向前端传这个类,在前端只要   ChangeList.方法或字段   就可以了

    class ChangeList(object):
        '''
        很牛逼的一个类,封装了所有视图函数想要往前端传的内容
        功能:使视图函数中的代码变的简洁
        '''
        def __init__(self,config,queryset):
            self.config=config#stark.py中写了派生类的话就是那个类,没写的话默认就是StarkConfig
            self.list_display=config.get_list_display()
            self.edit_link = config.get_edit_link()
            self.model_class=config.model_class#数据库的表
            self.request=config.request#StarkConfig中默认是None,不过程序运行后就会有
            self.show_add_btn=config.get_show_add_btn()
            # 搜索框
            self.show_search_form = config.get_show_search_form()
            self.search_form_val = config.request.GET.get(config.search_key, '')#搜索关键字“_q”,首次访问网页默认是空
            # 批量操作
            self.actions=config.get_actions()#得到派生类中写的actions的内容[]
            self.show_actions=config.get_show_actions()#操作框
            #组合搜索
            self.show_comb_filter=config.get_show_comb_filter()
            self.comb_filter=config.get_comb_filter()
    
            from utils.pager import Pagination
            #分页器
            current_page = self.request.GET.get('page', 1)#得到传入的page,没有默认为第一页
            total_count = queryset.count()#要显示的数据的量,queryset在视图函数中有数据库操作的赋值
            page_obj = Pagination(current_page, total_count, self.request.path_info, self.request.GET, per_page_count=5)
                                #当前页         数据量        当前url不带问号         ?后面的条件内容      设定的每页显示的数据量条数
            self.page_obj = page_obj#得到最终生成的分页器对象
    
            self.data_list = queryset[page_obj.start:page_obj.end]#得到分页后的数据,用于页面展示
    
        #批量操作
        def modify_actions(self):
            result = []#批量操作内容,默认为空,去派生类中定义
            for func in self.actions:#self.actions=config.get_actions(),默认为空
                temp = {'name':func.__name__,'text':func.short_desc}#name是函数名,text是自加的描述
                result.append(temp)
            return result
    
        def add_url(self):#添加操作的url
            query_str = self.request.GET.urlencode()
            if query_str:
                # 重新构造,用于跳转
                params = QueryDict(mutable=True)
                params[self.config._query_param_key] = query_str
                return self.config.get_add_url()+'?'+params.urlencode()
            return self.config.get_add_url()
    
        def head_list(self):
            #构造表头
            result = []
            # [checkbox,'id','name',edit,del]
            for field_name in self.list_display:
                if isinstance(field_name, str):
                    # 根据类和字段名称,获取字段对象的verbose_name
                    verbose_name = self.model_class._meta.get_field(field_name).verbose_name
                else:
                    verbose_name = field_name(self.config, is_header=True)# 去派生类中执行
                result.append(verbose_name)
            return result
    
        def body_list(self):
            # 处理表中的数据
            data_list = self.data_list#self.data_list = queryset[page_obj.start:page_obj.end]
            new_data_list = []
            for row in data_list:
                # row是 每一条数据对象UserInfo(id=2,name='alex2',age=181)
                temp = []
                for field_name in self.list_display:
                    if isinstance(field_name,str):#派生类中定义的显示字段
                        val = getattr(row,field_name)
                    else:#每个td都拥有的功能,checkbox、edit、delete、
                        val = field_name(self.config,row)
                    # 用于定制编辑列
                    if field_name in self.edit_link:
                        val = self.edit_link_tag(row.pk, val)
                    temp.append(val)
                new_data_list.append(temp)
            return new_data_list
    
        def gen_comb_filter(self):
            #生成器函数
            """
            [
                 FilterRow(((1,'男'),(2,'女'),)),
                 FilterRow([obj,obj,obj,obj ]),
                 FilterRow([obj,obj,obj,obj ]),
            ]
            """
            '''
                    comb_filter = [
                    v1.FilterOption('gender', is_choice=True),#关键字传参,代表是choice
                    v1.FilterOption('depart'),#, 筛选只显示id大于三的部门condition={'id__gt': 3}
                    v1.FilterOption('roles', True),#True传入,代表是多选
                ]
                    '''
            from django.db.models import ForeignKey,ManyToManyField
            for option in self.comb_filter:
                _field = self.model_class._meta.get_field(option.field_name)#字段
                if isinstance(_field,ForeignKey):
                    # 获取当前字段depart,关联的表 Department表并获取其所有数据
                    # print(field_name,_field.rel.to.objects.all())
                    row = FilterRow(option, option.get_queryset(_field), self.request)
                elif isinstance(_field,ManyToManyField):
                    # print(field_name, _field.rel.to.objects.all())
                    # data_list.append(  FilterRow(_field.rel.to.objects.all()) )
                    row = FilterRow(option,option.get_queryset(_field), self.request)
    
                else:
                    # print(field_name,_field.choices)
                    # data_list.append(  FilterRow(_field.choices) )
                    row = FilterRow(option,option.get_choices(_field),self.request)
                # 可迭代对象,迭代详细在FilterRow的__iter__中
                yield row
    
        def edit_link_tag(self,pk,text):
            query_str = self.request.GET.urlencode()  # page=2&nid=1
            params = QueryDict(mutable=True)
            params[self.config._query_param_key] = query_str
            return mark_safe('<a href="%s?%s">%s</a>' % (self.config.get_change_url(pk), params.urlencode(),text,))  # /stark/app01/userinfo/
    v1.py中的ChangeList类

    7.组合搜索(筛选)

      组合搜索(筛选功能)是一大难点,这里还需定义两个类FilterOption和FilterRow

      FilterOption:用于封装筛选条件的配置信息。FilterOption的实例化由派生类决定

      FilterRow:可迭代对象,封装了筛选中的每一行数据。

    class FilterOption(object):
        def __init__(self, field_name, multi=False, condition=None, is_choice=False,text_func_name=None, val_func_name=None):
            """
            :param field_name: 字段
            :param multi:  是否多选
            :param condition: 显示数据的筛选条件
            :param is_choice: 是否是choice
            """
            self.field_name = field_name
            self.multi = multi
            self.condition = condition
            self.is_choice = is_choice
            self.text_func_name = text_func_name#组合搜索时,页面上生成显示的文本的函数
            self.val_func_name = val_func_name#组合搜索时,页面上生成的a标签中的值的函数
    
        def get_queryset(self, _field):
            if self.condition:#是数据的筛选条件
                return _field.rel.to.objects.filter(**self.condition)#拿到筛选后的对象
            return _field.rel.to.objects.all()#默认“全部”按钮被选中,给出所有对象
    
        def get_choices(self, _field):#是choices
            return _field.choices
    
    #可迭代对象,封装了筛选中的每一行数据。
    class FilterRow(object):
        def __init__(self, option, data, request):
            self.option = option
            self.data = data#关联字段所关联的表的所有有关联的数据
            # request.GET
            self.request = request
    
        def __iter__(self):
            params = copy.deepcopy(self.request.GET)#深拷贝?后面的内容,得到QueryDict
            params._mutable = True#可修改
            current_id = params.get(self.option.field_name)  #params.get(字段),得到的是值
            current_id_list = params.getlist(self.option.field_name)  # [1,2,3]
    
            if self.option.field_name in params:#地址栏已存在筛选条件
                # del params[self.option.field_name],先删除
                origin_list = params.pop(self.option.field_name)#删除,并得到删除内容
                url = "{0}?{1}".format(self.request.path_info, params.urlencode())
                yield mark_safe('<a href="{0}">全部</a>'.format(url))#“全部按钮”不被选中
                params.setlist(self.option.field_name, origin_list)#将本身已存在的筛选条件放入params中
            else:
                url = "{0}?{1}".format(self.request.path_info, params.urlencode())#地址栏不存在筛选条件
                yield mark_safe('<a class="active" href="{0}">全部</a>'.format(url))#默认“全部”按钮被选中
    
            for val in self.data:
                if self.option.is_choice:# ( (1,男),(2,女)  )
                    pk, text = str(val[0]), val[1]
                else:#每个val都是对象
                    # pk, text = str(val.pk), str(val)
                    text = self.option.text_func_name(val) if self.option.text_func_name else str(val)
                    pk = str(self.option.val_func_name(val)) if self.option.val_func_name else str(val.pk)
                # 当前URL?option.field_name
                # 当前URL?gender=pk
                #制定url的显示规则:
                # self.request.path_info # http://127.0.0.1:8005/arya/crm/customer/?gender=1&id=2
                # self.request.GET['gender'] = 1 # &id=2gender=1
                if not self.option.multi:
                    # 单选
                    params[self.option.field_name] = pk#1,2
                    url = "{0}?{1}".format(self.request.path_info, params.urlencode())
                    if current_id == pk:#当前url筛选条件中的值
                        yield mark_safe("<a class='active' href='{0}'>{1}</a>".format(url, text))#该筛选按钮被选中
                    else:
                        yield mark_safe("<a href='{0}'>{1}</a>".format(url, text))#其余按钮,没被选中
                else:
                    # 多选 current_id_list = ["1","2"]
                    _params = copy.deepcopy(params)
                    id_list = _params.getlist(self.option.field_name)#["1","2","3","4"]
    
                    if pk in current_id_list:#值已存在,表示该按钮已被选中
                        id_list.remove(pk)#将该值从id_list中去除
                        _params.setlist(self.option.field_name, id_list)#["2","3","4"]
                        url = "{0}?{1}".format(self.request.path_info, _params.urlencode())
                        #该按钮被选中,但其a标签的href中跳转的链接即当前url去除本身按钮id的状态,即该按钮,被再次点击时,就会恢复未选中状态
                        yield mark_safe("<a class='active' href='{0}'>{1}</a>".format(url, text))
    
                    else:#值未存在
                        id_list.append(pk)
                        # params中被重新赋值
                        _params.setlist(self.option.field_name, id_list)
                        # 创建URL,赋予其被点时,使其产生被选中
                        url = "{0}?{1}".format(self.request.path_info, _params.urlencode())
                        yield mark_safe("<a href='{0}'>{1}</a>".format(url, text))
    FilterOption和FilterRow

    8.popup功能

      popup功能用于添加和编辑页面,在操作时选择外键关联的select框,可临时添加一个对象的功能

    添加和编辑的视图函数中需要有以下代码:

        def xxx_view(self,request):
    
            _popbackid = request.GET.get('_popbackid')#临时需要添加的外键字段所对应的输入框的id
            if request.method == 'POST':
                form = model_form_class(request.POST)
                if form.is_valid():
                    new_obj=form.save()
                    if _popbackid:
                        # 判断是否是来源于popup请求
                        # render一个页面,写自执行函数
                        # popUp('/stark/crm/userinfo/add/?_popbackid=id_consultant&model_name=customer&related_name=consultant')
                        from django.db.models.fields.reverse_related import ManyToOneRel, ManyToManyRel
                        result = {'status': False, 'id': None, 'text': None, 'popbackid': _popbackid}
    
                        model_name = request.GET.get('model_name')  # customer
                        related_name = request.GET.get('related_name')  # consultant, "None"
                        for related_object in new_obj._meta.related_objects:#关联表的对象
                            _model_name = related_object.field.model._meta.model_name
                            _related_name = related_object.related_name
                            # 判断外键关联字段是否是主键id
                            if (type(related_object) == ManyToOneRel):
                                _field_name = related_object.field_name
                            else:
                                _field_name = 'pk'
                            _limit_choices_to = related_object.limit_choices_to
                            if model_name == _model_name and related_name == str(_related_name):
                                is_exists = self.model_class.objects.filter(**_limit_choices_to, pk=new_obj.pk).exists()
                                if is_exists:
                                    # 如果新创建的用户是可查看的人,页面才增加
                                    # 分门别类做判断:
                                    result['status'] = True
                                    result['text'] = str(new_obj)
                                    result['id'] = getattr(new_obj, _field_name)
                                    return render(request, 'stark/popup_response.html',
                                                  {'json_result': json.dumps(result, ensure_ascii=False)})
                        return render(request, 'stark/popup_response.html',
                                      {'json_result': json.dumps(result, ensure_ascii=False)})
                    else:
                        list_query_str = request.GET.get(self._query_param_key)
                        list_url = '%s?%s' % (self.get_list_url(), list_query_str,)
    
                        return redirect(list_url)
                        # return redirect(self.get_list_url())
            return render(request, 'stark/add_view.html', {'form': form, 'config': self})
    视图函数

      因为编辑和添加的前端页面代码大量冲重合,所以这里引入form.html和templatetags。

    templatetages下创建change_form.py

    from django.template import Library
    from django.urls import reverse
    from stark.service.v1 import site
    
    register = Library()
    # 自定义标签
    @register.inclusion_tag('stark/form.html')
    def new_form(config,model_form_obj):
        new_form = []
        for bfield in model_form_obj:#model_form_obj是每一条记录
            temp = {'is_popup': False, 'item': bfield}
            # bfield.field是ModelForm读取对应的models.类,然后根据每一个数据库字段,生成Form的字段
            from django.forms.boundfield import BoundField
            from django.db.models.query import QuerySet
            from django.forms.models import ModelChoiceField
            if isinstance(bfield.field, ModelChoiceField):#是单选和多选————>外键字段
                related_class_name = bfield.field.queryset.model#得到字段的field
                if related_class_name in site._registry:#已注册
                    app_model_name = related_class_name._meta.app_label, related_class_name._meta.model_name
                    # FK,One,M2M: 当前字段所在的类名和related_name
                    model_name = config.model_class._meta.model_name
                    related_name = config.model_class._meta.get_field(bfield.name).rel.related_name
                    # print(model_name,related_name)
                    base_url = reverse("stark:%s_%s_add" % app_model_name)#应用名_类名_add,反向生成url
                    #bfield.auto_id是内置方法,得到该input框的id
                    # 带有回调参数的url
                    popurl = "%s?_popbackid=%s&model_name=%s&related_name=%s" % (base_url, bfield.auto_id,model_name,related_name)
    
                    temp['is_popup'] = True
                    temp['popup_url'] = popurl
            new_form.append(temp)#{'is_popup': True, 'item': bfield,'popup_url':popurl}
        return {'new_form':new_form}
    change_form.py

    templates下的form.html(内含jQuery,处理popup回调)

    <form method="post"  class="form-horizontal" novalidate>
        {% csrf_token %}
    {#    dic={'is_popup': True, 'item': bfield,'popup_url':popurl}#}
        {% for dic in new_form %}
            <div class="col-sm-4 col-sm-offset-4">
                <div class="form-group">
                    <label for="" class="col-sm-2 control-label">{{ dic.item.field.label }}:</label>
                    <div class="col-sm-9" style="position: relative">
                        {# modelform自动形成input#}
                        {{ dic.item }}
                        {% if dic.is_popup %}{# 单选或多选#}
                            <div style="position: absolute;top: 10px;left: 330px;">
                                <a href="" onclick="popUp('{{ dic.popup_url }}')"><i class="fa fa-arrows" aria-hidden="true"></i></a>
                            </div>
                        {% endif %}
                        <div style="position: absolute;font-size: 12px;top: 18px;right: 20px;color: #e4393c;background: #FFEBEB;">{{ dic.item.errors.0 }}</div>
                    </div>
                </div>
            </div>
        {% endfor %}
        <div class="col-sm-offset-7 col-sm-3">
            <input type="submit" class="btn btn-primary" value="提交">
        </div>
    
    
    </form>
    
    
    <script>
        function popUp(url) {
            var popupPage = window.open(url, url, "status=1, height:500, 600, toolbar=0, resizeable=0");
        }
        function popupCallback(dic) {
            if (dic.status) {
                var op = document.createElement('option');
                op.value = dic.id;
                op.text = dic.text;
                op.setAttribute('selected', 'selected');
                document.getElementById(dic.popbackid).appendChild(op);
    
            }
        }
    
    </script>
    form.html
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>正在关闭</title>
    </head>
    <body>
        <script>
            (function () {
                var dic = {{ json_result|safe }};
    {#            发起popup请求的页面执行该回调函数#}
                opener.popupCallback(dic);
                window.close();
            })()
    
        </script>
    </body>
    </html>
    popup_response.html

    添加和修改的html页面将会在之后给出

    9.视图函数

    9.1 静态文件

      创建一个static文件夹,用于存放静态文件。这里用到了bootstrap、font-awesome和jQuery

    9.2 基板

      因为增删改查的前端页面代码重复量较多,所以我们设置一个基板,用于减少代码冗余。

    {% load staticfiles %}
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>{% block title %}{% endblock %}</title>
        <link rel="stylesheet" href="{% static 'bootstrap-3.3.7-dist/css/bootstrap.css' %}">
        <link rel="stylesheet" href="{% static "mycss/stark-form.css" %}" />
        <link rel="stylesheet" href="{% static 'font-awesome/css/font-awesome.min.css' %}">
        {% block css %}
        
        {% endblock %}
    </head>
    <body>
    {% block body %}
    
    {% endblock %}
    
    
    {% block js %}
    
    {% endblock %}
    </body>
    </html>
    基板base_temp

    9.3 查

      注意组合搜索功能和批量功能

        # 默认列表页面
        def changelist_view(self, request,*args, **kwargs):
            #分页,已改写到ChangeList类中
            # from utils.pager import Pagination
            # current_page=request.GET.get('page',1)
            # total_count=self.model_class.objects.all().count()
            # page_obj=Pagination(current_page,total_count,request.path_info,request.GET,per_page_count=4)
    
            if request.method=='GET':
                comb_condition = {}#筛选条件默认为空
                option_list = self.get_comb_filter()#拿到派生类中定制的筛选条件
                for key in request.GET.keys():#?后面的键
                    value_list = request.GET.getlist(key)#拿到键对应的值[1,2,3]
                    flag = False
                    for option in option_list:#option是每一个删选条件
                        if option.field_name == key:#该条件已存在于地址栏
                            flag = True
                            break
                    if flag:
                        #comb_condition = {"id__in":[1,2,3].......}
                        comb_condition["%s__in" % key] = value_list
    
    
                # 带搜索条件的数据,没有搜索条件的话就是全部数据。有筛选条件的话,还得处理成筛选后的数据
                queryset=self.model_class.objects.filter(self.get_search_condition()).filter(**comb_condition).distinct()
    
                the_list=ChangeList(self,queryset)#封装好要向前端传的值
                return render(request, 'stark/changelist.html', {'the_list':the_list})
            elif request.method=='POST' and self.get_show_actions():#批量操作
                func_name_str = request.POST.get('list_action')#前端传的操作name
                action_func = getattr(self, func_name_str)#反射,得到处理的方式
                ret = action_func(request)
                if ret:
                    return ret
    查页面(后端)
    {% extends 'stark/base_temp.html' %}
    {% block title %}列表页面{% endblock %}
    {% block css %}
    <style>
            h1 {
                margin-bottom: 50px;
            }
    
            td, th {
                text-align: center;
            }
    
            .list-filter a {
                display: inline-block;
                padding: 3px 6px;
                border: 1px solid #2e6da4;
                margin: 3px 0;
            }
    
            .list-filter a.active {
                background-color: #2e6da4;
                color: white;
            }
    
    
        </style>
    {% endblock %}
    
    {% block body %}
        <div class="container">
        <div class="row">
            <div class="col-md-10 col-md-offset-1">
                <h1 class="text-center">列表页面</h1>
                {#        筛选栏#}
            {% if the_list.show_comb_filter %}
                <div class="list-filter">
                    {% for item in the_list.gen_comb_filter %}
                        <div>
                            {% for foo in item %}
                                {{ foo }}
                            {% endfor %}
                        </div>
                    {% endfor %}
                </div>
            {% endif %}
                {#        搜索栏#}
                {% if the_list.show_search_form %}
                    <div class="form-group pull-right">
                        <form method="get">
                            <input name="{{ the_list.config.search_key }}" value="{{ the_list.search_form_val }}"
                                   class="form-control"
                                   placeholder="请输入搜索条件" type="text" style="display:inline-block; 200px;"/>
                            <button class="btn btn-primary"><span class="glyphicon glyphicon-search"></span></button>
    
                        </form>
                    </div>
                {% endif %}
                {#        带有批量执行操作的表格#}
                <form method="post">
                    {% csrf_token %}
                    {% if the_list.show_actions %}
                        <div class="form-group">
                            <select name="list_action" class="form-control" style="display:inline-block; 200px;">
                                {% for item in the_list.modify_actions %}
                                    <option value="{{ item.name }}">{{ item.text }}</option>
                                {% endfor %}
    
                            </select>
                            <button class="btn btn-danger">执行</button>
                        </div>
                    {% endif %}
    
                    <table class="table table-bordered table-striped">
                        <thead>
                        <tr>
                            <th>编号</th>
                            {% for item in the_list.head_list %}
                                <th>{{ item }}</th>
                            {% endfor %}
                        </tr>
                        </thead>
                        <tbody>
                        {% for obj in the_list.body_list %}
                            <tr>
                                <td><b>({{ forloop.counter }})</b></td>
                                {% for col in obj %}
                                    <td>{{ col }}</td>
                                {% endfor %}
                            </tr>
                        {% endfor %}
                        </tbody>
                    </table>
                </form>
                {#        添加按钮#}
                <div class="pull-left">
                    {% if the_list.show_add_btn %}
                        <a href="{{ the_list.add_url }}" class="btn btn-info">&nbsp;&nbsp;&nbsp;&nbsp;添加&nbsp;&nbsp;&nbsp;&nbsp;</a>
                    {% endif %}
                </div>
                {#        分页器#}
                <div class="pager">
                    <nav aria-label="Page navigation">
                        <ul class="pagination">
    
                            {{ the_list.page_obj.page_html|safe }}
                        </ul>
                    </nav>
                </div>
            </div>
        </div>
    
    </div>
    {% endblock %}
    查页面(前端)

    9.4 modelform

      modelform有两种写法

    model_form_class = None
        def get_model_form_class(self):
            if self.model_form_class:
                return self.model_form_class
            from django.forms import ModelForm
    
        #方式一:
            # class TestModelForm(ModelForm):
            #     class Meta:
            #         model = self.model_class
            #         fields = "__all__"
            #
            #         error_messages = {
            #             "__all__":{
            #
            #                   },
            #         'email': {
            #         'required': '',
            #         'invalid': '邮箱格式错误..',
            #         }
            #         }
          #方式二: type创建TestModelForm类
            meta = type('Meta', (object,), {'model': self.model_class, 'fields': '__all__'})
            TestModelForm = type('TestModelForm', (ModelForm,), {'Meta': meta})
            return TestModelForm
    modelform

    9.5 增页面(含popup功能)

    class StarkConfig(object):
     #
        def add_view(self, request, *args, **kwargs):
            # 添加页面
            model_form_class = self.get_model_form_class()#根据modelform生成input
            _popbackid = request.GET.get('_popbackid')#临时需要添加的外键字段所对应的输入框的id
            if request.method == 'GET':
                form = model_form_class()
            else:
                form = model_form_class(request.POST)
                if form.is_valid():
                    new_obj=form.save()
                    if _popbackid:
                        # 判断是否是来源于popup请求
                        # render一个页面,写自执行函数
                        # popUp('/stark/crm/userinfo/add/?_popbackid=id_consultant&model_name=customer&related_name=consultant')
                        from django.db.models.fields.reverse_related import ManyToOneRel, ManyToManyRel
                        result = {'status': False, 'id': None, 'text': None, 'popbackid': _popbackid}
    
                        model_name = request.GET.get('model_name')  # customer
                        related_name = request.GET.get('related_name')  # consultant, "None"
                        for related_object in new_obj._meta.related_objects:#关联表的对象
                            _model_name = related_object.field.model._meta.model_name
                            _related_name = related_object.related_name
                            # 判断外键关联字段是否是主键id
                            if (type(related_object) == ManyToOneRel):
                                _field_name = related_object.field_name
                            else:
                                _field_name = 'pk'
                            _limit_choices_to = related_object.limit_choices_to
                            if model_name == _model_name and related_name == str(_related_name):
                                is_exists = self.model_class.objects.filter(**_limit_choices_to, pk=new_obj.pk).exists()
                                if is_exists:
                                    # 如果新创建的用户是可查看的人,页面才增加
                                    # 分门别类做判断:
                                    result['status'] = True
                                    result['text'] = str(new_obj)
                                    result['id'] = getattr(new_obj, _field_name)
                                    return render(request, 'stark/popup_response.html',
                                                  {'json_result': json.dumps(result, ensure_ascii=False)})
                        return render(request, 'stark/popup_response.html',
                                      {'json_result': json.dumps(result, ensure_ascii=False)})
                    else:
                        list_query_str = request.GET.get(self._query_param_key)
                        list_url = '%s?%s' % (self.get_list_url(), list_query_str,)
    
                        return redirect(list_url)
                        # return redirect(self.get_list_url())
            return render(request, 'stark/add_view.html', {'form': form, 'config': self})
    StarkConfig类中的add_view
    {% extends 'stark/base_temp.html' %}
    {% load change_form %}
    
    {% block title %}添加页面{% endblock %}
    
    {% block css %}{% endblock %}
    
    {% block body %}
        <h1 class="text-center">添加页面</h1>
        <form method="post"  novalidate>
            {% csrf_token %}
        {#    {% include 'stark/form.html' %}#}
            {% new_form config form  %}{#    def new_form(model_form_obj):#}
        </form>
    {% endblock %}
    add_view.html

    9.6 改页面(含popup功能)

    class StarkConfig(object):
        def change_view(self, request, nid,*args, **kwargs):
            # self.model_class.objects.filter(id=nid)
            obj = self.model_class.objects.filter(pk=nid).first()
            print(obj)
            if not obj:
                return redirect(self.get_list_url())
            model_form_class = self.get_model_form_class()
            _popbackid = request.GET.get('_popbackid')#临时需要添加的外键字段所对应的输入框的id
            # GET,显示标签+默认值
            if request.method == 'GET':
                form = model_form_class(instance=obj)
                return render(request, 'stark/change_view.html', {'form': form,'config': self})
            else:
                form = model_form_class(instance=obj, data=request.POST)
                if form.is_valid():
                    form.save()
                    list_query_str=request.GET.get(self._query_param_key)
                    list_url='%s?%s'%(self.get_list_url(),list_query_str,)
    
                    return redirect(list_url)
                return render(request, 'stark/change_view.html', {'form': form})
    StarkConfig类中的add_view
    {% extends 'stark/base_temp.html' %}
    {% load change_form %}
    
    {% block title %}添加页面{% endblock %}
    
    {% block css %}{% endblock %}
    
    {% block body %}
        <h1 class="text-center">修改页面</h1>
            <form method="post" novalidate>
    {#        {% include 'stark/form.html' %}#}
                {% new_form config form  %}{#    def new_form(model_form_obj):#}
            </form>
    {% endblock %}
    change_view.html

    9.7 删页面

      删页面最为简单,但也要注意跳转功能,总不能在第三页点删除键后跳到了第一页吧

    class StarkConfig(object):
        def delete_view(self, request, nid,*args, **kwargs):
            self.model_class.objects.filter(pk=nid).delete()
            list_query_str = request.GET.get(self._query_param_key)
            list_url = '%s?%s' % (self.get_list_url(), list_query_str,)
            return redirect(list_url)
    StarkConfig类中的delete_view

    end

      至此,插件v1就已全部完成。在使用时,需要在app中导入这个文件,即创建一个stark.py文件,在文件中进行注册。

      在stark.py中,只注册的话就执行默认功能,即只具备查、删、改功能。若想要拥有更多功能,需在stark.py中自己写一个派生类,利用钩子函数进行扩展。

    举例说明:

      这里进行对插件的使用的举例说明

      我们创建一个新的app并建表,然后在app下创建stark.py,无需写路由和视图函数即可得到增删改查以及模糊搜索、批量操作、条件筛选、popup等功能。具体是否拥有该功能,由权限决定。

    from django.db import models
    
    # Create your models here.
    class UserInfo(models.Model):
        name=models.CharField(max_length=32)
        pwd=models.CharField(max_length=32)
        email=models.EmailField(max_length=32)
        tp=models.ForeignKey('Type')
    
        class Meta:
            verbose_name_plural = "用户表"
        def __str__(self):
            return self.name
    class Type(models.Model):
        name=models.CharField(max_length=32)
        role=models.ManyToManyField('Role')
    
        class Meta:
            verbose_name_plural = "类型表"
    
        def __str__(self):
            return self.name
    
    class Role(models.Model):
        name=models.CharField(max_length=32)
        salary=models.IntegerField(default=0)
    
        class Meta:
            verbose_name_plural = "角色表"
        def __str__(self):
            return self.name
    
    
    
    class Host(models.Model):
        hostname = models.CharField(verbose_name='主机名',max_length=32)
        ip = models.GenericIPAddressField(verbose_name="IP",protocol='ipv4')
        port = models.IntegerField(verbose_name='端口')
    models.py
    print('我是stark')
    from django.shortcuts import HttpResponse,render,redirect
    from django.utils.safestring import mark_safe
    from django.conf.urls import url
    from stark.service import v1
    from app01 import models
    from django.forms import ModelForm
    class UserInfoModelForm(ModelForm):
        class Meta:
            model = models.UserInfo
            fields = '__all__'
            error_messages = {
                'name':{
                    'required':'用户名不能为空'
                }
            }
    
    class UserinfoConfig(v1.StarkConfig):
        '''
        自己定义的派生类,可以有29种额外的显示方式,效果与admin相同
        '''
    
        list_display=['id','name','pwd','email']
        def extra_url(self):
            url_list=[
                #除增删改查外,想要新增的url
            ]
            return url_list
        show_add_btn = True
        model_form_class = UserInfoModelForm
        show_search_form = True#搜索框
        search_fields = ['name__contains', 'email__contains']#模糊搜索
        show_actions = True#批量操作框
        #批量删除
        def multi_del(self,request):
            pk_list = request.POST.getlist('pk')#得到所有的勾选的项
            self.model_class.objects.filter(id__in=pk_list).delete()
            return HttpResponse('删除成功')
            # return redirect("http://www.baidu.com")
        multi_del.desc_text = "批量删除"#给函数内部加一个字段
    
        def multi_init(self,request):
            pk_list = request.POST.getlist('pk')
            #self.model_class.objects.filter(id__in=pk_list).delete()
            # return HttpResponse('删除成功')
            #return redirect("http://www.baidu.com")
        multi_init.desc_text = "初始化"
    
        actions = [multi_del, multi_init]#给actions加入定制的功能
    
    
    
    class HostModelForm(ModelForm):
        class Meta:
            model = models.Host
            fields = ['id','hostname','ip','port']
            error_messages = {
                'hostname':{
                    'required':'主机名不能为空',
                },
                'ip':{
                    'required': 'IP不能为空',
                    'invalid': 'IP格式错误',
                }
    
            }
    
    
    
    
    class HostConfig(v1.StarkConfig):
        def ip_port(self,obj=None,is_header=False):
            if is_header:
                return '自定义列'
            return "%s:%s" %(obj.ip,obj.port,)
    
        list_display = ['id','hostname','ip','port',ip_port]
        # get_list_display
    
        show_add_btn = True
        # get_show_add_btn
    
        model_form_class = HostModelForm
        # get_model_form_class
        def extra_url(self):
            urls = [
                url('^report/$',self.report_view)
            ]
            return urls
    
        def report_view(self,request):
            return HttpResponse('自定义报表')
    
        def delete_view(self,request,nid,*args,**kwargs):
            if request.method == "GET":
                return render(request,'my_delete.html')
            else:
                self.model_class.objects.filter(pk=nid).delete()
                return redirect(self.get_list_url())
    
    
    
    
    #相应的注册
    #第二个参数传入自己写的类时,可以拥有自己写的类中的额外的方法
    v1.site.register(models.UserInfo,UserinfoConfig)
    v1.site.register(models.Role)
    v1.site.register(models.Type)
    v1.site.register(models.Host,HostConfig)
    stark.py

    附:

    import copy
    import json
    from django.shortcuts import HttpResponse,render,redirect
    from django.urls import reverse
    from django.conf.urls import url, include
    from django.utils.safestring import mark_safe
    from django.http import QueryDict
    from django.db.models import Q
    
    #用于封装筛选条件的配置信息
    class FilterOption(object):
        def __init__(self, field_name, multi=False, condition=None, is_choice=False,text_func_name=None, val_func_name=None):
            """
            :param field_name: 字段
            :param multi:  是否多选
            :param condition: 显示数据的筛选条件
            :param is_choice: 是否是choice
            """
            self.field_name = field_name
            self.multi = multi
            self.condition = condition
            self.is_choice = is_choice
            self.text_func_name = text_func_name#组合搜索时,页面上生成显示的文本的函数
            self.val_func_name = val_func_name#组合搜索时,页面上生成的a标签中的值的函数
    
        def get_queryset(self, _field):
            if self.condition:#是数据的筛选条件
                return _field.rel.to.objects.filter(**self.condition)#拿到筛选后的对象
            return _field.rel.to.objects.all()#默认“全部”按钮被选中,给出所有对象
    
        def get_choices(self, _field):#是choices
            return _field.choices
    
    #可迭代对象,封装了筛选中的每一行数据。
    class FilterRow(object):
        def __init__(self, option, data, request):
            self.option = option
            self.data = data#关联字段所关联的表的所有有关联的数据
            # request.GET
            self.request = request
    
        def __iter__(self):
            params = copy.deepcopy(self.request.GET)#深拷贝?后面的内容,得到QueryDict
            params._mutable = True#可修改
            current_id = params.get(self.option.field_name)  #params.get(字段),得到的是值
            current_id_list = params.getlist(self.option.field_name)  # [1,2,3]
    
            if self.option.field_name in params:#地址栏已存在筛选条件
                # del params[self.option.field_name],先删除
                origin_list = params.pop(self.option.field_name)#删除,并得到删除内容
                url = "{0}?{1}".format(self.request.path_info, params.urlencode())
                yield mark_safe('<a href="{0}">全部</a>'.format(url))#“全部按钮”不被选中
                params.setlist(self.option.field_name, origin_list)#将本身已存在的筛选条件放入params中
            else:
                url = "{0}?{1}".format(self.request.path_info, params.urlencode())#地址栏不存在筛选条件
                yield mark_safe('<a class="active" href="{0}">全部</a>'.format(url))#默认“全部”按钮被选中
    
            for val in self.data:
                if self.option.is_choice:# ( (1,男),(2,女)  )
                    pk, text = str(val[0]), val[1]
                else:#每个val都是对象
                    # pk, text = str(val.pk), str(val)
                    text = self.option.text_func_name(val) if self.option.text_func_name else str(val)
                    pk = str(self.option.val_func_name(val)) if self.option.val_func_name else str(val.pk)
                # 当前URL?option.field_name
                # 当前URL?gender=pk
                #制定url的显示规则:
                # self.request.path_info # http://127.0.0.1:8005/arya/crm/customer/?gender=1&id=2
                # self.request.GET['gender'] = 1 # &id=2gender=1
                if not self.option.multi:
                    # 单选
                    params[self.option.field_name] = pk#1,2
                    url = "{0}?{1}".format(self.request.path_info, params.urlencode())
                    if current_id == pk:#当前url筛选条件中的值
                        yield mark_safe("<a class='active' href='{0}'>{1}</a>".format(url, text))#该筛选按钮被选中
                    else:
                        yield mark_safe("<a href='{0}'>{1}</a>".format(url, text))#其余按钮,没被选中
                else:
                    # 多选 current_id_list = ["1","2"]
                    _params = copy.deepcopy(params)
                    id_list = _params.getlist(self.option.field_name)#["1","2","3","4"]
    
                    if pk in current_id_list:#值已存在,表示该按钮已被选中
                        id_list.remove(pk)#将该值从id_list中去除
                        _params.setlist(self.option.field_name, id_list)#["2","3","4"]
                        url = "{0}?{1}".format(self.request.path_info, _params.urlencode())
                        #该按钮被选中,但其a标签的href中跳转的链接即当前url去除本身按钮id的状态,即该按钮,被再次点击时,就会恢复未选中状态
                        yield mark_safe("<a class='active' href='{0}'>{1}</a>".format(url, text))
    
                    else:#值未存在
                        id_list.append(pk)
                        # params中被重新赋值
                        _params.setlist(self.option.field_name, id_list)
                        # 创建URL,赋予其被点时,使其产生被选中
                        url = "{0}?{1}".format(self.request.path_info, _params.urlencode())
                        yield mark_safe("<a href='{0}'>{1}</a>".format(url, text))
    
    
    
    
    
    class ChangeList(object):
        '''
        很牛逼的一个类,封装了所有视图函数想要往前端传的内容
        功能:使视图函数中的代码变的简洁
        '''
        def __init__(self,config,queryset):
            self.config=config#stark.py中写了派生类的话就是那个类,没写的话默认就是StarkConfig
            self.list_display=config.get_list_display()
            self.edit_link = config.get_edit_link()
            self.model_class=config.model_class#数据库的表
            self.request=config.request#StarkConfig中默认是None,不过程序运行后就会有
            self.show_add_btn=config.get_show_add_btn()
            # 搜索框
            self.show_search_form = config.get_show_search_form()
            self.search_form_val = config.request.GET.get(config.search_key, '')#搜索关键字“_q”,首次访问网页默认是空
            # 批量操作
            self.actions=config.get_actions()#得到派生类中写的actions的内容[]
            self.show_actions=config.get_show_actions()#操作框
            #组合搜索
            self.show_comb_filter=config.get_show_comb_filter()
            self.comb_filter=config.get_comb_filter()
    
            from utils.pager import Pagination
            #分页器
            current_page = self.request.GET.get('page', 1)#得到传入的page,没有默认为第一页
            total_count = queryset.count()#要显示的数据的量,queryset在视图函数中有数据库操作的赋值
            page_obj = Pagination(current_page, total_count, self.request.path_info, self.request.GET, per_page_count=5)
                                #当前页         数据量        当前url不带问号         ?后面的条件内容      设定的每页显示的数据量条数
            self.page_obj = page_obj#得到最终生成的分页器对象
    
            self.data_list = queryset[page_obj.start:page_obj.end]#得到分页后的数据,用于页面展示
    
        #批量操作
        def modify_actions(self):
            result = []#批量操作内容,默认为空,去派生类中定义
            for func in self.actions:#self.actions=config.get_actions(),默认为空
                temp = {'name':func.__name__,'text':func.short_desc}#name是函数名,text是自加的描述
                result.append(temp)
            return result
    
        def add_url(self):#添加操作的url
            query_str = self.request.GET.urlencode()
            if query_str:
                # 重新构造,用于跳转
                params = QueryDict(mutable=True)
                params[self.config._query_param_key] = query_str
                return self.config.get_add_url()+'?'+params.urlencode()
            return self.config.get_add_url()
    
        def head_list(self):
            #构造表头
            result = []
            # [checkbox,'id','name',edit,del]
            for field_name in self.list_display:
                if isinstance(field_name, str):
                    # 根据类和字段名称,获取字段对象的verbose_name
                    verbose_name = self.model_class._meta.get_field(field_name).verbose_name
                else:
                    verbose_name = field_name(self.config, is_header=True)# 去派生类中执行
                result.append(verbose_name)
            return result
    
        def body_list(self):
            # 处理表中的数据
            data_list = self.data_list#self.data_list = queryset[page_obj.start:page_obj.end]
            new_data_list = []
            for row in data_list:
                # row是 每一条数据对象UserInfo(id=2,name='alex2',age=181)
                temp = []
                for field_name in self.list_display:
                    if isinstance(field_name,str):#派生类中定义的显示字段
                        val = getattr(row,field_name)
                    else:#每个td都拥有的功能,checkbox、edit、delete、
                        val = field_name(self.config,row)
                    # 用于定制编辑列
                    if field_name in self.edit_link:
                        val = self.edit_link_tag(row.pk, val)
                    temp.append(val)
                new_data_list.append(temp)
            return new_data_list
    
        def gen_comb_filter(self):
            #生成器函数
            """
            [
                 FilterRow(((1,'男'),(2,'女'),)),
                 FilterRow([obj,obj,obj,obj ]),
                 FilterRow([obj,obj,obj,obj ]),
            ]
            """
            '''
                    comb_filter = [
                    v1.FilterOption('gender', is_choice=True),#关键字传参,代表是choice
                    v1.FilterOption('depart'),#, 筛选只显示id大于三的部门condition={'id__gt': 3}
                    v1.FilterOption('roles', True),#True传入,代表是多选
                ]
                    '''
            from django.db.models import ForeignKey,ManyToManyField
            for option in self.comb_filter:
                _field = self.model_class._meta.get_field(option.field_name)#字段
                if isinstance(_field,ForeignKey):
                    # 获取当前字段depart,关联的表 Department表并获取其所有数据
                    # print(field_name,_field.rel.to.objects.all())
                    row = FilterRow(option, option.get_queryset(_field), self.request)
                elif isinstance(_field,ManyToManyField):
                    # print(field_name, _field.rel.to.objects.all())
                    # data_list.append(  FilterRow(_field.rel.to.objects.all()) )
                    row = FilterRow(option,option.get_queryset(_field), self.request)
    
                else:
                    # print(field_name,_field.choices)
                    # data_list.append(  FilterRow(_field.choices) )
                    row = FilterRow(option,option.get_choices(_field),self.request)
                # 可迭代对象,迭代详细在FilterRow的__iter__中
                yield row
    
        def edit_link_tag(self,pk,text):
            query_str = self.request.GET.urlencode()  # page=2&nid=1
            params = QueryDict(mutable=True)
            params[self.config._query_param_key] = query_str
            return mark_safe('<a href="%s?%s">%s</a>' % (self.config.get_change_url(pk), params.urlencode(),text,))  # /stark/app01/userinfo/
    
    
    
    
    
    
    
    class StarkConfig(object):
        """
            用于为每个类(即每张表)生成url对应关系,并处理用户请求的基类
        """
        def __init__(self,model_class,site):
            self.model_class=model_class
            self.site=site
            self.request=None
            self._query_param_key='_listfilter'#?后面的条件内容
            self.search_key='_q'#搜索关键字
    
    #####################################定制功能######################################
    #########1 默认每个tr都会拥有的td
        def checkbox(self,obj=None,is_header=False):
            if is_header:
                return '选择'
            return mark_safe('<input type="checkbox" name="pk" value="%s" >' %(obj.id,))
        def edit(self,obj=None,is_header=False):
            if is_header:
                return '编辑操作'
            #url地址栏的搜索条件
            query_str=self.request.GET.urlencode()
            if query_str:
                #重新构造<button class="btn btn-primary"></button>
                params=QueryDict(mutable=True)
                params[self._query_param_key]=query_str
                return mark_safe('<button class="btn btn-primary"><a href="%s?%s" style="color:white;">编辑</a></button>' %(self.get_change_url(obj.id),params.urlencode(),))
            return mark_safe('<button class="btn btn-primary"><a href="%s" style="color:white;">编辑</a></button>' %(self.get_change_url(obj.id),))
        def delete(self,obj=None,is_header=False):
            if is_header:
                return '删除操作'
            query_str = self.request.GET.urlencode()
            if query_str:
                # 重新构造
                params = QueryDict(mutable=True)
                params[self._query_param_key] = query_str
                return mark_safe('<button class="btn btn-danger"><a href="%s?%s" style="color:white;">删除</a></button>' % (self.get_delete_url(obj.id), params.urlencode(),))
    
            return mark_safe('<button class="btn btn-danger"><a href="%s" style="color:white;">删除</a></button>'%(self.get_delete_url(obj.id),) )
    
        list_display=[]
        #得到派生类中自定义的list_display
        def get_list_display(self):
            data=[]
            if self.list_display:#派生类中定义的要显示的字段
                data.extend(self.list_display)#加入到data中
                data.append(StarkConfig.edit)#加入编辑td
                data.append(StarkConfig.delete)#加入删除td
                data.insert(0,StarkConfig.checkbox)#在最前面插一个td
            return data
    
        edit_link=[]
        def get_edit_link(self):
            result=[]
            if self.edit_link:
                result.extend(self.edit_link)
            return result
    
    
    ######### 2是否显示add按钮
        show_add_btn = True  # 默认显示
        def get_show_add_btn(self):
            return self.show_add_btn
    
    #########3 关键字搜索
        show_search_form = False#默认不显示
        def get_show_search_form(self):
            return self.show_search_form
        search_fields = []#关键字默认为空
        def get_search_fields(self):
            result = []
            if self.search_fields:
                result.extend(self.search_fields)#派生类中自定义的关键字
            return result
    
        def get_search_condition(self):
            key_word = self.request.GET.get(self.search_key)#'_q'
            search_fields = self.get_search_fields()#关键字
            condition = Q()#创建Q对象用于与或
            condition.connector = 'or'#搜索条件之间用或连接
            if key_word and self.get_show_search_form():
                for field_name in search_fields:
                    condition.children.append((field_name, key_word))
            return condition
    #############4 actions
        show_actions = False#默认不显示
        def get_show_actions(self):
            return self.show_actions
    
        actions = []#默认批量操作内容为空
        def get_actions(self):
            result = []
            if self.actions:
                result.extend(self.actions)#加入派生类中自定制的批量操作
            return result
    
    
    #############5 组合搜索
        show_comb_filter = False
        def get_show_comb_filter(self):
            return self.show_comb_filter
    
        comb_filter=[]#默认为空
        def get_comb_filter(self):
            result=[]
            if self.comb_filter:
                result.extend(self.comb_filter)#得到派生类中的条件删选
            return result
    
    #############6排序
        order_by = []
        def get_order_by(self):
            result = []
            result.extend(self.order_by)
            return result
    
    
    
    
    ##################################访问相应网址时需要作数据处理的视图函数##########################
        # 默认列表页面
        def changelist_view(self, request,*args, **kwargs):
            #分页,已改写到ChangeList类中
            # from utils.pager import Pagination
            # current_page=request.GET.get('page',1)
            # total_count=self.model_class.objects.all().count()
            # page_obj=Pagination(current_page,total_count,request.path_info,request.GET,per_page_count=4)
    
            if request.method=='GET':
                comb_condition = {}#筛选条件默认为空
                option_list = self.get_comb_filter()#拿到派生类中定制的筛选条件
                for key in request.GET.keys():#?后面的键
                    value_list = request.GET.getlist(key)#拿到键对应的值[1,2,3]
                    flag = False
                    for option in option_list:#option是每一个删选条件
                        if option.field_name == key:#该条件已存在于地址栏
                            flag = True
                            break
                    if flag:
                        #comb_condition = {"id__in":[1,2,3].......}
                        comb_condition["%s__in" % key] = value_list
    
    
                # 带搜索条件的数据,没有搜索条件的话就是全部数据。有筛选条件的话,还得处理成筛选后的数据
                queryset=self.model_class.objects.filter(self.get_search_condition()).filter(**comb_condition).distinct()
    
                the_list=ChangeList(self,queryset)#封装好要向前端传的值
                return render(request, 'stark/changelist.html', {'the_list':the_list})
            elif request.method=='POST' and self.get_show_actions():#批量操作
                func_name_str = request.POST.get('list_action')#前端传的操作name
                action_func = getattr(self, func_name_str)#反射,得到处理的方式
                ret = action_func(request)
                if ret:
                    return ret
    
        # 一劳永逸的modelform
        model_form_class = None
        def get_model_form_class(self):
            if self.model_form_class:
                return self.model_form_class
            from django.forms import ModelForm
            # class TestModelForm(ModelForm):
            #     class Meta:
            #         model = self.model_class
            #         fields = "__all__"
            #
            #         error_messages = {
            #             "__all__":{
            #
            #                   },
            #         'email': {
            #         'required': '',
            #         'invalid': '邮箱格式错误..',
            #         }
            #         }
            # type创建TestModelForm类
            meta = type('Meta', (object,), {'model': self.model_class, 'fields': '__all__'})
            TestModelForm = type('TestModelForm', (ModelForm,), {'Meta': meta})
            return TestModelForm
    
        #
        def add_view(self, request, *args, **kwargs):
            # 添加页面
            model_form_class = self.get_model_form_class()#根据modelform生成input
            _popbackid = request.GET.get('_popbackid')#临时需要添加的外键字段所对应的输入框的id
            if request.method == 'GET':
                form = model_form_class()
            else:
                form = model_form_class(request.POST)
                if form.is_valid():
                    new_obj=form.save()
                    if _popbackid:
                        # 判断是否是来源于popup请求
                        # render一个页面,写自执行函数
                        # popUp('/stark/crm/userinfo/add/?_popbackid=id_consultant&model_name=customer&related_name=consultant')
                        from django.db.models.fields.reverse_related import ManyToOneRel, ManyToManyRel
                        result = {'status': False, 'id': None, 'text': None, 'popbackid': _popbackid}
    
                        model_name = request.GET.get('model_name')  # customer
                        related_name = request.GET.get('related_name')  # consultant, "None"
                        for related_object in new_obj._meta.related_objects:#关联表的对象
                            _model_name = related_object.field.model._meta.model_name
                            _related_name = related_object.related_name
                            # 判断外键关联字段是否是主键id
                            if (type(related_object) == ManyToOneRel):
                                _field_name = related_object.field_name
                            else:
                                _field_name = 'pk'
                            _limit_choices_to = related_object.limit_choices_to
                            if model_name == _model_name and related_name == str(_related_name):
                                is_exists = self.model_class.objects.filter(**_limit_choices_to, pk=new_obj.pk).exists()
                                if is_exists:
                                    # 如果新创建的用户是可查看的人,页面才增加
                                    # 分门别类做判断:
                                    result['status'] = True
                                    result['text'] = str(new_obj)
                                    result['id'] = getattr(new_obj, _field_name)
                                    return render(request, 'stark/popup_response.html',
                                                  {'json_result': json.dumps(result, ensure_ascii=False)})
                        return render(request, 'stark/popup_response.html',
                                      {'json_result': json.dumps(result, ensure_ascii=False)})
                    else:
                        list_query_str = request.GET.get(self._query_param_key)
                        list_url = '%s?%s' % (self.get_list_url(), list_query_str,)
    
                        return redirect(list_url)
                        # return redirect(self.get_list_url())
            return render(request, 'stark/add_view.html', {'form': form, 'config': self})
    
    
        #
        def delete_view(self, request, nid,*args, **kwargs):
            self.model_class.objects.filter(pk=nid).delete()
            list_query_str = request.GET.get(self._query_param_key)
            list_url = '%s?%s' % (self.get_list_url(), list_query_str,)
            return redirect(list_url)
        #
        def change_view(self, request, nid,*args, **kwargs):
            # self.model_class.objects.filter(id=nid)
            obj = self.model_class.objects.filter(pk=nid).first()
            print(obj)
            if not obj:
                return redirect(self.get_list_url())
            model_form_class = self.get_model_form_class()
            _popbackid = request.GET.get('_popbackid')#临时需要添加的外键字段所对应的输入框的id
            # GET,显示标签+默认值
            if request.method == 'GET':
                form = model_form_class(instance=obj)
                return render(request, 'stark/change_view.html', {'form': form,'config': self})
            else:
                form = model_form_class(instance=obj, data=request.POST)
                if form.is_valid():
                    form.save()
                    list_query_str=request.GET.get(self._query_param_key)
                    list_url='%s?%s'%(self.get_list_url(),list_query_str,)
    
                    return redirect(list_url)
                return render(request, 'stark/change_view.html', {'form': form})
    
    
    
    ############################反向生成url##########################################
        def get_change_url(self, nid):
            name = 'stark:%s_%s_change' % (self.model_class._meta.app_label, self.model_class._meta.model_name)
            edit_url = reverse(name, args=(nid,))
            return edit_url
    
        def get_list_url(self):
            name = 'stark:%s_%s_changelist' % (self.model_class._meta.app_label, self.model_class._meta.model_name)
            edit_url = reverse(name)
            return edit_url
    
        def get_add_url(self):
            name = 'stark:%s_%s_add' % (self.model_class._meta.app_label, self.model_class._meta.model_name)
            edit_url = reverse(name)
            return edit_url
    
        def get_delete_url(self, nid):
            name = 'stark:%s_%s_delete' % (self.model_class._meta.app_label, self.model_class._meta.model_name)
            edit_url = reverse(name, args=(nid,))
            return edit_url
    
    
    ##########################################################################################################
        #装饰器,为了传参数request
        def wrap(self,view_func):
            def inner(request,*args,**kwargs):
                self.request=request
                return view_func(request,*args,**kwargs)
            return inner
    
        def get_urls(self):#第五步
            app_model_name=(self.model_class._meta.app_label,self.model_class._meta.model_name,)#元祖(app名,表名)
            url_patterns=[
                url(r'^$',self.wrap(self.changelist_view),name='%s_%s_changelist'%app_model_name),
                url(r'^add/$',self.wrap(self.add_view),name='%s_%s_add'%app_model_name),
                url(r'^(d+)/delete/$',self.wrap(self.delete_view),name='%s_%s_delete'%app_model_name),
                url(r'^(d+)/change/$',self.wrap(self.change_view),name='%s_%s_change'%app_model_name),
            ]
            url_patterns.extend(self.extra_url())#除增删改查外,想要自定义的新增的url
            return url_patterns#最后就得到了需要用到的一堆url
        def extra_url(self):
            return []
    #############################################################################################
        @property
        def urls(self):#第四步
            return self.get_urls()
    
    
    ########传说中类与类之间的分界线############################################################################
    class StarkSite(object):
        '''
        单例模式创建的对象的类,是一个容器,用于放置处理请求对应关系
        {model.UserInfo:StarkConfig(model.UserInfo,self)}
        '''
        def __init__(self):
            self._registry = {}
    
        def register(self,model_class,stark_config_class=None):
            if not stark_config_class:
                #stark_config_class即29,没写那个派生类的时候默认给予StarkConfig
                stark_config_class=StarkConfig
            self._registry[model_class]=stark_config_class(model_class,self)
            #表名:stark_config_class(表名,self)
    
        def get_urls(self):#第三步,给url
            url_pattern=[]
            for model_class,stark_config_obj in self._registry.items():#去字典里取值
                app_name=model_class._meta.app_label#app名
                model_name=model_class._meta.model_name#表名
                curd_url=url(r'^%s/%s/'%(app_name,model_name),(stark_config_obj.urls,None,None))
                #拼接生成url,需执行stark_config_obj.urls———第四步
                url_pattern.append(curd_url)
            return url_pattern
    
        @property
        def urls(self):#第二步,要url
            return (self.get_urls(),None,'stark')
    
    
    
    site=StarkSite()#第一步,单例模式
    v1.py
  • 相关阅读:
    LCM与GCD算法
    LCS,LIS,LICS算法
    高精度算法(C/C++)
    SystemTap
    VMware15下解决Ubuntu18.04没有网络连接问题
    Git ssh-key 配置问题
    Ubuntu18.04更换国内源
    sql 错误日志存储路径设置
    资源
    System.Data.DataTable 基本方法
  • 原文地址:https://www.cnblogs.com/zhuminghui/p/8494645.html
Copyright © 2011-2022 走看看