可快速生成增删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, width: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;width: 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;width: 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

 

posted @ 2018-03-02 18:32  ''竹先森゜  阅读(2702)  评论(0编辑  收藏  举报