Merge "Add API filtering to paged tables"
This commit is contained in:
@@ -421,7 +421,20 @@ class FilterAction(BaseAction):
|
|||||||
|
|
||||||
.. attribute: filter_type
|
.. attribute: filter_type
|
||||||
|
|
||||||
A string representing the type of this filter. Default: ``"query"``.
|
A string representing the type of this filter. If this is set to
|
||||||
|
``"server"`` then ``filter_choices`` must also be provided.
|
||||||
|
Default: ``"query"``.
|
||||||
|
|
||||||
|
.. attribute: filter_choices
|
||||||
|
|
||||||
|
Required for server type filters. A tuple of tuples representing the
|
||||||
|
filter options. Tuple composition should evaluate to (string, string,
|
||||||
|
boolean), representing the filter parameter, display value, and whether
|
||||||
|
or not it should be applied to the API request as an API query
|
||||||
|
attribute. API type filters do not need to be accounted for in the
|
||||||
|
filter method since the API will do the filtering. However, server
|
||||||
|
type filters in general will need to be performed in the filter method.
|
||||||
|
By default this attribute is not provided.
|
||||||
|
|
||||||
.. attribute: needs_preloading
|
.. attribute: needs_preloading
|
||||||
|
|
||||||
@@ -443,10 +456,16 @@ class FilterAction(BaseAction):
|
|||||||
self.name = kwargs.get('name', self.name)
|
self.name = kwargs.get('name', self.name)
|
||||||
self.verbose_name = kwargs.get('verbose_name', _("Filter"))
|
self.verbose_name = kwargs.get('verbose_name', _("Filter"))
|
||||||
self.filter_type = kwargs.get('filter_type', "query")
|
self.filter_type = kwargs.get('filter_type', "query")
|
||||||
|
self.filter_choices = kwargs.get('filter_choices')
|
||||||
self.needs_preloading = kwargs.get('needs_preloading', False)
|
self.needs_preloading = kwargs.get('needs_preloading', False)
|
||||||
self.param_name = kwargs.get('param_name', 'q')
|
self.param_name = kwargs.get('param_name', 'q')
|
||||||
self.icon = "search"
|
self.icon = "search"
|
||||||
|
|
||||||
|
if self.filter_type == 'server' and self.filter_choices is None:
|
||||||
|
raise NotImplementedError('A FilterAction object with the '
|
||||||
|
'filter_type attribute set to "server" must also have a '
|
||||||
|
'filter_choices attribute.')
|
||||||
|
|
||||||
def get_param_name(self):
|
def get_param_name(self):
|
||||||
"""Returns the full query parameter name for this action.
|
"""Returns the full query parameter name for this action.
|
||||||
|
|
||||||
@@ -486,6 +505,17 @@ class FilterAction(BaseAction):
|
|||||||
raise NotImplementedError("The filter method has not been "
|
raise NotImplementedError("The filter method has not been "
|
||||||
"implemented by %s." % self.__class__)
|
"implemented by %s." % self.__class__)
|
||||||
|
|
||||||
|
def is_api_filter(self, filter_field):
|
||||||
|
"""Determine if the given filter field should be used as an
|
||||||
|
API filter.
|
||||||
|
"""
|
||||||
|
if self.filter_type == 'server':
|
||||||
|
for choice in self.filter_choices:
|
||||||
|
if (choice[0] == filter_field and len(choice) > 2 and
|
||||||
|
choice[2] is True):
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
class FixedFilterAction(FilterAction):
|
class FixedFilterAction(FilterAction):
|
||||||
"""A filter action with fixed buttons."""
|
"""A filter action with fixed buttons."""
|
||||||
|
@@ -1137,22 +1137,40 @@ class DataTable(object):
|
|||||||
and action.needs_preloading)
|
and action.needs_preloading)
|
||||||
valid_method = (request_method == action.method)
|
valid_method = (request_method == action.method)
|
||||||
if valid_method or needs_preloading:
|
if valid_method or needs_preloading:
|
||||||
|
filter_field = self.get_filter_field()
|
||||||
if self._meta.mixed_data_type:
|
if self._meta.mixed_data_type:
|
||||||
self._filtered_data = action.data_type_filter(self,
|
self._filtered_data = action.data_type_filter(self,
|
||||||
self.data,
|
self.data,
|
||||||
filter_string)
|
filter_string)
|
||||||
else:
|
elif not action.is_api_filter(filter_field):
|
||||||
self._filtered_data = action.filter(self,
|
self._filtered_data = action.filter(self,
|
||||||
self.data,
|
self.data,
|
||||||
filter_string)
|
filter_string)
|
||||||
return self._filtered_data
|
return self._filtered_data
|
||||||
|
|
||||||
def get_filter_string(self):
|
def get_filter_string(self):
|
||||||
|
"""Get the filter string value. For 'server' type filters this is
|
||||||
|
saved in the session so that it gets persisted across table loads.
|
||||||
|
For other filter types this is obtained from the POST dict.
|
||||||
|
"""
|
||||||
filter_action = self._meta._filter_action
|
filter_action = self._meta._filter_action
|
||||||
param_name = filter_action.get_param_name()
|
param_name = filter_action.get_param_name()
|
||||||
filter_string = self.request.POST.get(param_name, '')
|
filter_string = ''
|
||||||
|
if filter_action.filter_type == 'server':
|
||||||
|
filter_string = self.request.session.get(param_name, '')
|
||||||
|
else:
|
||||||
|
filter_string = self.request.POST.get(param_name, '')
|
||||||
return filter_string
|
return filter_string
|
||||||
|
|
||||||
|
def get_filter_field(self):
|
||||||
|
"""Get the filter field value used for 'server' type filters. This
|
||||||
|
is the value from the filter action's list of filter choices.
|
||||||
|
"""
|
||||||
|
filter_action = self._meta._filter_action
|
||||||
|
param_name = '%s_field' % filter_action.get_param_name()
|
||||||
|
filter_field = self.request.session.get(param_name, '')
|
||||||
|
return filter_field
|
||||||
|
|
||||||
def _populate_data_cache(self):
|
def _populate_data_cache(self):
|
||||||
self._data_cache = {}
|
self._data_cache = {}
|
||||||
# Set up hash tables to store data points for each column
|
# Set up hash tables to store data points for each column
|
||||||
|
@@ -14,6 +14,7 @@
|
|||||||
|
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
|
|
||||||
|
from django import shortcuts
|
||||||
from django.views import generic
|
from django.views import generic
|
||||||
|
|
||||||
from horizon.templatetags.horizon import has_permissions # noqa
|
from horizon.templatetags.horizon import has_permissions # noqa
|
||||||
@@ -180,6 +181,7 @@ class DataTableView(MultiTableView):
|
|||||||
|
|
||||||
def _get_data_dict(self):
|
def _get_data_dict(self):
|
||||||
if not self._data:
|
if not self._data:
|
||||||
|
self.update_server_filter_action()
|
||||||
self._data = {self.table_class._meta.name: self.get_data()}
|
self._data = {self.table_class._meta.name: self.get_data()}
|
||||||
return self._data
|
return self._data
|
||||||
|
|
||||||
@@ -212,6 +214,63 @@ class DataTableView(MultiTableView):
|
|||||||
context[self.context_object_name] = self.table
|
context[self.context_object_name] = self.table
|
||||||
return context
|
return context
|
||||||
|
|
||||||
|
def post(self, request, *args, **kwargs):
|
||||||
|
# If the server side table filter changed then go back to the first
|
||||||
|
# page of data. Otherwise GET and POST handling are the same.
|
||||||
|
if self.handle_server_filter(request):
|
||||||
|
return shortcuts.redirect(self.get_table().get_absolute_url())
|
||||||
|
return self.get(request, *args, **kwargs)
|
||||||
|
|
||||||
|
def get_server_filter_info(self, request):
|
||||||
|
filter_action = self.get_table()._meta._filter_action
|
||||||
|
if filter_action is None or filter_action.filter_type != 'server':
|
||||||
|
return None
|
||||||
|
param_name = filter_action.get_param_name()
|
||||||
|
filter_string = request.POST.get(param_name)
|
||||||
|
filter_string_session = request.session.get(param_name)
|
||||||
|
changed = (filter_string is not None and
|
||||||
|
filter_string != filter_string_session)
|
||||||
|
if filter_string is None and filter_string_session is not None:
|
||||||
|
filter_string = filter_string_session
|
||||||
|
filter_field_param = param_name + '_field'
|
||||||
|
filter_field = request.POST.get(filter_field_param)
|
||||||
|
filter_field_session = request.session.get(filter_field_param)
|
||||||
|
if filter_field is None and filter_field_session is not None:
|
||||||
|
filter_field = filter_field_session
|
||||||
|
filter_info = {
|
||||||
|
'action': filter_action,
|
||||||
|
'value_param': param_name,
|
||||||
|
'value': filter_string,
|
||||||
|
'field_param': filter_field_param,
|
||||||
|
'field': filter_field,
|
||||||
|
'changed': changed
|
||||||
|
}
|
||||||
|
return filter_info
|
||||||
|
|
||||||
|
def handle_server_filter(self, request):
|
||||||
|
"""Update the table server filter information in the session and
|
||||||
|
determine if the filter has been changed.
|
||||||
|
"""
|
||||||
|
filter_info = self.get_server_filter_info(request)
|
||||||
|
if filter_info is None:
|
||||||
|
return False
|
||||||
|
request.session[filter_info['value_param']] = filter_info['value']
|
||||||
|
if filter_info['field_param']:
|
||||||
|
request.session[filter_info['field_param']] = filter_info['field']
|
||||||
|
return filter_info['changed']
|
||||||
|
|
||||||
|
def update_server_filter_action(self):
|
||||||
|
"""Update the table server side filter action based on the current
|
||||||
|
filter. The filter info may be stored in the session and this will
|
||||||
|
restore it.
|
||||||
|
"""
|
||||||
|
filter_info = self.get_server_filter_info(self.request)
|
||||||
|
if filter_info is not None:
|
||||||
|
action = filter_info['action']
|
||||||
|
setattr(action, 'filter_string', filter_info['value'])
|
||||||
|
if filter_info['field_param']:
|
||||||
|
setattr(action, 'filter_field', filter_info['field'])
|
||||||
|
|
||||||
|
|
||||||
class MixedDataTableView(DataTableView):
|
class MixedDataTableView(DataTableView):
|
||||||
"""A class-based generic view to handle DataTable with mixed data
|
"""A class-based generic view to handle DataTable with mixed data
|
||||||
|
@@ -1,4 +1,5 @@
|
|||||||
# Copyright 2012 Nebula, Inc.
|
# Copyright 2012 Nebula, Inc.
|
||||||
|
# Copyright 2014 IBM Corp.
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
# not use this file except in compliance with the License. You may obtain
|
# not use this file except in compliance with the License. You may obtain
|
||||||
@@ -168,6 +169,20 @@ class MyFilterAction(tables.FilterAction):
|
|||||||
return filter(comp, objs)
|
return filter(comp, objs)
|
||||||
|
|
||||||
|
|
||||||
|
class MyServerFilterAction(tables.FilterAction):
|
||||||
|
filter_type = 'server'
|
||||||
|
filter_choices = (('name', 'Name', False),
|
||||||
|
('status', 'Status', True))
|
||||||
|
needs_preloading = True
|
||||||
|
|
||||||
|
def filter(self, table, items, filter_string):
|
||||||
|
filter_field = table.get_filter_field()
|
||||||
|
if filter_field == 'name' and filter_string:
|
||||||
|
return [item for item in items
|
||||||
|
if filter_string in item.name]
|
||||||
|
return items
|
||||||
|
|
||||||
|
|
||||||
class MyUpdateAction(tables.UpdateAction):
|
class MyUpdateAction(tables.UpdateAction):
|
||||||
def allowed(self, *args):
|
def allowed(self, *args):
|
||||||
return True
|
return True
|
||||||
@@ -221,6 +236,18 @@ class MyTable(tables.DataTable):
|
|||||||
row_actions = (MyAction, MyLinkAction, MyBatchAction, MyToggleAction)
|
row_actions = (MyAction, MyLinkAction, MyBatchAction, MyToggleAction)
|
||||||
|
|
||||||
|
|
||||||
|
class MyServerFilterTable(MyTable):
|
||||||
|
class Meta:
|
||||||
|
name = "my_table"
|
||||||
|
verbose_name = "My Table"
|
||||||
|
status_columns = ["status"]
|
||||||
|
columns = ('id', 'name', 'value', 'optional', 'status')
|
||||||
|
row_class = MyRow
|
||||||
|
column_class = MyColumn
|
||||||
|
table_actions = (MyServerFilterAction, MyAction, MyBatchAction)
|
||||||
|
row_actions = (MyAction, MyLinkAction, MyBatchAction, MyToggleAction)
|
||||||
|
|
||||||
|
|
||||||
class MyTableSelectable(MyTable):
|
class MyTableSelectable(MyTable):
|
||||||
class Meta:
|
class Meta:
|
||||||
name = "my_table"
|
name = "my_table"
|
||||||
@@ -904,6 +931,32 @@ class DataTableTests(test.TestCase):
|
|||||||
self.assertEqual(unicode(row_actions[0].verbose_name), "Delete Me")
|
self.assertEqual(unicode(row_actions[0].verbose_name), "Delete Me")
|
||||||
self.assertEqual(unicode(row_actions[1].verbose_name), "Log In")
|
self.assertEqual(unicode(row_actions[1].verbose_name), "Log In")
|
||||||
|
|
||||||
|
def test_server_filtering(self):
|
||||||
|
filter_value_param = "my_table__filter__q"
|
||||||
|
filter_field_param = '%s_field' % filter_value_param
|
||||||
|
|
||||||
|
# Server Filtering
|
||||||
|
req = self.factory.post('/my_url/')
|
||||||
|
req.session[filter_value_param] = '2'
|
||||||
|
req.session[filter_field_param] = 'name'
|
||||||
|
self.table = MyServerFilterTable(req, TEST_DATA)
|
||||||
|
handled = self.table.maybe_handle()
|
||||||
|
self.assertIsNone(handled)
|
||||||
|
self.assertQuerysetEqual(self.table.filtered_data,
|
||||||
|
['<FakeObject: object_2>'])
|
||||||
|
|
||||||
|
# Ensure API filtering does not filter on server, e.g. no filter here
|
||||||
|
req = self.factory.post('/my_url/')
|
||||||
|
req.session[filter_value_param] = 'up'
|
||||||
|
req.session[filter_field_param] = 'status'
|
||||||
|
self.table = MyServerFilterTable(req, TEST_DATA)
|
||||||
|
handled = self.table.maybe_handle()
|
||||||
|
self.assertIsNone(handled)
|
||||||
|
self.assertQuerysetEqual(self.table.filtered_data,
|
||||||
|
['<FakeObject: object_1>',
|
||||||
|
'<FakeObject: object_2>',
|
||||||
|
'<FakeObject: object_3>'])
|
||||||
|
|
||||||
def test_inline_edit_update_action_get_non_ajax(self):
|
def test_inline_edit_update_action_get_non_ajax(self):
|
||||||
# Non ajax inline edit request should return None.
|
# Non ajax inline edit request should return None.
|
||||||
url = ('/my_url/?action=cell_update'
|
url = ('/my_url/?action=cell_update'
|
||||||
@@ -1183,6 +1236,10 @@ class SingleTableView(table_views.DataTableView):
|
|||||||
return TEST_DATA
|
return TEST_DATA
|
||||||
|
|
||||||
|
|
||||||
|
class APIFilterTableView(SingleTableView):
|
||||||
|
table_class = MyServerFilterTable
|
||||||
|
|
||||||
|
|
||||||
class TableWithPermissions(tables.DataTable):
|
class TableWithPermissions(tables.DataTable):
|
||||||
id = tables.Column('id')
|
id = tables.Column('id')
|
||||||
|
|
||||||
@@ -1248,6 +1305,26 @@ class DataTableViewTests(test.TestCase):
|
|||||||
self.assertEqual(context['table_with_permissions_table'].__class__,
|
self.assertEqual(context['table_with_permissions_table'].__class__,
|
||||||
TableWithPermissions)
|
TableWithPermissions)
|
||||||
|
|
||||||
|
def test_api_filter_table_view(self):
|
||||||
|
filter_value_param = "my_table__filter__q"
|
||||||
|
filter_field_param = '%s_field' % filter_value_param
|
||||||
|
req = self.factory.post('/my_url/', {filter_value_param: 'up',
|
||||||
|
filter_field_param: 'status'})
|
||||||
|
req.user = self.user
|
||||||
|
view = APIFilterTableView()
|
||||||
|
view.request = req
|
||||||
|
view.kwargs = {}
|
||||||
|
view.handle_server_filter(req)
|
||||||
|
context = view.get_context_data()
|
||||||
|
self.assertEqual(context['table'].__class__, MyServerFilterTable)
|
||||||
|
data = view.get_data()
|
||||||
|
self.assertQuerysetEqual(data,
|
||||||
|
['<FakeObject: object_1>',
|
||||||
|
'<FakeObject: object_2>',
|
||||||
|
'<FakeObject: object_3>'])
|
||||||
|
self.assertEqual(req.session.get(filter_value_param), 'up')
|
||||||
|
self.assertEqual(req.session.get(filter_field_param), 'status')
|
||||||
|
|
||||||
|
|
||||||
class FormsetTableTests(test.TestCase):
|
class FormsetTableTests(test.TestCase):
|
||||||
|
|
||||||
|
@@ -55,6 +55,15 @@ class UpdateRow(tables.Row):
|
|||||||
return image
|
return image
|
||||||
|
|
||||||
|
|
||||||
|
class AdminImageFilterAction(tables.FilterAction):
|
||||||
|
filter_type = "server"
|
||||||
|
filter_choices = (('name', _("Image Name ="), True),
|
||||||
|
('status', _('Status ='), True),
|
||||||
|
('disk_format', _('Format ='), True),
|
||||||
|
('size_min', _('Min. Size (MB)'), True),
|
||||||
|
('size_max', _('Max. Size (MB)'), True))
|
||||||
|
|
||||||
|
|
||||||
class AdminImagesTable(project_tables.ImagesTable):
|
class AdminImagesTable(project_tables.ImagesTable):
|
||||||
name = tables.Column("name",
|
name = tables.Column("name",
|
||||||
link="horizon:admin:images:detail",
|
link="horizon:admin:images:detail",
|
||||||
@@ -65,5 +74,6 @@ class AdminImagesTable(project_tables.ImagesTable):
|
|||||||
row_class = UpdateRow
|
row_class = UpdateRow
|
||||||
status_columns = ["status"]
|
status_columns = ["status"]
|
||||||
verbose_name = _("Images")
|
verbose_name = _("Images")
|
||||||
table_actions = (AdminCreateImage, AdminDeleteImage)
|
table_actions = (AdminCreateImage, AdminDeleteImage,
|
||||||
|
AdminImageFilterAction)
|
||||||
row_actions = (AdminEditImage, ViewCustomProperties, AdminDeleteImage)
|
row_actions = (AdminEditImage, ViewCustomProperties, AdminDeleteImage)
|
||||||
|
@@ -16,6 +16,8 @@
|
|||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
from django.core.urlresolvers import reverse_lazy
|
from django.core.urlresolvers import reverse_lazy
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
@@ -29,6 +31,8 @@ from openstack_dashboard.dashboards.admin.images import forms
|
|||||||
from openstack_dashboard.dashboards.admin.images \
|
from openstack_dashboard.dashboards.admin.images \
|
||||||
import tables as project_tables
|
import tables as project_tables
|
||||||
|
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class IndexView(tables.DataTableView):
|
class IndexView(tables.DataTableView):
|
||||||
table_class = project_tables.AdminImagesTable
|
table_class = project_tables.AdminImagesTable
|
||||||
@@ -42,8 +46,7 @@ class IndexView(tables.DataTableView):
|
|||||||
|
|
||||||
def get_data(self):
|
def get_data(self):
|
||||||
images = []
|
images = []
|
||||||
filters = {'is_public': None}
|
filters = self.get_filters()
|
||||||
|
|
||||||
prev_marker = self.request.GET.get(
|
prev_marker = self.request.GET.get(
|
||||||
project_tables.AdminImagesTable._meta.prev_pagination_param, None)
|
project_tables.AdminImagesTable._meta.prev_pagination_param, None)
|
||||||
|
|
||||||
@@ -73,6 +76,28 @@ class IndexView(tables.DataTableView):
|
|||||||
exceptions.handle(self.request, msg)
|
exceptions.handle(self.request, msg)
|
||||||
return images
|
return images
|
||||||
|
|
||||||
|
def get_filters(self):
|
||||||
|
filters = {'is_public': None}
|
||||||
|
filter_field = self.table.get_filter_field()
|
||||||
|
filter_string = self.table.get_filter_string()
|
||||||
|
filter_action = self.table._meta._filter_action
|
||||||
|
if filter_field and filter_string and (
|
||||||
|
filter_action.is_api_filter(filter_field)):
|
||||||
|
if filter_field in ['size_min', 'size_max']:
|
||||||
|
invalid_msg = ('API query is not valid and is ignored: %s=%s'
|
||||||
|
% (filter_field, filter_string))
|
||||||
|
try:
|
||||||
|
filter_string = long(float(filter_string) * (1024 ** 2))
|
||||||
|
if filter_string >= 0:
|
||||||
|
filters[filter_field] = filter_string
|
||||||
|
else:
|
||||||
|
LOG.warning(invalid_msg)
|
||||||
|
except ValueError:
|
||||||
|
LOG.warning(invalid_msg)
|
||||||
|
else:
|
||||||
|
filters[filter_field] = filter_string
|
||||||
|
return filters
|
||||||
|
|
||||||
|
|
||||||
class CreateView(views.CreateView):
|
class CreateView(views.CreateView):
|
||||||
template_name = 'admin/images/create.html'
|
template_name = 'admin/images/create.html'
|
||||||
|
@@ -82,26 +82,28 @@ class AdminUpdateRow(project_tables.UpdateRow):
|
|||||||
|
|
||||||
|
|
||||||
class AdminInstanceFilterAction(tables.FilterAction):
|
class AdminInstanceFilterAction(tables.FilterAction):
|
||||||
|
# Change default name of 'filter' to distinguish this one from the
|
||||||
|
# project instances table filter, since this is used as part of the
|
||||||
|
# session property used for persisting the filter.
|
||||||
|
name = "filter_admin_instances"
|
||||||
filter_type = "server"
|
filter_type = "server"
|
||||||
filter_choices = (('project', _("Project")),
|
filter_choices = (('project', _("Project"), False),
|
||||||
('name', _("Name"))
|
('host', _("Host ="), True),
|
||||||
)
|
('name', _("Name"), True),
|
||||||
needs_preloading = True
|
('ip', _("IPv4 Address ="), True),
|
||||||
|
('ip6', _("IPv6 Address ="), True),
|
||||||
|
('status', _("Status ="), True),
|
||||||
|
('image', _("Image ID ="), True),
|
||||||
|
('flavor', _("Flavor ID ="), True))
|
||||||
|
|
||||||
def filter(self, table, instances, filter_string):
|
def filter(self, table, instances, filter_string):
|
||||||
"""Server side search.
|
"""Server side search.
|
||||||
When filtering is supported in the api, then we will handle in view
|
When filtering is supported in the api, then we will handle in view
|
||||||
"""
|
"""
|
||||||
filter_field = table.request.POST.get('instances__filter__q_field')
|
filter_field = table.get_filter_field()
|
||||||
self.filter_field = filter_field
|
|
||||||
self.filter_string = filter_string
|
|
||||||
if filter_field == 'project' and filter_string:
|
if filter_field == 'project' and filter_string:
|
||||||
return [inst for inst in instances
|
return [inst for inst in instances
|
||||||
if inst.tenant_name == filter_string]
|
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
|
return instances
|
||||||
|
|
||||||
|
|
||||||
|
@@ -72,11 +72,11 @@ class AdminIndexView(tables.DataTableView):
|
|||||||
instances = []
|
instances = []
|
||||||
marker = self.request.GET.get(
|
marker = self.request.GET.get(
|
||||||
project_tables.AdminInstancesTable._meta.pagination_param, None)
|
project_tables.AdminInstancesTable._meta.pagination_param, None)
|
||||||
|
search_opts = self.get_filters({'marker': marker, 'paginate': True})
|
||||||
try:
|
try:
|
||||||
instances, self._more = api.nova.server_list(
|
instances, self._more = api.nova.server_list(
|
||||||
self.request,
|
self.request,
|
||||||
search_opts={'marker': marker,
|
search_opts=search_opts,
|
||||||
'paginate': True},
|
|
||||||
all_tenants=True)
|
all_tenants=True)
|
||||||
except Exception:
|
except Exception:
|
||||||
self._more = False
|
self._more = False
|
||||||
@@ -126,6 +126,15 @@ class AdminIndexView(tables.DataTableView):
|
|||||||
inst.tenant_name = getattr(tenant, "name", None)
|
inst.tenant_name = getattr(tenant, "name", None)
|
||||||
return instances
|
return instances
|
||||||
|
|
||||||
|
def get_filters(self, filters):
|
||||||
|
filter_field = self.table.get_filter_field()
|
||||||
|
filter_action = self.table._meta._filter_action
|
||||||
|
if filter_action.is_api_filter(filter_field):
|
||||||
|
filter_string = self.table.get_filter_string()
|
||||||
|
if filter_field and filter_string:
|
||||||
|
filters[filter_field] = filter_string
|
||||||
|
return filters
|
||||||
|
|
||||||
|
|
||||||
class LiveMigrateView(forms.ModalFormView):
|
class LiveMigrateView(forms.ModalFormView):
|
||||||
form_class = project_forms.LiveMigrateForm
|
form_class = project_forms.LiveMigrateForm
|
||||||
|
@@ -781,12 +781,11 @@ POWER_DISPLAY_CHOICES = (
|
|||||||
|
|
||||||
|
|
||||||
class InstancesFilterAction(tables.FilterAction):
|
class InstancesFilterAction(tables.FilterAction):
|
||||||
|
filter_type = "server"
|
||||||
def filter(self, table, instances, filter_string):
|
filter_choices = (('name', _("Instance Name"), True),
|
||||||
"""Naive case-insensitive search."""
|
('status', _("Status ="), True),
|
||||||
q = filter_string.lower()
|
('image', _("Image ID ="), True),
|
||||||
return [instance for instance in instances
|
('flavor', _("Flavor ID ="), True))
|
||||||
if q in instance.name.lower()]
|
|
||||||
|
|
||||||
|
|
||||||
class InstancesTable(tables.DataTable):
|
class InstancesTable(tables.DataTable):
|
||||||
|
@@ -57,12 +57,12 @@ class IndexView(tables.DataTableView):
|
|||||||
def get_data(self):
|
def get_data(self):
|
||||||
marker = self.request.GET.get(
|
marker = self.request.GET.get(
|
||||||
project_tables.InstancesTable._meta.pagination_param, None)
|
project_tables.InstancesTable._meta.pagination_param, None)
|
||||||
|
search_opts = self.get_filters({'marker': marker, 'paginate': True})
|
||||||
# Gather our instances
|
# Gather our instances
|
||||||
try:
|
try:
|
||||||
instances, self._more = api.nova.server_list(
|
instances, self._more = api.nova.server_list(
|
||||||
self.request,
|
self.request,
|
||||||
search_opts={'marker': marker,
|
search_opts=search_opts)
|
||||||
'paginate': True})
|
|
||||||
except Exception:
|
except Exception:
|
||||||
self._more = False
|
self._more = False
|
||||||
instances = []
|
instances = []
|
||||||
@@ -120,6 +120,15 @@ class IndexView(tables.DataTableView):
|
|||||||
exceptions.handle(self.request, msg)
|
exceptions.handle(self.request, msg)
|
||||||
return instances
|
return instances
|
||||||
|
|
||||||
|
def get_filters(self, filters):
|
||||||
|
filter_field = self.table.get_filter_field()
|
||||||
|
filter_action = self.table._meta._filter_action
|
||||||
|
if filter_action.is_api_filter(filter_field):
|
||||||
|
filter_string = self.table.get_filter_string()
|
||||||
|
if filter_field and filter_string:
|
||||||
|
filters[filter_field] = filter_string
|
||||||
|
return filters
|
||||||
|
|
||||||
|
|
||||||
class LaunchInstanceView(workflows.WorkflowView):
|
class LaunchInstanceView(workflows.WorkflowView):
|
||||||
workflow_class = project_workflows.LaunchInstance
|
workflow_class = project_workflows.LaunchInstance
|
||||||
|
@@ -641,6 +641,9 @@ table form {
|
|||||||
input[type="text"] {
|
input[type="text"] {
|
||||||
padding-right: 26px;
|
padding-right: 26px;
|
||||||
}
|
}
|
||||||
|
select {
|
||||||
|
width: auto;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
td.no-transition {
|
td.no-transition {
|
||||||
|
Reference in New Issue
Block a user