Add category list pagination support

Also sorting is added.
Default sort is done by category name.

Change-Id: I50254cb58f3924a5a1400859ea30d788e572729f
Closes-Bug: #1474932
This commit is contained in:
Ekaterina Chernova 2015-07-31 11:51:07 +03:00
parent c31739dbdd
commit 0abdd93870
4 changed files with 159 additions and 22 deletions

View File

@ -21,6 +21,7 @@ import jsonschema
from oslo_config import cfg from oslo_config import cfg
from oslo_db import exception as db_exc from oslo_db import exception as db_exc
from oslo_log import log as logging from oslo_log import log as logging
from oslo_log import versionutils
from webob import exc from webob import exc
import murano.api.v1 import murano.api.v1
@ -120,6 +121,23 @@ def _validate_body(body):
class Controller(object): class Controller(object):
"""WSGI controller for application catalog resource in Murano v1 API.""" """WSGI controller for application catalog resource in Murano v1 API."""
def _validate_limit(self, value):
if value is None:
return
try:
value = int(value)
except ValueError:
msg = _("limit param must be an integer")
LOG.error(msg)
raise exc.HTTPBadRequest(explanation=msg)
if value <= 0:
msg = _("limit param must be positive")
LOG.error(msg)
raise exc.HTTPBadRequest(explanation=msg)
return value
def update(self, req, body, package_id): def update(self, req, body, package_id):
"""List of allowed changes: """List of allowed changes:
{ "op": "add", "path": "/tags", "value": [ "foo", "bar" ] } { "op": "add", "path": "/tags", "value": [ "foo", "bar" ] }
@ -158,28 +176,11 @@ class Controller(object):
return package.to_dict() return package.to_dict()
def search(self, req): def search(self, req):
def _validate_limit(value):
if value is None:
return
try:
value = int(value)
except ValueError:
msg = _("limit param must be an integer")
LOG.error(msg)
raise exc.HTTPBadRequest(explanation=msg)
if value <= 0:
msg = _("limit param must be positive")
LOG.error(msg)
raise exc.HTTPBadRequest(explanation=msg)
return value
policy.check("get_package", req.context) policy.check("get_package", req.context)
filters = _get_filters(req.GET.items()) filters = _get_filters(req.GET.items())
limit = _validate_limit(filters.get('limit')) limit = self._validate_limit(filters.get('limit'))
if limit is None: if limit is None:
limit = CONF.packages_opts.limit_param_default limit = CONF.packages_opts.limit_param_default
limit = min(CONF.packages_opts.api_limit_max, limit) limit = min(CONF.packages_opts.api_limit_max, limit)
@ -287,15 +288,65 @@ class Controller(object):
category = db_api.category_get(category_id, packages=True) category = db_api.category_get(category_id, packages=True)
return category.to_dict() return category.to_dict()
@versionutils.deprecated(as_of=versionutils.deprecated.LIBERTY,
in_favor_of='categories.list()')
def show_categories(self, req): def show_categories(self, req):
policy.check("get_category", req.context) policy.check("get_category", req.context)
categories = db_api.categories_list() categories = db_api.categories_list()
return {'categories': [category.name for category in categories]} return {'categories': [category.name for category in categories]}
def list_categories(self, req): def list_categories(self, req):
"""List all categories with pagination and sorting
Acceptable filter params:
:param sort_keys: an array of fields used to sort the list
:param sort_dir: the direction of the sort ('asc' or 'desc')
:param limit: the number of categories to list
:param marker: the ID of the last item in the previous page
"""
def _get_category_filters(req):
query_params = {}
valid_query_params = ['sort_keys', 'sort_dir', 'limit', 'marker']
for key, value in req.GET.items():
if key not in valid_query_params:
raise exc.HTTPBadRequest(
_('Bad value passed to filter.'
' Got {key}, exected:{valid}').format(
key=key, valid=', '.join(valid_query_params)))
if key == 'sort_keys':
available_sort_keys = ['name', 'created',
'updated', 'package_count', 'id']
value = [v.strip() for v in value.split(',')]
for sort_key in value:
if sort_key not in available_sort_keys:
raise exc.HTTPBadRequest(
explanation=_('Invalid sort key: {sort_key}.'
' Must be one of the following:'
' {available}').format(
sort_key=sort_key,
available=', '.join(available_sort_keys)))
if key == 'sort_dir':
if value not in ['asc', 'desc']:
msg = _('Invalid sort direction: {0}').format(value)
raise exc.HTTPBadRequest(explanation=msg)
query_params[key] = value
return query_params
policy.check("get_category", req.context) policy.check("get_category", req.context)
categories = db_api.categories_list()
return {'categories': [category.to_dict() for category in categories]} filters = _get_category_filters(req)
marker = filters.get('marker')
limit = self._validate_limit(filters.get('limit'))
result = {}
categories = db_api.categories_list(filters,
limit=limit,
marker=marker)
if len(categories) == limit:
result['next_marker'] = categories[-1].id
result['categories'] = [category.to_dict() for category in categories]
return result
def add_category(self, req, body=None): def add_category(self, req, body=None):
policy.check("add_category", req.context) policy.check("add_category", req.context)

