Merge "Implements subtree_as_ids query param"

This commit is contained in:
Jenkins 2015-02-03 07:28:20 +00:00 committed by Gerrit Code Review
commit cb0f9a1986
3 changed files with 173 additions and 1 deletions

View File

@ -425,6 +425,8 @@ class ProjectV3(controller.V3Controller):
subtree_as_list = 'subtree_as_list' in params and (
self.query_filter_is_true(params['subtree_as_list']))
subtree_as_ids = 'subtree_as_ids' in params and (
self.query_filter_is_true(params['subtree_as_ids']))
# parents_as_list and parents_as_ids are mutually exclusive
if parents_as_list and parents_as_ids:
@ -432,6 +434,12 @@ class ProjectV3(controller.V3Controller):
'params at the same time.')
raise exception.ValidationError(msg)
# subtree_as_list and subtree_as_ids are mutually exclusive
if subtree_as_list and subtree_as_ids:
msg = _('Cannot use subtree_as_list and subtree_as_ids query '
'params at the same time.')
raise exception.ValidationError(msg)
user_id = self.get_auth_context(context).get('user_id')
if parents_as_list:
@ -447,6 +455,9 @@ class ProjectV3(controller.V3Controller):
ref['id'], user_id)
ref['subtree'] = [ProjectV3.wrap_member(context, p)
for p in subtree]
elif subtree_as_ids:
ref['subtree'] = self.resource_api.get_projects_in_subtree_as_ids(
ref['id'])
@controller.protected()
def get_project(self, context, project_id):

View File

@ -281,6 +281,65 @@ class Manager(manager.Manager):
self._filter_projects_list(subtree, user_id)
return subtree
def _build_subtree_as_ids_dict(self, project_id, subtree_by_parent):
# NOTE(rodrigods): we perform a depth first search to construct the
# dictionaries representing each level of the subtree hierarchy. In
# order to improve this traversal performance, we create a cache of
# projects (subtree_py_parent) that accesses in constant time the
# direct children of a given project.
def traverse_subtree_hierarchy(project_id):
children = subtree_by_parent.get(project_id)
if not children:
return None
children_ids = {}
for child in children:
children_ids[child['id']] = traverse_subtree_hierarchy(
child['id'])
return children_ids
return traverse_subtree_hierarchy(project_id)
def get_projects_in_subtree_as_ids(self, project_id):
"""Gets the IDs from the projects in the subtree from a given project.
The project IDs are returned as a structured dictionary representing
their hierarchy. For example, considering the following project
hierarchy::
A
|
+-B-+
| |
C D
If we query for project A subtree, the expected return is the following
dictionary::
'subtree': {
B['id']: {
C['id']: None,
D['id']: None
}
}
"""
def _projects_indexed_by_parent(projects_list):
projects_by_parent = {}
for proj in projects_list:
parent_id = proj.get('parent_id')
if parent_id:
if parent_id in projects_by_parent:
projects_by_parent[parent_id].append(proj)
else:
projects_by_parent[parent_id] = [proj]
return projects_by_parent
subtree_list = self.list_projects_in_subtree(project_id)
subtree_as_ids = self._build_subtree_as_ids_dict(
project_id, _projects_indexed_by_parent(subtree_list))
return subtree_as_ids
@cache.on_arguments(should_cache_fn=SHOULD_CACHE,
expiration_time=EXPIRATION_TIME)
def get_domain(self, domain_id):

View File

@ -572,7 +572,98 @@ class AssignmentTestCase(test_v3.RestfulTestCase):
'project_id': projects[1]['project']['id']},
expected_status=400)
def test_get_project_with_subtree_list(self):
def test_get_project_with_subtree_as_ids(self):
"""Call ``GET /projects/{project_id}?subtree_as_ids``.
This test creates a more complex hierarchy to test if the structured
dictionary returned by using the ``subtree_as_ids`` query param
correctly represents the hierarchy.
The hierarchy contains 5 projects with the following structure::
+--A--+
| |
+--B--+ C
| |
D E
"""
projects = self._create_projects_hierarchy(hierarchy_size=2)
# Add another child to projects[0] - it will be projects[3]
new_ref = self.new_project_ref(
domain_id=self.domain_id,
parent_id=projects[0]['project']['id'])
resp = self.post('/projects',
body={'project': new_ref})
self.assertValidProjectResponse(resp, new_ref)
projects.append(resp.result)
# Add another child to projects[1] - it will be projects[4]
new_ref = self.new_project_ref(
domain_id=self.domain_id,
parent_id=projects[1]['project']['id'])
resp = self.post('/projects',
body={'project': new_ref})
self.assertValidProjectResponse(resp, new_ref)
projects.append(resp.result)
# Query for projects[0] subtree_as_ids
r = self.get(
'/projects/%(project_id)s?subtree_as_ids' % {
'project_id': projects[0]['project']['id']})
self.assertValidProjectResponse(r, projects[0]['project'])
subtree_as_ids = r.result['project']['subtree']
# The subtree hierarchy from projects[0] should have the following
# structure:
# {
# projects[1]: {
# projects[2]: None,
# projects[4]: None
# },
# projects[3]: None
# }
expected_dict = {
projects[1]['project']['id']: {
projects[2]['project']['id']: None,
projects[4]['project']['id']: None
},
projects[3]['project']['id']: None
}
self.assertDictEqual(expected_dict, subtree_as_ids)
# Now query for projects[1] subtree_as_ids
r = self.get(
'/projects/%(project_id)s?subtree_as_ids' % {
'project_id': projects[1]['project']['id']})
self.assertValidProjectResponse(r, projects[1]['project'])
subtree_as_ids = r.result['project']['subtree']
# The subtree hierarchy from projects[1] should have the following
# structure:
# {
# projects[2]: None,
# projects[4]: None
# }
expected_dict = {
projects[2]['project']['id']: None,
projects[4]['project']['id']: None
}
self.assertDictEqual(expected_dict, subtree_as_ids)
# Now query for projects[3] subtree_as_ids
r = self.get(
'/projects/%(project_id)s?subtree_as_ids' % {
'project_id': projects[3]['project']['id']})
self.assertValidProjectResponse(r, projects[3]['project'])
subtree_as_ids = r.result['project']['subtree']
# projects[3] has no subtree, subtree_as_ids must be None
self.assertIsNone(subtree_as_ids)
def test_get_project_with_subtree_as_list(self):
"""Call ``GET /projects/{project_id}?subtree_as_list``."""
projects = self._create_projects_hierarchy(hierarchy_size=2)
@ -585,6 +676,17 @@ class AssignmentTestCase(test_v3.RestfulTestCase):
self.assertNotIn(projects[0], r.result['project']['subtree'])
self.assertIn(projects[2], r.result['project']['subtree'])
def test_get_project_with_subtree_as_list_and_subtree_as_ids(self):
"""Call ``GET /projects/{project_id}?subtree_as_list&subtree_as_ids``.
"""
projects = self._create_projects_hierarchy(hierarchy_size=2)
self.get(
'/projects/%(project_id)s?subtree_as_list&subtree_as_ids' % {
'project_id': projects[1]['project']['id']},
expected_status=400)
def test_update_project(self):
"""Call ``PATCH /projects/{project_id}``."""
ref = self.new_project_ref(domain_id=self.domain_id)