批量操作与搜索
批量操作
在页面上展示下拉列表供用户选择,而且该下拉选项是可定制的
service\stark.py
StarkConfig()类
class StarkConfig(object): action_list = [] # 存放下拉菜单项 # 定义方法返回下拉列表,这里的self对应的是某个类,即可实现自定义 def get_action_list(self): val = [] val.extend(self.action_list) return val #返回下拉列表信息,这里key用函数名,用于后面判断请求是否合法 def get_action_dict(self): val = {} for item in self.get_action_list(): val[item.__name__] = item return val action_list = self.get_action_list() #将函数以字典列表的方式进行处理,以供模板渲染 action_list = [{"name": func.__name__, 'text': func.text} for func in action_list] #列表展示视图 def changelist_view(self, request): """ 所有URL的查看页面 :param request: :return: """ if request.method == 'POST': action_name = request.POST.get('action') if action_name not in self.get_action_dict(): return HttpResponse("非法请求") # 这个response可以自定义返回内容 responst = getattr(self, action_name)(request) if responst: return responst return render(request, 'stark/changelist.html', {'header_list': header_list, 'data_list': data_list, 'addbtn': self.get_add_btn(), 'action_list': action_list,'search_list':search_list}, )
app01\stark.py中自定义下拉选项
class UserinfoConf(StarkConfig): #自定义下拉列表函数进行初始化数据 def multi_init(self,request): return HttpResponse("自定义初始化") #这个text是下拉列表中显示的名称 multi_init.text = '批量初始化' #StarkConfig.multi_delete使用的父类中批量删除方法,也可以自定义。 action_list =[StarkConfig.multi_delete,multi_init]
注册自定义配置类
site.register(models.UserInfo,UserinfoConf)
自定义搜索字段
根据自定义搜索字段进行搜索展示,这里使用到django的Q查询
service\stark.py
class StarkConfig(object): search_list = [] # 搜索字段 #获取搜索字段,子类可以对search_list重写,重写后这里获取到重写内容 def get_search_list(self): """ :return:搜索字段列表 """ val=[] val.extend(self.search_list) return val # 数据展示视图 def changelist_view(self, request): """ 所有URL的查看页面 :param request: :return: """ # 处理搜索 search_list = self.get_search_list() q = request.GET.get('q')#这里的q是前台input传过来的参数 conn=Q() #查询通过“或”条件进行连接 conn.connector='OR' if q:#前台页面有查询条件 #通过append方法构建查询条件 for field in search_list: conn.children.append(('%s__contains' % field,q)) #将查询条件添加到filter中进行过滤 query_set = self.model_class.objects.filter(conn).order_by(*self.get_order_by()) return render(request, 'stark/changelist.html', {'header_list': header_list, 'data_list': data_list, 'addbtn': self.get_add_btn(), 'action_list': action_list,'search_list':search_list}, )
app01/stark.py
class DepartmentConfig(StarkConfig): #这里自定义查询参数,注意外键字段需要用“__”来连接,这样就是外键字段__被连接表字段 search_list = ['name', 'tel', 'user__name']
前台页面代码,展示具体数据以及数据操作按钮
{% extends 'stark/layout.html' %} {% block content %} <div style="margin: 8px 0"> {% if addbtn %} {{ addbtn }} {% endif %} </div> {% if search_list %} <div style="float: right;padding: 5px"> <form action="" method="get" class="form-inline"> <div class="form-group"> <input type="text" name="q" class="form-control"> <button type="submit" class="btn btn-default"> <i class="fa fa-search" aria-hidden="true"></i> </button> </div> </form> </div> {% endif %} <form action="" class="form-inline" method="post"> {% csrf_token %} {% if action_list %} <div class="form-group" style="padding: 5px;"> <select name="action" class="form-control"> <option>功能选择</option> {% for item in action_list %} <option value="{{ item.name }}">{{ item.text }}</option> {% endfor %} </select> <button type="submit" class="btn btn-default">执行</button> </div> {% endif %} <table class="table table-bordered"> <thead> <tr> {% for header in header_list %} <th>{{ header }}</th> {% endfor %} </tr> </thead> <tbody> {% for row in data_list %} <tr> {% for col in row %} <td>{{ col }}</td> {% endfor %} </tr> {% endfor %} </tbody> </table> </form> {% endblock %}
扩展搜索功能:URL跳转保留搜索条件
关键点:
- reversion函数中修改url地址,给添加上请求条件,通过http.QueryDict
def reverse_change_url(self, row): info = (self.site.namespace, self.model_class._meta.app_label, self.model_class._meta.model_name) edit_url = reverse('%s:%s_%s_change' % info, args=(row.pk,)) if not self.request.GET: return edit_url param_str = self.request.GET.urlencode() new_query_dict = QueryDict(mutable=True) new_query_dict['_filter'] = param_str edit_url = '%s?%s' % (edit_url, new_query_dict.urlencode(),) return edit_url
- 利用内部全局请求钩子函数,将请求进来的request保存到类中,在其他函数中就可以调用request
# 自定义内部钩子装饰器,让所有url进来都走这个函数,就可以批量添加功能 def wrapper(self, func): @wraps(func) # 保留原函数信息 def inner(request,*args, **kwargs): self.request=request return func(request,*args, **kwargs) return inner
service\stark.py
from functools import wraps from django.urls import re_path from django.shortcuts import HttpResponse, render, reverse from django.utils.safestring import mark_safe from django.forms import ModelForm from django.shortcuts import redirect from django.db.models import Q from django.http import QueryDict from types import FunctionType class StarkConfig(object): order_by = [] # 子类中重写该静态属性即可改变排序方法,默认按pk升序排列 list_display = [] # 类的静态字段提供需要展示的列 model_form_class = None action_list = [] # 存放下拉菜单项 search_list = [] # 搜索字段 def __init__(self, model_class): self.model_class = model_class self.site = site self.request=None self.back_condition_key='_filter' # 批量删除方法,可自定义 def multi_delete(self, request): """ 批量删除 :param request: :return: """ pk_list = request.POST.get('pk') try: self.model_class.objects.filter(pk__in=pk_list).delete() return HttpResponse("删除成功!") except Exception as e: return HttpResponse(e.msg) multi_delete.text = '批量删除' # 批量初始化方法,可自定义 def multi_init(self, request): print('批量初始化') multi_init.text = '批量初始化' # 显示行头的checkbox def display_checkbox(self, row=None, header=False): if header: # 表头行 return mark_safe("<input type='checkbox' name='pk'") else: # 数据行 return mark_safe("<input type='checkbox' name='pk' value='%s'>" % row.pk) # 显示行末的编辑按钮 def display_edit(self, row=None, header=False): if header: return "编辑" else: return mark_safe( "<a href='%s'><i class='fa fa-edit' aria-hidden='true'></i></a>" % self.reverse_change_url(row)) # 显示行末删除按钮 def display_del(self, row=None, header=False): if header: return "删除" else: return mark_safe( "<a href='%s'><i class='fa fa-trash-o' aria-hidden='true'></i></a>" % self.reverse_del_url(row)) # 行末显示删除和编辑按钮 def display_edit_del(self, row=None, header=False): if header: return "操作" else: tpl = "<a href='%s'><i class='fa fa-edit' aria-hidden='true'></i></a> | <a href='%s'><i class='fa fa-trash-o' aria-hidden='true'></i></a>" % ( self.reverse_change_url(row), self.reverse_del_url(row)) return mark_safe(tpl) # 获取添加按钮,可定制 def get_add_btn(self): # 有添加权限,返回按钮代码 if True: return mark_safe('<a href="%s" class="btn btn-success">添加</a>' % self.reverse_add_url()) else: return None # 获取排序列表,可定制 def get_order_by(self): # 用该方法来预留钩子 return self.order_by # 获取需要展示的字段列表,可定制 def get_list_display(self): # 用该方法来预留钩子,后期可添加功能,如根据角色来定义展示的内容 return self.list_display # 获取ModelFormClass,可定制 def get_model_form_class(self): if self.model_form_class: return self.model_form_class class AddModelForm(ModelForm): class Meta: model = self.model_class fields = "__all__" return AddModelForm # 创建类的方法,获取该类相关urls,这里不同的app进来对应的self就不一样 def get_urls(self): info = self.model_class._meta.app_label, self.model_class._meta.model_name urlpatterns = [ re_path(r'^list/$', self.wrapper(self.list_view), name='%s_%s_list' % info), re_path(r'^add/$', self.wrapper(self.add_view), name='%s_%s_add' % info), re_path(r'^(?P<pk>\d+)/change/$', self.wrapper(self.change_view), name='%s_%s_change' % info), re_path(r'^(?P<pk>\d+)/del/$', self.wrapper(self.delete_view), name='%s_%s_del' % info), ] extra = self.extra_url() if extra: # 扩展urls urlpatterns.append(extra) return urlpatterns # 返回下拉列表 def get_action_list(self): val = [] val.extend(self.action_list) return val def get_action_dict(self): val = {} for item in self.get_action_list(): val[item.__name__] = item return val #获取搜索字段 def get_search_list(self): """ :return:搜索字段列表 """ val=[] val.extend(self.search_list) return val # def get_search_condition(self,request): search_list = self.get_search_list() q = request.GET.get('q',"") conn = Q() conn.connector = 'OR' if q: for field in search_list: conn.children.append(('%s__contains' % field, q)) return search_list,q,conn # 生成删除链接 def reverse_del_url(self, row): info = (self.site.namespace, self.model_class._meta.app_label, self.model_class._meta.model_name) del_url = reverse('%s:%s_%s_del' % info, args=(row.pk,)) if not self.request.GET: return del_url param_str = self.request.GET.urlencode() new_query_dict = QueryDict(mutable=True) new_query_dict[self.back_condition_key] = param_str del_url = '%s?%s' % (del_url, new_query_dict.urlencode(),) return del_url # 生成添加连接 def reverse_add_url(self): info = (self.site.namespace, self.model_class._meta.app_label, self.model_class._meta.model_name) add_url= reverse("%s:%s_%s_add" % info) if not self.request.GET: return add_url param_str=self.request.GET.urlencode() new_query_dict=QueryDict(mutable=True) new_query_dict[self.back_condition_key]=param_str add_url='%s?%s' % (add_url,new_query_dict.urlencode(),) return add_url # 生成列表展示连接 def reverse_list_url(self): info = (self.site.namespace, self.model_class._meta.app_label, self.model_class._meta.model_name) list_url = reverse("%s:%s_%s_list" % info) origin_condition=self.request.GET.get(self.back_condition_key) if not origin_condition: return list_url list_url="%s?%s" % (list_url,origin_condition) return list_url # 生成修改连接 def reverse_change_url(self, row): info = (self.site.namespace, self.model_class._meta.app_label, self.model_class._meta.model_name) edit_url = reverse('%s:%s_%s_change' % info, args=(row.pk,)) if not self.request.GET: return edit_url param_str = self.request.GET.urlencode() new_query_dict = QueryDict(mutable=True) new_query_dict[self.back_condition_key] = param_str edit_url = '%s?%s' % (edit_url, new_query_dict.urlencode(),) return edit_url # 数据展示视图 def list_view(self, request): """ 所有URL的查看页面 :param request: :return: """ if request.method == 'POST': action_name = request.POST.get('action') if action_name not in self.get_action_dict(): return HttpResponse("非法请求") # 这个response可以自定义返回内容 responst = getattr(self, action_name)(request) if responst: return responst list_display = self.get_list_display() header_list = [] if list_display: for name_or_func in list_display: if isinstance(name_or_func, FunctionType): verbose_name = name_or_func(self, header=True) else: verbose_name = self.model_class._meta.get_field(name_or_func).verbose_name header_list.append(verbose_name) else: header_list.append(self.model_class._meta.model_name) # 处理搜索 search_list,q,conn=self.get_search_condition(request) query_set = self.model_class.objects.filter(conn).order_by(*self.get_order_by()) ####这里表格数据处理应该用yield和inclusion_tag重写### data_list = [] for row in query_set: row_list = [] if not list_display: # 用户没有指定需要展示的字段 row_list.append(row) data_list.append(row_list) continue for name_or_func in list_display: if isinstance(name_or_func, FunctionType): # 行头展示 col = name_or_func(self, row=row) else: col = getattr(row, name_or_func) row_list.append(col) data_list.append(row_list) ###下拉选项框#### action_list = self.get_action_list() action_list = [{"name": func.__name__, 'text': func.text} for func in action_list] return render(request, 'stark/changelist.html', {'header_list': header_list, 'data_list': data_list, 'addbtn': self.get_add_btn(), 'action_list': action_list,'search_list':search_list,'q':q}, ) # 数据添加视图 def add_view(self, request): """ 所有添加页面都在此处理 :param request: :return: """ if request.method == 'GET': form = self.get_model_form_class()() return render(request, 'stark/add.html', {'form': form}) form = self.get_model_form_class()(request.POST) if form.is_valid(): form.save() return redirect(self.reverse_list_url()) # 数据修改视图 def change_view(self, request, pk): """ 所有页面编辑函数 :param request: :param pk: :return: """ obj = self.model_class.objects.filter(pk=pk).first() if not obj: return HttpResponse("数据不存在") ModelFormClass = self.get_model_form_class() if request.method == 'GET': form = ModelFormClass(instance=obj) return render(request, 'stark/change.html', {'form': form}) form = ModelFormClass(data=request.POST, instance=obj) if form.is_valid(): form.save() return redirect(self.reverse_list_url()) return render(request, 'stark/change.html', {'form': form}) # 数据删除视图 def delete_view(self, request, pk): """ 所有删除页面 :param request: :param pk: :return: """ if request.method == 'GET': return render(request, 'stark/delete.html', {'cancel_url': self.reverse_list_url()}) if request.method == 'POST': obj = self.model_class.objects.filter(pk=pk).delete() return redirect(self.reverse_list_url()) # 自定义内部钩子装饰器,让所有url进来都走这个函数,就可以批量添加功能 def wrapper(self, func): @wraps(func) # 保留原函数信息 def inner(request,*args, **kwargs): self.request=request return func(request,*args, **kwargs) return inner # 钩子函数,可以在子类中自定义该方法,对urls进行扩展 def extra_url(self): pass @property # 该装饰器使调用该方法时直接用函数名即可,无需加括号 def urls(self): return self.get_urls() # 自定义注册类 class AdminSite(object): def __init__(self): self._registry = {} # 用于存储注册进来的类,key是类,值是StarkConfig或自定义的StarkConfig的子类 self.app_name = 'stark' self.namespace = 'stark' # 函数注册方法 def register(self, models_class, stark_config=None): if not stark_config: stark_config = StarkConfig self._registry[models_class] = stark_config(models_class) # 动态创建urls def get_urls(self): urlpatterns = [] for k, v in self._registry.items(): # 对注册的类进行循环,创建urls app_label = k._meta.app_label model_name = k._meta.model_name # v.urls是StarkConfig或自定义的StarkConfig的子类的urls,对路由进行再分发 urlpatterns.append(re_path(r"^%s/%s/" % (app_label, model_name,), (v.urls, None, None))) return urlpatterns @property def urls(self): # 返回一个元祖,这里返回内容就和include()方法返回的内容一样 return self.get_urls(), self.app_name, self.namespace # 实例化注册类,需要注册的时候,导入该实例对象即可,大家操作的都是同一个实例对象 site = AdminSite()
app01\stark.py
from app01 import models from django.urls import re_path from django.shortcuts import HttpResponse,render,redirect from stark.service.stark import site,StarkConfig def test(request): return HttpResponse("test page") class UserinfoConf(StarkConfig): order_by = ['-id']#按ID倒叙排列 list_display = [StarkConfig.display_checkbox,'id','name','password','email',StarkConfig.display_edit_del] search_list = [] #批量操作自定义 def multi_init(self,request): return HttpResponse("自定义初始化") multi_init.text = '批量初始化' action_list =[StarkConfig.multi_delete,multi_init] #扩展URL def extra_url(self): return re_path(r'test/',test) class DepartmentConfig(StarkConfig): list_display = [StarkConfig.display_checkbox,'id','name','tel','user',StarkConfig.display_edit_del] search_list = ['name', 'tel', 'user__name'] action_list = [StarkConfig.multi_delete,StarkConfig.multi_init] site.register(models.UserInfo,UserinfoConf) site.register(models.Department,DepartmentConfig)