diff --git a/awx/api/serializers.py b/awx/api/serializers.py index fb0b6852f3..c73a4148cf 100644 --- a/awx/api/serializers.py +++ b/awx/api/serializers.py @@ -3456,13 +3456,13 @@ class WorkflowApprovalTemplateSerializer(UnifiedJobTemplateSerializer): return res -# class WorkflowJobTemplateApprovalSerializer(UnifiedJobTemplateSerializer): -# class Meta: -# model = WorkflowJobTemplateApproval -# fields = ('*',) -# -# def post(self, obj): -# return # POST only!!! +class WorkflowJobTemplateApprovalSerializer(UnifiedJobTemplateSerializer): + class Meta: + model = WorkflowApprovalTemplate + fields = ('*',) + + def post(self, obj): + return # POST only!!! class LaunchConfigurationBaseSerializer(BaseSerializer): @@ -4746,7 +4746,8 @@ class ActivityStreamSerializer(BaseSerializer): ('o_auth2_access_token', ('id', 'user_id', 'description', 'application_id', 'scope')), ('o_auth2_application', ('id', 'name', 'description')), ('credential_type', ('id', 'name', 'description', 'kind', 'managed_by_tower')), - ('ad_hoc_command', ('id', 'name', 'status', 'limit')) + ('ad_hoc_command', ('id', 'name', 'status', 'limit')), + ('workflow_approval', ('id', 'unified_job_id')), ] return field_list @@ -4855,6 +4856,7 @@ class ActivityStreamSerializer(BaseSerializer): def _summarize_parent_ujt(self, obj, fk, summary_fields): summary_keys = {'job': 'job_template', 'workflow_job_template_node': 'workflow_job_template', + 'workflow_approval': 'workflow_approval_template', 'schedule': 'unified_job_template'} if fk not in summary_keys: return diff --git a/awx/api/views/__init__.py b/awx/api/views/__init__.py index 3d53916033..a2a4bc3daa 100644 --- a/awx/api/views/__init__.py +++ b/awx/api/views/__init__.py @@ -839,8 +839,6 @@ class SystemJobEventsList(SubListAPIView): return super(SystemJobEventsList, self).finalize_response(request, response, *args, **kwargs) - - class ProjectUpdateCancel(RetrieveAPIView): model = models.ProjectUpdate diff --git a/awx/main/access.py b/awx/main/access.py index ea3299b63a..65fe0badd5 100644 --- a/awx/main/access.py +++ b/awx/main/access.py @@ -2631,6 +2631,7 @@ class ActivityStreamAccess(BaseAccess): app_set = OAuth2ApplicationAccess(self.user).filtered_queryset() token_set = OAuth2TokenAccess(self.user).filtered_queryset() +# &&&&&& Activity Stream + RBAC here?? return qs.filter( Q(ad_hoc_command__inventory__in=inventory_set) | Q(o_auth2_application__in=app_set) | @@ -2796,11 +2797,11 @@ class WorkflowApprovalAccess(BaseAccess): self.user, 'read_role')) def get_queryset(self): - return super(UnifiedJobTemplateAccess, self).get_queryset().exclude( - workflowapprovaltemplate__isnull=False) + return super(WorkflowApprovalAccess, self).get_queryset().exclude( + workflow_approval_template__isnull=False) def can_approve_or_deny(self, obj): - if self.user.approval_role: + if self.user.approval_role or self.user.system_administrator: return True @@ -2828,8 +2829,8 @@ class WorkflowApprovalTemplateAccess(BaseAccess): self.user, 'read_role')) def get_queryset(self): - return super(UnifiedJobAccess, self).get_queryset().exclude( - workflowapproval__isnull=False) + return super(WorkflowApprovalTemplateAccess, self).get_queryset().filter( + approvals__isnull=False) for cls in BaseAccess.__subclasses__(): diff --git a/awx/main/migrations/0083_v360_workflow_approval.py b/awx/main/migrations/0083_v360_workflow_approval.py index 66b6bc0504..351d4427da 100644 --- a/awx/main/migrations/0083_v360_workflow_approval.py +++ b/awx/main/migrations/0083_v360_workflow_approval.py @@ -1,4 +1,4 @@ -# Generated by Django 2.2.2 on 2019-07-18 14:12 +# Generated by Django 2.2.2 on 2019-07-25 19:16 import awx.main.fields from django.db import migrations, models @@ -8,7 +8,7 @@ import django.db.models.deletion class Migration(migrations.Migration): dependencies = [ - ('main', '0082_v360_workflowapproval'), + ('main', '0082_v360_webhook_http_method'), ] operations = [ @@ -32,6 +32,16 @@ class Migration(migrations.Migration): 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.AlterField( + model_name='workflowjobnode', + name='unified_job_template', + field=models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='workflowjobnodes', to='main.UnifiedJobTemplate'), + ), + migrations.AlterField( + model_name='workflowjobtemplatenode', + name='unified_job_template', + field=models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='workflowjobtemplatenodes', to='main.UnifiedJobTemplate'), + ), migrations.CreateModel( name='WorkflowApproval', fields=[ @@ -40,4 +50,14 @@ class Migration(migrations.Migration): ], bases=('main.unifiedjob',), ), + migrations.AddField( + model_name='activitystream', + name='workflow_approval', + field=models.ManyToManyField(blank=True, to='main.WorkflowApproval'), + ), + migrations.AddField( + model_name='activitystream', + name='workflow_approval_template', + field=models.ManyToManyField(blank=True, to='main.WorkflowApprovalTemplate'), + ), ] diff --git a/awx/main/models/__init__.py b/awx/main/models/__init__.py index 65d246ee5f..6988951ce2 100644 --- a/awx/main/models/__init__.py +++ b/awx/main/models/__init__.py @@ -203,7 +203,7 @@ activity_stream_registrar.connect(WorkflowJobTemplate) activity_stream_registrar.connect(WorkflowJobTemplateNode) activity_stream_registrar.connect(WorkflowJob) activity_stream_registrar.connect(WorkflowApproval) -activity_stream_registrar.connect(WorkflowApprovalTemplate) +# activity_stream_registrar.connect(WorkflowApprovalTemplate) activity_stream_registrar.connect(OAuth2Application) activity_stream_registrar.connect(OAuth2AccessToken) diff --git a/awx/main/models/activity_stream.py b/awx/main/models/activity_stream.py index 852cea3eac..24d32f21d9 100644 --- a/awx/main/models/activity_stream.py +++ b/awx/main/models/activity_stream.py @@ -66,9 +66,8 @@ class ActivityStream(models.Model): workflow_job_node = models.ManyToManyField("WorkflowJobNode", blank=True) workflow_job_template = models.ManyToManyField("WorkflowJobTemplate", blank=True) workflow_job = models.ManyToManyField("WorkflowJob", blank=True) -# Possibly adding workflow_approval-related fields here?? &&&&&& -# workflow_approval_template = models.ManyToManyField("WorkflowApprovalTemplate", blank=True) -# workflow_approval = models.ManyToManyField("WorkflowApproval", blank=True) + workflow_approval_template = models.ManyToManyField("WorkflowApprovalTemplate", blank=True) + workflow_approval = models.ManyToManyField("WorkflowApproval", blank=True) unified_job_template = models.ManyToManyField("UnifiedJobTemplate", blank=True, related_name='activity_stream_as_unified_job_template+') unified_job = models.ManyToManyField("UnifiedJob", blank=True, related_name='activity_stream_as_unified_job+') ad_hoc_command = models.ManyToManyField("AdHocCommand", blank=True) diff --git a/awx/main/signals.py b/awx/main/signals.py index fb9bf39872..34658473f7 100644 --- a/awx/main/signals.py +++ b/awx/main/signals.py @@ -34,8 +34,8 @@ from awx.main.models import ( InventorySource, InventoryUpdateEvent, Job, JobEvent, JobHostSummary, JobTemplate, OAuth2AccessToken, Organization, Project, ProjectUpdateEvent, Role, SystemJob, SystemJobEvent, SystemJobTemplate, UnifiedJob, - UnifiedJobTemplate, User, UserSessionMembership, - ROLE_SINGLETON_SYSTEM_ADMINISTRATOR + UnifiedJobTemplate, User, UserSessionMembership, WorkflowJobTemplateNode, + WorkflowApprovalTemplate, ROLE_SINGLETON_SYSTEM_ADMINISTRATOR ) from awx.main.constants import CENSOR_VALUE from awx.main.utils import model_instance_diff, model_to_dict, camelcase_to_underscore, get_current_apps @@ -431,7 +431,7 @@ def model_serializer_mapping(): models.WorkflowJobTemplate: serializers.WorkflowJobTemplateWithSpecSerializer, models.WorkflowJobTemplateNode: serializers.WorkflowJobTemplateNodeSerializer, models.WorkflowApproval: serializers.WorkflowApprovalSerializer, - models.WorkflowApprovalTemplate: serializers.WorkflowApprovalTemplateSerializer, # &&&&&& + models.WorkflowApprovalTemplate: serializers.WorkflowApprovalTemplateSerializer, models.WorkflowJob: serializers.WorkflowJobSerializer, models.OAuth2AccessToken: serializers.OAuth2TokenSerializer, models.OAuth2Application: serializers.OAuth2ApplicationSerializer, @@ -506,11 +506,6 @@ def activity_stream_update(sender, instance, **kwargs): activity_entry.setting = conf_to_dict(instance) activity_entry.save() -# &&&&&& - # if isinstance(obj1, WorkflowApprovalTemplate) or isinstance(obj2_actual, WorkflowApprovalTemplate): - # continue - - def activity_stream_delete(sender, instance, **kwargs): if not activity_stream_enabled: @@ -645,23 +640,23 @@ 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(pre_delete, sender=WorkflowJobTemplateNode) +def delete_approval_nodes(sender, instance, **kwargs): + if type(instance.unified_job_template) is WorkflowApprovalTemplate: + instance.unified_job_template.delete() + + +# When setting UJT to anything other than "is approval node" - update this comment! +@receiver(pre_save, sender=WorkflowJobTemplateNode) +def placeholder_name(sender, instance, **kwargs): + try: + old = WorkflowJobTemplateNode.objects.get(id=instance.id) + except sender.DoesNotExist: + return + if old.unified_job_template == instance.unified_job_template: + return + if type(old.unified_job_template) is WorkflowApprovalTemplate: + old.unified_job_template.delete() @receiver(post_save, sender=Session)