From 7458788562122c0110f8c101d5579080e5df9b26 Mon Sep 17 00:00:00 2001 From: Wayne Witzel III Date: Fri, 7 Apr 2017 12:25:11 -0400 Subject: [PATCH] More updates for InventorySource changes --- awx/api/serializers.py | 11 +++------- awx/api/views.py | 17 +++++--------- awx/main/access.py | 21 +++++++----------- awx/main/migrations/0037_v320_release.py | 5 +++++ awx/main/models/inventory.py | 13 ----------- .../tests/functional/api/test_inventory.py | 15 +++++++++++++ .../functional/api/test_rbac_displays.py | 22 ------------------- 7 files changed, 37 insertions(+), 67 deletions(-) diff --git a/awx/api/serializers.py b/awx/api/serializers.py index 65c4f1de8f..996fb627f7 100644 --- a/awx/api/serializers.py +++ b/awx/api/serializers.py @@ -1238,7 +1238,7 @@ class HostSerializer(BaseSerializerWithVariables): class GroupSerializer(BaseSerializerWithVariables): - show_capabilities = ['start', 'copy', 'schedule', 'edit', 'delete'] + show_capabilities = ['copy', 'edit', 'delete'] class Meta: model = Group @@ -1427,13 +1427,12 @@ class InventorySourceSerializer(UnifiedJobTemplateSerializer, InventorySourceOpt status = serializers.ChoiceField(choices=InventorySource.INVENTORY_SOURCE_STATUS_CHOICES, read_only=True) last_update_failed = serializers.BooleanField(read_only=True) last_updated = serializers.DateTimeField(read_only=True) + show_capabilities = ['start', 'schedule', 'edit', 'delete'] class Meta: model = InventorySource - fields = ('*', 'inventory', 'update_on_launch', - 'update_cache_timeout') + \ + fields = ('*', 'name', 'inventory', 'update_on_launch', 'update_cache_timeout') + \ ('last_update_failed', 'last_updated') # Backwards compatibility. - read_only_fields = ('*', 'name', 'inventory') def get_related(self, obj): res = super(InventorySourceSerializer, self).get_related(obj) @@ -1450,8 +1449,6 @@ class InventorySourceSerializer(UnifiedJobTemplateSerializer, InventorySourceOpt )) if obj.inventory: res['inventory'] = self.reverse('api:inventory_detail', kwargs={'pk': obj.inventory.pk}) - if obj.group: - res['group'] = self.reverse('api:group_detail', kwargs={'pk': obj.group.pk}) # Backwards compatibility. if obj.current_update: res['current_update'] = self.reverse('api:inventory_update_detail', @@ -1467,8 +1464,6 @@ class InventorySourceSerializer(UnifiedJobTemplateSerializer, InventorySourceOpt return ret if 'inventory' in ret and not obj.inventory: ret['inventory'] = None - if 'group' in ret and not obj.group: - ret['group'] = None return ret diff --git a/awx/api/views.py b/awx/api/views.py index 9f6f2d5b1a..79ccc17922 100644 --- a/awx/api/views.py +++ b/awx/api/views.py @@ -2154,7 +2154,7 @@ class InventoryTreeView(RetrieveAPIView): root_group_pks = inventory.root_groups.order_by('name').values_list('pk', flat=True) groups_qs = inventory.groups groups_qs = groups_qs.select_related('inventory') - groups_qs = groups_qs.prefetch_related('inventory_source') + groups_qs = groups_qs.prefetch_related('inventory_sources') all_group_data = GroupSerializer(groups_qs, many=True).data all_group_data_map = dict((x['id'], x) for x in all_group_data) tree_data = [all_group_data_map[x] for x in root_group_pks] @@ -2164,22 +2164,17 @@ class InventoryTreeView(RetrieveAPIView): return Response(tree_data) -class InventoryInventorySourcesList(SubListAPIView): +class InventoryInventorySourcesList(SubListCreateAPIView): + + view_name = _('Inventory Source List') model = InventorySource serializer_class = InventorySourceSerializer parent_model = Inventory - relationship = None # Not defined since using get_queryset(). - view_name = _('Inventory Source List') + relationship = 'inventory_sources' + parent_key = 'inventory' new_in_14 = True - def get_queryset(self): - parent = self.get_parent_object() - self.check_parent_access(parent) - qs = self.request.user.get_queryset(self.model) - return qs.filter(Q(inventory__pk=parent.pk) | - Q(group__inventory__pk=parent.pk)) - class InventorySourceList(ListAPIView): diff --git a/awx/main/access.py b/awx/main/access.py index a781b062ab..a14ccc1ca1 100644 --- a/awx/main/access.py +++ b/awx/main/access.py @@ -670,7 +670,7 @@ class GroupAccess(BaseAccess): def get_queryset(self): qs = Group.objects.filter(inventory__in=Inventory.accessible_objects(self.user, 'read_role')) qs = qs.select_related('created_by', 'modified_by', 'inventory') - return qs.prefetch_related('parents', 'children', 'inventory_source').all() + return qs.prefetch_related('parents', 'children').all() def can_read(self, obj): return obj and self.user in obj.inventory.read_role @@ -720,11 +720,6 @@ class GroupAccess(BaseAccess): "active_jobs": active_jobs}) return True - def can_start(self, obj, validate_license=True): - if obj and obj.inventory: - return self.user.can_access(Inventory, 'start', obj.inventory, validate_license=validate_license) - return False - class InventorySourceAccess(BaseAccess): ''' @@ -736,10 +731,9 @@ class InventorySourceAccess(BaseAccess): def get_queryset(self): qs = self.model.objects.all() - qs = qs.select_related('created_by', 'modified_by', 'group', 'inventory') + qs = qs.select_related('created_by', 'modified_by', 'inventory') inventory_ids = self.user.get_queryset(Inventory) - return qs.filter(Q(inventory_id__in=inventory_ids) | - Q(group__inventory_id__in=inventory_ids)) + return qs.filter(Q(inventory_id__in=inventory_ids)) def can_read(self, obj): if obj and obj.inventory: @@ -748,8 +742,10 @@ class InventorySourceAccess(BaseAccess): return False def can_add(self, data): - # Automatically created from group or management command. - return False + if not data or 'inventory' not in data: + return False + # Checks for admin or change permission on inventory. + return self.check_related('inventory', Inventory, data) def can_change(self, obj, data): # Checks for admin or change permission on group. @@ -780,8 +776,7 @@ class InventoryUpdateAccess(BaseAccess): def get_queryset(self): qs = InventoryUpdate.objects.distinct() - qs = qs.select_related('created_by', 'modified_by', 'inventory_source__group', - 'inventory_source__inventory') + qs = qs.select_related('created_by', 'modified_by', 'inventory_source__inventory') inventory_sources_qs = self.user.get_queryset(InventorySource) return qs.filter(inventory_source__in=inventory_sources_qs) diff --git a/awx/main/migrations/0037_v320_release.py b/awx/main/migrations/0037_v320_release.py index 9f43910226..396052317b 100644 --- a/awx/main/migrations/0037_v320_release.py +++ b/awx/main/migrations/0037_v320_release.py @@ -24,6 +24,11 @@ class Migration(migrations.Migration): model_name='inventorysource', name='group', ), + migrations.AlterField( + model_name='inventorysource', + name='inventory', + field=models.ForeignKey(related_name='inventory_sources', default=None, to='main.Inventory', null=True), + ), # Facts Latest migrations.CreateModel( diff --git a/awx/main/models/inventory.py b/awx/main/models/inventory.py index 18025ced50..29685ad9f5 100644 --- a/awx/main/models/inventory.py +++ b/awx/main/models/inventory.py @@ -1060,7 +1060,6 @@ class InventorySource(UnifiedJobTemplate, InventorySourceOptions): related_name='inventory_sources', null=True, default=None, - editable=False, on_delete=models.CASCADE, ) update_on_launch = models.BooleanField( @@ -1169,16 +1168,6 @@ class InventorySource(UnifiedJobTemplate, InventorySourceOptions): success=list(success_notification_templates), any=list(any_notification_templates)) - def clean_source(self): - source = self.source - if source and self.group: - qs = self.group.inventory_sources.filter(source__in=CLOUD_INVENTORY_SOURCES) - existing_sources = qs.exclude(pk=self.pk) - if existing_sources.count(): - s = u', '.join([x.group.name for x in existing_sources]) - raise ValidationError(_('Unable to configure this item for cloud sync. It is already managed by %s.') % s) - return source - class InventoryUpdate(UnifiedJob, InventorySourceOptions, JobNotificationMixin): ''' @@ -1213,8 +1202,6 @@ class InventoryUpdate(UnifiedJob, InventorySourceOptions, JobNotificationMixin): def websocket_emit_data(self): websocket_data = super(InventoryUpdate, self).websocket_emit_data() - if self.inventory_source.group is not None: - websocket_data.update(dict(group_id=self.inventory_source.group.id)) return websocket_data def save(self, *args, **kwargs): diff --git a/awx/main/tests/functional/api/test_inventory.py b/awx/main/tests/functional/api/test_inventory.py index ac779f1151..27b929f372 100644 --- a/awx/main/tests/functional/api/test_inventory.py +++ b/awx/main/tests/functional/api/test_inventory.py @@ -97,6 +97,21 @@ def test_edit_inventory_group(put, group, alice, role_field, expected_status_cod put(reverse('api:group_detail', kwargs={'pk': group.id}), data, alice, expect=expected_status_code) +@pytest.mark.parametrize("role_field,expected_status_code", [ + (None, 403), + ('admin_role', 201), + ('update_role', 403), + ('adhoc_role', 403), + ('use_role', 403) +]) +@pytest.mark.django_db +def test_create_inventory_inventory_source(post, inventory, alice, role_field, expected_status_code): + data = { 'source': 'ec2', 'name': 'ec2-inv-source'} + if role_field: + getattr(inventory, role_field).members.add(alice) + post(reverse('api:inventory_inventory_sources_list', kwargs={'pk': inventory.id}), data, alice, expect=expected_status_code) + + @pytest.mark.parametrize("role_field,expected_status_code", [ (None, 403), ('admin_role', 204), diff --git a/awx/main/tests/functional/api/test_rbac_displays.py b/awx/main/tests/functional/api/test_rbac_displays.py index 7878e6d77b..83ab4cc1e8 100644 --- a/awx/main/tests/functional/api/test_rbac_displays.py +++ b/awx/main/tests/functional/api/test_rbac_displays.py @@ -341,28 +341,6 @@ def test_manual_projects_no_update(project, get, admin_user): assert not response.data['summary_fields']['user_capabilities']['schedule'] -@pytest.mark.django_db -def test_group_update_capabilities_possible(group, inventory_source, admin_user): - group.inventory_source = inventory_source - group.save() - - capabilities = get_user_capabilities(admin_user, group.inventory, method_list=['start']) - assert capabilities['start'] - - -@pytest.mark.django_db -def test_group_update_capabilities_impossible(group, inventory_source, admin_user): - "Manual groups can not be updated or scheduled" - inventory_source.source = "" - inventory_source.save() - group.inventory_source = inventory_source - group.save() - - capabilities = get_user_capabilities(admin_user, group, method_list=['edit', 'start', 'schedule']) - assert not capabilities['start'] - assert not capabilities['schedule'] - - @pytest.mark.django_db def test_license_check_not_called(mocker, job_template, project, org_admin, get): job_template.project = project