Implements parents_as_ids query param
This change implements the parents_as_ids query param. It gives the possibility to retrieve the parents from a project in a structured way by using nested dictionaries. It reuses the already implemented list_projects_parents() method and builds the dictionaries hierarchy using the returned list. bp hierarchical-multitenancy-improvements Change-Id: I5fb560a934881721cb233cc1549324a5171f470c
This commit is contained in:
parent
08afed58d3
commit
0ee272c17f
|
@ -416,18 +416,33 @@ class ProjectV3(controller.V3Controller):
|
|||
return ProjectV3.wrap_collection(context, refs, hints=hints)
|
||||
|
||||
def _expand_project_ref(self, context, ref):
|
||||
params = context['query_string']
|
||||
|
||||
parents_as_list = 'parents_as_list' in params and (
|
||||
self.query_filter_is_true(params['parents_as_list']))
|
||||
parents_as_ids = 'parents_as_ids' in params and (
|
||||
self.query_filter_is_true(params['parents_as_ids']))
|
||||
|
||||
subtree_as_list = 'subtree_as_list' in params and (
|
||||
self.query_filter_is_true(params['subtree_as_list']))
|
||||
|
||||
# parents_as_list and parents_as_ids are mutually exclusive
|
||||
if parents_as_list and parents_as_ids:
|
||||
msg = _('Cannot use parents_as_list and parents_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' in context['query_string'] and
|
||||
self.query_filter_is_true(
|
||||
context['query_string']['parents_as_list'])):
|
||||
|
||||
if parents_as_list:
|
||||
parents = self.resource_api.list_project_parents(
|
||||
ref['id'], user_id)
|
||||
ref['parents'] = [ProjectV3.wrap_member(context, p)
|
||||
for p in parents]
|
||||
elif parents_as_ids:
|
||||
ref['parents'] = self.resource_api.get_project_parents_as_ids(ref)
|
||||
|
||||
if ('subtree_as_list' in context['query_string'] and
|
||||
self.query_filter_is_true(
|
||||
context['query_string']['subtree_as_list'])):
|
||||
if subtree_as_list:
|
||||
subtree = self.resource_api.list_projects_in_subtree(
|
||||
ref['id'], user_id)
|
||||
ref['subtree'] = [ProjectV3.wrap_member(context, p)
|
||||
|
|
|
@ -230,6 +230,49 @@ class Manager(manager.Manager):
|
|||
self._filter_projects_list(parents, user_id)
|
||||
return parents
|
||||
|
||||
def _build_parents_as_ids_dict(self, project, parents_by_id):
|
||||
# NOTE(rodrigods): we don't rely in the order of the projects returned
|
||||
# by the list_project_parents() method. Thus, we create a project cache
|
||||
# (parents_by_id) in order to access each parent in constant time and
|
||||
# traverse up the hierarchy.
|
||||
def traverse_parents_hierarchy(project):
|
||||
parent_id = project.get('parent_id')
|
||||
if not parent_id:
|
||||
return None
|
||||
|
||||
parent = parents_by_id[parent_id]
|
||||
return {parent_id: traverse_parents_hierarchy(parent)}
|
||||
|
||||
return traverse_parents_hierarchy(project)
|
||||
|
||||
def get_project_parents_as_ids(self, project):
|
||||
"""Gets the IDs from the parents from a given project.
|
||||
|
||||
The project IDs are returned as a structured dictionary traversing up
|
||||
the hierarchy to the top level project. For example, considering the
|
||||
following project hierarchy::
|
||||
|
||||
A
|
||||
|
|
||||
+-B-+
|
||||
| |
|
||||
C D
|
||||
|
||||
If we query for project C parents, the expected return is the following
|
||||
dictionary::
|
||||
|
||||
'parents': {
|
||||
B['id']: {
|
||||
A['id']: None
|
||||
}
|
||||
}
|
||||
|
||||
"""
|
||||
parents_list = self.list_project_parents(project['id'])
|
||||
parents_as_ids = self._build_parents_as_ids_dict(
|
||||
project, {proj['id']: proj for proj in parents_list})
|
||||
return parents_as_ids
|
||||
|
||||
def list_projects_in_subtree(self, project_id, user_id=None):
|
||||
subtree = self.driver.list_projects_in_subtree(project_id)
|
||||
# If a user_id was provided, the returned list should be filtered
|
||||
|
|
|
@ -509,7 +509,46 @@ class AssignmentTestCase(test_v3.RestfulTestCase):
|
|||
'project_id': self.project_id})
|
||||
self.assertValidProjectResponse(r, self.project)
|
||||
|
||||
def test_get_project_with_parents_list(self):
|
||||
def test_get_project_with_parents_as_ids(self):
|
||||
"""Call ``GET /projects/{project_id}?parents_as_ids``."""
|
||||
projects = self._create_projects_hierarchy(hierarchy_size=2)
|
||||
|
||||
# Query for projects[2] parents_as_ids
|
||||
r = self.get(
|
||||
'/projects/%(project_id)s?parents_as_ids' % {
|
||||
'project_id': projects[2]['project']['id']})
|
||||
|
||||
self.assertValidProjectResponse(r, projects[2]['project'])
|
||||
parents_as_ids = r.result['project']['parents']
|
||||
|
||||
# Assert parents_as_ids is a structured dictionary correctly
|
||||
# representing the hierarchy. The request was made using projects[2]
|
||||
# id, hence its parents should be projects[1] and projects[0]. It
|
||||
# should have the following structure:
|
||||
# {
|
||||
# projects[1]: {
|
||||
# projects[0]: None
|
||||
# }
|
||||
# }
|
||||
expected_dict = {
|
||||
projects[1]['project']['id']: {
|
||||
projects[0]['project']['id']: None
|
||||
}
|
||||
}
|
||||
self.assertDictEqual(expected_dict, parents_as_ids)
|
||||
|
||||
# Query for projects[0] parents_as_ids
|
||||
r = self.get(
|
||||
'/projects/%(project_id)s?parents_as_ids' % {
|
||||
'project_id': projects[0]['project']['id']})
|
||||
|
||||
self.assertValidProjectResponse(r, projects[0]['project'])
|
||||
parents_as_ids = r.result['project']['parents']
|
||||
|
||||
# projects[0] has no parents, parents_as_ids must be None
|
||||
self.assertIsNone(parents_as_ids)
|
||||
|
||||
def test_get_project_with_parents_as_list(self):
|
||||
"""Call ``GET /projects/{project_id}?parents_as_list``."""
|
||||
projects = self._create_projects_hierarchy(hierarchy_size=2)
|
||||
|
||||
|
@ -522,6 +561,17 @@ class AssignmentTestCase(test_v3.RestfulTestCase):
|
|||
self.assertIn(projects[0], r.result['project']['parents'])
|
||||
self.assertNotIn(projects[2], r.result['project']['parents'])
|
||||
|
||||
def test_get_project_with_parents_as_list_and_parents_as_ids(self):
|
||||
"""Call ``GET /projects/{project_id}?parents_as_list&parents_as_ids``.
|
||||
|
||||
"""
|
||||
projects = self._create_projects_hierarchy(hierarchy_size=2)
|
||||
|
||||
self.get(
|
||||
'/projects/%(project_id)s?parents_as_list&parents_as_ids' % {
|
||||
'project_id': projects[1]['project']['id']},
|
||||
expected_status=400)
|
||||
|
||||
def test_get_project_with_subtree_list(self):
|
||||
"""Call ``GET /projects/{project_id}?subtree_as_list``."""
|
||||
projects = self._create_projects_hierarchy(hierarchy_size=2)
|
||||
|
|
Loading…
Reference in New Issue