排序
用户可以在自定义的handle类中来决定排序的规则
StarkHandel类中编写如下代码,给用户留出接口
class StarkHandle(object):
order_list = [] # 设定排序的规则
def get_order_list(self):
"""
获取排序规则的顺序列表
:return:
"""
return self.order_list or ['-id', ]
用户通过在自定义handle类中geiorder_list重新写值来改变排序规则。
class UserInfoHandle(StarkHandle):
per_page_count = 10 # 控制页面展示的内容条数
is_has_add_btn = True # 控制页面不展示添加按钮
order_list = ['age'] # 根据年龄排序
display_list = ["name", "age", get_choice_text('性别', 'gender'), "depart", "mail", StarkHandle.display_edit, StarkHandle.display_del]
StarkHandle类中列表展示视图函数,获取到数据库的所以数据后添加排序。
order_list = self.get_order_list()
query_set = self.model_class.objects.all().order_by(*order_list) # 获取排序之后的数据
模糊查询
功能:用户在输入框输入搜索关键字,点击搜索按钮,展示筛选出来的记录。
实现思路:
- 前端展示搜索框,数据使用form表单的get方式提交到后台
- 后台获取到的搜索关键字,要对哪列的数据进行匹配?这个可以交给使用stark组件的用户来决定,定义search_list获取某列或多列的搜索规则
- 搜索框的展示在用户配置了search_list时展示,没有配置search_list时不展示
页面中添加搜索框
以form表单的get方式提交搜索关键字
<div style="float: right">
<form action="" method="get" class="form-inline">
<input type="text" name="q" class="form-control" placeholder="关键字搜索">
<button type="submit" class="btn btn-primary">
<i class="fa fa-search" aria-hidden="true"></i>
</button>
</form>
</div>
后台接收关键字
后台接收到关键字后,需要对哪些列进行哪些规则的匹配?
可以让用户在自定义的Handle类中来制定规则,默认是没有搜索匹配规则的,如果用户制定了匹配规则搜索框显示,用户没有制定匹配规则,搜索框不展示。
StarkHandle类中
class StarkHandle(object):
search_list = [] # 设定搜索规则
def get_search_list(self):
"""
获取搜索的规则,对哪些列进行哪些规则的搜索
:return:
"""
return self.search_list
调用self.get_search_list()就可以拿到用户定义的搜索匹配规则,用户没有定义拿到的是当前类的空列表
用户自定义的类
class UserInfoHandle(StarkHandle):
per_page_count = 10 # 控制页面展示的内容条数
is_has_add_btn = True
search_list = ['name__contains', 'age__gt'] # 定制搜索匹配规则,名字包含搜索关键字,年龄大于搜索关键字
display_list = ["name", "age", get_choice_text('性别', 'gender'), "depart", "mail", StarkHandle.display_edit, StarkHandle.display_del]
多个匹配规则之间是 'or’的关系,使用django 提供的 Q 查询来完成数据匹配
search_value = request.GET.get('q', '') # 拿到页面传递过来的搜索关键字
conn = Q() # 构造多个条件 ‘或’ 查询
conn.connector = "OR"
search_list = self.get_search_list() # 拿到定制的列匹配规则
if search_value: # 组合Q查询条件
for search in search_list:
conn.children.append((search, search_value))
# 数据的查询, filter中的为空,拿到的是所有值
query_set = self.model_class.objects.filter(conn).order_by(*order_list) # 获取排序之后的数据
搜索框的展示
将search_list和search_value传递到前端页面
search_list用来决定搜索框是否展示,search_value来给搜索框中设定值。
{% if search_list %}
<div style="float: right">
<form action="" method="get" class="form-inline">
<input type="text" name="q" class="form-control" value="{{ search_value }}" placeholder="关键字搜索">
<button type="submit" class="btn btn-primary">
<i class="fa fa-search" aria-hidden="true"></i>
</button>
</form>
</div>
{% endif %}
页面展示
批量操作
思路:在每行数据前增加一列checkbox
选择框,点击不同功能的按钮,如批量修改、批量删除等,来完成批量功能。
页面展示checkbox框
通过给display_list
添加元素一个函数对象来完成。
class StarkHandle(object):
def display_checkbox(self, obj=None, is_header=None):
"""
定义一列数据,checkbox选择框
:param obj:
:param is_header:
:return:
"""
if is_header:
return "选择"
return mark_safe('<input type="checkbox" name="pk" value="{}">'.format(obj.pk))
在用户自定义的handle类中,自定义显示checkbox
class UserInfoHandle(StarkHandle):
per_page_count = 10 # 控制页面展示的内容条数
is_has_add_btn = True # 控制页面不展示添加按钮
order_list = ['age']
search_list = ['name__contains', 'age__gt']
# checkbox 的函数对象添加,作为第一个元素
display_list = [StarkHandle.display_checkbox, "name", "age", get_choice_text('性别', 'gender'), "depart", "mail", StarkHandle.display_edit, StarkHandle.display_del]
页面展示批量操作按钮
按钮以下拉框的形式展示,点击执行,执行选中的功能。
<div style="float: left; margin-bottom: 5px; margin-right:3px;" class="form-inline">
<select name="action" id="opt" class="form-control">
<option value="">请选择批量操作</option>
<option value="">批量初始化</option>
<option value="">批量移除</option>
</select>
<button type="submit" class="btn btn-primary">执行
</button>
</div>
页面效果
批量操作后端实现
a.下拉框中展示的数据在前端页面中写死了的,这不支持使用stark组件的用户进行扩展,所以前端展示哪些下拉框选项需要用户来定。如何来让用户扩展的功能我们写了太多次数了,下面直接上代码
StarkHandle类中代码
class StarkHandle(object):
action_list = []
def get_action_list(self):
"""
获取用户自定义的批量操作下拉框中展示的选项
:return:
"""
return self.action_list
在UserInfoHandle中
class UserInfoHandle(StarkHandle):
def multi_delete(self):
print("批量删除")
# 用于前端的展示中文字符
multi_delete.text = "批量删除"
def multi_init(self):
print("批量初始化")
# 用于前端的展示中文字符
multi_init.text = "批量初始化"
# 定义两个批量操作,批量删除,批量初始化
action_list = [multi_delete, multi_init]
这样就实现了用户扩展的的功能,在StarkHandle的change_list_view视图函数中调用self.get_action_list()就可以拿到用户扩展的下拉框选项。
b.用户选择批量操作,点击执行,执行对应的后端函数
在action_list中我们的元素是函数对象,我们需要对传入前端页面的数据进行构造:
构造字典的格式 '函数名称' : '下拉框中展示的文本'
action_dict = {
'multi_delete': '批量删除',
}
构造好的数据传递到前端,前端展示下拉框
{% if action_dict %}
<div style="float: left; margin-bottom: 5px; margin-right:3px;" class="form-inline">
<select name="action" id="opt" class="form-control">
<option value="">请选择批量操作</option>
{% for func_name,func_text in action_dict.items %}
<option value="{{ func_name }}">{{ func_text }}</option>
{% endfor %}
</select>
<button type="submit" class="btn btn-primary">执行
</button>
</div>
{% endif %}
数据提交选择使用form的post方式来提交。在后台检测前端提交的数据并进行处理
def change_list_view(self, request):
# 获取下拉框选项
action_list = self.get_action_list()
action_dict = {action.__name__: action.text for action in action_list}
if request.method == "POST":
func_name = request.POST.get('action')
if func_name and func_name in action_dict:
# 用户选中了批量操作,并且该操作在字典中,执行该函数
getattr(self, func_name)()
用户点击之后,在后端成功的调用了函数。
c.批量删除的函数功能实现
用户点击了之后,将选中的checkbox数据也传递到了后端,我们在对应的multi_delete中要对数据做批量删除,这个函数中需要有request对象,有可能会传递其他参数进来,所以函数参数修改,所有视图函数增加两个参数*args, **kwargs
方便后续扩展url对应视图函数的功能。
class UserInfoHandle(StarkHandle):
def multi_delete(self, request, *args, **kwargs):
pk_list = request.POST.getlist('pk')
self.model_class.objects.filter(pk__in=pk_list).delete()
# 用于前端的展示中文字符
multi_delete.text = "批量删除"
def multi_init(self, request, *args, **kwargs):
print("批量初始化")
# 用于前端的展示中文字符
multi_init.text = "批量初始化"
# 定义两个批量操作,批量删除,批量初始化
action_list = [multi_delete, multi_init]
def change_list_view(self, request, *args, **kwargs):
# 获取下拉框选项
action_list = self.get_action_list()
action_dict = {action.__name__: action.text for action in action_list}
if request.method == "POST":
func_name = request.POST.get('action')
if func_name and func_name in action_dict:
# 用户选中了批量操作,并且该操作在字典中,执行该函数
getattr(self, func_name)(request, *args, **kwargs)
批量删除功能完成。为了后面其他数据表对应的handle类中都可以使用批量操作,将multi_delete等的常用批量操作函数放到基类中。
完整代码:
StarkHanlde
class StarkHandle(object):
action_list = []
def get_action_list(self):
"""
获取用户自定义的批量操作下拉框中展示的选项
:return:
"""
return self.action_list
def change_list_view(self, request, *args, **kwargs):
# 获取下拉框选项
action_list = self.get_action_list()
action_dict = {action.__name__: action.text for action in action_list}
if request.method == "POST":
func_name = request.POST.get('action')
if func_name and func_name in action_dict:
# 用户选中了批量操作,并且该操作在字典中,执行该函数
func_response = getattr(self, func_name)(request, *args, **kwargs)
if func_response:
# 用于定制化,执行完批量操作之后的跳转
return func_response
# 获取排序
order_list = self.get_order_list()
# 搜索关键字
search_value = request.GET.get('q', '')
conn = Q() # 构造多个条件 ‘或’ 查询
conn.connector = "OR"
search_list = self.get_search_list()
if search_value:
for search in search_list:
conn.children.append((search, search_value))
header_list = []
display_list = self.get_display_list() # 通过get_display_list方法拿到要展示的数据,如果用户继承此类并重写了此方法就会调用子类中的该方法,达到扩展的目的
if display_list:
for key_or_func in display_list:
if isinstance(key_or_func, FunctionType):
# 是函数对象,调用该函数,将返回值作为表头内容
verbose_name = key_or_func(self, is_header=True)
else:
verbose_name = self.model_class._meta.get_field(key_or_func).verbose_name
header_list.append(verbose_name)
else:
# 用户没有继承这个类和重新给display_list赋值,使用当前类中的display_list
# 表头信息为该表名称
header_list.append(self.model_class._meta.model_name)
query_set = self.model_class.objects.filter(conn).order_by(*order_list) # 获取排序之后的数据
# 分页器初始化
all_count = query_set.count()
query_params = request.GET.copy() # 都是默认不可修改的
query_params._mutable = True # 设置之后,query_params可以修改
pager = Pagination(
current_page=request.GET.get('page'),
all_count=all_count,
base_url=request.path_info,
query_params=query_params,
per_page=self.per_page_count,
)
# 对数据库中的值进行切片,拿到当前页面展示的数据内容
data_list = query_set[pager.start:pager.end]
body_list = [] # 构造出给前端使用的数据结构,包含该表中那个的所有数据
"""
body_list = [
['胡说', '17', 'hushuo@qq.com'], # 数据表中的一行数据
['呼哈', '32', 'huha@qq.com']
]
"""
for item in data_list:
# 构造一行数据
row_list = []
if display_list:
for key_or_func in display_list:
if isinstance(key_or_func, FunctionType):
row_list.append(key_or_func(self, obj=item, is_header=False))
else:
row_list.append(getattr(item, key_or_func))
else:
# 展示对象信息到页面
row_list.append(item)
body_list.append(row_list)
return render(
request,
'stark/change_list.html',
{
'header_list': header_list,
'body_list': body_list,
'pager': pager,
'add_button': self.get_add_btn(),
'search_list': search_list,
'search_value': search_value,
'action_dict': action_dict,
}
)
UserInfoHandle
class UserInfoHandle(StarkHandle):
per_page_count = 10 # 控制页面展示的内容条数
is_has_add_btn = True # 控制页面不展示添加按钮
# model_form_class = UserInfoModelForm
# def save(self, form, is_update=False):
# form.instance.depart_id = 1
# form.save()
order_list = ['age'] # 排序规则
search_list = ['name__contains', 'age__gt'] # 查询规则
action_list = [StarkHandle.action_multi_delete, ] # 下拉框定制
display_list = [StarkHandle.display_checkbox, "name", "age", get_choice_text('性别', 'gender'), "depart", "mail", StarkHandle.display_edit, StarkHandle.display_del]
site.register(UserInfo, UserInfoHandle)
change_list.html
{% extends 'layout.html' %}
{% block content %}
<div class="luffy-container">
{% if search_list %}
<div style="float: right">
<form action="" method="get" class="form-inline">
<input type="text" name="q" class="form-control" value="{{ search_value }}" placeholder="关键字搜索">
<button type="submit" class="btn btn-primary">
<i class="fa fa-search" aria-hidden="true"></i>
</button>
</form>
</div>
{% endif %}
<form action="" method="post">
{% csrf_token %}
{% if action_dict %}
<div style="float: left; margin-bottom: 5px; margin-right:3px;" class="form-inline">
<select name="action" id="opt" class="form-control">
<option value="">请选择批量操作</option>
{% for func_name,func_text in action_dict.items %}
<option value="{{ func_name }}">{{ func_text }}</option>
{% endfor %}
</select>
<button type="submit" class="btn btn-primary">执行
</button>
</div>
{% endif %}
<div style="margin-bottom: 5px;float: left;">
{% if add_button %}
{{ add_button|safe }}
{% endif %}
</div>
<table border="1" class="table table-bordered">
<thead>
<tr>
{% for header in header_list %}
<th>{{ header }}</th>
{% endfor %}
</tr>
</thead>
<tbody>
{% for row in body_list %}
<tr>
{% for ele in row %}
<td>{{ ele }}</td>
{% endfor %}
</tr>
{% endfor %}
</tbody>
</table>
</form>
</div>
<nav>
<ul class="pagination">
{{ pager.page_html|safe }}
</ul>
</nav>
{% endblock %}
组合查询
实现思路
想要做出的效果是,直接点击一个或多个关键字来快速的筛选出结果。如:直接点击 男
这个关键字,快速筛选出表中所有性别的为的数据。
问题是,关键字是什么?从哪里得到的?
关键字搜索的是该表中的数据,所以肯定是与表有某种关系,对于关键字的展示分为了三种类型:
- choice 关键字为该字段对应的choice元组
- ForeignKey 关键字为关联的另一张表中的所有数据
- ManyToManyField 关键字为关联的另一张表中的所有数据
获取关键字
根据用户配置来决定关键字的展示,用户配置哪些字段作为关键字进行组合搜索。
UserInfoHandle类中的配置
search_group = ['gender', 'depart']
StarkHandel,对choice字段,FK,M2M的不同处理
class StarkHandle(object):
search_group = []
def get_search_group(self):
return self.search_group
def change_list_view(self, request, *args, **kwargs):
# 组合查询
from django.db.models import ForeignKey, ManyToManyField
search_group = self.get_search_group()
for item in search_group:
# 根据字符串拿到 字段对象
field_object = self.model_class._meta.get_field(item)
# FK M2M choice 的不同处理
if isinstance(field_object, ForeignKey) or isinstance(field_object, ManyToManyField):
# 关联字段的处理,拿到其关联字段对应表的所有数据
print(item, field_object.rel.model.objects.all())
else:
# choice的处理,拿到对应choices元组
print(item, field_object.choices)
刷新用户列表页面,后端的log展示,拿到了关键字数据。
获取关键字的扩展
当前获取的关键字是关联表中的所有数据,如depart字段关联的另一张表,我们不想所有的记录作为关键字,我们想id大于某个值的作为关键字去组合搜索,我们要支持用户定义这些搜索条件。
方式一:
我们修改配置文件的格式,修改为列表套字典的形式search_group=[{'field':'gender', 'db_condition: {}}, {'field': 'depart', 'db_condition': {'pd__gt': 3}}]
,我们再依次解析列表字典就可以拿到字段和其条件了。
但是这种方式有个缺点是,字典的key有可能由于用户的疏忽写错,导致程序出问题。在以后的代码中要注意传递数据不要使用列表字典嵌套的形式,构造一个类对象来传递数据,就不会存在key写错导致问题出现。
方式二:
使用类对象形式来传递关键字字段和筛选条件。
class Option(object):
def __init__(self, field, db_condition=None):
"""
替代列表套字典的形式,字典的key容易写错,导致出问题
:param field: 组合搜索关联的字段
:param db_condition: 数据库关联查询时的条件
"""
self.field = field
self.db_condition = db_condition
if not db_condition:
self.db_condition = {}
用户定义关键字字段和条件
from stark.service.v1 import site, StarkHandle, get_choice_text, Option
class UserInfoHandle(StarkHandle):
per_page_count = 10 # 控制页面展示的内容条数
is_has_add_btn = True # 控制页面不展示添加按钮
order_list = ['age']
search_list = ['name__contains', 'age__gt']
action_list = [StarkHandle.action_multi_delete, ]
search_group = [Option('gender'), Option('depart', {'pk__gt': 2})]
display_list = [StarkHandle.display_checkbox, "name", "age", get_choice_text('性别', 'gender'), "depart", "mail", StarkHandle.display_edit, StarkHandle.display_del]
修改对应访问的代码,从类实例化的对象中获取字段和条件。
class StarkHandle(object):
def change_list_view(self, request, *args, **kwargs):
# 组合查询
from django.db.models import ForeignKey, ManyToManyField
search_group = self.get_search_group()
for item in search_group:
# 根据字符串拿到 字段对象
field_object = self.model_class._meta.get_field(item.field)
# FK M2M choice 的不同处理
if isinstance(field_object, ForeignKey) or isinstance(field_object, ManyToManyField):
# 关联字段的处理,拿到符合条件的数据
print(item.field, field_object.rel.model.objects.filter(**item.db_condition))
else:
# choice的处理,拿到对应choices元组
print(item, field_object.choices)
获取关键字的代码放在这里显得chage_list_view的代码有点多,我们将这个功能放到Option
类中去实现。
class Option(object):
def __init__(self, field, db_condition=None):
"""
替代列表套字典的形式,字典的key容易写错,导致出问题
:param field: 组合搜索关联的字段
:param db_condition: 数据库关联查询时的条件
"""
self.field = field
if not db_condition:
self.db_condition = {}
self.db_condition = db_condition
def get_db_condition(self, request, *args, **kwargs):
return self.db_condition
def get_queryset_or_tuple(self, model_class, request, *args, **kwargs):
"""
根据字段获取数据库关联的数据
:return:
"""
field_object = model_class._meta.get_field(self.field)
# FK MTM choice 的不同处理
if isinstance(field_object, ForeignKey) or isinstance(field_object, ManyToManyField):
# 关联字段的处理,拿到其关联字段的所有内容
db_condition = self.get_db_condition(request, *args, **kwargs)
return field_object.rel.model.objects.filter(**db_condition)
else:
# choice的处理
return field_object.choices
视图函数中的使用
search_group = self.get_search_group()
for option_object in search_group:
# 根据字符串拿到 字段对象
row = option_object.get_queryset_or_tuple(self.model_class, request, *args, **kwargs)
print(option_object.field, row)
刷新页面的log展示
关键字在前端的基本展示
我们将数据返回到前端,前端拿到的有两种数据类型,元组和queryset对象,对其类型判断,还需要构造成标签的形式展示,前端做起来很麻烦,将其放在后端做。
a. get_queryset_or_tuple
返回的是一个元组或者queryset对象,我们通过新建一个类返回一个该类的对象,将该函数的返回值类型统一起来。返回的都是SearchGroupRow的类型。
class SearchGroupRow(object):
def __init__(self, queryset_or_tuple):
self.queryset_or_tuple = queryset_or_tuple
def __iter__(self):
if isinstance(self.queryset_or_tuple, tuple):
for item in self.queryset_or_tuple:
yield "<a>{}</a>".forma(item[1])
else:
for item in self.queryset_or_tuple:
yield "<a>{}</a>".format(str(item)
class Option(object):
def get_queryset_or_tuple(self, model_class, request, *args, **kwargs):
"""
根据字段获取数据库关联的数据
:return:
"""
field_object = model_class._meta.get_field(self.field)
# FK MTM choice 的不同处理
if isinstance(field_object, ForeignKey) or isinstance(field_object, ManyToManyField):
# 关联字段的处理,拿到其关联字段的所有内容
db_condition = self.get_db_condition(request, *args, **kwargs)
return SearchGroupRow(field_object.rel.model.objects.filter(**db_condition))
else:
# choice的处理
return SearchGroupRow(field_object.choices)
将SearchGroupRow
实例化对象通过实现__iter__()
方法变成可迭代对象,返回构造成的a标签,在页面遍历展示
<div>
{% for row in search_group_row_list %}
<div>
{% for item in row %}
{{ item|safe }}
{% endfor %}
</div>
{% endfor %}
</div>
页面效果,关键字展示到了页面
关键字高级显示和扩展
对于关键字的按钮的展示,按钮中的文本是写死的,我们要增加扩展让用户可以指定文本中内容,如
页面中,对于性别 默认的展示是 男 女 我们想在男后面加个小人,修改文本的内容
我们是在search_group = [Option('gender'), Option('depart', {'pk__gt': 2})]
中配置的,思路是,我们实例化对象时,传入一个函数对象,用该函数对象的返回值做关键字按钮的文本内容。如果用户没有编写使用默认的展示。
class Option(object):
def __init__(self, field, db_condition=None, text_func=None):
"""
替代列表套字典的形式,字典的key容易写错,导致出问题
:param field: 组合搜索关联的字段
:param db_condition: 数据库关联查询时的条件
:param text_func: 是个函数对象,用于用户自定制获取组合搜索关键字按钮显示的文本类容
"""
self.field = field
if not db_condition:
self.db_condition = {}
self.db_condition = db_condition
self.text_func = text_func
self.is_choice = False # 用来判断当前对象中存储的数据是否为choice数据
def get_db_condition(self, request, *args, **kwargs):
return self.db_condition
def get_queryset_or_tuple(self, model_class, request, *args, **kwargs):
"""
根据字段获取数据库关联的数据
:return:
"""
field_object = model_class._meta.get_field(self.field)
# FK MTM choice 的不同处理
if isinstance(field_object, ForeignKey) or isinstance(field_object, ManyToManyField):
# 关联字段的处理,拿到其关联字段的所有内容
db_condition = self.get_db_condition(request, *args, **kwargs)
return SearchGroupRow(field_object.rel.model.objects.filter(**db_condition), self)
else:
# choice的处理
self.is_choice = True
return SearchGroupRow(field_object.choices, self)
def get_text(self, field_object):
"""
获取文本的函数
:param field_object:
:return:
"""
if self.text_func:
return self.text_func(field_object)
if self.is_choice:
return field_object[1]
return str(field_object)
在SearchGroupRow中直接调用Option的get_text方法就能直接拿到按钮展示的文本。
class SearchGroupRow(object):
def __init__(self, queryset_or_tuple, option):
"""
接收的是一个元组或者queryset对象
:param queryset_or_tuple:
"""
self.queryset_or_tuple = queryset_or_tuple
self.option = option
def __iter__(self):
for item in self.queryset_or_tuple:
yield "<a>{}</a>".format(self.option.get_text(item))
用户自定义文本展示
search_group = [Option('gender', text_func=lambda field_object: field_object[1] + '666'), Option('depart', {'pk__gt': 2})]
用户自定义的按钮文本展示到页面
对于关键字的展示,我们应该在每一行以前展示该关键字的字段名表示是哪个内容做的筛选,如:
性别 男 女
部门 第三分部 第四分部
可以通过字段对象.verbosname
来拿到该字段的中文描述。
字段对象是在Option.get_queryset_or_tuple函数中拿到的,使用的地方是在SearchGroupRow中,所以在初始化SearchGroupRow对象将字段对象的verbose_name传递。
class SearchGroupRow(object):
def __init__(self, title, queryset_or_tuple, option):
"""
接收的是一个元组或者queryset对象
:param queryset_or_tuple:
"""
self.title = title
self.queryset_or_tuple = queryset_or_tuple
self.option = option
def __iter__(self):
yield self.title
yield "<a>全部</a>"
for item in self.queryset_or_tuple:
yield "<a>{}</a>".format(self.option.get_text(item))
class Option(object):
def get_queryset_or_tuple(self, model_class, request, *args, **kwargs):
"""
根据字段获取数据库关联的数据
:return:
"""
# 根据字符串拿到字段对象
field_object = model_class._meta.get_field(self.field)
# 拿到字段对象的中文描述信息
title = field_object.verbose_name
# FK MTM choice 的不同处理
if isinstance(field_object, ForeignKey) or isinstance(field_object, ManyToManyField):
# 关联字段的处理,拿到其关联字段的所有内容
db_condition = self.get_db_condition(request, *args, **kwargs)
return SearchGroupRow(title, field_object.rel.model.objects.filter(**db_condition), self)
else:
# choice的处理
self.is_choice = True
return SearchGroupRow(title, field_object.choices, self)
页面展示
页面显示优化
给组合搜索添加样式
search-group.css文件
.search-group {
padding: 5px 10px;
}
.search-group .row .whole {
min-width: 40px;
float: left;
display: inline-block;
padding: 5px 0 5px 8px;
margin: 3px;
font-weight: bold;
}
.search-group .row .others {
padding-left: 60px;
}
.search-group .row a {
display: inline-block;
padding: 5px 8px;
margin: 3px;
border: 1px solid #d4d4d4;
}
.search-group .row a {
display: inline-block;
padding: 5px 8px;
margin: 3px;
border: 1px solid #d4d4d4;
}
.search-group a.active {
color: #fff;
background-color: #337ab7;
border-color: #2e6da4;
}
在模板中导入
<link rel="stylesheet" href="{% static 'stark/css/search-group.css' %}">
change_list.html中组合搜索区域
{% if search_group_row_list %}
<div class="panel panel-default">
<div class="panel-heading">
<i class="fa fa-filter" aria-hidden="true"></i> 快速筛选
</div>
<div class="panel-body">
<div class="search-group">
{% for row in search_group_row_list %}
<div class="row">
{% for obj in row %}
{{ obj|safe }}
{% endfor %}
</div>
{% endfor %}
</div>
</div>
</div>
{% endif %}
进行关键字div区域划分,关键字过多的展示优化
class SearchGroupRow(object):
def __init__(self, title, queryset_or_tuple, option):
"""
接收的是一个元组或者queryset对象
:param title: 组合搜索列名称
:param queryset_or_tuple: 组合搜索关联获取到的数据
:param option: 配置
"""
self.title = title
self.queryset_or_tuple = queryset_or_tuple
self.option = option
def __iter__(self):
yield '<div class="whole">'
yield self.title
yield '</div>'
yield '<div class="others">'
yield "<a>全部</a>"
for item in self.queryset_or_tuple:
yield "<a>{}</a>".format(self.option.get_text(item))
yield '</div>'
页面效果:
关键字按钮添加URL
每个按钮都有一个url,构造url的思路是已get携带参数的形式向当前url拼接再发送到后端。如
点击 男 构建 url的参数为 ?gender=1 gender为数据库中的字段名,1为男对应的数据库存储的值
点击 男 并且点击 第三分部 ,构建的url是在原来url的基础上增加 depart=3 ,整体url的参数为?gender=1&depart=3
关键字按钮是在SearchGroupRow的__iter__
中生成的,所以在SearchGroupRow中需要拿到两个内容
- request.GET 携带的参数
- 关键字背后的值,choice为元组中的第一个值,关联字段为该对象的id。即男对应的是1,第三分部对应的是其对象的id值3
代码结构修改:
Option类
- 增加value_func函数扩展,用户定制文本背后的值
- SearchGroupRow实例化对象时将request.GET 参数传递
- get_value 来获取文本背后的值
class Option(object):
def __init__(self, field, db_condition=None, text_func=None, value_func=None):
"""
替代列表套字典的形式,字典的key容易写错,导致出问题
:param field: 组合搜索关联的字段
:param db_condition: 数据库关联查询时的条件
:param text_func: 是个函数对象,用于用户自定制获取组合搜索关键字按钮显示的文本类容
"""
self.field = field
self.db_condition = db_condition
if not db_condition:
self.db_condition = {}
self.text_func = text_func
self.value_func = value_func
self.is_choice = False # 用来判断当前对象中存储的数据是否为choice数据
def get_queryset_or_tuple(self, model_class, request, *args, **kwargs):
"""
根据字段获取数据库关联的数据
:return:
"""
# 根据字符串拿到字段对象
field_object = model_class._meta.get_field(self.field)
# 拿到字段对象的中文描述信息
title = field_object.verbose_name
# FK MTM choice 的不同处理
if isinstance(field_object, ForeignKey) or isinstance(field_object, ManyToManyField):
# 关联字段的处理,拿到其关联字段的所有内容
db_condition = self.get_db_condition(request, *args, **kwargs)
return SearchGroupRow(title, field_object.rel.model.objects.filter(**db_condition), self, request.GET)
else:
# choice的处理
self.is_choice = True
return SearchGroupRow(title, field_object.choices, self, request.GET)
def get_value(self, field_object):
"""
获取文本背后的值
:param fiedl_object:
:return:
"""
if self.value_func:
return self.value_func(field_object)
if self.is_choice:
return field_object[0]
return field_object.pk
SearchGroupRow
在构造url前,拿到了文本,文本背后对应的值,当前request.GET。
class SearchGroupRow(object):
def __init__(self, title, queryset_or_tuple, option, query_dict):
"""
接收的是一个元组或者queryset对象
:param title: 组合搜索列名称
:param queryset_or_tuple: 组合搜索关联获取到的数据
:param option: 配置
:param query_dict: request.GET
"""
self.title = title
self.queryset_or_tuple = queryset_or_tuple
self.option = option
self.query_dict = query_dict
def __iter__(self):
yield '<div class="whole">'
yield self.title
yield '</div>'
yield '<div class="others">'
yield "<a>全部</a>"
for item in self.queryset_or_tuple:
text = self.option.get_text(item)
value = self.option.get_value(item)
yield "<a>{}</a>".format(text)
yield '</div>'
构建url思路:按钮的url是当前url拼接上该按钮的参数,该按钮的参数应该携带之前的参数,需要进行参数的添加
def __iter__(self):
yield '<div class="whole">'
yield self.title
yield '</div>'
yield '<div class="others">'
yield "<a>全部</a>"
for item in self.queryset_or_tuple:
text = self.option.get_text(item)
value = self.option.get_value(item)
# 在保留原来参数的前提下,将每个按钮对应参数和原有参数组合起来,生成该按钮的url
query_dict = self.query_dict.copy()
query_dict._mutable = True
# 将关键字按钮添加,如 gender: 1
query_dict[self.option.field] = value
# urlencode 生成get 的url参数形式 gender=1&depart=3
yield "<a href='?{}'>{}</a>".format(query_dict.urlencode(), text)
yield '</div>'
页面效果展示:
什么都不点击,关键字对应的href
选中性别之后的url和关键字的href,关键字的href中携带了之前性别选中的参数。
添加默认选中的样式,再次点击选中的关键字取消选中
def __iter__(self):
yield '<div class="whole">'
yield self.title
yield '</div>'
yield '<div class="others">'
yield "<a>全部</a>"
for item in self.queryset_or_tuple:
text = self.option.get_text(item)
value = self.option.get_value(item)
# 在保留原来参数的前提下,将每个按钮对应参数和原有参数组合起来,生成该按钮的url
query_dict = self.query_dict.copy()
query_dict._mutable = True
origin_value_list = query_dict.getlist(self.option.field)
query_dict[self.option.field] = value
if str(value) in origin_value_list:
query_dict.pop(self.option.field)
yield "<a href='?{}' class='active'>{}</a>".format(query_dict.urlencode(), text)
else:
yield "<a href='?{}'>{}</a>".format(query_dict.urlencode(), text)
yield '</div>'
全部按钮的功能
当全部后面的内容没有一项被选中时,应该默认选中全部。
def __iter__(self):
yield '<div class="whole">'
yield self.title
yield '</div>'
yield '<div class="others">'
total_query_dict = self.query_dict.copy()
total_query_dict._mutable = True
origin_value_list = self.query_dict.getlist(self.option.field)
if not origin_value_list:
yield "<a href='?{}' class='active'>全部</a>".format(total_query_dict.urlencode())
else:
total_query_dict.pop(self.option.field)
yield "<a href='?{}'>全部</a>".format(total_query_dict.urlencode())
for item in self.queryset_or_tuple:
text = self.option.get_text(item)
value = self.option.get_value(item)
# 在保留原来参数的前提下,将每个按钮对应参数和原有参数组合起来,生成该按钮的url
query_dict = self.query_dict.copy()
query_dict._mutable = True
query_dict[self.option.field] = value
if str(value) in origin_value_list:
query_dict.pop(self.option.field)
yield "<a href='?{}' class='active'>{}</a>".format(query_dict.urlencode(), text)
else:
yield "<a href='?{}'>{}</a>".format(query_dict.urlencode(), text)
yield '</div>'
组合搜索
后端根据组合条件选择数据
def get_search_group_condition(self, request):
"""
获取组合搜索的条件
:param request:
:return:
"""
condition = {}
for option in self.get_search_group():
value_list = request.GET.getlist(option.field)
if not value_list:
continue
condition['{}__in'.format(option.field)] = value_list
return condition
def change_list_view(self, request, *args, **kwargs):
# 拿到搜索添加
search_group_condition = self.get_search_group_condition(request)
# 省略代码...
# 根据条件拿到数据
query_set = self.model_class.objects.filter(conn).filter(**search_group_condition).order_by(*order_list) # 获取排序之后的数据
页面展示
此时每一行的搜索条件只能选中一个,要优化为选择多个。
可以支持用户自定是否支持多选。
class Option(object):
def __init__(self, field, is_multi=False, db_condition=None, text_func=None, value_func=None):
### 1.增加is_multi 属性 ###
self.is_multi = is_multi
class SearchGroupRow(object):
def __iter__(self):
yield '<div class="whole">'
yield self.title
yield '</div>'
yield '<div class="others">'
total_query_dict = self.query_dict.copy()
total_query_dict._mutable = True
origin_value_list = self.query_dict.getlist(self.option.field)
if not origin_value_list:
yield "<a href='?{}' class='active'>全部</a>".format(total_query_dict.urlencode())
else:
total_query_dict.pop(self.option.field)
yield "<a href='?{}'>全部</a>".format(total_query_dict.urlencode())
for item in self.queryset_or_tuple:
text = self.option.get_text(item)
value = str(self.option.get_value(item))
query_dict = self.query_dict.copy()
query_dict._mutable = True
### 2.根据is_multi的值来生成不同的url参数 ###
# 在保留原来参数的前提下,将每个按钮对应参数和原有参数组合起来,生成该按钮的url
if not self.option.is_multi:
# 不支持多选
query_dict[self.option.field] = value
if value in origin_value_list:
query_dict.pop(self.option.field)
yield "<a href='?{}' class='active'>{}</a>".format(query_dict.urlencode(), text)
else:
yield "<a href='?{}'>{}</a>".format(query_dict.urlencode(), text)
else:
# 支持多选
multi_value_list = query_dict.getlist(self.option.field)
if value in multi_value_list:
multi_value_list.remove(value)
query_dict.setlist(self.option.field, multi_value_list)
yield "<a href='?{}' class='active'>{}</a>".format(query_dict.urlencode(), text)
else:
multi_value_list.append(value)
query_dict.setlist(self.option.field, multi_value_list)
yield "<a href='?{}'>{}</a>".format(query_dict.urlencode(), text)
yield '</div>'
class StarkHandle(object):
def get_search_group_condition(self, request):
"""
获取组合搜索的条件
:param request:
:return:
"""
condition = {}
for option in self.get_search_group():
### 3.根据是否支持多选,自动生成搜索条件,多选用__in 单选直接判断相等 ###
if option.is_multi:
value_list = request.GET.getlist(option.field)
if not value_list:
continue
condition['{}__in'.format(option.field)] = value_list
else:
# 不支持多选的条件
value = request.GET.get(option.field)
if not value:
continue
condition[option.field] = value
return condition
用户使用
class UserInfoHandle(StarkHandle):
per_page_count = 10 # 控制页面展示的内容条数
is_has_add_btn = True # 控制页面不展示添加按钮
order_list = ['age']
search_list = ['name__contains', 'age__gt']
action_list = [StarkHandle.action_multi_delete, ]
### 4. 为depart添加为多选 ###
search_group = [Option('gender', text_func=lambda field_object: field_object[1] + '666'), Option('depart', is_multi=True, text_func=lambda field_object: field_object.title)]
display_list = [StarkHandle.display_checkbox, "name", "age", get_choice_text('性别', 'gender'), "depart", "mail", StarkHandle.display_edit, StarkHandle.display_del]
页面效果: