Merge "Support offset/limit pagination in the API"
This commit is contained in:
commit
6486c9482c
@ -115,13 +115,14 @@ class ProjectGroupsController(rest.RestController):
|
|||||||
|
|
||||||
@decorators.db_exceptions
|
@decorators.db_exceptions
|
||||||
@secure(checks.guest)
|
@secure(checks.guest)
|
||||||
@wsme_pecan.wsexpose([wmodels.ProjectGroup], int, int, wtypes.text,
|
@wsme_pecan.wsexpose([wmodels.ProjectGroup], int, int, int, wtypes.text,
|
||||||
wtypes.text, wtypes.text, wtypes.text)
|
wtypes.text, wtypes.text, wtypes.text)
|
||||||
def get(self, marker=None, limit=None, name=None, title=None,
|
def get(self, marker=None, offset=None, limit=None, name=None, title=None,
|
||||||
sort_field='id', sort_dir='asc'):
|
sort_field='id', sort_dir='asc'):
|
||||||
"""Retrieve a list of projects groups.
|
"""Retrieve a list of projects groups.
|
||||||
|
|
||||||
:param marker: The resource id where the page should begin.
|
:param marker: The resource id where the page should begin.
|
||||||
|
:param offset: The offset to start the page at.
|
||||||
:param limit: The number of project groups to retrieve.
|
:param limit: The number of project groups to retrieve.
|
||||||
:param name: A string to filter the name by.
|
:param name: A string to filter the name by.
|
||||||
:param title: A string to filter the title by.
|
:param title: A string to filter the title by.
|
||||||
@ -133,9 +134,12 @@ class ProjectGroupsController(rest.RestController):
|
|||||||
limit = max(0, limit)
|
limit = max(0, limit)
|
||||||
|
|
||||||
# Resolve the marker record.
|
# Resolve the marker record.
|
||||||
|
marker_group = None
|
||||||
|
if marker is not None:
|
||||||
marker_group = project_groups.project_group_get(marker)
|
marker_group = project_groups.project_group_get(marker)
|
||||||
|
|
||||||
groups = project_groups.project_group_get_all(marker=marker_group,
|
groups = project_groups.project_group_get_all(marker=marker_group,
|
||||||
|
offset=offset,
|
||||||
limit=limit,
|
limit=limit,
|
||||||
name=name,
|
name=name,
|
||||||
title=title,
|
title=title,
|
||||||
@ -151,6 +155,8 @@ class ProjectGroupsController(rest.RestController):
|
|||||||
response.headers['X-Total'] = str(group_count)
|
response.headers['X-Total'] = str(group_count)
|
||||||
if marker_group:
|
if marker_group:
|
||||||
response.headers['X-Marker'] = str(marker_group.id)
|
response.headers['X-Marker'] = str(marker_group.id)
|
||||||
|
if offset is not None:
|
||||||
|
response.headers['X-Offset'] = str(offset)
|
||||||
|
|
||||||
return [wmodels.ProjectGroup.from_db_model(group) for group in groups]
|
return [wmodels.ProjectGroup.from_db_model(group) for group in groups]
|
||||||
|
|
||||||
|
@ -81,13 +81,15 @@ class ProjectsController(rest.RestController):
|
|||||||
|
|
||||||
@decorators.db_exceptions
|
@decorators.db_exceptions
|
||||||
@secure(checks.guest)
|
@secure(checks.guest)
|
||||||
@wsme_pecan.wsexpose([wmodels.Project], int, int, wtypes.text, wtypes.text,
|
@wsme_pecan.wsexpose([wmodels.Project], int, int, int, wtypes.text,
|
||||||
int, wtypes.text, wtypes.text)
|
wtypes.text, int, wtypes.text, wtypes.text)
|
||||||
def get(self, marker=None, limit=None, name=None, description=None,
|
def get(self, marker=None, offset=None, limit=None, name=None,
|
||||||
project_group_id=None, sort_field='id', sort_dir='asc'):
|
description=None, project_group_id=None, sort_field='id',
|
||||||
|
sort_dir='asc'):
|
||||||
"""Retrieve a list of projects.
|
"""Retrieve a list of projects.
|
||||||
|
|
||||||
:param marker: The resource id where the page should begin.
|
:param marker: The resource id where the page should begin.
|
||||||
|
:param offset: The offset to start the page at.
|
||||||
:param limit: The number of projects to retrieve.
|
:param limit: The number of projects to retrieve.
|
||||||
:param name: A string to filter the name by.
|
:param name: A string to filter the name by.
|
||||||
:param description: A string to filter the description by.
|
:param description: A string to filter the description by.
|
||||||
@ -102,10 +104,13 @@ class ProjectsController(rest.RestController):
|
|||||||
limit = max(0, limit)
|
limit = max(0, limit)
|
||||||
|
|
||||||
# Resolve the marker record.
|
# Resolve the marker record.
|
||||||
|
marker_project = None
|
||||||
|
if marker is not None:
|
||||||
marker_project = projects_api.project_get(marker)
|
marker_project = projects_api.project_get(marker)
|
||||||
|
|
||||||
projects = \
|
projects = \
|
||||||
projects_api.project_get_all(marker=marker_project,
|
projects_api.project_get_all(marker=marker_project,
|
||||||
|
offset=offset,
|
||||||
limit=limit,
|
limit=limit,
|
||||||
name=name,
|
name=name,
|
||||||
description=description,
|
description=description,
|
||||||
@ -123,6 +128,8 @@ class ProjectsController(rest.RestController):
|
|||||||
response.headers['X-Total'] = str(project_count)
|
response.headers['X-Total'] = str(project_count)
|
||||||
if marker_project:
|
if marker_project:
|
||||||
response.headers['X-Marker'] = str(marker_project.id)
|
response.headers['X-Marker'] = str(marker_project.id)
|
||||||
|
if offset is not None:
|
||||||
|
response.headers['X-Offset'] = str(offset)
|
||||||
|
|
||||||
return [wmodels.Project.from_db_model(p) for p in projects]
|
return [wmodels.Project.from_db_model(p) for p in projects]
|
||||||
|
|
||||||
|
@ -69,12 +69,12 @@ class StoriesController(rest.RestController):
|
|||||||
@decorators.db_exceptions
|
@decorators.db_exceptions
|
||||||
@secure(checks.guest)
|
@secure(checks.guest)
|
||||||
@wsme_pecan.wsexpose([wmodels.Story], wtypes.text, wtypes.text,
|
@wsme_pecan.wsexpose([wmodels.Story], wtypes.text, wtypes.text,
|
||||||
[wtypes.text], int, int, int, [wtypes.text], int, int,
|
[wtypes.text], int, int, int, [wtypes.text], int,
|
||||||
wtypes.text, wtypes.text, wtypes.text)
|
int, int, wtypes.text, wtypes.text, wtypes.text)
|
||||||
def get_all(self, title=None, description=None, status=None,
|
def get_all(self, title=None, description=None, status=None,
|
||||||
assignee_id=None, project_group_id=None, project_id=None,
|
assignee_id=None, project_group_id=None, project_id=None,
|
||||||
tags=None, marker=None, limit=None, tags_filter_type='all',
|
tags=None, marker=None, offset=None, limit=None,
|
||||||
sort_field='id', sort_dir='asc'):
|
tags_filter_type='all', sort_field='id', sort_dir='asc'):
|
||||||
"""Retrieve definitions of all of the stories.
|
"""Retrieve definitions of all of the stories.
|
||||||
|
|
||||||
:param title: A string to filter the title by.
|
:param title: A string to filter the title by.
|
||||||
@ -85,6 +85,7 @@ class StoriesController(rest.RestController):
|
|||||||
:param project_id: Filter stories by project ID.
|
:param project_id: Filter stories by project ID.
|
||||||
:param tags: A list of tags to filter by.
|
:param tags: A list of tags to filter by.
|
||||||
:param marker: The resource id where the page should begin.
|
:param marker: The resource id where the page should begin.
|
||||||
|
:param offset: The offset to start the page at.
|
||||||
:param limit: The number of stories to retrieve.
|
:param limit: The number of stories to retrieve.
|
||||||
:param tags_filter_type: Type of tags filter.
|
:param tags_filter_type: Type of tags filter.
|
||||||
:param sort_field: The name of the field to sort on.
|
:param sort_field: The name of the field to sort on.
|
||||||
@ -96,6 +97,8 @@ class StoriesController(rest.RestController):
|
|||||||
limit = max(0, limit)
|
limit = max(0, limit)
|
||||||
|
|
||||||
# Resolve the marker record.
|
# Resolve the marker record.
|
||||||
|
marker_story = None
|
||||||
|
if marker:
|
||||||
marker_story = stories_api.story_get(marker)
|
marker_story = stories_api.story_get(marker)
|
||||||
|
|
||||||
stories = stories_api \
|
stories = stories_api \
|
||||||
@ -107,6 +110,7 @@ class StoriesController(rest.RestController):
|
|||||||
project_id=project_id,
|
project_id=project_id,
|
||||||
tags=tags,
|
tags=tags,
|
||||||
marker=marker_story,
|
marker=marker_story,
|
||||||
|
offset=offset,
|
||||||
tags_filter_type=tags_filter_type,
|
tags_filter_type=tags_filter_type,
|
||||||
limit=limit, sort_field=sort_field,
|
limit=limit, sort_field=sort_field,
|
||||||
sort_dir=sort_dir)
|
sort_dir=sort_dir)
|
||||||
@ -126,6 +130,8 @@ class StoriesController(rest.RestController):
|
|||||||
response.headers['X-Total'] = str(story_count)
|
response.headers['X-Total'] = str(story_count)
|
||||||
if marker_story:
|
if marker_story:
|
||||||
response.headers['X-Marker'] = str(marker_story.id)
|
response.headers['X-Marker'] = str(marker_story.id)
|
||||||
|
if offset is not None:
|
||||||
|
response.headers['X-Offset'] = str(offset)
|
||||||
|
|
||||||
return [wmodels.Story.from_db_model(s) for s in stories]
|
return [wmodels.Story.from_db_model(s) for s in stories]
|
||||||
|
|
||||||
|
@ -64,15 +64,16 @@ class TimeLineEventsController(rest.RestController):
|
|||||||
|
|
||||||
@decorators.db_exceptions
|
@decorators.db_exceptions
|
||||||
@secure(checks.guest)
|
@secure(checks.guest)
|
||||||
@wsme_pecan.wsexpose([wmodels.TimeLineEvent], int, [wtypes.text], int, int,
|
@wsme_pecan.wsexpose([wmodels.TimeLineEvent], int, [wtypes.text], int,
|
||||||
wtypes.text, wtypes.text)
|
int, int, wtypes.text, wtypes.text)
|
||||||
def get_all(self, story_id=None, event_type=None, marker=None,
|
def get_all(self, story_id=None, event_type=None, marker=None,
|
||||||
limit=None, sort_field=None, sort_dir=None):
|
offset=None, limit=None, sort_field=None, sort_dir=None):
|
||||||
"""Retrieve all events that have happened under specified story.
|
"""Retrieve all events that have happened under specified story.
|
||||||
|
|
||||||
:param story_id: Filter events by story ID.
|
:param story_id: Filter events by story ID.
|
||||||
:param event_type: A selection of event types to get.
|
:param event_type: A selection of event types to get.
|
||||||
:param marker: The resource id where the page should begin.
|
:param marker: The resource id where the page should begin.
|
||||||
|
:param offset: The offset to start the page at.
|
||||||
:param limit: The number of events to retrieve.
|
:param limit: The number of events to retrieve.
|
||||||
:param sort_field: The name of the field to sort on.
|
:param sort_field: The name of the field to sort on.
|
||||||
:param sort_dir: Sort direction for results (asc, desc).
|
:param sort_dir: Sort direction for results (asc, desc).
|
||||||
@ -92,6 +93,8 @@ class TimeLineEventsController(rest.RestController):
|
|||||||
abort(400, msg)
|
abort(400, msg)
|
||||||
|
|
||||||
# Resolve the marker record.
|
# Resolve the marker record.
|
||||||
|
marker_event = None
|
||||||
|
if marker is not None:
|
||||||
marker_event = events_api.event_get(marker)
|
marker_event = events_api.event_get(marker)
|
||||||
|
|
||||||
event_count = events_api.events_get_count(story_id=story_id,
|
event_count = events_api.events_get_count(story_id=story_id,
|
||||||
@ -99,6 +102,7 @@ class TimeLineEventsController(rest.RestController):
|
|||||||
events = events_api.events_get_all(story_id=story_id,
|
events = events_api.events_get_all(story_id=story_id,
|
||||||
event_type=event_type,
|
event_type=event_type,
|
||||||
marker=marker_event,
|
marker=marker_event,
|
||||||
|
offset=offset,
|
||||||
limit=limit,
|
limit=limit,
|
||||||
sort_field=sort_field,
|
sort_field=sort_field,
|
||||||
sort_dir=sort_dir)
|
sort_dir=sort_dir)
|
||||||
@ -109,6 +113,8 @@ class TimeLineEventsController(rest.RestController):
|
|||||||
response.headers['X-Total'] = str(event_count)
|
response.headers['X-Total'] = str(event_count)
|
||||||
if marker_event:
|
if marker_event:
|
||||||
response.headers['X-Marker'] = str(marker_event.id)
|
response.headers['X-Marker'] = str(marker_event.id)
|
||||||
|
if offset is not None:
|
||||||
|
response.headers['X-Offset'] = str(offset)
|
||||||
|
|
||||||
return [wmodels.TimeLineEvent.resolve_event_values(
|
return [wmodels.TimeLineEvent.resolve_event_values(
|
||||||
wmodels.TimeLineEvent.from_db_model(event)) for event in events]
|
wmodels.TimeLineEvent.from_db_model(event)) for event in events]
|
||||||
|
@ -57,13 +57,14 @@ class UsersController(rest.RestController):
|
|||||||
|
|
||||||
@decorators.db_exceptions
|
@decorators.db_exceptions
|
||||||
@secure(checks.guest)
|
@secure(checks.guest)
|
||||||
@wsme_pecan.wsexpose([wmodels.User], int, int, wtypes.text, wtypes.text,
|
@wsme_pecan.wsexpose([wmodels.User], int, int, int, wtypes.text,
|
||||||
wtypes.text, wtypes.text)
|
wtypes.text, wtypes.text, wtypes.text)
|
||||||
def get(self, marker=None, limit=None, full_name=None,
|
def get(self, marker=None, offset=None, limit=None, full_name=None,
|
||||||
sort_field='id', sort_dir='asc'):
|
sort_field='id', sort_dir='asc'):
|
||||||
"""Page and filter the users in storyboard.
|
"""Page and filter the users in storyboard.
|
||||||
|
|
||||||
:param marker: The resource id where the page should begin.
|
:param marker: The resource id where the page should begin.
|
||||||
|
:param offset: The offset to start the page at.
|
||||||
:param limit: The number of users to retrieve.
|
:param limit: The number of users to retrieve.
|
||||||
:param username: A string of characters to filter the username with.
|
:param username: A string of characters to filter the username with.
|
||||||
:param full_name: A string of characters to filter the full_name with.
|
:param full_name: A string of characters to filter the full_name with.
|
||||||
@ -76,9 +77,13 @@ class UsersController(rest.RestController):
|
|||||||
limit = max(0, limit)
|
limit = max(0, limit)
|
||||||
|
|
||||||
# Resolve the marker record.
|
# Resolve the marker record.
|
||||||
|
marker_user = None
|
||||||
|
if marker is not None:
|
||||||
marker_user = users_api.user_get(marker)
|
marker_user = users_api.user_get(marker)
|
||||||
|
|
||||||
users = users_api.user_get_all(marker=marker_user, limit=limit,
|
users = users_api.user_get_all(marker=marker_user,
|
||||||
|
offset=offset,
|
||||||
|
limit=limit,
|
||||||
full_name=full_name,
|
full_name=full_name,
|
||||||
filter_non_public=True,
|
filter_non_public=True,
|
||||||
sort_field=sort_field,
|
sort_field=sort_field,
|
||||||
@ -91,6 +96,8 @@ class UsersController(rest.RestController):
|
|||||||
response.headers['X-Total'] = str(user_count)
|
response.headers['X-Total'] = str(user_count)
|
||||||
if marker_user:
|
if marker_user:
|
||||||
response.headers['X-Marker'] = str(marker_user.id)
|
response.headers['X-Marker'] = str(marker_user.id)
|
||||||
|
if offset is not None:
|
||||||
|
response.headers['X-Offset'] = str(offset)
|
||||||
|
|
||||||
return [wmodels.User.from_db_model(u) for u in users]
|
return [wmodels.User.from_db_model(u) for u in users]
|
||||||
|
|
||||||
|
@ -95,15 +95,26 @@ def get_engine():
|
|||||||
|
|
||||||
|
|
||||||
def paginate_query(query, model, limit, sort_key, marker=None,
|
def paginate_query(query, model, limit, sort_key, marker=None,
|
||||||
sort_dir=None, sort_dirs=None):
|
offset=None, sort_dir=None, sort_dirs=None):
|
||||||
|
if offset is not None:
|
||||||
|
# If we are doing offset-based pagination, don't set a
|
||||||
|
# limit or a marker.
|
||||||
|
# FIXME: Eventually the webclient should be smart enough
|
||||||
|
# to do marker-based pagination, at which point this will
|
||||||
|
# be unnecessary.
|
||||||
|
start, end = (offset, offset + limit)
|
||||||
|
limit, marker = (None, None)
|
||||||
try:
|
try:
|
||||||
return utils_paginate_query(query=query,
|
sorted_query = utils_paginate_query(query=query,
|
||||||
model=model,
|
model=model,
|
||||||
limit=limit,
|
limit=limit,
|
||||||
sort_keys=[sort_key],
|
sort_keys=[sort_key],
|
||||||
marker=marker,
|
marker=marker,
|
||||||
sort_dir=sort_dir,
|
sort_dir=sort_dir,
|
||||||
sort_dirs=sort_dirs)
|
sort_dirs=sort_dirs)
|
||||||
|
if offset is not None:
|
||||||
|
return sorted_query.slice(start, end)
|
||||||
|
return sorted_query
|
||||||
except ValueError as ve:
|
except ValueError as ve:
|
||||||
raise exc.DBValueError(message=str(ve))
|
raise exc.DBValueError(message=str(ve))
|
||||||
except InvalidSortKey:
|
except InvalidSortKey:
|
||||||
@ -187,8 +198,9 @@ def entity_get(kls, entity_id, filter_non_public=False, session=None):
|
|||||||
return entity
|
return entity
|
||||||
|
|
||||||
|
|
||||||
def entity_get_all(kls, filter_non_public=False, marker=None, limit=None,
|
def entity_get_all(kls, filter_non_public=False, marker=None, offset=None,
|
||||||
sort_field='id', sort_dir='asc', session=None, **kwargs):
|
limit=None, sort_field='id', sort_dir='asc', session=None,
|
||||||
|
**kwargs):
|
||||||
# Sanity checks, in case someone accidentally explicitly passes in 'None'
|
# Sanity checks, in case someone accidentally explicitly passes in 'None'
|
||||||
if not sort_field:
|
if not sort_field:
|
||||||
sort_field = 'id'
|
sort_field = 'id'
|
||||||
@ -208,6 +220,7 @@ def entity_get_all(kls, filter_non_public=False, marker=None, limit=None,
|
|||||||
limit=limit,
|
limit=limit,
|
||||||
sort_key=sort_field,
|
sort_key=sort_field,
|
||||||
marker=marker,
|
marker=marker,
|
||||||
|
offset=offset,
|
||||||
sort_dir=sort_dir)
|
sort_dir=sort_dir)
|
||||||
|
|
||||||
# Execute the query
|
# Execute the query
|
||||||
@ -220,7 +233,7 @@ def entity_get_all(kls, filter_non_public=False, marker=None, limit=None,
|
|||||||
raise exc.DBInvalidUnicodeParameter()
|
raise exc.DBInvalidUnicodeParameter()
|
||||||
|
|
||||||
if len(entities) > 0 and filter_non_public:
|
if len(entities) > 0 and filter_non_public:
|
||||||
sample_entity = entities[0] if len(entities) > 0 else None
|
sample_entity = entities[0]
|
||||||
public_fields = getattr(sample_entity, "_public_fields", [])
|
public_fields = getattr(sample_entity, "_public_fields", [])
|
||||||
|
|
||||||
entities = [_filter_non_public_fields(entity, public_fields)
|
entities = [_filter_non_public_fields(entity, public_fields)
|
||||||
|
@ -28,8 +28,8 @@ def project_get_by_name(name):
|
|||||||
return query.filter_by(name=name).first()
|
return query.filter_by(name=name).first()
|
||||||
|
|
||||||
|
|
||||||
def project_get_all(marker=None, limit=None, sort_field=None, sort_dir=None,
|
def project_get_all(marker=None, offset=None, limit=None, sort_field=None,
|
||||||
project_group_id=None, **kwargs):
|
sort_dir=None, project_group_id=None, **kwargs):
|
||||||
# Sanity checks, in case someone accidentally explicitly passes in 'None'
|
# Sanity checks, in case someone accidentally explicitly passes in 'None'
|
||||||
if not sort_field:
|
if not sort_field:
|
||||||
sort_field = 'id'
|
sort_field = 'id'
|
||||||
@ -45,6 +45,7 @@ def project_get_all(marker=None, limit=None, sort_field=None, sort_dir=None,
|
|||||||
limit=limit,
|
limit=limit,
|
||||||
sort_key=sort_field,
|
sort_key=sort_field,
|
||||||
marker=marker,
|
marker=marker,
|
||||||
|
offset=offset,
|
||||||
sort_dir=sort_dir)
|
sort_dir=sort_dir)
|
||||||
|
|
||||||
# Execute the query
|
# Execute the query
|
||||||
|
@ -56,8 +56,8 @@ def story_get(story_id, session=None):
|
|||||||
|
|
||||||
def story_get_all(title=None, description=None, status=None, assignee_id=None,
|
def story_get_all(title=None, description=None, status=None, assignee_id=None,
|
||||||
project_group_id=None, project_id=None, tags=None,
|
project_group_id=None, project_id=None, tags=None,
|
||||||
marker=None, limit=None, tags_filter_type="all",
|
marker=None, offset=None, limit=None,
|
||||||
sort_field='id', sort_dir='asc'):
|
tags_filter_type="all", sort_field='id', sort_dir='asc'):
|
||||||
# Sanity checks, in case someone accidentally explicitly passes in 'None'
|
# Sanity checks, in case someone accidentally explicitly passes in 'None'
|
||||||
if not sort_field:
|
if not sort_field:
|
||||||
sort_field = 'id'
|
sort_field = 'id'
|
||||||
@ -86,12 +86,12 @@ def story_get_all(title=None, description=None, status=None, assignee_id=None,
|
|||||||
query = query.filter(models.StorySummary.status.in_(status))
|
query = query.filter(models.StorySummary.status.in_(status))
|
||||||
|
|
||||||
# paginate the query
|
# paginate the query
|
||||||
|
|
||||||
query = api_base.paginate_query(query=query,
|
query = api_base.paginate_query(query=query,
|
||||||
model=models.StorySummary,
|
model=models.StorySummary,
|
||||||
limit=limit,
|
limit=limit,
|
||||||
sort_key=sort_field,
|
sort_key=sort_field,
|
||||||
marker=marker,
|
marker=marker,
|
||||||
|
offset=offset,
|
||||||
sort_dir=sort_dir)
|
sort_dir=sort_dir)
|
||||||
|
|
||||||
raw_stories = query.all()
|
raw_stories = query.all()
|
||||||
|
@ -25,10 +25,12 @@ def user_get(user_id, filter_non_public=False):
|
|||||||
return entity
|
return entity
|
||||||
|
|
||||||
|
|
||||||
def user_get_all(marker=None, limit=None, filter_non_public=False,
|
def user_get_all(marker=None, offset=None, limit=None,
|
||||||
sort_field=None, sort_dir=None, **kwargs):
|
filter_non_public=False, sort_field=None, sort_dir=None,
|
||||||
|
**kwargs):
|
||||||
return api_base.entity_get_all(models.User,
|
return api_base.entity_get_all(models.User,
|
||||||
marker=marker,
|
marker=marker,
|
||||||
|
offset=offset,
|
||||||
limit=limit,
|
limit=limit,
|
||||||
filter_non_public=filter_non_public,
|
filter_non_public=filter_non_public,
|
||||||
sort_field=sort_field,
|
sort_field=sort_field,
|
||||||
|
Loading…
Reference in New Issue
Block a user