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