diff --git a/doc/source/topics/settings.rst b/doc/source/topics/settings.rst index aaa4a28f3..8646a37a9 100644 --- a/doc/source/topics/settings.rst +++ b/doc/source/topics/settings.rst @@ -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`` ------------------------- diff --git a/horizon/tables/base.py b/horizon/tables/base.py index e5ee78099..79dcf0181 100644 --- a/horizon/tables/base.py +++ b/horizon/tables/base.py @@ -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 diff --git a/horizon/tables/views.py b/horizon/tables/views.py index 4b8f3cc26..28d64dac2 100644 --- a/horizon/tables/views.py +++ b/horizon/tables/views.py @@ -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() diff --git a/horizon/templates/horizon/common/_data_table.html b/horizon/templates/horizon/common/_data_table.html index 407ac61f4..5e802b241 100644 --- a/horizon/templates/horizon/common/_data_table.html +++ b/horizon/templates/horizon/common/_data_table.html @@ -46,7 +46,11 @@ {{ row.render }} {% empty %} - {{ table.get_empty_message }} + {% if table.needs_filter_first %} + {{ table.get_filter_first_message }} + {% else %} + {{ table.get_empty_message }} + {% endif %} {% endfor %} diff --git a/openstack_dashboard/dashboards/admin/images/tests.py b/openstack_dashboard/dashboards/admin/images/tests.py index 42c5e7978..a809176e9 100644 --- a/openstack_dashboard/dashboards/admin/images/tests.py +++ b/openstack_dashboard/dashboards/admin/images/tests.py @@ -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',)}) diff --git a/openstack_dashboard/dashboards/admin/images/views.py b/openstack_dashboard/dashboards/admin/images/views.py index fb859c1c5..35a0dfcd3 100644 --- a/openstack_dashboard/dashboards/admin/images/views.py +++ b/openstack_dashboard/dashboards/admin/images/views.py @@ -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 diff --git a/openstack_dashboard/dashboards/admin/instances/tests.py b/openstack_dashboard/dashboards/admin/instances/tests.py index e15c3f211..02cb898a2 100644 --- a/openstack_dashboard/dashboards/admin/instances/tests.py +++ b/openstack_dashboard/dashboards/admin/instances/tests.py @@ -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, []) diff --git a/openstack_dashboard/dashboards/admin/instances/views.py b/openstack_dashboard/dashboards/admin/instances/views.py index 7a83e9c24..498c91f08 100644 --- a/openstack_dashboard/dashboards/admin/instances/views.py +++ b/openstack_dashboard/dashboards/admin/instances/views.py @@ -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) diff --git a/openstack_dashboard/local/local_settings.py.example b/openstack_dashboard/local/local_settings.py.example index 62adc1d0a..00b51f1fa 100644 --- a/openstack_dashboard/local/local_settings.py.example +++ b/openstack_dashboard/local/local_settings.py.example @@ -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 \ No newline at end of file diff --git a/releasenotes/notes/bp-admin-views-filter-first-5b0d8a02b1271135.yaml b/releasenotes/notes/bp-admin-views-filter-first-5b0d8a02b1271135.yaml new file mode 100644 index 000000000..a529d5069 --- /dev/null +++ b/releasenotes/notes/bp-admin-views-filter-first-5b0d8a02b1271135.yaml @@ -0,0 +1,7 @@ +--- +features: + - > + [`blueprint 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. \ No newline at end of file