diff --git a/awx/api/serializers.py b/awx/api/serializers.py index fdddc9ba229a..e7e6b47514fb 100644 --- a/awx/api/serializers.py +++ b/awx/api/serializers.py @@ -3275,11 +3275,13 @@ class WorkflowJobTemplateSerializer(JobTemplateMixin, LabelsListMixin, UnifiedJo 'admin', 'execute', {'copy': 'organization.workflow_admin'} ] + limit = serializers.CharField(allow_blank=True, allow_null=True, required=False, default=None) class Meta: model = WorkflowJobTemplate fields = ('*', 'extra_vars', 'organization', 'survey_enabled', 'allow_simultaneous', - 'ask_variables_on_launch', 'inventory', 'ask_inventory_on_launch',) + 'ask_variables_on_launch', 'inventory', 'limit', 'ask_inventory_on_launch', + 'ask_limit_on_launch',) def get_related(self, obj): res = super(WorkflowJobTemplateSerializer, self).get_related(obj) @@ -3305,6 +3307,27 @@ def get_related(self, obj): def validate_extra_vars(self, value): return vars_validate_or_raise(value) + def validate(self, attrs): + attrs = super(WorkflowJobTemplateSerializer, self).validate(attrs) + + # process char_prompts + mock_obj = self.Meta.model() + field_names = set(field.name for field in mock_obj._meta.fields) + if self.instance: + for field in self.instance._meta.fields: + setattr(mock_obj, field.name, getattr(self.instance, field.name)) + + for field_name, value in list(attrs.items()): + setattr(mock_obj, field_name, value) + if field_name not in field_names: + attrs.pop(field_name) + + # Model `.save` needs the container dict, not the psuedo fields + if mock_obj.char_prompts: + attrs['char_prompts'] = mock_obj.char_prompts + + return attrs + class WorkflowJobTemplateWithSpecSerializer(WorkflowJobTemplateSerializer): ''' @@ -3317,13 +3340,14 @@ class Meta: class WorkflowJobSerializer(LabelsListMixin, UnifiedJobSerializer): + limit = serializers.CharField(allow_blank=True, allow_null=True, required=False, default=None) class Meta: model = WorkflowJob fields = ('*', 'workflow_job_template', 'extra_vars', 'allow_simultaneous', 'job_template', 'is_sliced_job', '-execution_node', '-event_processing_finished', '-controller_node', - 'inventory',) + 'inventory', 'limit',) def get_related(self, obj): res = super(WorkflowJobSerializer, self).get_related(obj) @@ -4105,12 +4129,14 @@ class WorkflowJobLaunchSerializer(BaseSerializer): queryset=Inventory.objects.all(), required=False, write_only=True ) + limit = serializers.CharField(required=False, write_only=True, allow_blank=True) workflow_job_template_data = serializers.SerializerMethodField() class Meta: model = WorkflowJobTemplate - fields = ('ask_inventory_on_launch', 'can_start_without_user_input', 'defaults', 'extra_vars', - 'inventory', 'survey_enabled', 'variables_needed_to_start', + fields = ('ask_inventory_on_launch', 'ask_limit_on_launch', + 'can_start_without_user_input', 'defaults', 'extra_vars', + 'inventory', 'limit', 'survey_enabled', 'variables_needed_to_start', 'node_templates_missing', 'node_prompts_rejected', 'workflow_job_template_data', 'survey_enabled', 'ask_variables_on_launch') read_only_fields = ('ask_inventory_on_launch', 'ask_variables_on_launch') @@ -4150,9 +4176,11 @@ def validate(self, attrs): WFJT_extra_vars = template.extra_vars WFJT_inventory = template.inventory + WFJT_limit = template.limit super(WorkflowJobLaunchSerializer, self).validate(attrs) template.extra_vars = WFJT_extra_vars template.inventory = WFJT_inventory + template.limit = WFJT_limit return accepted diff --git a/awx/api/views/__init__.py b/awx/api/views/__init__.py index 1c57c35bd841..e0a38116dc0c 100644 --- a/awx/api/views/__init__.py +++ b/awx/api/views/__init__.py @@ -3135,10 +3135,15 @@ def update_raw_data(self, data): extra_vars.setdefault(v, u'') if extra_vars: data['extra_vars'] = extra_vars - if obj.ask_inventory_on_launch: - data['inventory'] = obj.inventory_id - else: - data.pop('inventory', None) + modified_ask_mapping = models.WorkflowJobTemplate.get_ask_mapping() + modified_ask_mapping.pop('extra_vars') + for field_name, ask_field_name in obj.get_ask_mapping().items(): + if not getattr(obj, ask_field_name): + data.pop(field_name, None) + elif field == 'inventory': + data[field] = getattrd(obj, "%s.%s" % (field, 'id'), None) + else: + data[field] = getattr(obj, field) return data def post(self, request, *args, **kwargs): diff --git a/awx/main/migrations/0083_v360_WFJT_prompts.py b/awx/main/migrations/0083_v360_WFJT_prompts.py new file mode 100644 index 000000000000..2a5da60ec06a --- /dev/null +++ b/awx/main/migrations/0083_v360_WFJT_prompts.py @@ -0,0 +1,24 @@ +# Generated by Django 2.2.2 on 2019-07-23 17:56 + +import awx.main.fields +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('main', '0082_v360_webhook_http_method'), + ] + + operations = [ + migrations.AddField( + model_name='workflowjobtemplate', + name='char_prompts', + field=awx.main.fields.JSONField(blank=True, default=dict), + ), + migrations.AddField( + model_name='workflowjobtemplate', + name='ask_limit_on_launch', + field=awx.main.fields.AskForField(blank=True, default=False), + ), + ] diff --git a/awx/main/models/jobs.py b/awx/main/models/jobs.py index 12c691d1954e..b0feefacecf3 100644 --- a/awx/main/models/jobs.py +++ b/awx/main/models/jobs.py @@ -882,7 +882,7 @@ def display_extra_vars(self): ''' Hides fields marked as passwords in survey. ''' - if self.survey_passwords: + if hasattr(self, 'survey_passwords') and self.survey_passwords: extra_vars = parse_yaml_or_json(self.extra_vars).copy() for key, value in self.survey_passwords.items(): if key in extra_vars: @@ -916,6 +916,15 @@ def credential(self): return None +for field_name in JobTemplate.get_ask_mapping().keys(): + if field_name == 'extra_vars': + continue + try: + LaunchTimeConfigBase._meta.get_field(field_name) + except FieldDoesNotExist: + setattr(LaunchTimeConfigBase, field_name, NullablePromptPsuedoField(field_name)) + + class LaunchTimeConfig(LaunchTimeConfigBase): ''' Common model for all objects that save details of a saved launch config @@ -949,15 +958,6 @@ def extra_vars(self, extra_vars): self.extra_data = extra_vars -for field_name in JobTemplate.get_ask_mapping().keys(): - if field_name == 'extra_vars': - continue - try: - LaunchTimeConfig._meta.get_field(field_name) - except FieldDoesNotExist: - setattr(LaunchTimeConfig, field_name, NullablePromptPsuedoField(field_name)) - - class JobLaunchConfig(LaunchTimeConfig): ''' Historical record of user launch-time overrides for a job diff --git a/awx/main/models/workflow.py b/awx/main/models/workflow.py index d413f0566684..975ae0cc4d9b 100644 --- a/awx/main/models/workflow.py +++ b/awx/main/models/workflow.py @@ -287,7 +287,7 @@ def get_job_kwargs(self): return data -class WorkflowJobOptions(BaseModel): +class WorkflowJobOptions(LaunchTimeConfigBase): class Meta: abstract = True @@ -361,6 +361,7 @@ class Meta: on_delete=models.SET_NULL, related_name='workflows', ) + # declared here for help_text inventory = models.ForeignKey( 'Inventory', related_name='%(class)ss', @@ -374,6 +375,10 @@ class Meta: blank=True, default=False, ) + ask_limit_on_launch = AskForField( + blank=True, + default=False, + ) admin_role = ImplicitRoleField(parent_role=[ 'singleton:' + ROLE_SINGLETON_SYSTEM_ADMINISTRATOR, 'organization.workflow_admin_role' @@ -500,7 +505,7 @@ def _get_related_jobs(self): return WorkflowJob.objects.filter(workflow_job_template=self) -class WorkflowJob(UnifiedJob, WorkflowJobOptions, SurveyJobMixin, JobNotificationMixin, LaunchTimeConfigBase): +class WorkflowJob(UnifiedJob, WorkflowJobOptions, SurveyJobMixin, JobNotificationMixin): class Meta: app_label = 'main' ordering = ('id',)