diff --git a/awx/main/access.py b/awx/main/access.py index 92168078e8..6e7e87c5f9 100644 --- a/awx/main/access.py +++ b/awx/main/access.py @@ -430,7 +430,7 @@ class GroupAccess(BaseAccess): class InventorySourceAccess(BaseAccess): ''' - I can see inventory sources whenever I can see their group. + I can see inventory sources whenever I can see their group or inventory. I can change inventory sources whenever I can change their group. ''' @@ -439,11 +439,17 @@ class InventorySourceAccess(BaseAccess): def get_queryset(self): qs = self.model.objects.filter(active=True).distinct() qs = qs.select_related('created_by', 'group') - groups_qs = self.user.get_queryset(Group) - return qs.filter(group__in=groups_qs) + inventories_qs = self.user.get_queryset(Inventory) + return qs.filter(Q(inventory__in=inventories_qs) | + Q(group__inventory__in=inventories_qs)) def can_read(self, obj): - return obj and self.user.can_access(Group, 'read', obj.group) + if obj and obj.group: + return self.user.can_access(Group, 'read', obj.group) + elif obj and obj.inventory: + return self.user.can_access(Inventory, 'read', obj.inventory) + else: + return False def can_add(self, data): # Automatically created from group or management command. diff --git a/awx/main/models/__init__.py b/awx/main/models/__init__.py index 1c71365910..0e41b99e93 100644 --- a/awx/main/models/__init__.py +++ b/awx/main/models/__init__.py @@ -537,6 +537,7 @@ class InventorySource(PrimordialModel): related_name='inventory_sources', null=True, default=None, + editable=False, ) group = AutoOneToOneField( 'Group', diff --git a/awx/main/serializers.py b/awx/main/serializers.py index 8ede04577f..ca7b2a5fd8 100644 --- a/awx/main/serializers.py +++ b/awx/main/serializers.py @@ -43,10 +43,13 @@ SUMMARIZABLE_FK_FIELDS = { 'user': ('username', 'first_name', 'last_name'), 'team': DEFAULT_SUMMARY_FIELDS, 'inventory': DEFAULT_SUMMARY_FIELDS + ('has_active_failures', - 'hosts_with_active_failures'), - 'host': DEFAULT_SUMMARY_FIELDS + ('has_active_failures',), + 'hosts_with_active_failures', + 'has_inventory_sources'), + 'host': DEFAULT_SUMMARY_FIELDS + ('has_active_failures', + 'has_inventory_sources'), 'group': DEFAULT_SUMMARY_FIELDS + ('has_active_failures', - 'hosts_with_active_failures'), + 'hosts_with_active_failures', + 'has_inventory_sources'), 'project': DEFAULT_SUMMARY_FIELDS + ('status',), 'credential': DEFAULT_SUMMARY_FIELDS, 'permission': DEFAULT_SUMMARY_FIELDS, @@ -539,7 +542,8 @@ class GroupSerializer(BaseSerializerWithVariables): class Meta: model = Group fields = BASE_FIELDS + ('inventory', 'variables', 'has_active_failures', - 'hosts_with_active_failures') + 'hosts_with_active_failures', + 'has_inventory_sources') def get_related(self, obj): if obj is None: @@ -621,7 +625,7 @@ class InventorySourceSerializer(BaseSerializer): class Meta: model = InventorySource fields = ('id', 'url', 'related', 'summary_fields', 'created', - 'modified', 'group', 'source', 'source_path', + 'modified', 'inventory', 'group', 'source', 'source_path', 'source_vars', 'source_username', 'source_password', 'source_regions', 'source_tags', 'overwrite', 'overwrite_vars', 'update_on_launch', 'update_interval', @@ -655,12 +659,15 @@ class InventorySourceSerializer(BaseSerializer): return {} res = super(InventorySourceSerializer, self).get_related(obj) res.update(dict( - group = reverse('main:group_detail', args=(obj.group.pk,)), update = reverse('main:inventory_source_update_view', args=(obj.pk,)), inventory_updates = reverse('main:inventory_source_updates_list', args=(obj.pk,)), #hosts = reverse('main:inventory_source_hosts_list', args=(obj.pk,)), #groups = reverse('main:inventory_source_groups_list', args=(obj.pk,)), )) + if obj.inventory: + res['inventory'] = reverse('main:inventory_detail', args=(obj.inventory.pk,)) + if obj.group: + res['group'] = reverse('main:group_detail', args=(obj.group.pk,)) if obj.current_update: res['current_update'] = reverse('main:inventory_update_detail', args=(obj.current_update.pk,)) diff --git a/awx/main/views.py b/awx/main/views.py index 87b9b6ff41..2b39c987c7 100644 --- a/awx/main/views.py +++ b/awx/main/views.py @@ -11,6 +11,7 @@ import sys from django.conf import settings from django.contrib.auth.models import User from django.core.urlresolvers import reverse +from django.db.models import Q from django.shortcuts import get_object_or_404, render_to_response from django.template import RequestContext from django.utils.datastructures import SortedDict @@ -713,7 +714,8 @@ class InventoryInventorySourcesList(SubListAPIView): parent = self.get_parent_object() self.check_parent_access(parent) qs = self.request.user.get_queryset(self.model) - return qs.filter(group__inventory__pk=parent.pk) + return qs.filter(Q(inventory__pk=parent.pk) | + Q(group__inventory__pk=parent.pk)) class InventorySourceDetail(RetrieveUpdateAPIView): diff --git a/awx/ui/static/js/helpers/Groups.js b/awx/ui/static/js/helpers/Groups.js index ce72815c10..a4890730bb 100644 --- a/awx/ui/static/js/helpers/Groups.js +++ b/awx/ui/static/js/helpers/Groups.js @@ -137,7 +137,7 @@ angular.module('GroupsHelper', [ 'RestServices', 'Utilities', 'ListGenerator', ' var view = GenerateList; var list = InventorySummary; var scope = view.inject(InventorySummary, { mode: 'summary', id: 'tree-form', breadCrumbs: false }); - var defaultUrl = GetBasePath('inventory') + scope['inventory_id'] + '/inventory_sources/'; + var defaultUrl = GetBasePath('inventory') + scope['inventory_id'] + '/inventory_sources/?group__isnull=false'; if (scope.PostRefreshRemove) { scope.PostRefreshRemove();