diff --git a/awx/api/serializers.py b/awx/api/serializers.py index 2c904e2a7f..5ee7d1b3b7 100644 --- a/awx/api/serializers.py +++ b/awx/api/serializers.py @@ -2976,12 +2976,9 @@ class JobTemplateMixin(object): ''' def _recent_jobs(self, obj): - if hasattr(obj, 'workflow_jobs'): - job_mgr = obj.workflow_jobs - else: - job_mgr = obj.jobs + job_mgr = obj.unifiedjob_unified_jobs.non_polymorphic().only('id', 'status', 'finished') return [{'id': x.id, 'status': x.status, 'finished': x.finished} - for x in job_mgr.all().order_by('-created')[:10]] + for x in job_mgr.order_by('-created')[:10]] def get_summary_fields(self, obj): d = super(JobTemplateMixin, self).get_summary_fields(obj) diff --git a/awx/main/migrations/0050_v340_unified_jt_set_null.py b/awx/main/migrations/0050_v340_unified_jt_set_null.py new file mode 100644 index 0000000000..7ad65087f4 --- /dev/null +++ b/awx/main/migrations/0050_v340_unified_jt_set_null.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.11 on 2018-09-10 17:41 +from __future__ import unicode_literals + +import awx.main.utils.polymorphic +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('main', '0049_v340_add_job_template'), + ] + + operations = [ + migrations.AlterField( + model_name='unifiedjob', + name='unified_job_template', + field=models.ForeignKey(default=None, editable=False, null=True, on_delete=awx.main.utils.polymorphic.SET_NULL, related_name='unifiedjob_unified_jobs', to='main.UnifiedJobTemplate'), + ), + ] diff --git a/awx/main/models/jobs.py b/awx/main/models/jobs.py index 1671496191..bb1b229a43 100644 --- a/awx/main/models/jobs.py +++ b/awx/main/models/jobs.py @@ -320,10 +320,13 @@ class JobTemplate(UnifiedJobTemplate, JobOptions, SurveyJobTemplateMixin, Resour def resources_needed_to_start(self): return [fd for fd in ['project', 'inventory'] if not getattr(self, '{}_id'.format(fd))] - def create_unified_job(self, **kwargs): + def create_job(self, **kwargs): ''' Create a new job based on this template. ''' + return self.create_unified_job(**kwargs) + + def create_unified_job(self, **kwargs): split_event = bool( self.job_shard_count > 1 and not kwargs.pop('_prevent_sharding', False) @@ -345,7 +348,7 @@ class JobTemplate(UnifiedJobTemplate, JobOptions, SurveyJobTemplateMixin, Resour create_kwargs = dict(workflow_job=job, unified_job_template=self, ancestor_artifacts=dict(job_shard=idx)) - wfjn = WorkflowJobNode.objects.create(**create_kwargs) + WorkflowJobNode.objects.create(**create_kwargs) return job def get_absolute_url(self, request=None): @@ -480,7 +483,7 @@ class JobTemplate(UnifiedJobTemplate, JobOptions, SurveyJobTemplateMixin, Resour RelatedJobsMixin ''' def _get_related_jobs(self): - return Job.objects.filter(job_template=self) + return UnifiedJob.objects.filter(unified_job_template=self) class Job(UnifiedJob, JobOptions, SurveyJobMixin, JobNotificationMixin, TaskManagerJobMixin): diff --git a/awx/main/models/unified_jobs.py b/awx/main/models/unified_jobs.py index 5dd1241e0e..cbda5109e2 100644 --- a/awx/main/models/unified_jobs.py +++ b/awx/main/models/unified_jobs.py @@ -321,8 +321,6 @@ class UnifiedJobTemplate(PolymorphicModel, CommonModelNameNotUnique, Notificatio ''' Create a new unified job based on this unified job template. ''' - from awx.main.models import JobTemplate, WorkflowJob - new_job_passwords = kwargs.pop('survey_passwords', {}) eager_fields = kwargs.pop('_eager_fields', None) @@ -553,7 +551,7 @@ class UnifiedJob(PolymorphicModel, PasswordFieldsModel, CommonModelNameNotUnique default=None, editable=False, related_name='%(class)s_unified_jobs', - on_delete=models.SET_NULL, + on_delete=polymorphic.SET_NULL, ) launch_type = models.CharField( max_length=20, @@ -834,7 +832,7 @@ class UnifiedJob(PolymorphicModel, PasswordFieldsModel, CommonModelNameNotUnique ''' unified_job_class = self.__class__ unified_jt_class = self._get_unified_job_template_class() - parent_field_name = unified_job_class._get_parent_field_name() + parent_field_name = self._get_parent_field_name() fields = unified_jt_class._get_unified_job_field_names() | set([parent_field_name]) create_data = {} @@ -881,6 +879,8 @@ class UnifiedJob(PolymorphicModel, PasswordFieldsModel, CommonModelNameNotUnique config = JobLaunchConfig(job=self) if parent is None: parent = getattr(self, self._get_parent_field_name()) + if parent is None: + return valid_fields = parent.get_ask_mapping().keys() # Special cases allowed for workflows if hasattr(self, 'extra_vars'): diff --git a/awx/main/tests/functional/api/test_job.py b/awx/main/tests/functional/api/test_job.py index 8cd26e71ba..1685c62304 100644 --- a/awx/main/tests/functional/api/test_job.py +++ b/awx/main/tests/functional/api/test_job.py @@ -122,6 +122,22 @@ def test_job_relaunch_on_failed_hosts(post, inventory, project, machine_credenti assert r.data.get('limit') == hosts +@pytest.mark.django_db +def test_shard_jt_recent_jobs(shard_job_factory, admin_user, get): + workflow_job = shard_job_factory(3, spawn=True) + shard_jt = workflow_job.job_template + r = get( + url=shard_jt.get_absolute_url(), + user=admin_user, + expect=200 + ) + job_ids = [entry['id'] for entry in r.data['summary_fields']['recent_jobs']] + assert workflow_job.pk in job_ids + for node in workflow_job.workflow_nodes.all(): + job = node.job + assert job.pk in job_ids + + @pytest.mark.django_db def test_block_unprocessed_events(delete, admin_user, mocker): time_of_finish = parse("Thu Feb 28 09:10:20 2013 -0500") diff --git a/awx/main/tests/functional/models/test_job.py b/awx/main/tests/functional/models/test_job.py index 9926e42b5d..c2614b17f1 100644 --- a/awx/main/tests/functional/models/test_job.py +++ b/awx/main/tests/functional/models/test_job.py @@ -82,6 +82,7 @@ def test_job_host_summary_representation(host): host.delete() assert 'N/A changed=1 dark=2 failures=3 ok=4 processed=5 skipped=6' == six.text_type(jhs) + @pytest.mark.django_db class TestShardingModels: @@ -97,6 +98,6 @@ class TestShardingModels: job = shard_job_factory(3, jt_kwargs={'ask_limit_on_launch': True}, prompts={'limit': 'foobar'}, spawn=True) assert job.launch_config.prompts_dict() == {'limit': 'foobar'} for node in job.workflow_nodes.all(): - assert node.limit == None # data not saved in node prompts + assert node.limit is None # data not saved in node prompts job = node.job assert job.limit == 'foobar' diff --git a/awx/main/tests/functional/models/test_unified_job.py b/awx/main/tests/functional/models/test_unified_job.py index ff3af2439b..f587e4c448 100644 --- a/awx/main/tests/functional/models/test_unified_job.py +++ b/awx/main/tests/functional/models/test_unified_job.py @@ -58,9 +58,7 @@ class TestCreateUnifiedJob: job_with_links.save() job_with_links.credentials.add(machine_credential) job_with_links.credentials.add(net_credential) - with mocker.patch('awx.main.models.unified_jobs.UnifiedJobTemplate._get_unified_job_field_names', - return_value=['inventory', 'credential', 'limit']): - second_job = job_with_links.copy_unified_job() + second_job = job_with_links.copy_unified_job() # Check that job data matches the original variables assert second_job.credential == job_with_links.credential diff --git a/awx/main/tests/unit/api/serializers/test_job_template_serializers.py b/awx/main/tests/unit/api/serializers/test_job_template_serializers.py index a6f41debb9..8719b9b1b9 100644 --- a/awx/main/tests/unit/api/serializers/test_job_template_serializers.py +++ b/awx/main/tests/unit/api/serializers/test_job_template_serializers.py @@ -71,14 +71,19 @@ class TestJobTemplateSerializerGetRelated(): class TestJobTemplateSerializerGetSummaryFields(): def test__recent_jobs(self, mocker, job_template, jobs): - job_template.jobs.all = mocker.MagicMock(**{'order_by.return_value': jobs}) - job_template.jobs.all.return_value = job_template.jobs.all + job_template.unifiedjob_unified_jobs = mocker.MagicMock(**{ + 'non_polymorphic.return_value': mocker.MagicMock(**{ + 'only.return_value': mocker.MagicMock(**{ + 'order_by.return_value': jobs + }) + }) + }) serializer = JobTemplateSerializer() recent_jobs = serializer._recent_jobs(job_template) - job_template.jobs.all.assert_called_once_with() - job_template.jobs.all.order_by.assert_called_once_with('-created') + job_template.unifiedjob_unified_jobs.non_polymorphic.assert_called_once_with() + job_template.unifiedjob_unified_jobs.non_polymorphic().only().order_by.assert_called_once_with('-created') assert len(recent_jobs) == 10 for x in jobs[:10]: assert recent_jobs == [{'id': x.id, 'status': x.status, 'finished': x.finished} for x in jobs[:10]]