OpenStack Horizon Filter 功能的实现


下图是 admin dashboard 里的 filter,可根据 Project 和 Name 过滤 instance。

下图是 project dashboard 里的 filter,可以过滤表格里的任意一列

代码以当前 stable/icehouse 分支为准

首先从后端看起,horizon/tables/ 有 FilterAction class

class FilterAction(BaseAction):


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]admin


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]
        return instances


可以看到这里有三种 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')),
    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;
      });'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',
        'hide': this.hide,
        onBefore: function () {
          var table = $(table_selector);
        onAfter: function () {
          var template, table, colspan, params;
          table = $(table_selector);
        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 里读数据。

    def filtered_data(self):
        # This function should be using django.utils.functional.cached_property
        # decorator, but unfortunately due to bug in Django
        # it would make it fail
        # when being mocked by mox in tests.
        if not hasattr(self, '_filtered_data'):
            self._filtered_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._filtered_data = action.filter(self,
        return self._filtered_data

    def get_rows(self):
        """Return the row data for this table broken out by columns."""
        rows = []
            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
        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