View File

@ -413,9 +413,20 @@ def category_get(category_id, session=None, packages=False):
return category return category
def categories_list(): def categories_list(filters=None, limit=None, marker=None):
if filters is None:
filters = {}
sort_keys = filters.get('sort_keys', ['name'])
sort_dir = filters.get('sort_dir', 'asc')
session = db_session.get_session() session = db_session.get_session()
return session.query(models.Category).all() query = session.query(models.Category)
if marker is not None:
marker = category_get(marker, session)
query = utils.paginate_query(
query, models.Category, limit, sort_keys, marker, sort_dir)
return query.all()
def category_get_names(): def category_get_names():

View File

@ -406,3 +406,54 @@ This is a fake zip archive
result_message = result.text.replace('\n', '') result_message = result.text.replace('\n', '')
self.assertIn('Category name should be 80 characters maximum', self.assertIn('Category name should be 80 characters maximum',
result_message) result_message)
def test_list_category(self):
names = ['cat1', 'cat2']
for name in names:
db_catalog_api.category_add(name)
self._set_policy_rules({'get_category': '@'})
self.expect_policy_check('get_category')
req = self._get('/catalog/categories')
result = req.get_response(self.api)
self.assertEqual(200, result.status_code)
result_categories = json.loads(result.body)['categories']
self.assertEqual(2, len(result_categories))
self.assertEqual(names, [c['name'] for c in result_categories])
params = {'sort_keys': 'created, id'}
req = self._get('/catalog/categories', params)
self.expect_policy_check('get_category')
result = req.get_response(self.api)
self.assertEqual(200, result.status_code)
result_categories = json.loads(result.body)['categories']
self.assertEqual(names, [c['name'] for c in result_categories])
names.reverse()
params = {'sort_dir': 'desc'}
req = self._get('/catalog/categories', params)
self.expect_policy_check('get_category')
result = req.get_response(self.api)
self.assertEqual(200, result.status_code)
result_categories = json.loads(result.body)['categories']
self.assertEqual(names, [c['name'] for c in result_categories])
def test_list_category_negative(self):
self._set_policy_rules({'get_category': '@'})
self.expect_policy_check('get_category')
req = self._get('/catalog/categories', {'sort_dir': 'test'})
result = req.get_response(self.api)
self.assertEqual(400, result.status_code)
self.expect_policy_check('get_category')
req = self._get('/catalog/categories', {'sort_keys': 'test'})
result = req.get_response(self.api)
self.assertEqual(400, result.status_code)
self.expect_policy_check('get_category')
req = self._get('/catalog/categories', {'test': ['test']})
result = req.get_response(self.api)
self.assertEqual(400, result.status_code)

View File

@ -497,3 +497,27 @@ class CatalogDBTestCase(base.MuranoWithDBTestCase):
api.package_update(id1, [patch], self.context) api.package_update(id1, [patch], self.context)
self.assertRaises(exc.HTTPConflict, api.package_update, self.assertRaises(exc.HTTPConflict, api.package_update,
id2, [patch], self.context_2) id2, [patch], self.context_2)
def test_category_paginate(self):
"""Paginate through a list of categories using limit and marker"""
category_names = ['cat1', 'cat2', 'cat3', 'cat4', 'cat5']
categories = []
for name in category_names:
categories.append(api.category_add(name))
uuids = [c.id for c in categories]
page = api.categories_list(limit=2)
self.assertEqual(category_names[:2], [c.name for c in page])
last = page[-1].id
page = api.categories_list(limit=3, marker=last)
self.assertEqual(category_names[2:5], [c.name for c in page])
page = api.categories_list(marker=uuids[-1])
self.assertEqual([], page)
category_names.reverse()
page = api.categories_list({'sort_dir': 'desc'})
self.assertEqual(category_names, [c.name for c in page])