diff --git a/awx/api/serializers.py b/awx/api/serializers.py index 35f1e33a98..e0e9d2c3bd 100644 --- a/awx/api/serializers.py +++ b/awx/api/serializers.py @@ -3446,7 +3446,7 @@ class WorkflowApprovalTemplateSerializer(UnifiedJobTemplateSerializer): res.update(dict( jobs = self.reverse('api:workflow_approval_template_jobs_list', kwargs={'pk': obj.pk}), - notification_templates_started = self.reverse('api:workflow_approval_template_notification_templates_started_list', kwargs={'pk': obj.pk}), + notification_templates_needs_approval = self.reverse('api:workflow_approval_template_notification_templates_needs_approval', kwargs={'pk': obj.pk}), notification_templates_success = self.reverse('api:workflow_approval_template_notification_templates_success_list', kwargs={'pk': obj.pk}), notification_templates_error = self.reverse('api:workflow_approval_template_notification_templates_error_list', kwargs={'pk': obj.pk}), )) @@ -3520,6 +3520,8 @@ class LaunchConfigurationBaseSerializer(BaseSerializer): ujt = attrs['unified_job_template'] elif self.instance: ujt = self.instance.unified_job_template + if ujt is None: + return {'workflow_job_template': attrs['workflow_job_template']} # build additional field survey_passwords to track redacted variables password_dict = {} diff --git a/awx/api/urls/workflow_approval_template.py b/awx/api/urls/workflow_approval_template.py index e379196826..ee6d793bde 100644 --- a/awx/api/urls/workflow_approval_template.py +++ b/awx/api/urls/workflow_approval_template.py @@ -7,7 +7,7 @@ from awx.api.views import ( WorkflowApprovalTemplateDetail, WorkflowApprovalTemplateJobsList, WorkflowApprovalTemplateNotificationTemplatesErrorList, - WorkflowApprovalTemplateNotificationTemplatesStartedList, + WorkflowApprovalTemplateNotificationTemplatesNeedsApprovalList, WorkflowApprovalTemplateNotificationTemplatesSuccessList, ) @@ -15,8 +15,8 @@ from awx.api.views import ( urls = [ 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(), - name='workflow_approval_template_notification_templates_started_list'), + url(r'^(?P[0-9]+)/notification_templates_needs_approval/$', WorkflowApprovalTemplateNotificationTemplatesNeedsApprovalList.as_view(), + name='workflow_approval_template_notification_templates_needs_approval'), url(r'^(?P[0-9]+)/notification_templates_error/$', WorkflowApprovalTemplateNotificationTemplatesErrorList.as_view(), name='workflow_approval_template_notification_templates_error_list'), url(r'^(?P[0-9]+)/notification_templates_success/$', WorkflowApprovalTemplateNotificationTemplatesSuccessList.as_view(), diff --git a/awx/api/views/__init__.py b/awx/api/views/__init__.py index cf08ea554c..b716e01f05 100644 --- a/awx/api/views/__init__.py +++ b/awx/api/views/__init__.py @@ -4434,9 +4434,9 @@ class WorkflowApprovalTemplateNotificationTemplatesAnyList(SubListCreateAttachDe parent_model = models.WorkflowApprovalTemplate -class WorkflowApprovalTemplateNotificationTemplatesStartedList(WorkflowApprovalTemplateNotificationTemplatesAnyList): +class WorkflowApprovalTemplateNotificationTemplatesNeedsApprovalList(WorkflowApprovalTemplateNotificationTemplatesAnyList): - relationship = 'notification_templates_started' + relationship = 'notification_templates_needs_approval' class WorkflowApprovalTemplateNotificationTemplatesErrorList(WorkflowApprovalTemplateNotificationTemplatesAnyList): diff --git a/awx/main/models/base.py b/awx/main/models/base.py index 9925dc6049..341aa6fb1d 100644 --- a/awx/main/models/base.py +++ b/awx/main/models/base.py @@ -392,6 +392,13 @@ class NotificationFieldsModel(BaseModel): related_name='%(class)s_notification_templates_for_started' ) + # &&&&&& Placeholder for workflow pause/approve notifications + # notification_templates_needs_approval = models.ManyToManyField( + # "NotificationTemplate", + # blank=True, + # related_name='%(class)s_notification_templates_for_needs_approval' + # ) + def prevent_search(relation): """ diff --git a/awx/main/models/notifications.py b/awx/main/models/notifications.py index e3b69128d8..b1d902de90 100644 --- a/awx/main/models/notifications.py +++ b/awx/main/models/notifications.py @@ -486,6 +486,5 @@ class JobNotificationMixin(object): def send_it(local_nt=nt, local_subject=notification_subject, local_body=notification_body): def _func(): send_notifications.delay([local_nt.generate_notification(local_subject, local_body).id], - job_id=self.id) return _func connection.on_commit(send_it()) diff --git a/awx/main/models/workflow.py b/awx/main/models/workflow.py index 19775fcbc7..75d7c546bb 100644 --- a/awx/main/models/workflow.py +++ b/awx/main/models/workflow.py @@ -72,7 +72,7 @@ class WorkflowNodeBase(CreatedModifiedModel, LaunchTimeConfig): unified_job_template = models.ForeignKey( 'UnifiedJobTemplate', related_name='%(class)ss', - blank=False, + blank=True, null=True, default=None, on_delete=models.SET_NULL, @@ -636,6 +636,31 @@ 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.all() + # error_notification_templates = list(base_notification_templates.filter( + # unifiedjobtemplate_notification_templates_for_errors__in=[self])) + # needs_approval_notification_templates = list(base_notification_templates.filter( + # notification_templates_needs_approval__in=[self])) + # success_notification_templates = list(base_notification_templates.filter( + # unifiedjobtemplate_notification_templates_for_success__in=[self])) + # return dict(error=list(error_notification_templates), + # needs_approval=list(needs_approval_notification_templates), + # success=list(success_notification_templates)) +# &&&&&& Approval nodes don't have orgs! + # 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), + # needs_approval=list(needs_approval_notification_templates), + # success=list(success_notification_templates)) + class WorkflowApproval(UnifiedJob): class Meta: diff --git a/awx/main/scheduler/task_manager.py b/awx/main/scheduler/task_manager.py index b79abbad0d..bcecee809d 100644 --- a/awx/main/scheduler/task_manager.py +++ b/awx/main/scheduler/task_manager.py @@ -23,6 +23,7 @@ from awx.main.models import ( Project, ProjectUpdate, SystemJob, + # &&&&&& WorkflowApproval, WorkflowJob, WorkflowJobTemplate ) @@ -238,6 +239,11 @@ class TaskManager(): task.send_notification_templates('running') logger.debug('Transitioning %s to running status.', task.log_format) schedule_task_manager() + # elif type(task) is WorkflowApproval: (&&&&&& placeholder for notification work) + # task.status = 'pending' + # task.send_notification_templates('pending') + # logger.debug('Transitioning %s to pending status.', task.log_format) + # schedule_task_manager() elif not task.supports_isolation() and rampart_group.controller_id: # non-Ansible jobs on isolated instances run on controller task.instance_group = rampart_group.controller diff --git a/awx/main/signals.py b/awx/main/signals.py index 8b1de3a082..9a5dcec578 100644 --- a/awx/main/signals.py +++ b/awx/main/signals.py @@ -638,6 +638,25 @@ def delete_inventory_for_org(sender, instance, **kwargs): logger.debug(e) +# &&&&&& Placeholder code below for approval node deletion. +# @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) + + @receiver(post_save, sender=Session) def save_user_session_membership(sender, **kwargs): session = kwargs.get('instance', None) diff --git a/awx/main/tests/functional/api/test_workflow_node.py b/awx/main/tests/functional/api/test_workflow_node.py index 04b02e87a1..4402ce3812 100644 --- a/awx/main/tests/functional/api/test_workflow_node.py +++ b/awx/main/tests/functional/api/test_workflow_node.py @@ -26,26 +26,6 @@ def node(workflow_job_template, post, admin_user, job_template): ) - -@pytest.mark.django_db -def test_blank_UJT_unallowed(workflow_job_template, post, admin_user): - url = reverse('api:workflow_job_template_workflow_nodes_list', - kwargs={'pk': workflow_job_template.pk}) - r = post(url, {}, user=admin_user, expect=400) - assert 'unified_job_template' in r.data - - -@pytest.mark.django_db -def test_cannot_remove_UJT(node, patch, admin_user): - r = patch( - node.get_absolute_url(), - data={'unified_job_template': None}, - user=admin_user, - expect=400 - ) - assert 'unified_job_template' in r.data - - @pytest.mark.django_db def test_node_rejects_unprompted_fields(inventory, project, workflow_job_template, post, admin_user): job_template = JobTemplate.objects.create(