From 2bb5374685d3e8dc1a5ab7632df002b9bac50fd3 Mon Sep 17 00:00:00 2001 From: Chris Church Date: Mon, 26 Aug 2013 12:57:41 -0400 Subject: [PATCH] Added inventory tree view for AC-360. --- awx/main/models/__init__.py | 5 ++++ awx/main/serializers.py | 14 +++++++++++ awx/main/templates/main/_new_in_awx.md | 1 + awx/main/templates/main/api_view.md | 2 +- .../templates/main/inventory_tree_view.md | 15 +++++++++++ awx/main/templates/main/list_api_view.md | 2 +- .../templates/main/list_create_api_view.md | 2 +- awx/main/templates/main/retrieve_api_view.md | 2 +- .../main/retrieve_update_destroy_api_view.md | 2 +- awx/main/templates/main/sub_list_api_view.md | 2 +- .../main/sub_list_create_api_view.md | 2 +- awx/main/tests/inventory.py | 25 +++++++++++++++++++ awx/main/urls.py | 1 + awx/main/views.py | 23 ++++++++++++++--- 14 files changed, 88 insertions(+), 10 deletions(-) create mode 100644 awx/main/templates/main/_new_in_awx.md create mode 100644 awx/main/templates/main/inventory_tree_view.md diff --git a/awx/main/models/__init__.py b/awx/main/models/__init__.py index 1f23c720c4..4ee3480c73 100644 --- a/awx/main/models/__init__.py +++ b/awx/main/models/__init__.py @@ -216,6 +216,11 @@ class Inventory(CommonModel): self.has_active_failures = has_active_failures self.save() + @property + def root_groups(self): + group_pks = self.groups.values_list('pk', flat=True) + return self.groups.exclude(parents__pk__in=group_pks).distinct() + class Host(CommonModelNameNotUnique): ''' A managed node diff --git a/awx/main/serializers.py b/awx/main/serializers.py index d08f6d20d4..e267fd5821 100644 --- a/awx/main/serializers.py +++ b/awx/main/serializers.py @@ -263,6 +263,7 @@ class InventorySerializer(BaseSerializerWithVariables): root_groups = reverse('main:inventory_root_groups_list', args=(obj.pk,)), variable_data = reverse('main:inventory_variable_data', args=(obj.pk,)), script = reverse('main:inventory_script_view', args=(obj.pk,)), + tree = reverse('main:inventory_tree_view', args=(obj.pk,)), organization = reverse('main:organization_detail', args=(obj.organization.pk,)), )) return res @@ -315,6 +316,19 @@ class GroupSerializer(BaseSerializerWithVariables): )) return res +class GroupTreeSerializer(GroupSerializer): + + children = serializers.SerializerMethodField('get_children') + + class Meta: + model = Group + fields = BASE_FIELDS + ('inventory', 'variables', 'has_active_failures', + 'children') + + def get_children(self, obj): + children_qs = obj.children.filter(active=True) + return GroupTreeSerializer(children_qs, many=True).data + class BaseVariableDataSerializer(BaseSerializer): def to_native(self, obj): diff --git a/awx/main/templates/main/_new_in_awx.md b/awx/main/templates/main/_new_in_awx.md new file mode 100644 index 0000000000..766b41a28d --- /dev/null +++ b/awx/main/templates/main/_new_in_awx.md @@ -0,0 +1 @@ +{% if new_in_13 %}> _New in AWX 1.3_{% endif %} diff --git a/awx/main/templates/main/api_view.md b/awx/main/templates/main/api_view.md index 65b67e4a05..77c3bde798 100644 --- a/awx/main/templates/main/api_view.md +++ b/awx/main/templates/main/api_view.md @@ -1,3 +1,3 @@ {{ docstring }} -{% if new_in_13 %}> _New in AWX 1.3_{% endif %} +{% include "main/_new_in_awx.md" %} diff --git a/awx/main/templates/main/inventory_tree_view.md b/awx/main/templates/main/inventory_tree_view.md new file mode 100644 index 0000000000..d76dc468b0 --- /dev/null +++ b/awx/main/templates/main/inventory_tree_view.md @@ -0,0 +1,15 @@ +# Group Tree for this {{ model_verbose_name|title }}: + +Make a GET request to this resource to retrieve a hierarchical view of groups +associated with the selected {{ model_verbose_name }}. + +The resulting data structure contains a list of root groups, with each group +also containing a list of its children. + +## Results + +Each group data structure includes the following fields: + +{% include "main/_result_fields_common.md" %} + +{% include "main/_new_in_awx.md" %} diff --git a/awx/main/templates/main/list_api_view.md b/awx/main/templates/main/list_api_view.md index 90a3e8d529..080d1d2f65 100644 --- a/awx/main/templates/main/list_api_view.md +++ b/awx/main/templates/main/list_api_view.md @@ -5,4 +5,4 @@ Make a GET request to this resource to retrieve the list of {% include "main/_list_common.md" %} -{% if new_in_13 %}> _New in AWX 1.3_{% endif %} +{% include "main/_new_in_awx.md" %} diff --git a/awx/main/templates/main/list_create_api_view.md b/awx/main/templates/main/list_create_api_view.md index 2e2ba03f9b..07344e5b2d 100644 --- a/awx/main/templates/main/list_create_api_view.md +++ b/awx/main/templates/main/list_create_api_view.md @@ -9,4 +9,4 @@ fields to create a new {{ model_verbose_name }}: {% include "main/_result_fields_common.md" %} {% endwith %} -{% if new_in_13 %}> _New in AWX 1.3_{% endif %} +{% include "main/_new_in_awx.md" %} diff --git a/awx/main/templates/main/retrieve_api_view.md b/awx/main/templates/main/retrieve_api_view.md index 79a1131f76..73e4e0885f 100644 --- a/awx/main/templates/main/retrieve_api_view.md +++ b/awx/main/templates/main/retrieve_api_view.md @@ -5,5 +5,5 @@ record containing the following fields: {% include "main/_result_fields_common.md" %} -{% if new_in_13 %}> _New in AWX 1.3_{% endif %} +{% include "main/_new_in_awx.md" %} diff --git a/awx/main/templates/main/retrieve_update_destroy_api_view.md b/awx/main/templates/main/retrieve_update_destroy_api_view.md index 1478d81d46..b3ff94be77 100644 --- a/awx/main/templates/main/retrieve_update_destroy_api_view.md +++ b/awx/main/templates/main/retrieve_update_destroy_api_view.md @@ -17,4 +17,4 @@ For a PATCH request, include only the fields that are being modified. Make a DELETE request to this resource to delete this {{ model_verbose_name }}. -{% if new_in_13 %}> _New in AWX 1.3_{% endif %} +{% include "main/_new_in_awx.md" %} diff --git a/awx/main/templates/main/sub_list_api_view.md b/awx/main/templates/main/sub_list_api_view.md index bbbfbff036..2bfaa88789 100644 --- a/awx/main/templates/main/sub_list_api_view.md +++ b/awx/main/templates/main/sub_list_api_view.md @@ -6,4 +6,4 @@ Make a GET request to this resource to retrieve a list of {% include "main/_list_common.md" %} -{% if new_in_13 %}> _New in AWX 1.3_{% endif %} +{% include "main/_new_in_awx.md" %} diff --git a/awx/main/templates/main/sub_list_create_api_view.md b/awx/main/templates/main/sub_list_create_api_view.md index 92f9764bc2..5dd089e6cb 100644 --- a/awx/main/templates/main/sub_list_create_api_view.md +++ b/awx/main/templates/main/sub_list_create_api_view.md @@ -36,4 +36,4 @@ remove the {{ model_verbose_name }} from this {{ parent_model_verbose_name }} without deleting the {{ model_verbose_name }}. {% endif %} -{% if new_in_13 %}> _New in AWX 1.3_{% endif %} +{% include "main/_new_in_awx.md" %} diff --git a/awx/main/tests/inventory.py b/awx/main/tests/inventory.py index 92c936f599..495e44bd0b 100644 --- a/awx/main/tests/inventory.py +++ b/awx/main/tests/inventory.py @@ -663,6 +663,31 @@ class InventoryTest(BaseTest): # on a group resource, I can see related resources for variables, inventories, and children # and these work + def test_get_inventory_tree(self): + # Group A is parent of B, B is parent of C, C is parent of D. + g_a = self.inventory_a.groups.create(name='A') + g_b = self.inventory_a.groups.create(name='B') + g_b.parents.add(g_a) + g_c = self.inventory_a.groups.create(name='C') + g_c.parents.add(g_b) + g_d = self.inventory_a.groups.create(name='D') + g_d.parents.add(g_c) + + url = reverse('main:inventory_tree_view', args=(self.inventory_a.pk,)) + with self.current_user(self.super_django_user): + response = self.get(url, expect=200) + + self.assertTrue(isinstance(response, list)) + self.assertEqual(len(response), 1) + self.assertEqual(response[0]['id'], g_a.pk) + self.assertEqual(len(response[0]['children']), 1) + self.assertEqual(response[0]['children'][0]['id'], g_b.pk) + self.assertEqual(len(response[0]['children'][0]['children']), 1) + self.assertEqual(response[0]['children'][0]['children'][0]['id'], g_c.pk) + self.assertEqual(len(response[0]['children'][0]['children'][0]['children']), 1) + self.assertEqual(response[0]['children'][0]['children'][0]['children'][0]['id'], g_d.pk) + self.assertEqual(len(response[0]['children'][0]['children'][0]['children'][0]['children']), 0) + def test_migrate_children_when_group_removed(self): # Group A is parent of B, B is parent of C, C is parent of D. g_a = self.inventory_a.groups.create(name='A') diff --git a/awx/main/urls.py b/awx/main/urls.py index 1fae981a0a..6719435c31 100644 --- a/awx/main/urls.py +++ b/awx/main/urls.py @@ -62,6 +62,7 @@ inventory_urls = patterns('awx.main.views', url(r'^(?P[0-9]+)/root_groups/$', 'inventory_root_groups_list'), url(r'^(?P[0-9]+)/variable_data/$', 'inventory_variable_data'), url(r'^(?P[0-9]+)/script/$', 'inventory_script_view'), + url(r'^(?P[0-9]+)/tree/$', 'inventory_tree_view'), ) host_urls = patterns('awx.main.views', diff --git a/awx/main/views.py b/awx/main/views.py index 3c91511d4e..acaa3abd28 100644 --- a/awx/main/views.py +++ b/awx/main/views.py @@ -550,9 +550,7 @@ class InventoryRootGroupsList(SubListCreateAPIView): parent = self.get_parent_object() self.check_parent_access(parent) qs = self.request.user.get_queryset(self.model) - all_pks = parent.groups.values_list('pk', flat=True) - sublist_qs = parent.groups.exclude(parents__pk__in=all_pks).distinct() - return qs & sublist_qs + return qs & parent.root_groups class BaseVariableData(RetrieveUpdateAPIView): @@ -618,6 +616,25 @@ class InventoryScriptView(RetrieveAPIView): return Response(data) +class InventoryTreeView(RetrieveAPIView): + + model = Inventory + filter_backends = () + new_in_13 = True + + def retrieve(self, request, *args, **kwargs): + inventory = self.get_object() + groups_qs = inventory.root_groups.filter(active=True) + data = GroupTreeSerializer(groups_qs, many=True).data + return Response(data) + + def get_description_context(self): + d = super(InventoryTreeView, self).get_description_context() + d.update({ + 'serializer_fields': GroupTreeSerializer().metadata(), + }) + return d + class JobTemplateList(ListCreateAPIView): model = JobTemplate