在项目中的表格上方,通常都会添加一个搜索窗口,按输入内容进行搜索。搜索过程是前端输入内容,提交一个表单到相应的路由函数,表单内容在函数中获取是通过request.args.get(‘q’, ”)。我这里搜索表单的id是q,如果获取的内容不存在则内容为空,则不过滤,否则通过Model.query.filter()来过滤相应的内容。下面通过不同部分来看看具体实现。
实现原理是Postgresql的LIKE语句,在项目中是用过SQLAlchemy的ilke方法来实现。
jinja2模板
搜索框是通过宏定义的。
{% import 'macros/form.html' as f with context' %}
{{ f.search('admin.users') }}
下面看看宏的具体实现办法。templates/macros/form.html
。重点看下面两段代码:
...
{# Render a form tag that contains a CSRF token and all hidden fields. #}
{%- macro form_tag(endpoint, fid='', css_class='', method='post') -%}
<form action="{{ url_for(endpoint, **kwargs) }}" method="{{ method }}"
id="{{ fid }}" class="{{ css_class }}" role="form">
{{ form.hidden_tag() }}
{{ caller () }}
</form>
{%- endmacro -%}
{# Render a form for searching. #}
{%- macro search(endpoint) -%}
{% call form_tag(endpoint, method='get') %}
<label for="q"></label>
<div class="input-group md-margin-bottom">
<input type="text" class="form-control"
id="q" name="q" value="{{ request.args.get('q', '') }}"
placeholder="Search by typing, then press enter...">
<span class="input-group-addon">
<i class="fa fa-fw fa-search"></i>
</span>
</div>
{% endcall %}
{%- endmacro -%}
我们在模板中调用的f.search宏,并传入了admin.users这个endpoint,在search这个宏通过call form_tag这个宏来渲染一个表单,search宏 call内部的内容是放在form_tag的 {{ caller () }}处的,连起来看就是渲染了一个有csrf保护的表单,并且传入有搜索内容的输入窗口,提交出发的动作是路由到admin.users这个视图函数。所以接下来看看试图函数的处理。
views
也是关注代码处理搜索的部分。
@admin.route('/users', defaults={'page': 1})
@admin.route('/users/page/<int:page>')
def users(page):
search_form = SearchForm()
bulk_form = BulkDeleteForm()
sort_by = User.sort_by(request.args.get('sort', 'created_on'),
request.args.get('direction', 'desc'))
order_values = '{0} {1}'.format(sort_by[0], sort_by[1])
paginated_users = User.query \
.filter(User.search(request.args.get('q', ''))) \
.order_by(User.role.asc(), User.payment_id, text(order_values)) \
.paginate(page, 50, True)
return render_template('admin/user/index.html',
form=search_form, bulk_form=bulk_form,
users=paginated_users)
表单提交的内容作为参数传入了.filter(User.search(request.args.get('q', '')))
。这里调用了model User的search方法,稍后再分析那边的代码,这里还可以看到,我们最后传入给模板的users其实是有过滤有排序并且分页的对象。
接下来才到搜索的重点,model部分。
User
User是项目中定义用户的模型,通过SQLAlchemy和数据库建立关联。上面说到了搜索是用过User的search方法实现的,代码如下:
@classmethod
def search(cls, query):
"""
Search a resource by 1 or more fields.
:param query: Search query
:type query: str
:return: SQLAlchemy filter
"""
if not query:
return ''
search_query = '%{0}%'.format(query)
search_chain = (User.email.ilike(search_query),
User.username.ilike(search_query))
return or_(*search_chain)
view函数中传入的表单内容其实就是search函数的query参数(查询信息)。首先我们看下Postgresql的LIKE语句,ILIKE只是让匹配内容与大小写无关,lower(a) LIKE lower(other)和a ILIKE other等效。具体教程。
PostgreSQL的LIKE操作符是用来反对使用通配符的模式匹配的文本值。如果搜索表达式可以匹配的模式表达式,LIKE运算将返回true,也就是1。有两个通配符与LIKE运算符一起使用:
百分号 (%)
下划线 (_)
百分号表示零个,一个或多个数字或字符。下划线代表一个单一的数字或字符。这些符号可以被组合使用。
那么结合我们传入的内容,search_query查询信息实际就是包含了我们输入内容的字符串。比如输入的是123
,那么可能aa123bb
, 123df23
等都是匹配的内容。然后将查询信息传入ilike,这个ilike是SQLAlchemy中对列使用的方法,就是为了构建pg数据的ILIKE语句。这里为了让输入内容匹配多行,我们通过构建search_chain来让搜索内容匹配两行,并通过or_方法生成或
关系的连接表达式。
最后就是filter接收这个SQL表达式来筛选内容以达到搜索的目的。