Stark 组件:快速开发神器 —— 锦上添花
Stark 组件:快速开发神器 —— 锦上添花
- 一、分页
- 二、排序
- 三、搜索
- 1.关键字搜索
- 2.组合搜索
- 四、批量操作
经过前面几个篇章,我们的 Stark 组件已经能够批量生成 URL,快速实现增删改查了,接下来这篇文章,就要对已经完成的 Stark 组件增加更多更有趣的功能。
一、分页
Django 其实有自带的分页器功能,但是呢,我们是想开发一套可以随处使用的组件,因此可以仿照 Django 的分页器自己写一个。
# Stark/utils/pagingDevice.py
class PagingDevice:def __init__(self, currentPage, allCount, baseUrl, queryParams, perPage=20, pageCount=11):"""初始化分页器:param currentPage:当前页码:param allCount: 数据库中总条数:param baseUrl: 基础 URL:param queryParams: 包含所有当前 URL 的 QueryDict 对象:param perPage: 每页显示数据量:param pageCount: 页面最多显示页码数"""try:self.currentPage = int(currentPage)if self.currentPage <= 0:raise Exception()except Exception:self.currentPage = 1self.allCount, self.baseUrl, self.queryParams, self.perPage, self.pageCount = \allCount, baseUrl, queryParams, perPage, pageCount# 计算总页码,如果有余要+1pagerCount, remainder = divmod(allCount, perPage)if remainder:pagerCount += 1self.pagerCount = pagerCountself.halfPagerPageCount = int(pagerCount / 2)@propertydef start(self):"""数据起始索引"""return (self.currentPage - 1) * self.perPage@propertydef end(self):"""数据结束索引"""return self.currentPage * self.perPagedef PageHtml(self):"""生成分页器 HTML 代码"""if self.pagerCount < self.pageCount:# 如果数据总页码 < 页面上最多显示的页码数pagerStart, pagerEnd = 1, self.pagerCountelse:# 数据总页码数 > 页面上最多显示的页码数if self.currentPage <= self.halfPagerPageCount:# 如果当前页码 < 页面上最大显示的页码半数pagerStart, pagerEnd = 1, self.pagerCountelse:if (self.currentPage + self.halfPagerPageCount) > self.pagerCount:# 如果当前页 + 页面上最大显示的页码半数 > 数据总页码pagerStart, pagerEnd = self.pagerCount - self.pageCount + 1, self.pageCountelse:pagerStart, pagerEnd = self.currentPage - self.halfPagerPageCount, self.currentPage + self.halfPagerPageCountpageList = []if self.currentPage <= 1:previousPage = '<li><a href="#">上一页</a></li>'else:self.queryParams["page"] = self.currentPage - 1previousPage = '<li><a href="%s?%s">上一页</a></li>' % (self.baseUrl, self.queryParams.urlencode())pageList.append(previousPage)for index in range(pagerStart, pagerEnd - 1):self.queryParams["page"] = indexif self.currentPage == index:item = '<li class="active"><a href="%s?%s">%s</a></li>' % (self.baseUrl, self.queryParams.urlencode(), index)else:item = '<li><a href="%s?%s">%s</a></li>' % (self.baseUrl, self.queryParams.urlencode(), index)pageList.append(item)if self.currentPage >= self.pagerCount:nextPage = '<li><a href="#">下一页</a></li>'else:self.queryParams["page"] = self.currentPage + 1nextPage = '<li><a href="%s?%s">下一页</a></li>' % (self.baseUrl, self.queryParams.urlencode())pageList.append(nextPage)pageStr = "".join(pageList)return pageStr
然后在 checkView 视图函数中添加上我们的分页器:
def checkView(self, request, *args, **kwargs):"""查看功能视图函数"""# --------------------- 1.显示表格 ---------------------displayList = self.getDisplayList(request, *args, **kwargs)# 1.1、处理表格表头headerList = []if displayList:for item in displayList:verboseName = item(self, obj=None, isHeader=True) if isinstance(item, FunctionType) \else self.model._meta.get_field(item).verbose_nameheaderList.append(verboseName)else:headerList.append(self.model._meta.model_name)# 1.2、处理表格内容bodyList = []dataQuerySet = self.model.objects.all()for row in dataQuerySet:rowList = []if displayList:for item in displayList:rowList.append(item(self, obj=row, isHeader=False, *args, **kwargs) if isinstance(item, FunctionType)else getattr(row, item))else:rowList.append(row)bodyList.append(rowList)# --------------------- 2.添加按钮 ---------------------addButton = self.getAddButton(request, *args, **kwargs)# --------------------- 3.分页器 ---------------------allCount = dataQuerySet.count()queryParams = request.GET.copy()queryParams._mutable = TruepagingDevice = PagingDevice(currentPage=request.GET.get("page"),allCount=allCount,baseUrl=request.path_info,queryParams=queryParams,perPage=self.perPageCount)bodyList = bodyList[pagingDevice.start:pagingDevice.end]return render(request, "stark/checkView.html", {"headerList": headerList,"bodyList": bodyList,"dataQuerySet": dataQuerySet,"addButton": addButton,"pager": pagingDevice,})
然后在 checkView.html 中加上分页器:
<nav class="center"><ul class="pagination">{{ pager.pageHtml|safe }}</ul></nav>
这样分页器就做好了:
二、排序
我们现在是按照默认的 ID 排序,但有的时候可能我们不想按照它排序,因此可以定制一个排序功能。
def getOrderList(self):"""获取页面排序的表格,预留自定义扩展定制排序选项"""return self.orderList or ["-id"]
因为排序要在获取数据库数据的时候进行,因此应该将排序放在显示表格之前:
def checkView(self, request, *args, **kwargs):"""查看功能视图函数"""dataQuerySet = self.model.objects.all()# --------------------- 4.排序 ---------------------orderList = self.getOrderList()dataQuerySet = dataQuerySet.order_by(*orderList)# --------------------- 1.显示表格 ---------------------displayList = self.getDisplayList(request, *args, **kwargs)# 1.1、处理表格表头headerList = []if displayList:for item in displayList:verboseName = item(self, obj=None, isHeader=True) if isinstance(item, FunctionType) \else self.model._meta.get_field(item).verbose_nameheaderList.append(verboseName)else:headerList.append(self.model._meta.model_name)# 1.2、处理表格内容bodyList = []for row in dataQuerySet:rowList = []if displayList:for item in displayList:rowList.append(item(self, obj=row, isHeader=False, *args, **kwargs) if isinstance(item, FunctionType)else getattr(row, item))else:rowList.append(row)bodyList.append(rowList)# --------------------- 2.添加按钮 ---------------------addButton = self.getAddButton(request, *args, **kwargs)# --------------------- 3.分页器 ---------------------allCount = dataQuerySet.count()queryParams = request.GET.copy()queryParams._mutable = TruepagingDevice = PagingDevice(currentPage=request.GET.get("page"),allCount=allCount,baseUrl=request.path_info,queryParams=queryParams,perPage=self.perPageCount)bodyList = bodyList[pagingDevice.start:pagingDevice.end]return render(request, "stark/checkView.html", {"headerList": headerList,"bodyList": bodyList,"dataQuerySet": dataQuerySet,"addButton": addButton,"pager": pagingDevice,})
这样就能实现对表格按照 ID 逆序排列:
如果想要按照其它方式排列,在子类中重写 orderList 即可。
三、搜索
1.关键字搜索
搜索功能可以帮助我们快速筛选目标数据,因此,我们的 Stark 组件当然不能少了关键字搜索了。
def getSearchList(self):"""获取搜索列表"""return self.searchList
def changeView(self, request, pk, *args, **kwargs):"""修改功能视图函数"""form = NonecurrentChangeObject = self.model.objects.filter(pk=pk).first()if not currentChangeObject:return render(request, "404.html")modelForm = self.getModelForm(isAdd=False, request=request, pk=pk, *args, **kwargs)if request.method == "GET":form = modelForm(instance=currentChangeObject)elif request.method == "POST":form = modelForm(data=request.POST, instance=currentChangeObject)if form.is_valid():return HttpResponse(self.save(request=request, form=form, isUpdate=True, *args, **kwargs)) \or redirect(self.reverseListUrl(*args, **kwargs))return render(request, self.changeTemplate or "stark/addOrChange.html", {"form": form})def checkView(self, request, *args, **kwargs):"""查看功能视图函数"""dataQuerySet = self.model.objects.all()# --------------------- 5.搜索 ---------------------searchList = self.getSearchList()if searchList:searchValue = request.GET.get("keyword", None)connect = Q()connect.connector = "OR"if searchValue:for item in searchList:connect.children.append((item, searchValue))dataQuerySet = dataQuerySet.filter(connect)# --------------------- 4.排序 ---------------------orderList = self.getOrderList()if orderList:dataQuerySet = dataQuerySet.order_by(*orderList)# --------------------- 1.显示表格 ---------------------displayList = self.getDisplayList(request, *args, **kwargs)# 1.1、处理表格表头headerList = []if displayList:for item in displayList:verboseName = item(self, obj=None, isHeader=True) if isinstance(item, FunctionType) \else self.model._meta.get_field(item).verbose_nameheaderList.append(verboseName)else:headerList.append(self.model._meta.model_name)# 1.2、处理表格内容bodyList = []for row in dataQuerySet:rowList = []if displayList:for item in displayList:rowList.append(item(self, obj=row, isHeader=False, *args, **kwargs) if isinstance(item, FunctionType)else getattr(row, item))else:rowList.append(row)bodyList.append(rowList)# --------------------- 2.添加按钮 ---------------------addButton = self.getAddButton(request, *args, **kwargs)# --------------------- 3.分页器 ---------------------allCount = dataQuerySet.count()queryParams = request.GET.copy()queryParams._mutable = TruepagingDevice = PagingDevice(currentPage=request.GET.get("page"),allCount=allCount,baseUrl=request.path_info,queryParams=queryParams,perPage=self.perPageCount)bodyList = bodyList[pagingDevice.start:pagingDevice.end]return render(request, "stark/checkView.html", {"addButton": addButton,"searchList": searchList,"headerList": headerList,"bodyList": bodyList,"dataQuerySet": dataQuerySet,"pager": pagingDevice,})
然后在 RbacUserHandler 中定义可搜索的字段:
from Stark.main import StarkHandler, getM2M, getDatetime, getChoiceclass RbacUserHandler(StarkHandler):def __init__(self, site, modelClass, prefix):super().__init__(site, modelClass, prefix)self.displayList = ["username", "email", "score", getChoice("等级", "grade"), getM2M("职务", "roles"), "team","department", getDatetime("加入时间", "dateJoined")]self.searchList = ["username"]
最后在 checkView 页面也要加上搜索框:
{% if searchList %}<div style="float: right;margin: 5px 0;"><form method="GET" class="form-inline"><div class="form-group"><label><input class="form-control" type="text" name="keyword" value="{{ search_value }}"placeholder="关键字搜索"></label><button class="btn btn-primary" type="submit"><i class="fa fa-search" aria-hidden="true"></i></button></div></form></div>{% endif %}
2.组合搜索
通过关键字搜索只能实现单一的搜索功能,我们有时想要的可以能是多个条件筛选的组合搜索结果。
对于组合搜索来说情况比较多,比如好几个字段都支持组合搜搜,还有某一个字段支持多选,类似这些功能比较复杂,因此我们可以将组合搜索的功能抽象成一个类。
可以先写 getSearchGroup 方法,通过这个方法获取组合搜索的条件:
def getSearchGroup(self, request):"""获取组合搜索条件"""condition = {}for option in self.searchGroup:if option.isMulti:searchGroupValuesList = request.GET.getlist(option.field)if searchGroupValuesList:condition["%s__in" % option.field] = searchGroupValuesListelse:searchGroupValue = request.GET.get(option.field)if searchGroupValue:condition[option.field] = searchGroupValuereturn condition
对于可以多选的字段,通过condition["%s__in" % option.field] = searchGroupValuesList
的方式比较,而对于单选的字段,可以通过condition[option.field] = searchGroupValue
的方式比较,前边是筛选语句,后边是筛选值。
例如对于 RBACUserInfo 来说,我们可以定义几个组合搜索的字段:
self.searchGroup = [SearchGroupOption(field="team"),SearchGroupOption(field="department"),SearchGroupOption(field="grade"),]
team 和 department 都是单选,而 grade 可以多选。
这时候我们可以编写 SearchGroupOption 类了,首先确定这个类需要什么参数:
- 支持组合搜索的字段;
- 组合搜索的按钮显示问呢;
- 组合搜索的按钮值
- 该字段是否支持多选
class SearchGroupOption:def __init__(self, field, text=None, value=None, isMulti=False, condition=None):""":param field:组合搜索关联的字段:param text: 显示组合搜索的按钮文本:param value: 显示组合搜索按钮值:param isMulti: 是否支持多选:param condition: 数据库查询条件"""self.field, self.text, self.value, self.isMulti, self.condition, self.isChoice = \field, text, value, isMulti, condition if condition else {}, Falsedef getCondition(self, request, *args, **kwargs):return self.conditiondef getFieldData(self, modelClass, request, *args, **kwargs):"""根据字段去数据库获取关联的数据"""fieldObject = modelClass._meta.get_field(self.field)verboseName = fieldObject.verbose_nameif isinstance(fieldObject, ForeignKey) or isinstance(fieldObject, ManyToManyField):condition = self.getCondition(request=request, *args, **kwargs)return SearchGroupRow(header=verboseName,fieldData=fieldObject.remote_field.model.objects.filter(**condition),option=self, queryDict=request.GET)else:self.isChoice = Truereturn SearchGroupRow(header=verboseName, fieldData=fieldObject.choices, option=self, queryDict=request.GET)def getText(self, fieldObject):"""获取选项文本"""if self.text:return self.text(fieldObject)if self.isChoice:return fieldObject[1]return str(fieldObject)def getValue(self, fieldObject):"""获取选项值"""if self.value:return self.value(fieldObject)if self.isChoice:return fieldObject[0]return fieldObject.pk
SearchGroupRow 其实就是每一个组合搜索行,只不过我们也给封装成一个类:
class SearchGroupRow:def __init__(self, header, fieldData, option, queryDict):""":param header:组合搜索列名:param fieldData: 关联数据:param option: 配置:param queryDict: request.GET"""self.title, self.fieldData, self.option, self.queryDict = header, fieldData, option, queryDictdef __iter__(self):yield '<span class="col-md-1" style="font-size: 17px;">' + self.title + '</span>'yield '<div class="col-md-11 choice" style="display: inline-block; margin-bottom: 20px;">'# 1.request.GET 拷贝一份并将其设置为可修改类型totalQueryDict = self.queryDict.copy()totalQueryDict._mutable = True# 2.获取当前的总搜索条件originValueList = self.queryDict.getlist(self.option.field)if not originValueList:yield "<a class='active btn btn-info' href='?%s'>全部</a>" % totalQueryDict.urlencode()else:totalQueryDict.pop(self.option.field)yield '<a class="btn btn-default" href="?%s">全部</a>' % totalQueryDict.urlencode()# 3.遍历所有支持组合搜索的字段for item in self.fieldData:# 3.1、获取按钮显示文本text = self.option.getText(item)# 3.2、获取按钮值value = str(self.option.getValue(item))queryDict = self.queryDict.copy()queryDict._mutable = Trueif self.option.isMulti:# 3.3、如果当前选项支持多选multiValueList = queryDict.getlist(self.option.field)if value in multiValueList:multiValueList.remove(value)queryDict.setlist(self.option.field, multiValueList)yield "<a class='active btn btn-info' href='?%s'>%s</a>" % (queryDict.urlencode(), text)else:multiValueList.append(value)queryDict.setlist(self.option.field, multiValueList)yield "<a class='btn btn-default' href='?%s'>%s</a>" % (queryDict.urlencode(), text)else:# 3.4、如果当前选项不支持多选queryDict[self.option.field] = valueif value in originValueList:queryDict.pop(self.option.field)yield '<a class="active btn btn-info" href="?%s">%s</a>' % (queryDict.urlencode(), text)else:yield '<a class="btn btn-default" href="?%s">%s</a>' % (queryDict.urlencode(), text)yield '</div>'
__iter__ 方法可以通过 yield 将属性转换为可遍历的类型。
这样在 checkView.html 中就可以遍历这个类,其实就是一个 HTML 代码:
{% if searchGroupRowList %}<div class="panel panel-default"><div class="panel-heading"><i class="fa fa-filter" aria-hidden="true"></i><span>快速筛选</span></div><div class="panel-body"><div class="search-group">{% for row in searchGroupRowList %}<div class="row">{% for obj in row %}{{ obj|safe }}{% endfor %}</div>{% endfor %}</div></div></div>{% endif %}
这样就可以实现组合搜索的功能了:
四、批量操作
假如出现了一批不和我心意的人,数量巨大,这时候我们要是一个一个的删除费时费力,因此,我们要为 Stark 组件开发一个批量操作的功能。
对于批量操作,哦们首先要有一个多选框:
def getCheckbox(self, obj=None, isHeader=None, *args, **kwargs):"""多选框"""return "选择" if isHeader else mark_safe('<input type="checkbox" name="pk" value="%s" />' % obj.pk)
把它加到 displayList 中就可以在页面中显示一个选择框。
我们先写一个简单的批量删除的功能:
def getActionList(self):"""获取批量操作列表"""return self.actionListdef actionMultiDelete(self, request, *args, **kwargs):"""批量删除"""pkList = request.POST.getlist("pk")self.model.objects.filter(id__in=pkList).delete()
如果想添加其它的批量操作功能可以在子类中编写相应的方法去处理:
from Stark.main import StarkHandler, getM2M, getDatetime, getChoice, SearchGroupOptionclass RbacUserHandler(StarkHandler):def __init__(self, site, modelClass, prefix):super().__init__(site, modelClass, prefix)self.displayList = [StarkHandler.getCheckbox, "username", "email", "score", getChoice("等级", "grade"), getM2M("职务", "roles"), "team","department", getDatetime("加入时间", "dateJoined")]self.searchList = ["username"]StarkHandler.actionMultiDelete.text = "批量删除成员"self.actionList = [StarkHandler.actionMultiDelete]self.searchGroup = [SearchGroupOption(field="team"),SearchGroupOption(field="department"),SearchGroupOption(field="grade", isMulti=True),]
这里我们就不搞了,写一个通用的就 OK。
在我们返回页面的时候,如果先获取数据后执行批量操作的话,页面显示的数据还是原来的,因此我们需要把批量操作放在获取数据库数据之前:
def checkView(self, request, *args, **kwargs):"""查看功能视图函数"""# --------------------- 6.批量操作 ---------------------actionList = self.getActionList()actionDict = {func.__name__: func.text for func in actionList}if request.method == "POST":actionFuncName = request.POST.get("action")if actionFuncName and actionFuncName in actionDict:actionResponse = getattr(self, actionFuncName)(request, *args, **kwargs)if actionResponse:return HttpResponse(actionResponse)dataQuerySet = self.model.objects.all()
这样,一个批量删除的功能就完成了:
添加了分页、排序、关键字搜索、组合搜索和批量操作之后的 Stark 组件已经基本完善了,接下里就是针对每一个数据表做个性化处理。
Stark 组件:快速开发神器 —— 锦上添花相关推荐
- Stark 组件:快速开发神器 —— 页面显示
说道 Stark 你是不是不会想到他--Tony Stark,超级英雄钢铁侠,这也是我的偶像. 不过我们今天要开发的 Stark 组件,倒是跟他的人工智能助手 JARVIS 有些类似,是帮助我们快速开 ...
- Stark 组件:快速开发神器 —— 模板设计
说道 Stark 你是不是不会想到他--Tony Stark,超级英雄钢铁侠,这也是我的偶像. 不过我们今天要开发的 Stark 组件,倒是跟他的人工智能助手 JARVIS 有些类似,是帮助我们快速开 ...
- Stark 组件:快速开发神器 —— 自动生成 URL
说道 Stark 你是不是不会想到他--Tony Stark,超级英雄钢铁侠,这也是我的偶像. 不过我们今天要开发的 Stark 组件,倒是跟他的人工智能助手 JARVIS 有些类似,是帮助我们快速开 ...
- 介绍一个软件开发工具,堪称快速开发神器
软件快速开发平台是一种软件开发工具,以通用技术架构(如MVC)为基础,集成常用建模工具.二次开发包.基础解决方案等而成.可以大幅缩减编码率,使开发者有更多时间关注客户需求,在项目的需求.设计.开发.测 ...
- SpringBoot 接口快速开发神器(接口可视化界面实现)
点击关注公众号,实用技术文章及时了解 简介 magic-api 是一个基于Java的接口快速开发框架,编写接口将通过magic-api提供的UI界面完成,自动映射为HTTP接口,无需定义Control ...
- APIJSON 自动化接口和文档的快速开发神器
认识APIJSON APIJSON是一种JSON传输结构协议.客户端可以定义任何JSON结构去向服务端发起请求,服务端就会返回对应结构的JSON字符串,所求即所得.一次请求任意结构任意数据,方便灵活, ...
- python 全栈开发,Day112(内容回顾,单例模式,路由系统,stark组件)
python 全栈开发,Day112(内容回顾,单例模式,路由系统,stark组件) 一.内容回顾 类可否作为字典的key 初级 举例: class Foo(object):pass_registry ...
- CRM 开发 - 权限组件/stark组件/CRM业务
CRM开发(系列) - 武沛齐 - 博客园CRM,客户关系管理系统(Customer Relationship Management).企业用CRM技术来管理与客户之间的关系,以求提升企业成功的管理方 ...
- SNF快速开发平台MVC-高级查询组件
1. 高级查询 在我们做项目的时候经常想要按名称.编号进行查询数据,可在开发时会把最常用的查询条件写上,不常用的就不写了,也是因为把所有字段都写上太多了,布局不好看而且不实用.还有些查询条件几百年 ...
最新文章
- 2021年大数据ZooKeeper(六):ZooKeeper选举机制
- bug诞生记——const_cast引发只读数据区域写违例
- 一套iOS面试题解答
- 【Android 逆向】修改运行中的 Android 进程的内存数据 ( 运行环境搭建 Android 模拟器安装 | 拷贝 Android 平台可执行文件和动态库到 /data/system )
- python程序设计与科学计算pdf_用Python做科学计算 pdf版
- 《构架之美》阅读笔记四
- Linux中输入输出重定向和管道
- JqGrid 列时间格式化
- Github | 谷歌机器学习课程中文版
- day19 java数组的常用算法和排序
- 使用libbpf-bootstrap构建BPF应用程序
- ADO.NET数据访问模式
- STM32实战总结:HAL之触摸屏
- java linkedblockingqueue_Java LinkedBlockingQueue take()用法及代码示例
- 什么是5G聚合路由器?
- 想进Google,先来做做Google招聘题
- 苹果酸-天冬氨酸穿梭
- 百度统计中的索引量和site:命令的百度收录量不一致的原因
- 什么是Native方法
- 灰度图像彩色化算法研究
热门文章
- socket.io c++库编译不成功的注意事项
- angularJs-脏检查
- Web 四种常见的POST提交数据方式
- C#操作Excel(读取)
- (整理)ubuntu10.10安装低版本的编译器(低版本)(gcc)(ubuntu)
- Castle实践9-在Castle IOC容器中使用AspectSharp(全面剖析AspectSharp Facility)
- 计算机网络期中考试题周静,期中考试优秀作文
- android 原始编译过程,Android编译系统环境初始化过程分析.doc
- c语言学习之用筛选法求100之内的素数。
- linux命令查看g 版本,如何查看linux版本