Xadmin组件构建之分页、search查询与action批量操作
1 分页
当我们进行search查询的时候查询出来的数据可能也会多,这也要用分页展示,这里就涉及到一个问题:
我们点击其它页数时怎么确保还是查询出来的数据(url怎么保留搜索条件)?这里就需要在点击页数的时候url应该是动态变化的。
比如我们在查询书的时候,输入1进行查询,这里可以把书的名字或者价格里包含1的书全部查找出来
点击查询结果的第一页url为:
http://127.0.0.1:8008/Xadmin/app01/book/?q=1&page=1
点击查询结果的第二页url变为:
http://127.0.0.1:8008/Xadmin/app01/book/?q=1&page=2
上面q=1代表的就是查询条件,我们需要一个能保存搜索条件的自定义分页组件:
思路:
当我们访问http://127.0.0.1:8008/Xadmin/app01/book/?q=1&page=1的时候这个url肯定会发到一个视图中,通过requests.get就能拿到q=1&page=1
然后对它进行处理添加到分页中a标签的href中
Xadmin\utils\page.py
""" 自定义分页组件,可以保存搜索条件 """ class Pagination(object): def __init__(self, current_page, all_count, base_url, params, per_page_num=8, pager_count=11, ): """ 封装分页相关数据 :param current_page: 当前页 :param all_count: 数据库中的数据总条数 :param base_url: 分页中显示的URL前缀 :param params: 新添加的一个参数,用来接收 request.GET :param per_page_num: 每页显示的数据条数 :param pager_count: 最多显示的页码个数 """ try: current_page = int(current_page) except Exception as e: current_page = 1 if current_page < 1: current_page = 1 self.current_page = current_page self.all_count = all_count self.per_page_num = per_page_num self.base_url = base_url # 总页码 all_pager, tmp = divmod(all_count, per_page_num) if tmp: all_pager += 1 self.all_pager = all_pager self.pager_count = pager_count # 最多显示页码数 self.pager_count_half = int((pager_count - 1) / 2) import copy #request.get得到的queryset不能更改 params = copy.deepcopy(params) #params就是传过来的 request.GET 拷贝一份再进行修改, params._mutable = True ##mutable是可更改的意思,默认为true,可以更改 self.params = params # self.params : {"page":77,"title":"python","nid":1} # 拷贝之后可以使用urlencode方法 # urlencode可以把key-value这样的键值对转换成我们想要的格式,返回的是a=1&b=2这样的字符串 # print(self.params.urlencode()) # page=77&title=python&nid=1 @property def start(self): return (self.current_page - 1) * self.per_page_num @property def end(self): return self.current_page * self.per_page_num def page_html(self): # 如果总页码 < 11个: if self.all_pager <= self.pager_count: pager_start = 1 pager_end = self.all_pager + 1 # 总页码 > 11 else: # 当前页如果<=页面上最多显示(11-1)/2个页码 if self.current_page <= self.pager_count_half: pager_start = 1 pager_end = self.pager_count + 1 # 当前页大于5 else: # 页码翻到最后 if (self.current_page + self.pager_count_half) > self.all_pager: pager_start = self.all_pager - self.pager_count + 1 pager_end = self.all_pager + 1 else: pager_start = self.current_page - self.pager_count_half pager_end = self.current_page + self.pager_count_half + 1 page_html_list = [] self.params["page"] = 1 first_page = '<li><a href="%s?%s">首页</a></li>' % (self.base_url, self.params.urlencode(),) page_html_list.append(first_page) if self.current_page <= 1: prev_page = '<li class="disabled"><a href="#">上一页</a></li>' else: self.params["page"] = self.current_page - 1 prev_page = '<li><a href="%s?%s">上一页</a></li>' % (self.base_url, self.params.urlencode(),) page_html_list.append(prev_page) for i in range(pager_start, pager_end): # self.params : {"page":77,"title":"python","nid":1} self.params["page"] = i # {"page":72,"title":"python","nid":1} if i == self.current_page: temp = '<li class="active"><a href="%s?%s">%s</a></li>' % (self.base_url, self.params.urlencode(), i,) else: temp = '<li><a href="%s?%s">%s</a></li>' % (self.base_url, self.params.urlencode(), i,) page_html_list.append(temp) if self.current_page >= self.all_pager: next_page = '<li class="disabled"><a href="#">下一页</a></li>' else: self.params["page"] = self.current_page + 1 next_page = '<li><a href="%s?%s">下一页</a></li>' % (self.base_url, self.params.urlencode(),) page_html_list.append(next_page) self.params["page"] = self.all_pager last_page = '<li><a href="%s?%s">尾页</a></li>' % (self.base_url, self.params.urlencode(),) page_html_list.append(last_page) return ''.join(page_html_list)
Xadmin\service\Xadmin.py
分页属于展示页面,我们定义在show_list类下
from Xadmin.utils.page import Pagination # 构建表头数据和构建表单数据本应该放在ModelXadminl类下面list_view视图函数中,但数据太多放在里面会会显得杂乱 # 这里定义一个类专门用来在页面展示数据,把ModelXadminl类中的self以及list_view函数中的data_list和request三个参数传过来 class show_list(object): def __init__(self, config, data_list, request): self.config = config # config代表传过来的self, self.config就是ModelXadminl类中的实例化对象self self.data_list = data_list self.request = request # 分页 data_count = self.data_list.count() current_page = int(self.request.GET.get("page", 1)) base_path = self.request.path # /Xadmin/app01/book/ self.pagination = Pagination(current_page, data_count, base_path, self.request.GET, per_page_num=2, pager_count=11, ) self.page_data = self.data_list[self.pagination.start:self.pagination.end] class ModelXadmin def list_view(self, request): # 按照showlist展示数据 showlist = show_list(self, data_list, request) return render(request, 'list_view.html', locals())
list_view.html
<nav class="pull-right"> <ul class="pagination"> {{ showlist.pagination.page_html|safe }} </ul> </nav>
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <link rel="stylesheet" href="/static/bootstrap/css/bootstrap.min.css"> <script src="/static/jquery-3.3.1.js"></script> <style> .filter a { text-decoration: none; {# 去除a标签默认样式的下划线 #} color: grey; } .active { color: blue !important; } </style> </head> <body> <h3>查看{{ model_name }}数据</h3> <div class="container"> <div class="row"> <div class="col-lg-9"> <!--添加数据--> <a href="{{ add_url }}" class="btn btn-primary">添加数据</a> <!--搜索数据开始--> {% if showlist.config.search_fields %} <form action="" class="pull-right"> <input type="text" name="q" value="{{ showlist.config.key_word }}"> <button>submit</button> </form> {% endif %} <!--搜索数据结束--> <!--添加form表单是为了在点击Go时确定发送数据的范围(不包括上面submit里面的内容)--> <form action="" method="post"> {% csrf_token %} <!--action操作开始--> <select name="action" id="" style="width: 200px;padding: 5px 8px;display: inline-block"> <option value="">---------------</option> {% for item in showlist.get_action_list %} <option value="{{ item.name }}">{{ item.desc }}</option> {% endfor %} </select> <button type="submit" class="btn btn-info">Go</button> <!--action操作结束--> <!--表格数据开始--> <table class="table table-bordered table-striped"> <thead> <tr> {% for item in showlist.get_header %} <th>{{ item }}</th> {% endfor %} </tr> </thead> <tbody> {% for data in showlist.get_body %} <tr> {% for item in data %} <td>{{ item }}</td> {% endfor %} </tr> {% endfor %} </tbody> </table> <!--表格数据结束--> <!--分页开始--> <nav class="pull-right"> <ul class="pagination"> {{ showlist.pagination.page_html|safe }} </ul> </nav> <!--分页结束--> </form> </div> <!--filter开始--> <div class="col-md-3"> <!--如果list_filter有值说明是用户自定义的,展示除来--> {% if showlist.config.list_filter %} <div class="filter"> <h4 style="">Filter</h4> {% for filter_field,linktags in showlist.get_filter_linktags.items %} <div class="well"> <!-- class="well"为加面板--> <p>By {{ filter_field.upper }}</p> {% for link in linktags %} <p>{{ link|safe }}</p> {% endfor %} </div> {% endfor %} </div> {% endif %} </div> <!--filter结束--> </div> </div> <script> //给表头复选框加上点击事件(点击表头复选框,下面框全部选中,反之全部取消) $("#choice").click(function () { if ($(this).prop("checked")) { //prop() 方法用于设置或返回被选元素的属性和值 $(".choice_item").prop("checked", true) //把所有的复选框都设置为选中 } else { $(".choice_item").prop("checked", false) } }) </script> </body> </html>
2 search查询
list_view.html
value="{{ showlist.config.key_word }}是为了在查询显示结果时input框中仍然保存搜索的条件
{% if showlist.config.search_fields %} <form action="" class="pull-right"> <input type="text" name="q" value="{{ showlist.config.key_word }}"> <button>submit</button> </form> {% endif %}
app01\xadmin.py
class BookConfig(ModelXadmin): search_fields=["title","price"] #定义搜索框按照这两个字段进行筛选
Xadmin\service\Xadmin.py
from django.db.models import Q
# 定义每张表的配置类样式 class ModelXadmin(object): search_fields = [] # 获取serach的Q对象的函数 def get_serach_conditon(self, request):
print(request.GET)#当输入北京进行search查询时:<QueryDict:{'q':['北京']}> key_word = request.GET.get("q", "") # 第一次访问的时候查询框肯定没有值,用""表示默认为空 self.key_word = key_word # 这时候ModelXadmin的实例化对象里面就有了一个self.key_word这个值 # 可以通过showlist.config.key_word获取 search_connection = Q() # Q查询:通过字符串查询 if key_word: # self.search_fields #["title","price"] search_connection.connector = "or" # 通过这个参数可以将Q对象默认的and关系变成or for search_field in self.search_fields: search_connection.children.append((search_field + "__contains", key_word)) # 模糊查询 return search_connection def list_view(self, request): # 这里注册用哪个样式类(默认、自定义),self就是谁 # 获取serach的Q对象 search_connection = self.get_serach_conditon(request)
#print(search_connection) #(or:('title__contains','北京’),('price__contains','北京')) # 筛选获取当前表所有数据 data_list = self.model.objects.all().filter(search_connection) # <QuerySet [<Book: 北京折叠>]> # 按照showlist展示数据 showlist = show_list(self, data_list, request) # 实例化一个对象showlist,并传三个参数,其中self是当前ModelXadmin的实例化对象 # 把self传给show_list类后,它里面的__init__进行接收(上面show_list函数用来接收self的参数是config) return render(request, 'list_view.html', locals())
3 action批量处理
app01\Xadmin.py
class BookConfig(ModelXadmin): # 定制action操作 def patch_init(self, request, queryset): print(queryset) #当我们选中北京折叠与蜘蛛侠的复选框进行批量初始化时: <QuerySet [<Book: 北京折叠>, <Book: 蜘蛛侠>]> # queryset就是我们选中的数据,这里把我们选中的数据全部更新为123 queryset.update(price=123) return HttpResponse("批量初始化OK") # 为我们自定义的函数加上中文描述的属性 patch_init.short_description = "批量初始化" # 最后把函数放入actions列表中 actions = [patch_init]
Xadmin\service\Xadmin.py
# 构建表头数据和构建表单数据本应该放在ModelXadminl类下面list_view视图函数中,但数据太多放在里面会会显得杂乱 # 这里定义一个类专门用来在页面展示数据,把ModelXadminl类中的self以及list_view函数中的data_list和request三个参数传过来 class show_list(object): def __init__(self, config, data_list, request): self.config = config # config代表传过来的self, self.config就是ModelXadminl类中的实例化对象self self.data_list = data_list self.request = request
# actions self.actions=self.config.new_actions() # actions里面装的是函数,每个表都有默认的批量删除[patch_delete,]
#定义一个函数,构建数据结构:temp=[ "name": "patch_init","desc":"批量初始化"] def get_action_list(self): temp=[] for action in self.actions: temp.append({ "name":action.__name__, #取函数的名称: "patch_init" "desc":action.short_description # 取函数的描述:"批量初始化" }) return temp
# 定义每张表的配置类样式 class ModelXadmin(object): actions = []# 定制action操作:批量删除--定义到父类中,面对所有的表都有这个默认操作
def patch_delete(self, request, queryset): queryset.delete() patch_delete.short_description = "批量删除" def new_actions(self): temp=[] temp.append(ModelXadmin.patch_delete) #批量删除 temp.extend(self.actions) #如果用户定制了自己的actions就用用户自定义的,没有就添加其父类ModelXadmin下actions空列表 return temp
def check(self, obj=None, is_header=False): if is_header: return mark_safe("<input id='choice' type='checkbox'>") # 添加name='selected'_pk value='%s'是为批量操作actions做准备 return mark_safe("<input class='choice_item' type='checkbox' name='selected_pk' value='%s'>"%obj.pk) def list_view(self, request): # 这里注册用哪个样式类(默认、自定义),self就是谁 if request.method == "POST": # 只有action是post操作 print("POST:", request.POST) #POST: <QueryDict: {'csrfmiddlewaretoken': ['7e……i5'], # 'action': ['patch_init'], 'selected_pk': ['1', '5']}> action = request.POST.get("action") # 'action': ['patch_init'] selected_pk = request.POST.getlist("selected_pk") #传过来的是一个列表,用getlist取值 'selected_pk': ['1', '5'] action_func = getattr(self, action) #反射,相当于在自己所在的类(BookConfig)下找patch_init方法,自己类没有去父类找 queryset = self.model.objects.filter(pk__in=selected_pk) #查询当前选中的复选框对象 <QuerySet [<Book: 北京折叠>, <Book: 蓦然回首>]> ret = action_func(request, queryset) # return ret #放开这句批量操作之后返回的是批量初始化函数返回的响应 # 筛选获取当前表所有数据 data_list = self.model.objects.all().filter(search_connection) # <QuerySet [<Book: 北京折叠> ]> # 按照showlist展示数据 showlist = show_list(self, data_list, request) # 实例化一个对象showlist,并传三个参数,其中self是当前ModelXadmin的实例化对象 # 把self传给show_list类后,它里面的__init__进行接收(上面show_list函数用来接收self的参数是config) return render(request, 'list_view.html', locals())
list_view.html
<select name="action" id="" style="width: 200px;padding: 5px 8px;display: inline-block"> <option value="">---------------</option> {% for item in showlist.get_action_list %} <option value="{{ item.name }}">{{ item.desc }}</option> {% endfor %} </select> <button type="submit" class="btn btn-info">Go</button>
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <link rel="stylesheet" href="/static/bootstrap/css/bootstrap.min.css"> <script src="/static/jquery-3.3.1.js"></script> <style> .filter a { text-decoration: none; {# 去除a标签默认样式的下划线 #} color: grey; } .active { color: blue !important; } </style> </head> <body> <h3>查看{{ model_name }}数据</h3> <div class="container"> <div class="row"> <div class="col-lg-9"> <!--添加数据--> <a href="{{ add_url }}" class="btn btn-primary">添加数据</a> <!--搜索数据开始--> {% if showlist.config.search_fields %} <form action="" class="pull-right"> <input type="text" name="q" value="{{ showlist.config.key_word }}"> <button>submit</button> </form> {% endif %} <!--搜索数据结束--> <!--添加form表单是为了在点击Go时确定发送数据的范围(不包括上面submit里面的内容)--> <form action="" method="post"> {% csrf_token %} <!--action操作开始--> <select name="action" id="" style="width: 200px;padding: 5px 8px;display: inline-block"> <option value="">---------------</option> {% for item in showlist.get_action_list %} <option value="{{ item.name }}">{{ item.desc }}</option> {% endfor %} </select> <button type="submit" class="btn btn-info">Go</button> <!--action操作结束--> <!--表格数据开始--> <table class="table table-bordered table-striped"> <thead> <tr> {% for item in showlist.get_header %} <th>{{ item }}</th> {% endfor %} </tr> </thead> <tbody> {% for data in showlist.get_body %} <tr> {% for item in data %} <td>{{ item }}</td> {% endfor %} </tr> {% endfor %} </tbody> </table> <!--表格数据结束--> <!--分页开始--> <nav class="pull-right"> <ul class="pagination"> {{ showlist.pagination.page_html|safe }} </ul> </nav> <!--分页结束--> </form> </div> <!--filter开始--> <div class="col-md-3"> <!--如果list_filter有值说明是用户自定义的,展示除来--> {% if showlist.config.list_filter %} <div class="filter"> <h4 style="">Filter</h4> {% for filter_field,linktags in showlist.get_filter_linktags.items %} <div class="well"> <!-- class="well"为加面板--> <p>By {{ filter_field.upper }}</p> {% for link in linktags %} <p>{{ link|safe }}</p> {% endfor %} </div> {% endfor %} </div> {% endif %} </div> <!--filter结束--> </div> </div> <script> //给表头复选框加上点击事件(点击表头复选框,下面框全部选中,反之全部取消) $("#choice").click(function () { if ($(this).prop("checked")) { //prop() 方法用于设置或返回被选元素的属性和值 $(".choice_item").prop("checked", true) //把所有的复选框都设置为选中 } else { $(".choice_item").prop("checked", false) } }) </script> </body> </html>
浙公网安备 33010602011771号