Implement admin_filter_first in Instances and Images Admin Views
Implementing ADMIN_FILTER_DATA_FIRST setting will allow operators to require admin users provide a search criteria first before loading data into the Admin views. Starting with Instances and Images admin views since they already have server side filtering options. Implements blueprint: admin-views-filter-first Change-Id: I846690854fd443bb98692674bf9fe6733dd04263
This commit is contained in:
parent
8408be31dc
commit
488efd784f
@ -1542,6 +1542,17 @@ Can be used to selectively disable certain costly extensions for performance
|
||||
reasons.
|
||||
|
||||
|
||||
``ADMIN_FILTER_DATA_FIRST``
|
||||
---------------------------
|
||||
|
||||
.. versionadded:: 10.0.0(Newton)
|
||||
|
||||
Default: ``False``
|
||||
|
||||
If True, when admin views load, an empty table will be rendered and the
|
||||
user will be asked to provide a search criteria first (in case no search
|
||||
criteria was provided) before loading any data.
|
||||
|
||||
``OPERATION_LOG_ENABLED``
|
||||
-------------------------
|
||||
|
||||
|
@ -1063,6 +1063,11 @@ class DataTableOptions(object):
|
||||
'data_type_name',
|
||||
"_table_data_type")
|
||||
|
||||
self.filter_first_message = \
|
||||
getattr(options,
|
||||
'filter_first_message',
|
||||
_('Please specify a search criteria first.'))
|
||||
|
||||
|
||||
class DataTableMetaclass(type):
|
||||
"""Metaclass to add options to DataTable class and collect columns."""
|
||||
@ -1177,6 +1182,8 @@ class DataTable(object):
|
||||
self.breadcrumb = None
|
||||
self.current_item_id = None
|
||||
self.permissions = self._meta.permissions
|
||||
self.needs_filter_first = False
|
||||
self._filter_first_message = self._meta.filter_first_message
|
||||
|
||||
# Create a new set
|
||||
columns = []
|
||||
@ -1324,6 +1331,12 @@ class DataTable(object):
|
||||
"""Returns the message to be displayed when there is no data."""
|
||||
return self._no_data_message
|
||||
|
||||
def get_filter_first_message(self):
|
||||
"""Return the message to be displayed when the user needs to provide
|
||||
first a search criteria before loading any data.
|
||||
"""
|
||||
return self._filter_first_message
|
||||
|
||||
def get_object_by_id(self, lookup):
|
||||
"""Returns the data object from the table's dataset which matches
|
||||
the ``lookup`` parameter specified. An error will be raised if
|
||||
|
@ -14,6 +14,7 @@
|
||||
|
||||
from collections import defaultdict
|
||||
|
||||
from django.conf import settings
|
||||
from django import shortcuts
|
||||
|
||||
from horizon import views
|
||||
@ -30,9 +31,11 @@ class MultiTableMixin(object):
|
||||
self.table_classes = getattr(self, "table_classes", [])
|
||||
self._data = {}
|
||||
self._tables = {}
|
||||
|
||||
self._data_methods = defaultdict(list)
|
||||
self.get_data_methods(self.table_classes, self._data_methods)
|
||||
self.admin_filter_first = getattr(settings,
|
||||
'ADMIN_FILTER_DATA_FIRST',
|
||||
False)
|
||||
|
||||
def _get_data_dict(self):
|
||||
if not self._data:
|
||||
@ -116,10 +119,15 @@ class MultiTableMixin(object):
|
||||
def has_more_data(self, table):
|
||||
return False
|
||||
|
||||
def needs_filter_first(self, table):
|
||||
return False
|
||||
|
||||
def handle_table(self, table):
|
||||
name = table.name
|
||||
data = self._get_data_dict()
|
||||
self._tables[name].data = data[table._meta.name]
|
||||
self._tables[name].needs_filter_first = \
|
||||
self.needs_filter_first(table)
|
||||
self._tables[name]._meta.has_more_data = self.has_more_data(table)
|
||||
self._tables[name]._meta.has_prev_data = self.has_prev_data(table)
|
||||
handled = self._tables[name].maybe_handle()
|
||||
|
@ -46,7 +46,11 @@
|
||||
{{ row.render }}
|
||||
{% empty %}
|
||||
<tr class="{% cycle 'odd' 'even' %} empty">
|
||||
{% if table.needs_filter_first %}
|
||||
<td colspan="{{ columns|length }}">{{ table.get_filter_first_message }}</td>
|
||||
{% else %}
|
||||
<td colspan="{{ columns|length }}">{{ table.get_empty_message }}</td>
|
||||
{% endif %}
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
|
@ -68,6 +68,13 @@ class ImagesViewTest(test.BaseAdminViewTests):
|
||||
self.assertEqual(len(res.context['images_table'].data),
|
||||
len(self.images.list()))
|
||||
|
||||
@test.update_settings(ADMIN_FILTER_DATA_FIRST=True)
|
||||
def test_images_list_with_admin_filter_first(self):
|
||||
res = self.client.get(reverse('horizon:admin:images:index'))
|
||||
self.assertTemplateUsed(res, 'admin/images/index.html')
|
||||
images = res.context['table'].data
|
||||
self.assertItemsEqual(images, [])
|
||||
|
||||
@override_settings(API_RESULT_PAGE_SIZE=2)
|
||||
@test.create_stubs({api.glance: ('image_list_detailed',),
|
||||
api.keystone: ('tenant_list',)})
|
||||
|
@ -41,6 +41,7 @@ LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class IndexView(tables.DataTableView):
|
||||
DEFAULT_FILTERS = {'is_public': None}
|
||||
table_class = project_tables.AdminImagesTable
|
||||
template_name = 'admin/images/index.html'
|
||||
page_title = _("Images")
|
||||
@ -51,13 +52,27 @@ class IndexView(tables.DataTableView):
|
||||
def has_more_data(self, table):
|
||||
return self._more
|
||||
|
||||
def needs_filter_first(self, table):
|
||||
return self._needs_filter_first
|
||||
|
||||
def get_data(self):
|
||||
images = []
|
||||
|
||||
if not policy.check((("image", "get_images"),), self.request):
|
||||
msg = _("Insufficient privilege level to retrieve image list.")
|
||||
messages.info(self.request, msg)
|
||||
return images
|
||||
filters = self.get_filters()
|
||||
|
||||
if self.admin_filter_first and \
|
||||
len(filters) == len(self.DEFAULT_FILTERS):
|
||||
self._prev = False
|
||||
self._more = False
|
||||
self._needs_filter_first = True
|
||||
return images
|
||||
|
||||
self._needs_filter_first = False
|
||||
|
||||
prev_marker = self.request.GET.get(
|
||||
project_tables.AdminImagesTable._meta.prev_pagination_param, None)
|
||||
|
||||
@ -97,7 +112,7 @@ class IndexView(tables.DataTableView):
|
||||
return images
|
||||
|
||||
def get_filters(self):
|
||||
filters = {'is_public': None}
|
||||
filters = self.DEFAULT_FILTERS.copy()
|
||||
filter_field = self.table.get_filter_field()
|
||||
filter_string = self.table.get_filter_string()
|
||||
filter_action = self.table._meta._filter_action
|
||||
|
@ -400,3 +400,10 @@ class InstanceViewTest(test.BaseAdminViewTests):
|
||||
res = self.client.get(url)
|
||||
|
||||
self.assertRedirectsNoFollow(res, INDEX_URL)
|
||||
|
||||
@test.update_settings(ADMIN_FILTER_DATA_FIRST=True)
|
||||
def test_index_with_admin_filter_first(self):
|
||||
res = self.client.get(INDEX_URL)
|
||||
self.assertTemplateUsed(res, 'admin/instances/index.html')
|
||||
instances = res.context['table'].data
|
||||
self.assertItemsEqual(instances, [])
|
||||
|
@ -70,11 +70,27 @@ class AdminIndexView(tables.DataTableView):
|
||||
def has_more_data(self, table):
|
||||
return self._more
|
||||
|
||||
def needs_filter_first(self, table):
|
||||
return self._needs_filter_first
|
||||
|
||||
def get_data(self):
|
||||
instances = []
|
||||
marker = self.request.GET.get(
|
||||
project_tables.AdminInstancesTable._meta.pagination_param, None)
|
||||
search_opts = self.get_filters({'marker': marker, 'paginate': True})
|
||||
default_search_opts = {'marker': marker, 'paginate': True}
|
||||
|
||||
search_opts = self.get_filters(default_search_opts.copy())
|
||||
|
||||
"""If admin_filter_first is set and if there are not other filters
|
||||
selected, then search criteria must be provided and return an empty
|
||||
list"""
|
||||
if self.admin_filter_first and \
|
||||
len(search_opts) == len(default_search_opts):
|
||||
self._needs_filter_first = True
|
||||
self._more = False
|
||||
return instances
|
||||
|
||||
self._needs_filter_first = False
|
||||
# Gather our tenants to correlate against IDs
|
||||
try:
|
||||
tenants, has_more = api.keystone.tenant_list(self.request)
|
||||
|
@ -771,3 +771,8 @@ REST_API_REQUIRED_SETTINGS = ['OPENSTACK_HYPERVISOR_FEATURES',
|
||||
# until today (if set to None). This setting should be used to limit the amount
|
||||
# of data fetched by default when rendering the Overview panel.
|
||||
#OVERVIEW_DAYS_RANGE = 1
|
||||
|
||||
# To allow operators to require admin users provide a search criteria first
|
||||
# before loading any data into the admin views, set the following attribute to
|
||||
# True
|
||||
#ADMIN_FILTER_DATA_FIRST=False
|
@ -0,0 +1,7 @@
|
||||
---
|
||||
features:
|
||||
- >
|
||||
[`blueprint admin-views-filter-first <https://blueprints.launchpad.net/horizon/+spec/admin-views-filter-first>`_]
|
||||
This blueprint provides a configurable setting to allow operators
|
||||
require admin users to provide a search criteria first before loading data
|
||||
into admin views.
|
Loading…
Reference in New Issue
Block a user