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>
list_view.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>
list_view.html

 

posted @ 2020-05-31 16:17  zh_小猿  阅读(380)  评论(0)    收藏  举报