From cea5ebadb728db7e80e62e16f4c39873d108f037 Mon Sep 17 00:00:00 2001 From: Chris Meyers Date: Mon, 12 Sep 2016 11:04:20 -0400 Subject: [PATCH] split Node in job and job template node --- awx/api/serializers.py | 43 ++++++--- awx/api/urls.py | 25 ++++-- awx/api/views.py | 73 +++++++++++----- awx/main/access.py | 41 ++++++++- .../migrations/0033_v310_add_workflows.py | 51 ++++++++--- awx/main/models/activity_stream.py | 3 +- awx/main/models/workflow.py | 87 ++++++++++--------- awx/main/tests/manual/workflows/linear.py | 16 ++-- awx/main/tests/manual/workflows/parallel.py | 4 +- 9 files changed, 232 insertions(+), 111 deletions(-) mode change 100644 => 100755 awx/main/tests/manual/workflows/linear.py mode change 100644 => 100755 awx/main/tests/manual/workflows/parallel.py diff --git a/awx/api/serializers.py b/awx/api/serializers.py index d65fbd0ea9..3f49cca391 100644 --- a/awx/api/serializers.py +++ b/awx/api/serializers.py @@ -2228,31 +2228,51 @@ class WorkflowJobTemplateListSerializer(UnifiedJobTemplateSerializer): class WorkflowJobTemplateSerializer(WorkflowJobTemplateListSerializer): pass -class WorkflowNodeSerializer(BaseSerializer): +class WorkflowNodeBaseSerializer(BaseSerializer): #workflow_job_template = UnifiedJobTemplateSerializer() class Meta: - model = WorkflowNode # TODO: workflow_job and job read-only - fields = ('id', 'url', 'related', 'workflow_job_template', 'unified_job_template', 'success_nodes', 'failure_nodes', 'always_nodes', 'job',) + fields = ('id', 'url', 'related', 'success_nodes', 'failure_nodes', 'always_nodes',) + +class WorkflowJobTemplateNodeSerializer(WorkflowNodeBaseSerializer): + class Meta: + model = WorkflowJobTemplateNode + fields = ('*', 'workflow_job_template', 'unified_job_template',) def get_related(self, obj): - res = super(WorkflowNodeSerializer, self).get_related(obj) + res = super(WorkflowJobTemplateNodeSerializer, self).get_related(obj) + res['success_nodes'] = reverse('api:workflow_job_template_node_success_nodes_list', args=(obj.pk,)) + res['failure_nodes'] = reverse('api:workflow_job_template_node_failure_nodes_list', args=(obj.pk,)) + res['always_nodes'] = reverse('api:workflow_job_template_node_always_nodes_list', args=(obj.pk,)) if obj.workflow_job_template: res['workflow_job_template'] = reverse('api:workflow_job_template_detail', args=(obj.workflow_job_template.pk,)) if obj.unified_job_template: res['unified_job_template'] = obj.unified_job_template.get_absolute_url() + return res + +class WorkflowJobNodeSerializer(WorkflowNodeBaseSerializer): + class Meta: + model = WorkflowJobTemplateNode + fields = ('*', 'workflow_job_template', 'unified_job_template', 'job', 'workflow_job',) + + def get_related(self, obj): + res = super(WorkflowJobNodeSerializer, self).get_related(obj) + res['success_nodes'] = reverse('api:workflow_job_node_success_nodes_list', args=(obj.pk,)) + res['failure_nodes'] = reverse('api:workflow_job_node_failure_nodes_list', args=(obj.pk,)) + res['always_nodes'] = reverse('api:workflow_job_node_always_nodes_list', args=(obj.pk,)) + if obj.workflow_job_template: + res['workflow_job_template'] = reverse('api:workflow_job_template_detail', args=(obj.workflow_job_template.pk,)) if obj.job: res['job'] = reverse('api:job_detail', args=(obj.job.pk,)) if obj.workflow_job: res['workflow_job'] = reverse('api:workflow_job_detail', args=(obj.workflow_job.pk,)) - res['success_nodes'] = reverse('api:workflow_node_success_nodes_list', args=(obj.pk,)) - res['failure_nodes'] = reverse('api:workflow_node_failure_nodes_list', args=(obj.pk,)) - res['always_nodes'] = reverse('api:workflow_node_always_nodes_list', args=(obj.pk,)) - return res -class WorkflowNodeDetailSerializer(WorkflowNodeSerializer): +class WorkflowJobNodeListSerializer(WorkflowJobNodeSerializer): + pass + +class WorkflowJobTemplateNodeDetailSerializer(WorkflowJobTemplateNodeSerializer): ''' Influence the api browser sample data to not include workflow_job_template @@ -2262,14 +2282,13 @@ class WorkflowNodeDetailSerializer(WorkflowNodeSerializer): Maybe something to do with workflow_job_template being a relational field? ''' def build_relational_field(self, field_name, relation_info): - field_class, field_kwargs = super(WorkflowNodeDetailSerializer, self).build_relational_field(field_name, relation_info) + field_class, field_kwargs = super(WorkflowJobTemplateNodeDetailSerializer, self).build_relational_field(field_name, relation_info) if self.instance and field_name == 'workflow_job_template': field_kwargs['read_only'] = True field_kwargs.pop('queryset', None) return field_class, field_kwargs - -class WorkflowNodeListSerializer(WorkflowNodeSerializer): +class WorkflowJobTemplateNodeListSerializer(WorkflowJobTemplateNodeSerializer): pass class JobListSerializer(JobSerializer, UnifiedJobListSerializer): diff --git a/awx/api/urls.py b/awx/api/urls.py index e7240e39e2..af81d227d7 100644 --- a/awx/api/urls.py +++ b/awx/api/urls.py @@ -262,8 +262,8 @@ workflow_job_template_urls = patterns('awx.api.views', url(r'^(?P[0-9]+)/launch/$', 'workflow_job_template_launch'), url(r'^(?P[0-9]+)/workflow_nodes/$', 'workflow_job_template_workflow_nodes_list'), # url(r'^(?P[0-9]+)/cancel/$', 'workflow_job_template_cancel'), - #url(r'^(?P[0-9]+)/nodes/$', 'workflow_job_template_node_list'), ) + workflow_job_urls = patterns('awx.api.views', url(r'^$', 'workflow_job_list'), url(r'^(?P[0-9]+)/$', 'workflow_job_detail'), @@ -290,12 +290,20 @@ label_urls = patterns('awx.api.views', url(r'^(?P[0-9]+)/$', 'label_detail'), ) -workflow_node_urls = patterns('awx.api.views', - url(r'^$', 'workflow_node_list'), - url(r'^(?P[0-9]+)/$', 'workflow_node_detail'), - url(r'^(?P[0-9]+)/success_nodes/$', 'workflow_node_success_nodes_list'), - url(r'^(?P[0-9]+)/failure_nodes/$', 'workflow_node_failure_nodes_list'), - url(r'^(?P[0-9]+)/always_nodes/$', 'workflow_node_always_nodes_list'), +workflow_job_template_node_urls = patterns('awx.api.views', + url(r'^$', 'workflow_job_template_node_list'), + url(r'^(?P[0-9]+)/$', 'workflow_job_template_node_detail'), + url(r'^(?P[0-9]+)/success_nodes/$', 'workflow_job_template_node_success_nodes_list'), + url(r'^(?P[0-9]+)/failure_nodes/$', 'workflow_job_template_node_failure_nodes_list'), + url(r'^(?P[0-9]+)/always_nodes/$', 'workflow_job_template_node_always_nodes_list'), +) + +workflow_job_node_urls = patterns('awx.api.views', + url(r'^$', 'workflow_job_node_list'), + url(r'^(?P[0-9]+)/$', 'workflow_job_node_detail'), + url(r'^(?P[0-9]+)/success_nodes/$', 'workflow_job_node_success_nodes_list'), + url(r'^(?P[0-9]+)/failure_nodes/$', 'workflow_job_node_failure_nodes_list'), + url(r'^(?P[0-9]+)/always_nodes/$', 'workflow_job_node_always_nodes_list'), ) schedule_urls = patterns('awx.api.views', @@ -350,7 +358,8 @@ v1_urls = patterns('awx.api.views', url(r'^workflow_job_templates/',include(workflow_job_template_urls)), url(r'^workflow_jobs/' ,include(workflow_job_urls)), url(r'^labels/', include(label_urls)), - url(r'^workflow_nodes/', include(workflow_node_urls)), + url(r'^workflow_job_template_nodes/', include(workflow_job_template_node_urls)), + #url(r'^workflow_job_nodes/', include(workflow_job_node_urls)), url(r'^unified_job_templates/$','unified_job_template_list'), url(r'^unified_jobs/$', 'unified_job_list'), url(r'^activity_stream/', include(activity_stream_urls)), diff --git a/awx/api/views.py b/awx/api/views.py index 517ad0a3d7..d42aae1127 100644 --- a/awx/api/views.py +++ b/awx/api/views.py @@ -2615,31 +2615,31 @@ class JobTemplateObjectRolesList(SubListAPIView): return Role.objects.filter(content_type=content_type, object_id=po.pk) # TODO: -class WorkflowNodeList(ListCreateAPIView): +class WorkflowJobTemplateNodeList(ListCreateAPIView): - model = WorkflowNode - serializer_class = WorkflowNodeSerializer + model = WorkflowJobTemplateNode + serializer_class = WorkflowJobTemplateNodeListSerializer new_in_310 = True # TODO: -class WorkflowNodeDetail(RetrieveUpdateDestroyAPIView): +class WorkflowJobTemplateNodeDetail(RetrieveUpdateDestroyAPIView): - model = WorkflowNode - serializer_class = WorkflowNodeDetailSerializer + model = WorkflowJobTemplateNode + serializer_class = WorkflowJobTemplateNodeDetailSerializer new_in_310 = True -class WorkflowNodeChildrenBaseList(EnforceParentRelationshipMixin, SubListCreateAttachDetachAPIView): +class WorkflowJobTemplateNodeChildrenBaseList(EnforceParentRelationshipMixin, SubListCreateAttachDetachAPIView): - model = WorkflowNode - serializer_class = WorkflowNodeListSerializer + model = WorkflowJobTemplateNode + serializer_class = WorkflowJobTemplateNodeListSerializer always_allow_superuser = True # TODO: RBAC - parent_model = WorkflowNode + parent_model = WorkflowJobTemplateNode relationship = '' enforce_parent_relationship = 'workflow_job_template' new_in_310 = True ''' - Limit the set of WorkflowNodes to the related nodes of specified by + Limit the set of WorkflowJobTemplateNodes to the related nodes of specified by 'relationship' ''' def get_queryset(self): @@ -2647,18 +2647,46 @@ class WorkflowNodeChildrenBaseList(EnforceParentRelationshipMixin, SubListCreate self.check_parent_access(parent) return getattr(parent, self.relationship).all() -class WorkflowNodeSuccessNodesList(WorkflowNodeChildrenBaseList): - +class WorkflowJobTemplateNodeSuccessNodesList(WorkflowJobTemplateNodeChildrenBaseList): relationship = 'success_nodes' -class WorkflowNodeFailureNodesList(WorkflowNodeChildrenBaseList): - +class WorkflowJobTemplateNodeFailureNodesList(WorkflowJobTemplateNodeChildrenBaseList): relationship = 'failure_nodes' -class WorkflowNodeAlwaysNodesList(WorkflowNodeChildrenBaseList): - +class WorkflowJobTemplateNodeAlwaysNodesList(WorkflowJobTemplateNodeChildrenBaseList): relationship = 'always_nodes' +''' +class WorkflowJobNodeChildrenBaseList(EnforceParentRelationshipMixin, SubListCreateAttachDetachAPIView): + + model = WorkflowJobNode + serializer_class = WorkflowJobNodeListSerializer + always_allow_superuser = True # TODO: RBAC + parent_model = WorkflowJobTemplateNode + relationship = '' + enforce_parent_relationship = 'workflow_job_template' + new_in_310 = True + + # + #Limit the set of WorkflowJobTemplateNodes to the related nodes of specified by + #'relationship' + # + def get_queryset(self): + parent = self.get_parent_object() + self.check_parent_access(parent) + return getattr(parent, self.relationship).all() + +class WorkflowJobNodeSuccessNodesList(WorkflowJobNodeChildrenBaseList): + relationship = 'success_nodes' + +class WorkflowJobNodeFailureNodesList(WorkflowJobNodeChildrenBaseList): + relationship = 'failure_nodes' + +class WorkflowJobNodeAlwaysNodesList(WorkflowJobNodeChildrenBaseList): + relationship = 'always_nodes' +''' + + # TODO: class WorkflowJobTemplateList(ListCreateAPIView): @@ -2705,11 +2733,11 @@ class WorkflowJobTemplateLaunch(GenericAPIView): # TODO: class WorkflowJobTemplateWorkflowNodesList(SubListCreateAPIView): - model = WorkflowNode - serializer_class = WorkflowNodeListSerializer + model = WorkflowJobTemplateNode + serializer_class = WorkflowJobTemplateNodeListSerializer always_allow_superuser = True # TODO: RBAC parent_model = WorkflowJobTemplate - relationship = 'workflow_nodes' + relationship = 'workflow_job_template_nodes' parent_key = 'workflow_job_template' # TODO: @@ -2740,14 +2768,13 @@ class WorkflowJobDetail(RetrieveDestroyAPIView): class WorkflowJobWorkflowNodesList(SubListAPIView): - model = WorkflowNode - serializer_class = WorkflowNodeListSerializer + model = WorkflowJobNode + serializer_class = WorkflowJobNodeListSerializer always_allow_superuser = True # TODO: RBAC parent_model = WorkflowJob relationship = 'workflow_job_nodes' parent_key = 'workflow_job' - class SystemJobTemplateList(ListAPIView): model = SystemJobTemplate diff --git a/awx/main/access.py b/awx/main/access.py index 588041c6b9..c7eb368cad 100644 --- a/awx/main/access.py +++ b/awx/main/access.py @@ -1133,11 +1133,43 @@ class SystemJobAccess(BaseAccess): model = SystemJob # TODO: -class WorkflowNodeAccess(BaseAccess): +class WorkflowJobTemplateNodeAccess(BaseAccess): ''' - I can see/use a WorkflowNode if I have permission to associated Workflow Job Template + I can see/use a WorkflowJobTemplateNode if I have permission to associated Workflow Job Template ''' - model = WorkflowNode + model = WorkflowJobTemplateNode + + def get_queryset(self): + if self.user.is_superuser or self.user.is_system_auditor: + return self.model.objects.all() + + @check_superuser + def can_read(self, obj): + return True + + @check_superuser + def can_add(self, data): + if not data: # So the browseable API will work + return True + + return True + + @check_superuser + def can_change(self, obj, data): + if self.can_add(data) is False: + return False + + return True + + def can_delete(self, obj): + return self.can_change(obj, None) + +# TODO: +class WorkflowJobNodeAccess(BaseAccess): + ''' + I can see/use a WorkflowJobNode if I have permission to associated Workflow Job + ''' + model = WorkflowJobNode def get_queryset(self): if self.user.is_superuser or self.user.is_system_auditor: @@ -1863,6 +1895,7 @@ register_access(Role, RoleAccess) register_access(NotificationTemplate, NotificationTemplateAccess) register_access(Notification, NotificationAccess) register_access(Label, LabelAccess) -register_access(WorkflowNode, WorkflowNodeAccess) +register_access(WorkflowJobTemplateNode, WorkflowJobTemplateNodeAccess) +register_access(WorkflowJobNode, WorkflowJobNodeAccess) register_access(WorkflowJobTemplate, WorkflowJobTemplateAccess) register_access(WorkflowJob, WorkflowJobAccess) diff --git a/awx/main/migrations/0033_v310_add_workflows.py b/awx/main/migrations/0033_v310_add_workflows.py index 10f4879537..1ca0462edf 100644 --- a/awx/main/migrations/0033_v310_add_workflows.py +++ b/awx/main/migrations/0033_v310_add_workflows.py @@ -26,6 +26,21 @@ class Migration(migrations.Migration): }, bases=('main.unifiedjob', models.Model, awx.main.models.notifications.JobNotificationMixin, awx.main.models.workflow.WorkflowJobInheritNodesMixin), ), + migrations.CreateModel( + name='WorkflowJobNode', + fields=[ + ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), + ('created', models.DateTimeField(default=None, editable=False)), + ('modified', models.DateTimeField(default=None, editable=False)), + ('always_nodes', models.ManyToManyField(related_name='workflowjobnodes_always', to='main.WorkflowJobNode', blank=True)), + ('failure_nodes', models.ManyToManyField(related_name='workflowjobnodes_failure', to='main.WorkflowJobNode', blank=True)), + ('job', models.ForeignKey(related_name='unified_job_nodes', on_delete=django.db.models.deletion.SET_NULL, default=None, blank=True, to='main.UnifiedJob', null=True)), + ('success_nodes', models.ManyToManyField(related_name='workflowjobnodes_success', to='main.WorkflowJobNode', blank=True)), + ], + options={ + 'abstract': False, + }, + ), migrations.CreateModel( name='WorkflowJobTemplate', fields=[ @@ -36,19 +51,30 @@ class Migration(migrations.Migration): bases=('main.unifiedjobtemplate', models.Model), ), migrations.CreateModel( - name='WorkflowNode', + name='WorkflowJobTemplateNode', fields=[ ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), ('created', models.DateTimeField(default=None, editable=False)), ('modified', models.DateTimeField(default=None, editable=False)), - ('always_nodes', models.ManyToManyField(related_name='parent_always_nodes', to='main.WorkflowNode', blank=True)), - ('failure_nodes', models.ManyToManyField(related_name='parent_failure_nodes', to='main.WorkflowNode', blank=True)), - ('job', models.ForeignKey(related_name='unified_job_nodes', on_delete=django.db.models.deletion.SET_NULL, default=None, blank=True, to='main.UnifiedJob', null=True)), - ('success_nodes', models.ManyToManyField(related_name='parent_success_nodes', to='main.WorkflowNode', blank=True)), - ('unified_job_template', models.ForeignKey(related_name='unified_jt_workflow_nodes', on_delete=django.db.models.deletion.SET_NULL, default=None, blank=True, to='main.UnifiedJobTemplate', null=True)), - ('workflow_job', models.ForeignKey(related_name='workflow_job_nodes', on_delete=django.db.models.deletion.SET_NULL, default=None, blank=True, to='main.WorkflowJob', null=True)), - ('workflow_job_template', models.ForeignKey(related_name='workflow_nodes', default=None, blank=True, to='main.WorkflowJobTemplate', null=True)), + ('always_nodes', models.ManyToManyField(related_name='workflowjobtemplatenodes_always', to='main.WorkflowJobTemplateNode', blank=True)), + ('failure_nodes', models.ManyToManyField(related_name='workflowjobtemplatenodes_failure', to='main.WorkflowJobTemplateNode', blank=True)), + ('success_nodes', models.ManyToManyField(related_name='workflowjobtemplatenodes_success', to='main.WorkflowJobTemplateNode', blank=True)), + ('unified_job_template', models.ForeignKey(related_name='workflowjobtemplatenodes', on_delete=django.db.models.deletion.SET_NULL, default=None, blank=True, to='main.UnifiedJobTemplate', null=True)), + ('workflow_job_template', models.ForeignKey(related_name='workflow_job_template_nodes', default=None, blank=True, to='main.WorkflowJobTemplate', null=True)), ], + options={ + 'abstract': False, + }, + ), + migrations.AddField( + model_name='workflowjobnode', + name='unified_job_template', + field=models.ForeignKey(related_name='workflowjobnodes', on_delete=django.db.models.deletion.SET_NULL, default=None, blank=True, to='main.UnifiedJobTemplate', null=True), + ), + migrations.AddField( + model_name='workflowjobnode', + name='workflow_job', + field=models.ForeignKey(related_name='workflow_job_nodes', on_delete=django.db.models.deletion.SET_NULL, default=None, blank=True, to='main.WorkflowJob', null=True), ), migrations.AddField( model_name='workflowjob', @@ -60,6 +86,11 @@ class Migration(migrations.Migration): name='workflow_job', field=models.ManyToManyField(to='main.WorkflowJob', blank=True), ), + migrations.AddField( + model_name='activitystream', + name='workflow_job_node', + field=models.ManyToManyField(to='main.WorkflowJobNode', blank=True), + ), migrations.AddField( model_name='activitystream', name='workflow_job_template', @@ -67,7 +98,7 @@ class Migration(migrations.Migration): ), migrations.AddField( model_name='activitystream', - name='workflow_node', - field=models.ManyToManyField(to='main.WorkflowNode', blank=True), + name='workflow_job_template_node', + field=models.ManyToManyField(to='main.WorkflowJobTemplateNode', blank=True), ), ] diff --git a/awx/main/models/activity_stream.py b/awx/main/models/activity_stream.py index bcc5cef0c7..b0d58fc031 100644 --- a/awx/main/models/activity_stream.py +++ b/awx/main/models/activity_stream.py @@ -49,7 +49,8 @@ class ActivityStream(models.Model): permission = models.ManyToManyField("Permission", blank=True) job_template = models.ManyToManyField("JobTemplate", blank=True) job = models.ManyToManyField("Job", blank=True) - workflow_node = models.ManyToManyField("WorkflowNode", blank=True) + workflow_job_template_node = models.ManyToManyField("WorkflowJobTemplateNode", blank=True) + workflow_job_node = models.ManyToManyField("WorkflowJobNode", blank=True) workflow_job_template = models.ManyToManyField("WorkflowJobTemplate", blank=True) workflow_job = models.ManyToManyField("WorkflowJob", blank=True) unified_job_template = models.ManyToManyField("UnifiedJobTemplate", blank=True, related_name='activity_stream_as_unified_job_template+') diff --git a/awx/main/models/workflow.py b/awx/main/models/workflow.py index 48cdcee27b..af97b9b2c8 100644 --- a/awx/main/models/workflow.py +++ b/awx/main/models/workflow.py @@ -18,11 +18,11 @@ from awx.main.models.rbac import ( ) from awx.main.fields import ImplicitRoleField -__all__ = ['WorkflowJobTemplate', 'WorkflowJob', 'WorkflowJobOptions', 'WorkflowNode'] - -class WorkflowNode(CreatedModifiedModel): +__all__ = ['WorkflowJobTemplate', 'WorkflowJob', 'WorkflowJobOptions', 'WorkflowJobNode', 'WorkflowJobTemplateNode',] +class WorkflowNodeBase(CreatedModifiedModel): class Meta: + abstract = True app_label = 'main' # TODO: RBAC @@ -31,41 +31,55 @@ class WorkflowNode(CreatedModifiedModel): parent_role='workflow_job_template.admin_role', ) ''' - - # TODO: Ensure the API forces workflow_job_template being set - workflow_job_template = models.ForeignKey( - 'WorkflowJobTemplate', - related_name='workflow_nodes', + success_nodes = models.ManyToManyField( + 'self', blank=True, - null=True, - default=None, - on_delete=models.CASCADE, + symmetrical=False, + related_name='%(class)ss_success', + ) + failure_nodes = models.ManyToManyField( + 'self', + blank=True, + symmetrical=False, + related_name='%(class)ss_failure', + ) + always_nodes = models.ManyToManyField( + 'self', + blank=True, + symmetrical=False, + related_name='%(class)ss_always', ) unified_job_template = models.ForeignKey( 'UnifiedJobTemplate', - related_name='unified_jt_workflow_nodes', + related_name='%(class)ss', blank=True, null=True, default=None, on_delete=models.SET_NULL, ) - success_nodes = models.ManyToManyField( - 'self', - related_name='parent_success_nodes', + +class WorkflowJobTemplateNode(WorkflowNodeBase): + # TODO: Ensure the API forces workflow_job_template being set + workflow_job_template = models.ForeignKey( + 'WorkflowJobTemplate', + related_name='workflow_job_template_nodes', blank=True, - symmetrical=False, + null=True, + default=None, + on_delete=models.CASCADE, ) - failure_nodes = models.ManyToManyField( - 'self', - related_name='parent_failure_nodes', + + def get_absolute_url(self): + return reverse('api:workflow_job_template_node_detail', args=(self.pk,)) + +class WorkflowJobNode(WorkflowNodeBase): + job = models.ForeignKey( + 'UnifiedJob', + related_name='unified_job_nodes', blank=True, - symmetrical=False, - ) - always_nodes = models.ManyToManyField( - 'self', - related_name='parent_always_nodes', - blank=True, - symmetrical=False, + null=True, + default=None, + on_delete=models.SET_NULL, ) workflow_job = models.ForeignKey( 'WorkflowJob', @@ -75,17 +89,9 @@ class WorkflowNode(CreatedModifiedModel): default=None, on_delete=models.SET_NULL, ) - job = models.ForeignKey( - 'UnifiedJob', - related_name='unified_job_nodes', - blank=True, - null=True, - default=None, - on_delete=models.SET_NULL, - ) def get_absolute_url(self): - return reverse('api:workflow_node_detail', args=(self.pk,)) + return reverse('api:workflow_job_node_detail', args=(self.pk,)) class WorkflowJobOptions(BaseModel): class Meta: @@ -147,22 +153,17 @@ class WorkflowJobInheritNodesMixin(object): for old_related_node in old_related_nodes: new_related_node_id = node_ids_map[old_related_node.id] - new_related_node = WorkflowNode.objects.get(id=new_related_node_id) + new_related_node = WorkflowJobNode.objects.get(id=new_related_node_id) new_node_type_mgr.add(new_related_node) def inherit_jt_workflow_nodes(self): new_nodes = [] - old_nodes = self.workflow_job_template.workflow_nodes.all() + old_nodes = self.workflow_job_template.workflow_job_template_nodes.all() node_ids_map = {} for old_node in old_nodes: - new_node = WorkflowNode.objects.get(id=old_node.pk) - new_node.workflow_job = self - new_node.job = None - new_node.workflow_job_template = None - new_node.pk = None - new_node.save() + new_node = WorkflowJobNode.objects.create(workflow_job=self, unified_job_template=old_node.unified_job_template) new_nodes.append(new_node) node_ids_map[old_node.id] = new_node.id diff --git a/awx/main/tests/manual/workflows/linear.py b/awx/main/tests/manual/workflows/linear.py old mode 100644 new mode 100755 index 2b096fb8cd..ebb2c99b45 --- a/awx/main/tests/manual/workflows/linear.py +++ b/awx/main/tests/manual/workflows/linear.py @@ -1,6 +1,6 @@ # AWX from awx.main.models import ( - WorkflowNode, + WorkflowJobTemplateNode, WorkflowJobTemplate, ) from awx.main.models.jobs import JobTemplate @@ -10,16 +10,16 @@ def do_init_workflow(job_template_success, job_template_fail, job_template_never wfjt.delete() wfjt, created = WorkflowJobTemplate.objects.get_or_create(name="linear workflow") print(wfjt.id) - WorkflowNode.objects.all().delete() + WorkflowJobTemplateNode.objects.all().delete() if created: nodes_success = [] nodes_fail = [] nodes_never = [] for i in range(0, 2): - nodes_success.append(WorkflowNode.objects.create(workflow_job_template=wfjt, unified_job_template=job_template_success)) - nodes_fail.append(WorkflowNode.objects.create(workflow_job_template=wfjt, unified_job_template=job_template_fail)) - nodes_never.append(WorkflowNode.objects.create(workflow_job_template=wfjt, unified_job_template=job_template_never)) - nodes_never.append(WorkflowNode.objects.create(workflow_job_template=wfjt, unified_job_template=job_template_never)) + nodes_success.append(WorkflowJobTemplateNode.objects.create(workflow_job_template=wfjt, unified_job_template=job_template_success)) + nodes_fail.append(WorkflowJobTemplateNode.objects.create(workflow_job_template=wfjt, unified_job_template=job_template_fail)) + nodes_never.append(WorkflowJobTemplateNode.objects.create(workflow_job_template=wfjt, unified_job_template=job_template_never)) + nodes_never.append(WorkflowJobTemplateNode.objects.create(workflow_job_template=wfjt, unified_job_template=job_template_never)) nodes_fail[1].delete() nodes_success[0].success_nodes.add(nodes_fail[0]) @@ -32,8 +32,8 @@ def do_init_workflow(job_template_success, job_template_fail, job_template_never def do_init(): jt_success = JobTemplate.objects.get(id=5) - jt_fail= JobTemplate.objects.get(id=9) - jt_never= JobTemplate.objects.get(id=11) + jt_fail= JobTemplate.objects.get(id=6) + jt_never= JobTemplate.objects.get(id=7) do_init_workflow(jt_success, jt_fail, jt_never) if __name__ == "__main__": diff --git a/awx/main/tests/manual/workflows/parallel.py b/awx/main/tests/manual/workflows/parallel.py old mode 100644 new mode 100755 index 071d4e1e94..47c35a9839 --- a/awx/main/tests/manual/workflows/parallel.py +++ b/awx/main/tests/manual/workflows/parallel.py @@ -32,8 +32,8 @@ def do_init_workflow(job_template_success, job_template_fail, job_template_never def do_init(): jt_success = JobTemplate.objects.get(id=5) - jt_fail= JobTemplate.objects.get(id=9) - jt_never= JobTemplate.objects.get(id=11) + jt_fail= JobTemplate.objects.get(id=6) + jt_never= JobTemplate.objects.get(id=7) jt_parallel = [] jt_parallel.append(JobTemplate.objects.get(id=16))