From 294d6551b992b52ae0b7d6acfe8bce330925725b Mon Sep 17 00:00:00 2001 From: beeankha Date: Thu, 18 Jul 2019 12:07:22 -0400 Subject: [PATCH] Polishing up work on new endpoint --- awx/api/serializers.py | 9 ++-- awx/api/urls/urls.py | 2 +- awx/api/urls/workflow_approval_template.py | 2 - awx/api/urls/workflow_job_template_node.py | 2 +- awx/api/views/__init__.py | 13 ++---- awx/api/views/root.py | 1 - awx/main/access.py | 20 ++++----- awx/main/management/commands/cleanup_jobs.py | 2 - .../migrations/0082_v360_workflowapproval.py | 43 +++++++++++++++++++ awx/main/models/__init__.py | 2 +- awx/main/models/organization.py | 1 - awx/main/models/rbac.py | 6 +-- awx/main/models/workflow.py | 23 ---------- awx/main/registrar.py | 2 +- awx/main/signals.py | 23 +--------- 15 files changed, 71 insertions(+), 80 deletions(-) create mode 100644 awx/main/migrations/0082_v360_workflowapproval.py diff --git a/awx/api/serializers.py b/awx/api/serializers.py index d4ab2811e6..35f1e33a98 100644 --- a/awx/api/serializers.py +++ b/awx/api/serializers.py @@ -3099,7 +3099,7 @@ class JobRelaunchSerializer(BaseSerializer): attrs = super(JobRelaunchSerializer, self).validate(attrs) return attrs -# &&&&&& + class JobCreateScheduleSerializer(BaseSerializer): can_schedule = serializers.SerializerMethodField() @@ -3601,7 +3601,7 @@ class WorkflowJobTemplateNodeSerializer(LaunchConfigurationBaseSerializer): def get_related(self, obj): res = super(WorkflowJobTemplateNodeSerializer, self).get_related(obj) - res['create_approval_job_template'] = self.reverse('api:workflow_job_template_node_create_approval', kwargs={'pk': obj.pk}) + res['create_approval_template'] = self.reverse('api:workflow_job_template_node_create_approval', kwargs={'pk': obj.pk}) res['success_nodes'] = self.reverse('api:workflow_job_template_node_success_nodes_list', kwargs={'pk': obj.pk}) res['failure_nodes'] = self.reverse('api:workflow_job_template_node_failure_nodes_list', kwargs={'pk': obj.pk}) res['always_nodes'] = self.reverse('api:workflow_job_template_node_always_nodes_list', kwargs={'pk': obj.pk}) @@ -3670,13 +3670,16 @@ class WorkflowJobTemplateNodeDetailSerializer(WorkflowJobTemplateNodeSerializer) field_kwargs.pop('queryset', None) return field_class, field_kwargs -# &&&&&& + class WorkflowJobTemplateNodeCreateApprovalSerializer(BaseSerializer): class Meta: model = WorkflowApprovalTemplate fields = ('timeout', 'name', 'description',) + def to_representation(self, obj): + return {} + class JobListSerializer(JobSerializer, UnifiedJobListSerializer): pass diff --git a/awx/api/urls/urls.py b/awx/api/urls/urls.py index beaa7532c7..ede960ecb6 100644 --- a/awx/api/urls/urls.py +++ b/awx/api/urls/urls.py @@ -133,7 +133,7 @@ v2_urls = [ url(r'^unified_job_templates/$', UnifiedJobTemplateList.as_view(), name='unified_job_template_list'), url(r'^unified_jobs/$', UnifiedJobList.as_view(), name='unified_job_list'), url(r'^activity_stream/', include(activity_stream_urls)), - url(r'^workflow_approval_templates/', include(workflow_approval_template_urls)), # &&&&&& Take this line out completely? + url(r'^workflow_approval_templates/', include(workflow_approval_template_urls)), url(r'^workflow_approvals/', include(workflow_approval_urls)), ] diff --git a/awx/api/urls/workflow_approval_template.py b/awx/api/urls/workflow_approval_template.py index 1d6345d01f..e379196826 100644 --- a/awx/api/urls/workflow_approval_template.py +++ b/awx/api/urls/workflow_approval_template.py @@ -4,7 +4,6 @@ from django.conf.urls import url from awx.api.views import ( - WorkflowApprovalTemplateList, WorkflowApprovalTemplateDetail, WorkflowApprovalTemplateJobsList, WorkflowApprovalTemplateNotificationTemplatesErrorList, @@ -14,7 +13,6 @@ from awx.api.views import ( urls = [ - url(r'^$', WorkflowApprovalTemplateList.as_view(), name='workflow_approval_template_list'), url(r'^(?P[0-9]+)/$', WorkflowApprovalTemplateDetail.as_view(), name='workflow_approval_template_detail'), url(r'^(?P[0-9]+)/approvals/$', WorkflowApprovalTemplateJobsList.as_view(), name='workflow_approval_template_jobs_list'), url(r'^(?P[0-9]+)/notification_templates_started/$', WorkflowApprovalTemplateNotificationTemplatesStartedList.as_view(), diff --git a/awx/api/urls/workflow_job_template_node.py b/awx/api/urls/workflow_job_template_node.py index 76ba375cb7..868c728a88 100644 --- a/awx/api/urls/workflow_job_template_node.py +++ b/awx/api/urls/workflow_job_template_node.py @@ -21,7 +21,7 @@ urls = [ url(r'^(?P[0-9]+)/failure_nodes/$', WorkflowJobTemplateNodeFailureNodesList.as_view(), name='workflow_job_template_node_failure_nodes_list'), url(r'^(?P[0-9]+)/always_nodes/$', WorkflowJobTemplateNodeAlwaysNodesList.as_view(), name='workflow_job_template_node_always_nodes_list'), url(r'^(?P[0-9]+)/credentials/$', WorkflowJobTemplateNodeCredentialsList.as_view(), name='workflow_job_template_node_credentials_list'), - url(r'^(?P[0-9]+)/create_approval_job_template/$', WorkflowJobTemplateNodeCreateApproval.as_view(), name='workflow_job_template_node_create_approval'), + url(r'^(?P[0-9]+)/create_approval_template/$', WorkflowJobTemplateNodeCreateApproval.as_view(), name='workflow_job_template_node_create_approval'), ] __all__ = ['urls'] diff --git a/awx/api/views/__init__.py b/awx/api/views/__init__.py index 8fa5bbff45..cf08ea554c 100644 --- a/awx/api/views/__init__.py +++ b/awx/api/views/__init__.py @@ -3018,7 +3018,6 @@ class WorkflowJobTemplateNodeCreateApproval(RetrieveAPIView): model = models.WorkflowJobTemplateNode serializer_class = serializers.WorkflowJobTemplateNodeCreateApprovalSerializer -# &&&&&& def post(self, request, *args, **kwargs): serializer = self.get_serializer(data=request.data) if not serializer.is_valid(): @@ -3597,7 +3596,7 @@ class JobRelaunch(RetrieveAPIView): headers = {'Location': new_job.get_absolute_url(request=request)} return Response(data, status=status.HTTP_201_CREATED, headers=headers) -# &&&&&& Reference + class JobCreateSchedule(RetrieveAPIView): model = models.Job @@ -4422,12 +4421,6 @@ for attr, value in list(locals().items()): setattr(this_module, name, view) -class WorkflowApprovalTemplateList(ListCreateAPIView): - - model = models.WorkflowApprovalTemplate - serializer_class = serializers.WorkflowApprovalTemplateSerializer - - class WorkflowApprovalTemplateDetail(RelatedJobsPreventDeleteMixin, RetrieveUpdateDestroyAPIView): model = models.WorkflowApprovalTemplate @@ -4481,11 +4474,12 @@ class WorkflowApprovalDetail(UnifiedJobDeletionMixin, RetrieveDestroyAPIView): model = models.WorkflowApproval serializer_class = serializers.WorkflowApprovalSerializer -# &&&&&& Include checks in the below two post methods + class WorkflowApprovalApprove(RetrieveAPIView): model = models.WorkflowApproval serializer_class = serializers.WorkflowApprovalViewSerializer + # &&&&&& To address later def post(self, request, *args, **kwargs): obj = self.get_object() obj.approve() @@ -4496,6 +4490,7 @@ class WorkflowApprovalDeny(RetrieveAPIView): model = models.WorkflowApproval serializer_class = serializers.WorkflowApprovalViewSerializer + # &&&&&& To address later def post(self, request, *args, **kwargs): obj = self.get_object() obj.deny() diff --git a/awx/api/views/root.py b/awx/api/views/root.py index df5f6f7f0e..922041c8b5 100644 --- a/awx/api/views/root.py +++ b/awx/api/views/root.py @@ -124,7 +124,6 @@ class ApiVersionRootView(APIView): data['activity_stream'] = reverse('api:activity_stream_list', request=request) data['workflow_job_templates'] = reverse('api:workflow_job_template_list', request=request) data['workflow_jobs'] = reverse('api:workflow_job_list', request=request) - data['workflow_approval_templates'] = reverse('api:workflow_approval_template_list', request=request) # &&&&&& Take this line out completely? data['workflow_approvals'] = reverse('api:workflow_approval_list', request=request) data['workflow_job_template_nodes'] = reverse('api:workflow_job_template_node_list', request=request) data['workflow_job_nodes'] = reverse('api:workflow_job_node_list', request=request) diff --git a/awx/main/access.py b/awx/main/access.py index 5b7017d9f3..7dd41e3500 100644 --- a/awx/main/access.py +++ b/awx/main/access.py @@ -2788,18 +2788,17 @@ class WorkflowApprovalAccess(BaseAccess): return True def filtered_queryset(self): - return self.model.objects.all() + return self.model.objects.filter( + unified_job_node__in=WorkflowJobNode.accessible_pk_qs( + self.user, 'read_role')) - def can_start(self, obj, validate_license=True): - return False - # &&&&&& ??? Start of the RBAC method ??? + # &&&&&& # def can_approve_or_deny(self, obj): - # if self.user.is_superuser: # &&&&&& add "or self.user.approval_role"? + # if self.user.is_superuser: or "self.user.approval_role"? # return True # return self.can_change(obj, ????) -# &&&&&& Why is the below not showing up as a class now?? class WorkflowApprovalTemplateAccess(BaseAccess): ''' I can create approval nodes when: @@ -2811,16 +2810,17 @@ class WorkflowApprovalTemplateAccess(BaseAccess): model = WorkflowApprovalTemplate prefetch_related = ('created_by', 'modified_by',) - # &&&&&& I need to get the admin role of the WFJT, where WFJT is provided in the key portion vs the data (Alan said that, what does it mean exactly???) @check_superuser def can_add(self, data): if data is None: # Hide direct creation in API browser return False - return ( - self.check_related('workflow_approval_template', UnifiedJobTemplate, role_field='admin_role') + else: + return (self.check_related('workflow_approval_template', UnifiedJobTemplate, role_field='admin_role')) def filtered_queryset(self): - return self.model.filter(workflowjobtemplatenodes__workflow_job_template=WorkflowJobTemplate.accessible_pk_qs(self.user, 'read_role')) + return self.model.objects.filter( + workflowjobtemplatenodes__workflow_job_template__in=WorkflowJobTemplate.accessible_pk_qs( + self.user, 'read_role')) for cls in BaseAccess.__subclasses__(): diff --git a/awx/main/management/commands/cleanup_jobs.py b/awx/main/management/commands/cleanup_jobs.py index f43a34c6b6..81f405da4c 100644 --- a/awx/main/management/commands/cleanup_jobs.py +++ b/awx/main/management/commands/cleanup_jobs.py @@ -200,8 +200,6 @@ class Command(BaseCommand): skipped += WorkflowJob.objects.filter(created__gte=self.cutoff).count() return skipped, deleted -# &&&&&& Add cleanup of orphaned approval nodes here? - def cleanup_notifications(self): skipped, deleted = 0, 0 notifications = Notification.objects.filter(created__lt=self.cutoff) diff --git a/awx/main/migrations/0082_v360_workflowapproval.py b/awx/main/migrations/0082_v360_workflowapproval.py new file mode 100644 index 0000000000..570402a3f1 --- /dev/null +++ b/awx/main/migrations/0082_v360_workflowapproval.py @@ -0,0 +1,43 @@ +# Generated by Django 2.2.2 on 2019-07-18 14:12 + +import awx.main.fields +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('main', '0081_v360_notify_on_start'), + ] + + operations = [ + migrations.CreateModel( + name='WorkflowApprovalTemplate', + fields=[ + ('unifiedjobtemplate_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='main.UnifiedJobTemplate')), + ('timeout', models.IntegerField(blank=True, default=0, help_text='The amount of time (in seconds) before the approval node expires and fails.')), + ], + bases=('main.unifiedjobtemplate',), + ), + migrations.AddField( + model_name='organization', + name='approval_role', + field=awx.main.fields.ImplicitRoleField(editable=False, null='True', on_delete=django.db.models.deletion.CASCADE, parent_role='admin_role', related_name='+', to='main.Role'), + preserve_default='True', + ), + migrations.AddField( + model_name='workflowjobtemplate', + name='approval_role', + field=awx.main.fields.ImplicitRoleField(editable=False, null='True', on_delete=django.db.models.deletion.CASCADE, parent_role=['singleton:system_auditor', 'organization.approval_role', 'admin_role'], related_name='+', to='main.Role'), + preserve_default='True', + ), + migrations.CreateModel( + name='WorkflowApproval', + fields=[ + ('unifiedjob_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='main.UnifiedJob')), + ('workflow_approval_template', models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='approvals', to='main.WorkflowApprovalTemplate')), + ], + bases=('main.unifiedjob',), + ), + ] diff --git a/awx/main/models/__init__.py b/awx/main/models/__init__.py index 627578a854..1704fe345b 100644 --- a/awx/main/models/__init__.py +++ b/awx/main/models/__init__.py @@ -174,7 +174,7 @@ def o_auth2_token_get_absolute_url(self, request=None): OAuth2AccessToken.add_to_class('get_absolute_url', o_auth2_token_get_absolute_url) -# &&&&&& "Add model here" - Alan +# &&&&&& Add model here from awx.main.registrar import activity_stream_registrar # noqa activity_stream_registrar.connect(Organization) activity_stream_registrar.connect(Inventory) diff --git a/awx/main/models/organization.py b/awx/main/models/organization.py index 21fb0fd7d1..96b88a6b64 100644 --- a/awx/main/models/organization.py +++ b/awx/main/models/organization.py @@ -89,7 +89,6 @@ class Organization(CommonModel, NotificationFieldsModel, ResourceMixin, CustomVi 'notification_admin_role', 'credential_admin_role', 'job_template_admin_role',], ) -# &&&&&& The below keeps complaining - fixed by new migration file, perhaps? approval_role = ImplicitRoleField( parent_role='admin_role', ) diff --git a/awx/main/models/rbac.py b/awx/main/models/rbac.py index 39a2109ad5..67d21e873d 100644 --- a/awx/main/models/rbac.py +++ b/awx/main/models/rbac.py @@ -48,7 +48,7 @@ role_names = { 'read_role': _('Read'), 'update_role': _('Update'), 'use_role': _('Use'), - 'approval_role': _('Approve'), # &&&&&& Added this here! + 'approval_role': _('Approve'), } role_descriptions = { @@ -71,7 +71,7 @@ role_descriptions = { 'read_role': _('May view settings for the %s'), 'update_role': _('May update the %s'), 'use_role': _('Can use the %s in a job template'), - 'approval_role': _('Can approve or deny a workflow approval node'), # &&&&&& ...and here! + 'approval_role': _('Can approve or deny a workflow approval node'), } @@ -482,7 +482,7 @@ def get_roles_on_resource(resource, accessor): ).values_list('role_field', flat=True).distinct() ] -# &&&&&& This area is giving trouble? + def role_summary_fields_generator(content_object, role_field): global role_descriptions global role_names diff --git a/awx/main/models/workflow.py b/awx/main/models/workflow.py index a0948e36b9..19775fcbc7 100644 --- a/awx/main/models/workflow.py +++ b/awx/main/models/workflow.py @@ -395,7 +395,6 @@ class WorkflowJobTemplate(UnifiedJobTemplate, WorkflowJobOptions, SurveyJobTempl 'singleton:' + ROLE_SINGLETON_SYSTEM_AUDITOR, 'organization.auditor_role', 'execute_role', 'admin_role' ]) -# &&&&&& The below keeps complaining - fixed by new migration file, perhaps? approval_role = ImplicitRoleField(parent_role=[ 'singleton:' + ROLE_SINGLETON_SYSTEM_AUDITOR, 'organization.approval_role', 'admin_role', @@ -637,28 +636,6 @@ class WorkflowApprovalTemplate(UnifiedJobTemplate): def get_absolute_url(self, request=None): return reverse('api:workflow_approval_template_detail', kwargs={'pk': self.pk}, request=request) - # @property - # def notification_templates(self): - # # Return all notification_templates defined on the Job Template, on the Project, and on the Organization for each trigger type - # base_notification_templates = NotificationTemplate.objects - # error_notification_templates = list(base_notification_templates.filter( - # unifiedjobtemplate_notification_templates_for_errors__in=[self, self.project])) - # started_notification_templates = list(base_notification_templates.filter( - # unifiedjobtemplate_notification_templates_for_started__in=[self, self.project])) - # success_notification_templates = list(base_notification_templates.filter( - # unifiedjobtemplate_notification_templates_for_success__in=[self, self.project])) -# &&&&&& Approvals don't have orgs! How to pull them in? Alan said to "get creative"! - # if self.project is not None and self.project.organization is not None: - # error_notification_templates = set(error_notification_templates + list(base_notification_templates.filter( - # organization_notification_templates_for_errors=self.project.organization))) - # started_notification_templates = set(started_notification_templates + list(base_notification_templates.filter( - # organization_notification_templates_for_started=self.project.organization))) - # success_notification_templates = set(success_notification_templates + list(base_notification_templates.filter( - # organization_notification_templates_for_success=self.project.organization))) - # return dict(error=list(error_notification_templates), - # started=list(started_notification_templates), - # success=list(success_notification_templates)) - class WorkflowApproval(UnifiedJob): class Meta: diff --git a/awx/main/registrar.py b/awx/main/registrar.py index f7f32d839b..6d0ccfe495 100644 --- a/awx/main/registrar.py +++ b/awx/main/registrar.py @@ -3,7 +3,7 @@ from django.db.models.signals import pre_save, post_save, pre_delete, m2m_changed -# &&&&&& Where the signals are hooked up ?? + class ActivityStreamRegistrar(object): def __init__(self): diff --git a/awx/main/signals.py b/awx/main/signals.py index fa50b97fe9..8b1de3a082 100644 --- a/awx/main/signals.py +++ b/awx/main/signals.py @@ -356,25 +356,6 @@ def update_host_last_job_after_job_deleted(sender, **kwargs): _update_host_last_jhs(host) -# &&&&&& Argh. Which one looks better? -# @receiver(pre_delete, sender=Job) -# def delete_detached_approval_nodes(sender, instance, **kwargs): -# for l in instance.labels.all(): -# if l.is_candidate_for_detach(): -# l.delete() -# -# -# @receiver(pre_delete, sender=Organization) -# def delete_detached_approval_nodes(sender, instance, **kwargs): -# approval_node = ??? -# user = get_current_user_or_none() -# for node in approval_node: -# try: -# node.schedule_deletion(user_id=getattr(user, 'id', None)) -# except RuntimeError as e: -# logger.debug(e) - - # Set via ActivityStreamRegistrar to record activity stream events @@ -454,7 +435,7 @@ def model_serializer_mapping(): models.OAuth2Application: serializers.OAuth2ApplicationSerializer, } -# &&&&&& Can customize how/what the activity stream shows info + def activity_stream_create(sender, instance, created, **kwargs): if created and activity_stream_enabled: # TODO: remove deprecated_group conditional in 3.3 @@ -482,8 +463,6 @@ def activity_stream_create(sender, instance, created, **kwargs): object1=object1, changes=json.dumps(changes), actor=get_current_user_or_none()) - # if type(instance) == WorkflowApproval: &&&&&& - # changes['status'] = #??? #TODO: Weird situation where cascade SETNULL doesn't work # it might actually be a good idea to remove all of these FK references since # we don't really use them anyway.