Rest API for Users list filter
Add the filter functionality that Keystone supports. Used by Randy's Magic Search patch: https://review.openstack.org/#/c/151386/. Also refactor out parse_filters_kwargs method which is used by several rest api. Co-Authored-By: Justin Pomeroy <jpomero@linux.vnet.ibm.com> Change-Id: Ib65395eae345e1368fbb45f6b4ce3cbb9595d61b Partially-Implements: blueprint filtered-client-side-table
This commit is contained in:
parent
e90d224c02
commit
2cca73ff5f
@ -13,13 +13,13 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
/*global angular,horizon*/
|
||||
(function () {
|
||||
'use strict';
|
||||
function KeystoneAPI(apiService) {
|
||||
// Users
|
||||
this.getUsers = function() {
|
||||
return apiService.get('/api/keystone/users/')
|
||||
this.getUsers = function(params) {
|
||||
var config = (params) ? {'params': params} : {};
|
||||
return apiService.get('/api/keystone/users/', config)
|
||||
.error(function () {
|
||||
horizon.alert('error', gettext('Unable to retrieve users'));
|
||||
});
|
||||
@ -150,8 +150,9 @@ limitations under the License.
|
||||
};
|
||||
|
||||
// Projects
|
||||
this.getProjects = function() {
|
||||
return apiService.get('/api/keystone/projects/')
|
||||
this.getProjects = function(params) {
|
||||
var config = (params) ? {'params': params} : {};
|
||||
return apiService.get('/api/keystone/projects/', config)
|
||||
.error(function () {
|
||||
horizon.alert('error', gettext('Unable to retrieve projects'));
|
||||
});
|
||||
|
@ -255,7 +255,7 @@ def tenant_delete(request, project):
|
||||
|
||||
|
||||
def tenant_list(request, paginate=False, marker=None, domain=None, user=None,
|
||||
admin=True):
|
||||
admin=True, filters=None):
|
||||
manager = VERSIONS.get_project_manager(request, admin=admin)
|
||||
page_size = utils.get_page_size(request)
|
||||
|
||||
@ -270,7 +270,13 @@ def tenant_list(request, paginate=False, marker=None, domain=None, user=None,
|
||||
tenants.pop(-1)
|
||||
has_more_data = True
|
||||
else:
|
||||
tenants = manager.list(domain=domain, user=user)
|
||||
kwargs = {
|
||||
"domain": domain,
|
||||
"user": user
|
||||
}
|
||||
if filters is not None:
|
||||
kwargs.update(filters)
|
||||
tenants = manager.list(**kwargs)
|
||||
return (tenants, has_more_data)
|
||||
|
||||
|
||||
@ -284,7 +290,7 @@ def tenant_update(request, project, name=None, description=None,
|
||||
enabled=enabled, domain=domain, **kwargs)
|
||||
|
||||
|
||||
def user_list(request, project=None, domain=None, group=None):
|
||||
def user_list(request, project=None, domain=None, group=None, filters=None):
|
||||
if VERSIONS.active < 3:
|
||||
kwargs = {"tenant_id": project}
|
||||
else:
|
||||
@ -293,6 +299,8 @@ def user_list(request, project=None, domain=None, group=None):
|
||||
"domain": domain,
|
||||
"group": group
|
||||
}
|
||||
if filters is not None:
|
||||
kwargs.update(filters)
|
||||
users = keystoneclient(request, admin=True).users.list(**kwargs)
|
||||
return [VERSIONS.upgrade_v2_user(user) for user in users]
|
||||
|
||||
|
@ -22,18 +22,6 @@ from openstack_dashboard.api.rest import utils as rest_utils
|
||||
from openstack_dashboard.api.rest import urls
|
||||
|
||||
|
||||
def _parse_filters(request):
|
||||
"""Extract REST filter parameters from the request GET args.
|
||||
|
||||
We iterate like this so we avoid Django GET's odd behaviour of
|
||||
handing a list-of-param through some QueryDict accesses.
|
||||
"""
|
||||
filters = {}
|
||||
for param in request.GET:
|
||||
filters[param] = request.GET[param]
|
||||
return filters
|
||||
|
||||
|
||||
@urls.register
|
||||
class Volumes(generic.View):
|
||||
"""API for cinder volumes.
|
||||
@ -56,12 +44,13 @@ class Volumes(generic.View):
|
||||
"""
|
||||
# TODO(clu_): when v2 pagination stuff in Cinder API merges
|
||||
# (https://review.openstack.org/#/c/118450), handle here accordingly
|
||||
|
||||
if request.GET.get('all_projects') == 'true':
|
||||
result = api.cinder.volume_list(request, {'all_tenants': 1})
|
||||
else:
|
||||
result = api.cinder.volume_list(
|
||||
request,
|
||||
search_opts=_parse_filters(request)
|
||||
search_opts=rest_utils.parse_filters_kwargs(request)[0]
|
||||
)
|
||||
return {'items': [u.to_dict() for u in result]}
|
||||
|
||||
@ -81,6 +70,6 @@ class VolumeSnapshots(generic.View):
|
||||
"""
|
||||
result = api.cinder.volume_snapshot_list(
|
||||
request,
|
||||
search_opts=_parse_filters(request)
|
||||
search_opts=rest_utils.parse_filters_kwargs(request)[0]
|
||||
)
|
||||
return {'items': [u.to_dict() for u in result]}
|
||||
|
@ -25,24 +25,6 @@ from openstack_dashboard.api.rest import urls
|
||||
CLIENT_KEYWORDS = {'marker', 'sort_dir', 'sort_key', 'paginate'}
|
||||
|
||||
|
||||
def _parse_filters_kwargs(request):
|
||||
"""REST request parameters are separated appropriately.
|
||||
|
||||
Glance client processes some keywords separately
|
||||
from filters and takes them as separate inputs.
|
||||
This potentially may not be needed when Glance
|
||||
v2 support is brought into Horizon via a separate effort.
|
||||
"""
|
||||
filters = {}
|
||||
kwargs = {}
|
||||
for param in request.GET:
|
||||
if param in CLIENT_KEYWORDS:
|
||||
kwargs[param] = request.GET[param]
|
||||
else:
|
||||
filters[param] = request.GET[param]
|
||||
return filters, kwargs
|
||||
|
||||
|
||||
@urls.register
|
||||
class Image(generic.View):
|
||||
"""API for retrieving a single image
|
||||
@ -93,7 +75,8 @@ class Images(generic.View):
|
||||
separate work stream: https://review.openstack.org/#/c/150084/
|
||||
"""
|
||||
|
||||
filters, kwargs = _parse_filters_kwargs(request)
|
||||
filters, kwargs = rest_utils.parse_filters_kwargs(request,
|
||||
CLIENT_KEYWORDS)
|
||||
|
||||
images, has_more_data, has_prev_data = api.glance.image_list_detailed(
|
||||
request, filters=filters, **kwargs)
|
||||
@ -164,7 +147,8 @@ class MetadefsNamespaces(generic.View):
|
||||
filters.
|
||||
"""
|
||||
|
||||
filters, kwargs = _parse_filters_kwargs(request)
|
||||
filters, kwargs = rest_utils.parse_filters_kwargs(request,
|
||||
CLIENT_KEYWORDS)
|
||||
|
||||
namespaces, has_more, has_prev = api.glance.metadefs_namespace_list(
|
||||
request, filters=filters, **kwargs)
|
||||
|
@ -28,23 +28,31 @@ class Users(generic.View):
|
||||
"""API for keystone users.
|
||||
"""
|
||||
url_regex = r'keystone/users/$'
|
||||
client_keywords = {'project_id', 'domain_id', 'group_id'}
|
||||
|
||||
@rest_utils.ajax()
|
||||
def get(self, request):
|
||||
"""Get a list of users.
|
||||
|
||||
By default, a listing of all users for the current domain are
|
||||
returned. You may specify GET parameters for project_id, group_id and
|
||||
domain_id to change that listing's context.
|
||||
returned. You may specify GET parameters for project_id, domain_id and
|
||||
group_id to change that listing's context.
|
||||
|
||||
The listing result is an object with property "items".
|
||||
"""
|
||||
domain_context = request.session.get('domain_context')
|
||||
|
||||
filters = rest_utils.parse_filters_kwargs(request,
|
||||
self.client_keywords)[0]
|
||||
if len(filters) == 0:
|
||||
filters = None
|
||||
|
||||
result = api.keystone.user_list(
|
||||
request,
|
||||
project=request.GET.get('project_id'),
|
||||
domain=request.GET.get('domain_id', domain_context),
|
||||
group=request.GET.get('group_id')
|
||||
group=request.GET.get('group_id'),
|
||||
filters=filters
|
||||
)
|
||||
return {'items': [u.to_dict() for u in result]}
|
||||
|
||||
@ -358,6 +366,8 @@ class Projects(generic.View):
|
||||
interchangeably.
|
||||
"""
|
||||
url_regex = r'keystone/projects/$'
|
||||
client_keywords = {'paginate', 'marker', 'domain_id',
|
||||
'user_id', 'admin'}
|
||||
|
||||
@rest_utils.ajax()
|
||||
def get(self, request):
|
||||
@ -366,7 +376,7 @@ class Projects(generic.View):
|
||||
By default a listing of all projects for the current domain are
|
||||
returned.
|
||||
|
||||
You may specify GET parameters for project_id (string), user_id
|
||||
You may specify GET parameters for domain_id (string), user_id
|
||||
(string) and admin (boolean) to change that listing's context.
|
||||
Additionally, paginate (boolean) and marker may be used to get
|
||||
paginated listings.
|
||||
@ -378,13 +388,20 @@ class Projects(generic.View):
|
||||
has_more
|
||||
Boolean indicating there are more results when pagination is used.
|
||||
"""
|
||||
|
||||
filters = rest_utils.parse_filters_kwargs(request,
|
||||
self.client_keywords)[0]
|
||||
if len(filters) == 0:
|
||||
filters = None
|
||||
|
||||
result, has_more = api.keystone.tenant_list(
|
||||
request,
|
||||
paginate=request.GET.get('paginate', False),
|
||||
marker=request.GET.get('marker'),
|
||||
domain=request.GET.get('domain_id'),
|
||||
user=request.GET.get('user_id'),
|
||||
admin=request.GET.get('admin', True)
|
||||
admin=request.GET.get('admin', True),
|
||||
filters=filters
|
||||
)
|
||||
# return (list of results, has_more_data)
|
||||
return dict(has_more=has_more, items=[d.to_dict() for d in result])
|
||||
|
@ -130,3 +130,20 @@ def ajax(authenticated=True, data_required=False):
|
||||
|
||||
return _wrapped
|
||||
return decorator
|
||||
|
||||
|
||||
def parse_filters_kwargs(request, client_keywords={}):
|
||||
"""Extract REST filter parameters from the request GET args.
|
||||
|
||||
Client processes some keywords separately from filters and takes
|
||||
them as separate inputs. This will ignore those keys to avoid
|
||||
potential conflicts.
|
||||
"""
|
||||
filters = {}
|
||||
kwargs = {}
|
||||
for param in request.GET:
|
||||
if param in client_keywords:
|
||||
kwargs[param] = request.GET[param]
|
||||
else:
|
||||
filters[param] = request.GET[param]
|
||||
return filters, kwargs
|
||||
|
@ -106,40 +106,3 @@ class ImagesRestTestCase(test.TestCase):
|
||||
self.assertStatusCode(response, 200)
|
||||
gc.metadefs_namespace_get.assert_called_once_with(request,
|
||||
"1")
|
||||
|
||||
def test_parse_filters_keywords(self):
|
||||
kwargs = {
|
||||
'sort_dir': '1',
|
||||
'sort_key': '2',
|
||||
}
|
||||
filters = {
|
||||
'filter1': '1',
|
||||
'filter2': '2',
|
||||
}
|
||||
|
||||
# Combined
|
||||
request_params = dict(kwargs)
|
||||
request_params.update(filters)
|
||||
request = self.mock_rest_request(
|
||||
**{'GET': dict(request_params)})
|
||||
output_filters, output_kwargs = glance._parse_filters_kwargs(request)
|
||||
self.assertDictEqual(kwargs, output_kwargs)
|
||||
self.assertDictEqual(filters, output_filters)
|
||||
|
||||
# Empty Filters
|
||||
request = self.mock_rest_request(**{'GET': dict(kwargs)})
|
||||
output_filters, output_kwargs = glance._parse_filters_kwargs(request)
|
||||
self.assertDictEqual(kwargs, output_kwargs)
|
||||
self.assertDictEqual({}, output_filters)
|
||||
|
||||
# Emtpy keywords
|
||||
request = self.mock_rest_request(**{'GET': dict(filters)})
|
||||
output_filters, output_kwargs = glance._parse_filters_kwargs(request)
|
||||
self.assertDictEqual({}, output_kwargs)
|
||||
self.assertDictEqual(filters, output_filters)
|
||||
|
||||
# Empty both
|
||||
request = self.mock_rest_request(**{'GET': dict()})
|
||||
output_filters, output_kwargs = glance._parse_filters_kwargs(request)
|
||||
self.assertDictEqual({}, output_kwargs)
|
||||
self.assertDictEqual({}, output_filters)
|
||||
|
@ -59,7 +59,27 @@ class KeystoneRestTestCase(test.TestCase):
|
||||
self.assertEqual(response.content,
|
||||
'{"items": [{"name": "Ni!"}, {"name": "Ptang!"}]}')
|
||||
kc.user_list.assert_called_once_with(request, project=None,
|
||||
domain='the_domain', group=None)
|
||||
domain='the_domain', group=None,
|
||||
filters=None)
|
||||
|
||||
@mock.patch.object(keystone.api, 'keystone')
|
||||
def test_user_get_list_with_filters(self, kc):
|
||||
filters = {'enabled': True}
|
||||
request = self.mock_rest_request(**{
|
||||
'session.get': mock.Mock(return_value='the_domain'),
|
||||
'GET': dict(**filters),
|
||||
})
|
||||
kc.user_list.return_value = [
|
||||
mock.Mock(**{'to_dict.return_value': {'name': 'Ni!'}}),
|
||||
mock.Mock(**{'to_dict.return_value': {'name': 'Ptang!'}})
|
||||
]
|
||||
response = keystone.Users().get(request)
|
||||
self.assertStatusCode(response, 200)
|
||||
self.assertEqual(response.content,
|
||||
'{"items": [{"name": "Ni!"}, {"name": "Ptang!"}]}')
|
||||
kc.user_list.assert_called_once_with(request, project=None,
|
||||
domain='the_domain', group=None,
|
||||
filters=filters)
|
||||
|
||||
def test_user_create_full(self):
|
||||
self._test_user_create(
|
||||
@ -442,7 +462,26 @@ class KeystoneRestTestCase(test.TestCase):
|
||||
'"items": [{"name": "Ni!"}, {"name": "Ptang!"}]}')
|
||||
kc.tenant_list.assert_called_once_with(request, paginate=False,
|
||||
marker=None, domain=None,
|
||||
user=None, admin=True)
|
||||
user=None, admin=True,
|
||||
filters=None)
|
||||
|
||||
@mock.patch.object(keystone.api, 'keystone')
|
||||
def test_project_get_list_with_filters(self, kc):
|
||||
filters = {'name': 'Ni!'}
|
||||
request = self.mock_rest_request(**{'GET': dict(**filters)})
|
||||
kc.tenant_list.return_value = ([
|
||||
mock.Mock(**{'to_dict.return_value': {'name': 'Ni!'}}),
|
||||
mock.Mock(**{'to_dict.return_value': {'name': 'Ni!'}})
|
||||
], False)
|
||||
with mock.patch.object(settings, 'DEBUG', True):
|
||||
response = keystone.Projects().get(request)
|
||||
self.assertStatusCode(response, 200)
|
||||
self.assertEqual(response.content, '{"has_more": false, '
|
||||
'"items": [{"name": "Ni!"}, {"name": "Ni!"}]}')
|
||||
kc.tenant_list.assert_called_once_with(request, paginate=False,
|
||||
marker=None, domain=None,
|
||||
user=None, admin=True,
|
||||
filters=filters)
|
||||
|
||||
def test_project_create_full(self):
|
||||
self._test_project_create(
|
||||
|
@ -126,3 +126,43 @@ class RestUtilsTestCase(test.TestCase):
|
||||
self.assertStatusCode(response, 201)
|
||||
self.assertEqual(response['location'], '/api/spam/spam123')
|
||||
self.assertEqual(response.content, '"spam!"')
|
||||
|
||||
def test_parse_filters_keywords(self):
|
||||
kwargs = {
|
||||
'sort_dir': '1',
|
||||
'sort_key': '2',
|
||||
}
|
||||
filters = {
|
||||
'filter1': '1',
|
||||
'filter2': '2',
|
||||
}
|
||||
|
||||
# Combined
|
||||
request_params = dict(kwargs)
|
||||
request_params.update(filters)
|
||||
request = self.mock_rest_request(**{'GET': dict(request_params)})
|
||||
output_filters, output_kwargs = utils.parse_filters_kwargs(
|
||||
request, kwargs)
|
||||
self.assertDictEqual(kwargs, output_kwargs)
|
||||
self.assertDictEqual(filters, output_filters)
|
||||
|
||||
# Empty Filters
|
||||
request = self.mock_rest_request(**{'GET': dict(kwargs)})
|
||||
output_filters, output_kwargs = utils.parse_filters_kwargs(
|
||||
request, kwargs)
|
||||
self.assertDictEqual(kwargs, output_kwargs)
|
||||
self.assertDictEqual({}, output_filters)
|
||||
|
||||
# Empty keywords
|
||||
request = self.mock_rest_request(**{'GET': dict(filters)})
|
||||
output_filters, output_kwargs = utils.parse_filters_kwargs(
|
||||
request)
|
||||
self.assertDictEqual({}, output_kwargs)
|
||||
self.assertDictEqual(filters, output_filters)
|
||||
|
||||
# Empty both
|
||||
request = self.mock_rest_request(**{'GET': dict()})
|
||||
output_filters, output_kwargs = utils.parse_filters_kwargs(
|
||||
request)
|
||||
self.assertDictEqual({}, output_kwargs)
|
||||
self.assertDictEqual({}, output_filters)
|
||||
|
Loading…
x
Reference in New Issue
Block a user