关于项目中组合搜索的一点代码
2018-03-28 本文已影响0人
不_一
CRM组合搜索实现关键代码
Config配置相关,创建CombineFilterOption对象组成列表
class UserInfoConfig(v1.StarkConfig):
list_display = ['name', 'username', 'email', 'depart']
edit_link = ['name']
# model_form_class = UserInfoModelForm
show_search_form = True
show_combine_filter = True
combine_filter = [
v1.CombineFilterOption('depart', text_func_name=lambda x: str(x), val_func_name=lambda x: x.code),
]
v1.site.register(models.UserInfo, UserInfoConfig)
组合搜索option类的定义
class CombineFilterOption(object):
"""
用于对组合筛选条件配置数据的封装(抽象)
[('字段名称', 是否多选, 字段的筛选条件, 是否是choice),
('字段名称', 是否多选, 字段的筛选条件, 是否是choice),
('字段名称', 是否多选, 字段的筛选条件, 是否是choice),]
"""
def __init__(self, field_name, multi=False, condition=None, is_choice=False, text_func_name=None,
val_func_name=None):
"""
:param field_name: 组合筛选字段
:param multi: 某个字段筛选是否是多选
:param condition: 某个字段是有多个可选项,但不是所有项都支持筛选
:param is_choice: 某个字段是不是choice类型,一般有三个类型(ForeignKey,ManyToManyField,choice)
:param text_func_name: 组合筛选时,针对Foreignkey或字段M2M,筛选条件的值是什么,默认是pk(id)
:param val_func_name: 组合筛选时,针对foreignkey或M2M 显示文本是什么?默认是str(obj),可以显示为id或者关联字段
"""
self.field_name = field_name
self.multi = multi
self.condition = condition
self.is_choice = is_choice
self.text_func_name = text_func_name
self.val_func_name = val_func_name
def get_queryset_display(self, field_obj):
"""
:param field_obj: models.CharField(max_length=32) 对象
:return: 根据field_obj 反查该字段的类,即数据表,然后根据表去查数据
"""
if self.condition:
return field_obj.rel.to.objects.filter(**self.condition)
return field_obj.rel.to.objects.all()
def get_choice_display(self, field_obj):
"""
获取choice类型的筛选条件的显示值
:param field_obj:
:return:
"""
return field_obj.choices
前端渲染:
{% if cl.show_combine_filter %}
<div class="combine_filter" style="margin-top:20px;">
{% for item in cl.get_comb_filter_data %}
<div class="combine_filter_line">
{% for col_html in item %}
{{ col_html }}
{% endfor %}
</div>
{% endfor %}
</div>
{% endif %}
ChangeList类的方法get_comb_filter_data
通过for循环拿到一个个实际生成前端a标签的CombineFilterRow对象
def get_comb_filter_data(self):
"""
生成器函数,根据组合筛选的配置,从数据库中拿到前端页面要显示的数据,
页面显示的一行行数据可以说是一种数据类型,可以对该数据进行抽象,定义一个类
cfo_obj.get_queryset_display(field_obj)---CombineFilterOption 对象的一个方法,根据该方法可以拿到models中某类的数据
该方法需要一个field_obj参数,其为model class类中的一个字段(title = models.CharField(max_length)
row是一个对象,可迭代对象.
:return:
"""
from django.db.models import ForeignKey, ManyToManyField
for cfo_obj in self.get_combine_filter:
field_obj = self.model_class._meta.get_field(cfo_obj.field_name)
if isinstance(field_obj, ForeignKey):
row = CombineFilterRow(cfo_obj, cfo_obj.get_queryset_display(field_obj), self.config.request)
elif isinstance(field_obj, ManyToManyField):
row = CombineFilterRow(cfo_obj, cfo_obj.get_queryset_display(field_obj), self.config.request)
else:
row = CombineFilterRow(cfo_obj, cfo_obj.get_choice_display(field_obj), self.config.request)
yield row
从上面代码来看,for循环组合搜索列表,然后yield拿到一个CombineFilterRow对象,这个对象定义了iter方法用来生成一个个a标签
代码定义如下
class CombineFilterRow(object):
"""
联合筛选要显示的数据对象,该对象不是简单的字典或者列表,而是一个类对象
一个对象代表一个筛选条件,一个对象拥有一个__iter__方法,该方法用于拿到一个个筛选值
"""
def __init__(self, option, data, request):
"""
:param option: 配置项 v1.CombineFilterOption('gender', is_choice=True) 一个对象
:param data: 通过配置从数据库中选取的页面要显示的数据:<QuerySet [<Department: 灯光>,
<Department: 动作指导>]>
:param request: 当前页面request请求
"""
self.data = data
self.option = option
self.request = request
def __iter__(self):
"""
该方法是针对一个对象:当for循环一个对象时,调用__iter__方法
:return:前端对CombineFilterRow对象进行循环时,生成一系列的a标签
"""
params = self.request.GET.copy() # 拿到当前页面的筛选条件
url_key = self.option.field_name # 调用该方法当前字段名称,该名称为url中的筛选条件.生成url用
current_val = params.get(url_key) # 单选用,当前值可能为空
current_val_list = params.getlist(url_key) # 多选用,值也可能为空
current_path_info = self.request.path_info
if url_key in params: # 为全选a标签做判断
params_copy = params.copy()
params_copy.pop(url_key)
select_all_url = "{0}?{1}".format(current_path_info, params_copy.urlencode())
yield mark_safe('<a href="{0}">全部</a>'.format(select_all_url))
else: # url_key没在params,说明对该字段没有做筛选,即全选选中
select_all_url = "{0}?{1}".format(current_path_info, params.urlencode())
yield mark_safe('<a href="{0}" class="active">全部</a>'.format(select_all_url))
for data_obj in self.data: # 为当前字段的可选项,进行a标签生成
if self.option.is_choice:
pk, text = str(data_obj[0]), data_obj[1]
else:
text = self.option.text_func_name(data_obj) if self.option.text_func_name else str(data_obj)
pk = self.option.val_func_name(data_obj) if self.option.val_func_name else data_obj.pk
# pk, text = str(data_obj.pk), str(data_obj)
if self.option.multi: # 根据单选还是多选给每个a标签赋不同的href值
# 多选
params_copy = params.copy()
val_list = params_copy.getlist(url_key)
if pk in current_val_list:
val_list.remove(pk)
params_copy.setlist(url_key, val_list)
multi_select_url = "{0}?{1}".format(current_path_info, params_copy.urlencode())
yield mark_safe('<a href="{1}" class="active">{0}</a>'.format(text, multi_select_url))
else:
val_list.append(pk)
params_copy.setlist(url_key, val_list)
multi_select_url = "{0}?{1}".format(current_path_info, params_copy.urlencode())
yield mark_safe('<a href="{1}">{0}</a>'.format(text, multi_select_url))
else:
params[url_key] = pk # 为这一行每个点击的a标签,获取href值用
single_select_url = "{0}?{1}".format(current_path_info, params.urlencode())
if current_val == pk:
yield mark_safe('<a href="{1}" class="active">{0}</a>'.format(text, single_select_url))
else:
yield mark_safe('<a href="{1}" >{0}</a>'.format(text, single_select_url))
if self.option.multi:
yield mark_safe('<span class="text-danger pull-right center-block" style="margin-right: 100px;">多选</span>')
else:
yield mark_safe('<span class="text-danger pull-right center-block" style="margin-right: 100px;">单选</span>')