搜索相关
# 排序
可以根据我们的心情, 指定排序规则.
排序规则: eg - ["name","-age"]
先按照name正序排序,name相同的按照年龄从大到小倒序排序..
准备一点数据
import os
import sys
import django
base_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
sys.path.append(base_dir)
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'dc_stark.settings')
django.setup()
if __name__ == '__main__':
from apps.app01 import models
for i in range(1, 302):
models.UserInfo.objects.create(
name=f'dc_{i}',
age=10,
email=f'18954787{i}@qq.com',
depart_id=1,
)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 关键字搜索
Q对象的应用场景: 关键字搜索!!
先来看看效果!!
★实现思路:
在页面里设置搜索表单,点击搜索按钮,以GET的方式(会在url中添加param参数) 提交到当前url!
后台通过 request.GET.get("input搜索框name属性值", "")
获取到关键字后, 根据定义的列进行查找, 若是多列应满足"或"的逻辑
(eg: ["name__contains","email"]
姓名列里含有"dc"关键字或邮箱列里等于"dc"关键字的 前者模糊查找,后者精确查找!!
Q对象的使用, 简单举一个栗子:
keyword = request.GET.get("keyword", "").strip()
con = Q()
if keyword:
# 名字 or 手机号 or 级别的标题中有关键字
con.connector = 'OR'
con.children.append(('username__contains', keyword))
con.children.append(('mobile__contains', keyword))
con.children.append(('level__title__contains', keyword))
queryset = models.Customer.objects.filter(con).filter(active=1).select_related('level', 'creator')
2
3
4
5
6
7
8
9
要点一: 搜索框是个表单, 用GET方式提交数据到后台!!
要点二: filter里多条件时,是依据and条件连接的; 该场景下我们应使用Q对象构造构造复杂的or逻辑!!
要点三: 控制关键字搜索表单是否进行显示!
要点四: 搜索关键字应该保留!
# 批量操作
╮( ̄▽ ̄"")╭
先来看看效果
★ 注意4个要点!!
■ 要点1
使用"自定义函数扩展", 在表格中添加checkbox列!
■ 要点2
在页面上 生成 批量操作的下拉框+按钮 !!
批量操作的下拉框select里有多少option用户通过action_list由自己自定制的;
action_list为空的话,页面上将不显示 批量操作的下拉框!
- 批注:Python处处皆对象,函数也是一个对象!往模版中传递一个函数的话,不用加括号,该函数会自动执行!
So,我们将其转变成了字典的形式, {"函数对象.__name__ 即某个批量操作的函数名":该函数对象.text 即文本内容}
■ 要点3
使用js将 散落各处的 批量操作的选择 和 表格里的多选框 放到 "同一个表单" 中, POST请求提交表单数据到后台!
- 批注:武sir是用form标签包裹起来的,因为这几个div是放在一起的;
但我的代码,用了flex,这几处散落各地了,所以我用js变相实现啦!!
■ 要点4:
实现选中的批量操作(eg: 示例中是批量删除), 操作成功后, 可跳转其它页面!!
- 批注:可能会用这样的应用场景,批量删除成功后,想让其跳转到详细的批量删除记录的页面!
在示例代码中,批量操作的函数若无返回值,跳转的是当前页面;若有返回值,返回什么,就跳转什么!
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
关键代码如下:
# 组合搜索框
(*≧ω≦) 必拿下!
★ 一定一定一定 要明确整体的一个逻辑, 不然, 看代码会懵逼的(´・Д・)」!!
该小节的整体逻辑是这样子的, 如下:
search_group = [
{"field":"gender", "db_condition":{}}
{"field":"depart", "db_condition":{}}
]
"★" step1: 字典通过Option类进行了一次封装
search_group = [
Option("gender"),
Option("depart")
]
"★" step2: Option类通过SearchGroupRow类再进行了一次封装
相当于
search_group = [
SearchGroupRow(gender字段对象的choices属性, self, title, request.GET)
SearchGroupRow(depart外键字段对象所关联的那张表.objects.filter(**db_condition), self, title, request.GET),
]
SearchGroupRow实例化时的第一个参数就是 queryset_or_tuple, 它是 根据字段(choice、FK、M2M) 找到其关联的那些数据.
queryset_or_tuple可能是<QuerySet [<Depart: 司法部>,<Depart: 教育部>]>,也可能是((1, '男'), (2, '女'), (0, '未知'))
我们将上面的两个SearchGroupRow实例放到了search_group_row_list中,并传递给了前端模版
{% for row in search_group_row_list %} # -- 每个row就是SearchGroupRow实例
# -- ★这个for循环它会自动执行SearchGroupRow实例的__iter__方法
# __iter__方法中 会拿到 对应的queryset_or_tuple, 对其里面的每一个数据进行处理!!
{% for item in row %}
{{ item|safe }}
{% endfor %}
{% endfor %}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# 搜索条件封装和扩展
目标: 根据字段(choice、FK、M2M) 找到其关联的数据.
一代目有很多不完善的地方, 于是优化成了 二代目.. Hhh
大体思路:
1> 在自定义的handler中 (示例中是 UserInfoHandler), 配置search_group!
2> 根据字符串 (eg: "gender"、"depart") 去ORM表找到 对应的字段对象.
3> 先进行类型判断, 然后获取关联的数据!!
一代目的代码进行了以下的优化:
1. 不用字典,而是封装成了对象!对象的取值相较于字典取值代码更简洁 + 在search_group中配置时,也更简单.
"★"要习惯这样的写法!!
2. 做了代码的拆分,将组合搜索的代码,拆分到了Option类中!
3. ★★★ 扩展点:
get_db_condition函数,我们可以写个类继承Option并重写该方法
在重写的方法中,根据当前url来自定义一些复杂的搜索条件!!
简单回顾下: path转换器--kwargs、无名分组--args、有名分组--kwargs、url参数--request.GET
2
3
4
5
6
7
8
get_db_condition(self,*args,**kwargs)
比如, 路由中的path转换器的值会被 **kwargs
接收!!
关键代码如下:
截图中的示例中, 得到的组合搜索数据里面 要么是 queryset对象, 要么是元祖..
eg: <QuerySet [<Depart: 教育部1>, <Depart: 司法部>]>
、((1, '男'), (2, '女'), (0, '未知'))
# 搜索基本展示
在上面已经能拿到 组合搜索的数据, 现在需要展示在页面上, 供用户进行选择!!
目标: 在页面上显示组合搜索框, 框里有多个按钮选项!!
# 封装统一对象
将 queryset和元祖进行封装, 封装成一个统一的对象!!
分析如下:
现目前的代码, 得到的组合搜索数据里面 要么是 queryset对象, 要么是元祖 "你看上面的截图嘛,print的结果就是这样的!"
search_group_row_list = [<QuerySet [<Depart: 教育部1>, <Depart: 司法部>]>,((1, '男'), (2, '女'), (0, '未知'))]
紧接着拿到前端模版中展示,伪代码如下:
for row in search_group_row_list:
for item in row:
if item是对象:
<a href="">item.title</a>
else:
<a href="">item.1</a>
在前端模版中,不好进行类型判断,最后展示的每个元素还要加a标签.后续还要实现默认选中等功能.
所以在伪代码的基础上还要加一系列的if-else判断.
这么分析下来,在前端模版中实现非常复杂,所以我们想着在后端尽可能的先实现这些,前端模版就只负责循环就好!!
2
3
4
5
6
7
8
9
10
11
12
13
ψ(`∇´)ψ 复习一个python的小知识!! 很重要哈!
# 若一个类中"定义了__iter__方法",且"该方法返回了一个迭代器",那么就称该类实例化对象是可迭代对象.
# - 即该对象可以被循环!当被循环时,会自动执行类中的__iter__方法!!
# - yield生成器是特殊的迭代器!
class SearchGroupRow:
def __init__(self, queryset_or_tuple):
self.queryset_or_tuple = queryset_or_tuple
def __iter__(self):
# return iter(self.queryset_or_tuple)
yield 1
yield 2
yield 3
row = SearchGroupRow([1, 2, 3])
for item in row:
print(item)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
具体关键代码实现如下:
So, 我们成功将需要在前端模版中的判断放到了后端!! 棒!
# 文本显示自定制
像页面上展示的 "男"、"女" .. ORM表中就写死了, 是默认的.. 我们想让用户自定制呢?
扩展性更高啦!! 比如显示成 "男666"、"女666"..
在现目前已有的代码上进行了修改, 修改的关键代码在截图中已经做了标注!
此时前端模版中的代码 {% for item in row %}
就是在执行 SearchGroupRow里的 __iter__
..
# 组合搜索框的样式
(¯﹃¯)
关键代码如下:
# 组合搜索实现
(*≧ω≦) 必拿下!
# 为按钮生成url
目标: 为组合搜索按钮生成url目标.
假设开发时配置的组合搜索是这样的:
search_group = [
Option("gender"),
Option("depart")
]
那么在Star组件中会对着两个Option实例进行进一步封装,进而得到两个SearchGroupRow实例!!
每个SearchGroupRow实例都有queryset_or_tuple属性.在该假设中,其属性值分别为
((1, "男"),(2, "女"),(0, "未知"),) 、<QuerySet [<Depart: 教育部>, <Depart: 司法部>]>
在页面上展示组合搜索框里有以下两行按钮:
性别 [全部] [男] [女] [未知]
部门 [全部] [教育部] [司法部]
2
3
4
5
6
7
8
9
10
11
12
# 组合搜索-单选
该示例的组合搜索是 性别的单选+部门的单选.
先来看看效果
接下来,我们来分析下, 为这些按钮添加url!! 遵循怎样的规则呢?
1> 生成的url不能影响当前访问的url! (即当我选中"男"后,再选中"司法部","男"的选中不受影响.
2> 生成的url中GET参数的值是按钮的文本在数据库中存储对应的值!
3> 第一次点击该按钮选中,再次点击同一按钮,应该取消选中!
4> 先选中"男",再选中"女", url的GET参数 depart的值 会由1变成了2.. 部门一行同理.
5> 当"男"、"女"、"未知" 都没选时,(即url中没有gender参数.) 性别一行的"全部"应该被选中! 部门一行同理.
■ step1: 我们访问http://127.0.0.1:8000/stark/app01/userinfo/list/时, 每个按钮渲染的url如下:
[全部] /stark/app01/userinfo/list/? '选中' -- 当前访问的url中没有gender参数
[男] /stark/app01/userinfo/list/?gender=1
[女] /stark/app01/userinfo/list/?gender=2
[未知] /stark/app01/userinfo/list/?gender=0
--- --- ---
[全部] /stark/app01/userinfo/list/? '选中' -- 当前访问的url中没有depart参数
[教育部] /stark/app01/userinfo/list/?depart=1
[司法部] /stark/app01/userinfo/list/?depart=2
■ step2: 我们点击[男]这个按钮时,即访问地址 /stark/app01/userinfo/list/?gender=1, 每个按钮渲染的url如下:
[全部] /stark/app01/userinfo/list/?
[男] /stark/app01/userinfo/list/? '选中' -- 将gender剔除掉,才能满足再次点击取消选中
[女] /stark/app01/userinfo/list/?gender=2
[未知] /stark/app01/userinfo/list/?gender=0
--- --- ---
[全部] /stark/app01/userinfo/list/?gender=1 '选中' -- 新渲染的url中有gender参数,是为了不影响gender的选中
[教育部] /stark/app01/userinfo/list/?gender=1&depart=1
[司法部] /stark/app01/userinfo/list/?gender=1&depart=2
■ step3: 我们再点击[女]这个按钮时,即访问地址 /stark/app01/userinfo/list/?gender=2, 每个按钮渲染的url如下:
[全部] /stark/app01/userinfo/list/?
[男] /stark/app01/userinfo/list/?gender=1
[女] /stark/app01/userinfo/list/? '选中' -- 将gender剔除掉,才能满足再次点击取消选中
[未知] /stark/app01/userinfo/list/?gender=0
--- --- ---
[全部] /stark/app01/userinfo/list/?gender=2 '选中'
[教育部] /stark/app01/userinfo/list/?gender=2&depart=1
[司法部] /stark/app01/userinfo/list/?gender=2&depart=2
■ step3: 我们再点击[司法部]这个按钮时,即访问地址 /stark/app01/userinfo/list/?gender=2&depart=2, 每个按钮渲染的url如下:
[全部] /stark/app01/userinfo/list/?depart=2
[男] /stark/app01/userinfo/list/?gender=1&depart=2
[女] /stark/app01/userinfo/list/?depart=2 '选中'--将gender剔除掉,再次点击取消选中且不影响depart的选中
[未知] /stark/app01/userinfo/list/?gender=0&depart=2
--- --- ---
[全部] /stark/app01/userinfo/list/?gender=2
[教育部] /stark/app01/userinfo/list/?gender=2&depart=1
[司法部] /stark/app01/userinfo/list/?gender=2 '选中'--将depart剔除掉,再次点击取消选中且不影响gender的选中
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
关键代码如下: (让我自己写,够呛(¯﹃¯)
# 组合搜索-多选
在配置search_group时, 若给Option实例化多添加一个参数 is_multi=True, 那么该字段的组合搜索就支持多选!!
默认是不支持的.
eg: search_group = [Option("gender"),Option("depart", is_multi=True)]
先来看看效果:
关键代码如下: (规则看代码注释, 单选看懂了, 多选也就那么回事...
# 进行筛选
★ 你看代码! 关键字搜索和组合搜索是一起生效的!!
先来看看效果:
关键代码如下: (规则啥的详见注释, 我累了(・_・;
# ※ 后续: 数据过滤
列表显示的数据先进行下过滤!! / 列表数据显示前作额外的约束. ★ 这也是个扩展点!!
★ 编辑页面和删除页面针对的obj, 需要啥过滤条件得到.. 也作了相应的扩展! 在下小节的CURD的页面自定制中进行了蓝色标注!!
# ※ 后续: CURD的页面自定制
CURD的页面支持自定制, 不指定的话就用默认的
关键代码如下, (红色标注的部分
截图中的代码,有一点纰漏,新增和添加和删除页面的GET请求使用的模版应与post请求一样,需要这样写!
self.add_template or 'stark/change.html'
self.change_template or 'stark/change.html'
self.delete_template or 'stark/delete.html'
2
3
4
# ※ 后续: 反向生成url的优化!
(*≧ω≦)
说到底, 考察的就是实参和形参的传递!
# ◎ BUG
解决一个bug!!
加distinct()去重!! 场景: 多对多字段, 在组合搜索里 多选时!!
queryset = prev_queryset.filter(conn).filter(**search_group_condition).distinct().order_by(*order_list)
# 若不加distinct,会在表格里显示重复行!
2
# 附录
该篇博客实现的stark组件的功能, 都在下方展示啦! (相当于说明文档了 (*≧ω≦)
示例代码如下:
from apps.stark.service.v1 import site, StarkHandler, get_choice_text, StarkModelForm, Option
class MyOption(Option):
def get_db_condition(self, request, *args, **kwargs):
# return {'id__gt': request.GET.get('nid')}
return {'id__gt': 0}
class UserInfoHandler(StarkHandler):
list_display = [StarkHandler.display_checkbox,
"name", "age", "email", "depart",
get_choice_text("性别", 'gender'),
StarkHandler.display_edit,
StarkHandler.display_del]
order_list = ["-id"] # 表格中数据的排序规则
search_list = ["name__contains", "email__contains"] # 关键字搜索
# 里面有多少元素,批量操作的select下拉框里就有多少个option
action_list = [StarkHandler.action_multi_delete, ]
search_group = [
Option("gender"),
Option("depart", {'id__gt': 0}, is_multi=True),
# MyOption("depart", is_multi=True), # 自定义复杂的查询规则
# Option("gender", text_func=lambda item: item[1] + "666"), # 显示的文本自定制
]
site.register(models.UserInfo, UserInfoHandler)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
目前, stark组件有700多行代码, 篇幅原因就不粘贴啦!!