OpenStack Horizon Filter 功能的实现
怕自己以后忘了,随手记一下。
下图是 admin dashboard 里的 filter,可根据 Project 和 Name 过滤 instance。
下图是 project dashboard 里的 filter,可以过滤表格里的任意一列
代码以当前 stable/icehouse 分支为准
首先从后端看起,horizon/tables/actions.py 有 FilterAction class
class FilterAction(BaseAction):
openstack_dashboard/dashboards/project/instances/tables.py 有
class InstancesFilterAction(tables.FilterAction):
def filter(self, table, instances, filter_string):
"""Naive case-insensitive search."""
q = filter_string.lower()
return [instance for instance in instances
if q in instance.name.lower()]admin
openstack_dashboard/dashboards/admin/instances/tables.py
class AdminInstanceFilterAction(tables.FilterAction):
filter_type = "server"
filter_choices = (('project', _("Project")),
('name', _("Name"))
)
needs_preloading = True
def filter(self, table, instances, filter_string):
"""Server side search.
When filtering is supported in the api, then we will handle in view
"""
filter_field = table.request.POST.get('instances__filter__q_field')
self.filter_field = filter_field
self.filter_string = filter_string
if filter_field == 'project' and filter_string:
return [inst for inst in instances
if inst.tenant_name == filter_string]
if filter_field == 'name' and filter_string:
q = filter_string.lower()
return [instance for instance in instances
if q in instance.name.lower()]
return instances
然后我们看下前端的实现,horizon/templates/horizon/common/_data_table_table_actions.html
可以看到这里有三种 filter_type
,InstancesFilterAction 默认是 query,AdminInstanceFilterAction 是 server。query 和 server 有什么不同呢?我们去看看 js。
horizon.datatables.set_table_query_filter = function (parent) {
$(parent).find('table').each(function (index, elm) {
var input = $($(elm).find('div.table_search.client input')),
table_selector;
if (input.length > 0) {
// Disable server-side searcing if we have client-side searching since
// (for now) the client-side is actually superior. Server-side filtering
// remains as a noscript fallback.
// TODO(gabriel): figure out an overall strategy for making server-side
// filtering the preferred functional method.
input.on('keypress', function (evt) {
if (evt.keyCode === 13) {
return false;
}
});
input.next('button.btn-search').on('click keypress', function (evt) {
return false;
});
// Enable the client-side searching.
table_selector = 'table#' + $(elm).attr('id');
input.quicksearch(table_selector + ' tbody tr', {
'delay': 300,
'loader': 'span.loading',
'bind': 'keyup click',
'show': this.show,
'hide': this.hide,
onBefore: function () {
var table = $(table_selector);
horizon.datatables.remove_no_results_row(table);
},
onAfter: function () {
var template, table, colspan, params;
table = $(table_selector);
horizon.datatables.update_footer_count(table);
horizon.datatables.add_no_results_row(table);
horizon.datatables.fix_row_striping(table);
},
prepareQuery: function (val) {
return new RegExp(val, "i");
},
testQuery: function (query, txt, _row) {
return query.test($(_row).find('td:not(.hidden):not(.actions_column)').text());
}
});
}
});
};
如果 filter_type 是 query,就直接调用 js 来 filter,用的是 jQuery quicksearch。(Icehouse 好像用的不是这个版本)
那么服务端的 filter 是怎么实现的呢?
class DataTable 里的 filtered_data 会调用 action 的 filter 函数,而 get_rows 函数会从 filtered_data 里读数据。
@property
def filtered_data(self):
# This function should be using django.utils.functional.cached_property
# decorator, but unfortunately due to bug in Django
# https://code.djangoproject.com/ticket/19872 it would make it fail
# when being mocked by mox in tests.
if not hasattr(self, '_filtered_data'):
self._filtered_data = self.data
if self._meta.filter and self._meta._filter_action:
action = self._meta._filter_action
filter_string = self.get_filter_string()
request_method = self.request.method
needs_preloading = (not filter_string
and request_method == 'GET'
and action.needs_preloading)
valid_method = (request_method == action.method)
if valid_method or needs_preloading:
if self._meta.mixed_data_type:
self._filtered_data = action.data_type_filter(self,
self.data,
filter_string)
else:
self._filtered_data = action.filter(self,
self.data,
filter_string)
return self._filtered_data
def get_rows(self):
"""Return the row data for this table broken out by columns."""
rows = []
try:
for datum in self.filtered_data:
row = self._meta.row_class(self, datum)
if self.get_object_id(datum) == self.current_item_id:
self.selected = True
row.classes.append('current_selected')
rows.append(row)
except Exception:
# Exceptions can be swallowed at the template level here,
# re-raising as a TemplateSyntaxError makes them visible.
LOG.exception("Error while rendering table rows.")
exc_info = sys.exc_info()
raise template.TemplateSyntaxError, exc_info[1], exc_info[2]
return rows
comments powered by Disqus