Merge "Rest API for Users list filter"

This commit is contained in:
Jenkins 2015-03-06 01:15:32 +00:00 committed by Gerrit Code Review
commit 30e014cf25
9 changed files with 144 additions and 86 deletions

View File

@ -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'));
});

View File

@ -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]

View File

@ -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]}

View File

@ -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)

View File

@ -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])

View File

@ -133,3 +133,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

View File

@ -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)

View File

@ -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(

View File

@ -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)