server/api: let users control page size
This commit is contained in:
parent
fba239855c
commit
d97a332d23
2
API.md
2
API.md
|
@ -77,7 +77,7 @@ data.
|
||||||
|
|
||||||
|
|
||||||
### Listing users
|
### Listing users
|
||||||
Request: `GET /users/?page=<page>&query=<query>`
|
Request: `GET /users/?page=<page>&pageSize=<page-size>&query=<query>`
|
||||||
Output:
|
Output:
|
||||||
```json5
|
```json5
|
||||||
{
|
{
|
||||||
|
|
|
@ -38,11 +38,13 @@ class UserListApi(BaseApi):
|
||||||
auth.verify_privilege(context.user, 'users:list')
|
auth.verify_privilege(context.user, 'users:list')
|
||||||
query = context.get_param_as_string('query')
|
query = context.get_param_as_string('query')
|
||||||
page = context.get_param_as_int('page', 1)
|
page = context.get_param_as_int('page', 1)
|
||||||
count, user_list = self._search_executor.execute(context.session, query, page)
|
page_size = min(100, context.get_param_as_int('pageSize', required=False) or 100)
|
||||||
|
count, user_list = self._search_executor.execute(
|
||||||
|
context.session, query, page, page_size)
|
||||||
return {
|
return {
|
||||||
'query': query,
|
'query': query,
|
||||||
'page': page,
|
'page': page,
|
||||||
'pageSize': self._search_executor.page_size,
|
'pageSize': page_size,
|
||||||
'total': count,
|
'total': count,
|
||||||
'users': [_serialize_user(context.user, user) for user in user_list],
|
'users': [_serialize_user(context.user, user) for user in user_list],
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,17 +13,16 @@ class SearchExecutor(object):
|
||||||
ORDER_ASC = 2
|
ORDER_ASC = 2
|
||||||
|
|
||||||
def __init__(self, search_config):
|
def __init__(self, search_config):
|
||||||
self.page_size = 100
|
|
||||||
self._search_config = search_config
|
self._search_config = search_config
|
||||||
|
|
||||||
def execute(self, session, query_text, page):
|
def execute(self, session, query_text, page, page_size):
|
||||||
'''
|
'''
|
||||||
Parse input and return tuple containing total record count and filtered
|
Parse input and return tuple containing total record count and filtered
|
||||||
entities.
|
entities.
|
||||||
'''
|
'''
|
||||||
filter_query = self._prepare(session, query_text)
|
filter_query = self._prepare(session, query_text)
|
||||||
entities = filter_query \
|
entities = filter_query \
|
||||||
.offset((page - 1) * self.page_size).limit(self.page_size).all()
|
.offset((page - 1) * page_size).limit(page_size).all()
|
||||||
count_query = filter_query.statement \
|
count_query = filter_query.statement \
|
||||||
.with_only_columns([sqlalchemy.func.count()]).order_by(None)
|
.with_only_columns([sqlalchemy.func.count()]).order_by(None)
|
||||||
count = filter_query.session.execute(count_query).scalar()
|
count = filter_query.session.execute(count_query).scalar()
|
||||||
|
|
|
@ -25,12 +25,16 @@ def mock_context(parent):
|
||||||
parent.context = context
|
parent.context = context
|
||||||
|
|
||||||
def mock_params(context, params):
|
def mock_params(context, params):
|
||||||
def get_param_as_string(key, default=None):
|
def get_param_as_string(key, default=None, required=False):
|
||||||
if key not in params:
|
if key not in params:
|
||||||
|
if required:
|
||||||
|
raise RuntimeError('Param is missing!')
|
||||||
return default
|
return default
|
||||||
return params[key]
|
return params[key]
|
||||||
def get_param_as_int(key, default=None):
|
def get_param_as_int(key, default=None, required=False):
|
||||||
if key not in params:
|
if key not in params:
|
||||||
|
if required:
|
||||||
|
raise RuntimeError('Param is missing!')
|
||||||
return default
|
return default
|
||||||
return int(params[key])
|
return int(params[key])
|
||||||
context.get_param_as_string = get_param_as_string
|
context.get_param_as_string = get_param_as_string
|
||||||
|
|
|
@ -9,11 +9,20 @@ class TestUserSearchExecutor(DatabaseTestCase):
|
||||||
self.search_config = search.UserSearchConfig()
|
self.search_config = search.UserSearchConfig()
|
||||||
self.executor = search.SearchExecutor(self.search_config)
|
self.executor = search.SearchExecutor(self.search_config)
|
||||||
|
|
||||||
def _test(self, query, page, expected_count, expected_user_names):
|
def _test(self, query, page, page_size, expected_count, expected_user_names):
|
||||||
count, users = self.executor.execute(self.session, query, page)
|
count, users = self.executor.execute(self.session, query, page, page_size)
|
||||||
self.assertEqual(count, expected_count)
|
self.assertEqual(count, expected_count)
|
||||||
self.assertEqual([u.name for u in users], expected_user_names)
|
self.assertEqual([u.name for u in users], expected_user_names)
|
||||||
|
|
||||||
|
def _test_raises(self, query, page, page_size):
|
||||||
|
self.assertRaises(
|
||||||
|
errors.SearchError,
|
||||||
|
self.executor.execute,
|
||||||
|
self.session,
|
||||||
|
query,
|
||||||
|
page,
|
||||||
|
page_size)
|
||||||
|
|
||||||
def test_filter_by_creation_time(self):
|
def test_filter_by_creation_time(self):
|
||||||
user1 = util.mock_user('u1')
|
user1 = util.mock_user('u1')
|
||||||
user2 = util.mock_user('u2')
|
user2 = util.mock_user('u2')
|
||||||
|
@ -21,7 +30,7 @@ class TestUserSearchExecutor(DatabaseTestCase):
|
||||||
user2.creation_time = datetime(2015, 1, 1)
|
user2.creation_time = datetime(2015, 1, 1)
|
||||||
self.session.add_all([user1, user2])
|
self.session.add_all([user1, user2])
|
||||||
for alias in ['creation-time', 'creation-date']:
|
for alias in ['creation-time', 'creation-date']:
|
||||||
self._test('%s:2014' % alias, 1, 1, ['u1'])
|
self._test('%s:2014' % alias, 1, 100, 1, ['u1'])
|
||||||
|
|
||||||
def test_filter_by_negated_creation_time(self):
|
def test_filter_by_negated_creation_time(self):
|
||||||
user1 = util.mock_user('u1')
|
user1 = util.mock_user('u1')
|
||||||
|
@ -30,7 +39,7 @@ class TestUserSearchExecutor(DatabaseTestCase):
|
||||||
user2.creation_time = datetime(2015, 1, 1)
|
user2.creation_time = datetime(2015, 1, 1)
|
||||||
self.session.add_all([user1, user2])
|
self.session.add_all([user1, user2])
|
||||||
for alias in ['creation-time', 'creation-date']:
|
for alias in ['creation-time', 'creation-date']:
|
||||||
self._test('-%s:2014' % alias, 1, 1, ['u2'])
|
self._test('-%s:2014' % alias, 1, 100, 1, ['u2'])
|
||||||
|
|
||||||
def test_filter_by_ranged_creation_time(self):
|
def test_filter_by_ranged_creation_time(self):
|
||||||
user1 = util.mock_user('u1')
|
user1 = util.mock_user('u1')
|
||||||
|
@ -41,12 +50,11 @@ class TestUserSearchExecutor(DatabaseTestCase):
|
||||||
user3.creation_time = datetime(2015, 1, 1)
|
user3.creation_time = datetime(2015, 1, 1)
|
||||||
self.session.add_all([user1, user2, user3])
|
self.session.add_all([user1, user2, user3])
|
||||||
for alias in ['creation-time', 'creation-date']:
|
for alias in ['creation-time', 'creation-date']:
|
||||||
self._test('%s:2014..2014-06' % alias, 1, 2, ['u1', 'u2'])
|
self._test('%s:2014..2014-06' % alias, 1, 100, 2, ['u1', 'u2'])
|
||||||
self._test('%s:2014-06..2015-01-01' % alias, 1, 2, ['u2', 'u3'])
|
self._test('%s:2014-06..2015-01-01' % alias, 1, 100, 2, ['u2', 'u3'])
|
||||||
self._test('%s:2014-06..' % alias, 1, 2, ['u2', 'u3'])
|
self._test('%s:2014-06..' % alias, 1, 100, 2, ['u2', 'u3'])
|
||||||
self._test('%s:..2014-06' % alias, 1, 2, ['u1', 'u2'])
|
self._test('%s:..2014-06' % alias, 1, 100, 2, ['u1', 'u2'])
|
||||||
self.assertRaises(
|
self._test_raises('%s:..' % alias, 1, 100)
|
||||||
errors.SearchError, self.executor.execute, self.session, '%s:..', 1)
|
|
||||||
|
|
||||||
def test_filter_by_negated_ranged_creation_time(self):
|
def test_filter_by_negated_ranged_creation_time(self):
|
||||||
user1 = util.mock_user('u1')
|
user1 = util.mock_user('u1')
|
||||||
|
@ -57,8 +65,8 @@ class TestUserSearchExecutor(DatabaseTestCase):
|
||||||
user3.creation_time = datetime(2015, 1, 1)
|
user3.creation_time = datetime(2015, 1, 1)
|
||||||
self.session.add_all([user1, user2, user3])
|
self.session.add_all([user1, user2, user3])
|
||||||
for alias in ['creation-time', 'creation-date']:
|
for alias in ['creation-time', 'creation-date']:
|
||||||
self._test('-%s:2014..2014-06' % alias, 1, 1, ['u3'])
|
self._test('-%s:2014..2014-06' % alias, 1, 100, 1, ['u3'])
|
||||||
self._test('-%s:2014-06..2015-01-01' % alias, 1, 1, ['u1'])
|
self._test('-%s:2014-06..2015-01-01' % alias, 1, 100, 1, ['u1'])
|
||||||
|
|
||||||
def test_filter_by_composite_creation_time(self):
|
def test_filter_by_composite_creation_time(self):
|
||||||
user1 = util.mock_user('u1')
|
user1 = util.mock_user('u1')
|
||||||
|
@ -69,7 +77,7 @@ class TestUserSearchExecutor(DatabaseTestCase):
|
||||||
user3.creation_time = datetime(2015, 1, 1)
|
user3.creation_time = datetime(2015, 1, 1)
|
||||||
self.session.add_all([user1, user2, user3])
|
self.session.add_all([user1, user2, user3])
|
||||||
for alias in ['creation-time', 'creation-date']:
|
for alias in ['creation-time', 'creation-date']:
|
||||||
self._test('%s:2014-01,2015' % alias, 1, 2, ['u1', 'u3'])
|
self._test('%s:2014-01,2015' % alias, 1, 100, 2, ['u1', 'u3'])
|
||||||
|
|
||||||
def test_filter_by_negated_composite_creation_time(self):
|
def test_filter_by_negated_composite_creation_time(self):
|
||||||
user1 = util.mock_user('u1')
|
user1 = util.mock_user('u1')
|
||||||
|
@ -80,52 +88,50 @@ class TestUserSearchExecutor(DatabaseTestCase):
|
||||||
user3.creation_time = datetime(2015, 1, 1)
|
user3.creation_time = datetime(2015, 1, 1)
|
||||||
self.session.add_all([user1, user2, user3])
|
self.session.add_all([user1, user2, user3])
|
||||||
for alias in ['creation-time', 'creation-date']:
|
for alias in ['creation-time', 'creation-date']:
|
||||||
self._test('-%s:2014-01,2015' % alias, 1, 1, ['u2'])
|
self._test('-%s:2014-01,2015' % alias, 1, 100, 1, ['u2'])
|
||||||
|
|
||||||
def test_filter_by_name(self):
|
def test_filter_by_name(self):
|
||||||
self.session.add(util.mock_user('u1'))
|
self.session.add(util.mock_user('u1'))
|
||||||
self.session.add(util.mock_user('u2'))
|
self.session.add(util.mock_user('u2'))
|
||||||
self._test('name:u1', 1, 1, ['u1'])
|
self._test('name:u1', 1, 100, 1, ['u1'])
|
||||||
self._test('name:u2', 1, 1, ['u2'])
|
self._test('name:u2', 1, 100, 1, ['u2'])
|
||||||
|
|
||||||
def test_filter_by_negated_name(self):
|
def test_filter_by_negated_name(self):
|
||||||
self.session.add(util.mock_user('u1'))
|
self.session.add(util.mock_user('u1'))
|
||||||
self.session.add(util.mock_user('u2'))
|
self.session.add(util.mock_user('u2'))
|
||||||
self._test('-name:u1', 1, 1, ['u2'])
|
self._test('-name:u1', 1, 100, 1, ['u2'])
|
||||||
self._test('-name:u2', 1, 1, ['u1'])
|
self._test('-name:u2', 1, 100, 1, ['u1'])
|
||||||
|
|
||||||
def test_filter_by_composite_name(self):
|
def test_filter_by_composite_name(self):
|
||||||
self.session.add(util.mock_user('u1'))
|
self.session.add(util.mock_user('u1'))
|
||||||
self.session.add(util.mock_user('u2'))
|
self.session.add(util.mock_user('u2'))
|
||||||
self.session.add(util.mock_user('u3'))
|
self.session.add(util.mock_user('u3'))
|
||||||
self._test('name:u1,u2', 1, 2, ['u1', 'u2'])
|
self._test('name:u1,u2', 1, 100, 2, ['u1', 'u2'])
|
||||||
|
|
||||||
def test_filter_by_negated_composite_name(self):
|
def test_filter_by_negated_composite_name(self):
|
||||||
self.session.add(util.mock_user('u1'))
|
self.session.add(util.mock_user('u1'))
|
||||||
self.session.add(util.mock_user('u2'))
|
self.session.add(util.mock_user('u2'))
|
||||||
self.session.add(util.mock_user('u3'))
|
self.session.add(util.mock_user('u3'))
|
||||||
self._test('-name:u1,u3', 1, 1, ['u2'])
|
self._test('-name:u1,u3', 1, 100, 1, ['u2'])
|
||||||
|
|
||||||
def test_filter_by_ranged_name(self):
|
def test_filter_by_ranged_name(self):
|
||||||
self.assertRaises(
|
self._test_raises('name:u1..u2', 1, 100)
|
||||||
errors.SearchError, self.executor.execute, self.session, 'name:u1..u2', 1)
|
|
||||||
|
|
||||||
def test_paging(self):
|
def test_paging(self):
|
||||||
self.executor.page_size = 1
|
|
||||||
self.session.add(util.mock_user('u1'))
|
self.session.add(util.mock_user('u1'))
|
||||||
self.session.add(util.mock_user('u2'))
|
self.session.add(util.mock_user('u2'))
|
||||||
self._test('', 1, 2, ['u1'])
|
self._test('', 1, 1, 2, ['u1'])
|
||||||
self._test('', 2, 2, ['u2'])
|
self._test('', 2, 1, 2, ['u2'])
|
||||||
|
|
||||||
def test_order_by_name(self):
|
def test_order_by_name(self):
|
||||||
self.session.add(util.mock_user('u1'))
|
self.session.add(util.mock_user('u1'))
|
||||||
self.session.add(util.mock_user('u2'))
|
self.session.add(util.mock_user('u2'))
|
||||||
self._test('order:name', 1, 2, ['u1', 'u2'])
|
self._test('order:name', 1, 100, 2, ['u1', 'u2'])
|
||||||
self._test('-order:name', 1, 2, ['u2', 'u1'])
|
self._test('-order:name', 1, 100, 2, ['u2', 'u1'])
|
||||||
self._test('order:name,asc', 1, 2, ['u1', 'u2'])
|
self._test('order:name,asc', 1, 100, 2, ['u1', 'u2'])
|
||||||
self._test('order:name,desc', 1, 2, ['u2', 'u1'])
|
self._test('order:name,desc', 1, 100, 2, ['u2', 'u1'])
|
||||||
self._test('-order:name,asc', 1, 2, ['u2', 'u1'])
|
self._test('-order:name,asc', 1, 100, 2, ['u2', 'u1'])
|
||||||
self._test('-order:name,desc', 1, 2, ['u1', 'u2'])
|
self._test('-order:name,desc', 1, 100, 2, ['u1', 'u2'])
|
||||||
|
|
||||||
def test_invalid_tokens(self):
|
def test_invalid_tokens(self):
|
||||||
for query in [
|
for query in [
|
||||||
|
@ -135,20 +141,24 @@ class TestUserSearchExecutor(DatabaseTestCase):
|
||||||
'order:name,asc,desc',
|
'order:name,asc,desc',
|
||||||
'bad:x',
|
'bad:x',
|
||||||
'special:unsupported']:
|
'special:unsupported']:
|
||||||
self.assertRaises(
|
self._test_raises(query, 1, 100)
|
||||||
errors.SearchError, self.executor.execute, self.session, query, 1)
|
|
||||||
|
|
||||||
def test_anonymous(self):
|
def test_anonymous(self):
|
||||||
self.session.add(util.mock_user('u1'))
|
self.session.add(util.mock_user('u1'))
|
||||||
self.session.add(util.mock_user('u2'))
|
self.session.add(util.mock_user('u2'))
|
||||||
self._test('u1', 1, 1, ['u1'])
|
self._test('u1', 1, 100, 1, ['u1'])
|
||||||
self._test('u2', 1, 1, ['u2'])
|
self._test('u2', 1, 100, 1, ['u2'])
|
||||||
|
|
||||||
|
def test_empty_search(self):
|
||||||
|
self.session.add(util.mock_user('u1'))
|
||||||
|
self.session.add(util.mock_user('u2'))
|
||||||
|
self._test('', 1, 100, 2, ['u1', 'u2'])
|
||||||
|
|
||||||
def test_negated_anonymous(self):
|
def test_negated_anonymous(self):
|
||||||
self.session.add(util.mock_user('u1'))
|
self.session.add(util.mock_user('u1'))
|
||||||
self.session.add(util.mock_user('u2'))
|
self.session.add(util.mock_user('u2'))
|
||||||
self._test('-u1', 1, 1, ['u2'])
|
self._test('-u1', 1, 100, 1, ['u2'])
|
||||||
self._test('-u2', 1, 1, ['u1'])
|
self._test('-u2', 1, 100, 1, ['u1'])
|
||||||
|
|
||||||
def test_combining(self):
|
def test_combining(self):
|
||||||
user1 = util.mock_user('u1')
|
user1 = util.mock_user('u1')
|
||||||
|
@ -158,10 +168,9 @@ class TestUserSearchExecutor(DatabaseTestCase):
|
||||||
user2.creation_time = datetime(2014, 6, 1)
|
user2.creation_time = datetime(2014, 6, 1)
|
||||||
user3.creation_time = datetime(2015, 1, 1)
|
user3.creation_time = datetime(2015, 1, 1)
|
||||||
self.session.add_all([user1, user2, user3])
|
self.session.add_all([user1, user2, user3])
|
||||||
self._test('creation-time:2014 u1', 1, 1, ['u1'])
|
self._test('creation-time:2014 u1', 1, 100, 1, ['u1'])
|
||||||
self._test('creation-time:2014 u2', 1, 1, ['u2'])
|
self._test('creation-time:2014 u2', 1, 100, 1, ['u2'])
|
||||||
self._test('creation-time:2016 u2', 1, 0, [])
|
self._test('creation-time:2016 u2', 1, 100, 0, [])
|
||||||
|
|
||||||
def test_special(self):
|
def test_special(self):
|
||||||
self.assertRaises(
|
self._test_raises('special:-', 1, 100)
|
||||||
errors.SearchError, self.executor.execute, self.session, 'special:-', 1)
|
|
||||||
|
|
Loading…
Reference in New Issue