From 19ccb5e213992b6e44cbb9affb360e14fc8986d1 Mon Sep 17 00:00:00 2001 From: Christian Adams Date: Fri, 15 May 2020 15:13:49 -0400 Subject: [PATCH] Mark job_explanation strings after they are read from the db - For strings that need to be translated, but are saved in the db: * They must be marked for translation using gettext_noop() to be translated. * And must also be marked for translation with _() when read from db and shown to the user. * [Ref]: https://docs.djangoproject.com/en/3.0/topics/i18n/translation/#marking-strings-as-no-op --- awx/api/serializers.py | 4 +++- awx/main/scheduler/task_manager.py | 14 +++++++------- awx/main/tasks.py | 5 +++-- .../tests/functional/models/test_notifications.py | 5 ++++- 4 files changed, 17 insertions(+), 11 deletions(-) diff --git a/awx/api/serializers.py b/awx/api/serializers.py index 631d8afeca..75531f6e8a 100644 --- a/awx/api/serializers.py +++ b/awx/api/serializers.py @@ -806,7 +806,9 @@ class UnifiedJobSerializer(BaseSerializer): td = now() - obj.started ret['elapsed'] = (td.microseconds + (td.seconds + td.days * 24 * 3600) * 10 ** 6) / (10 ** 6 * 1.0) ret['elapsed'] = float(ret['elapsed']) - + # Because this string is saved in the db in the source language, + # it must be marked for translation after it is pulled from the db, not when set + ret['job_explanation'] = _(obj.job_explanation) return ret diff --git a/awx/main/scheduler/task_manager.py b/awx/main/scheduler/task_manager.py index 97a429a415..247f37544d 100644 --- a/awx/main/scheduler/task_manager.py +++ b/awx/main/scheduler/task_manager.py @@ -10,7 +10,7 @@ import random # Django from django.db import transaction, connection -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import ugettext_lazy as _, gettext_noop from django.utils.timezone import now as tz_now # AWX @@ -114,7 +114,7 @@ class TaskManager(): logger.info('Refusing to start recursive workflow-in-workflow id={}, wfjt={}, ancestors={}'.format( job.id, spawn_node.unified_job_template.pk, [wa.pk for wa in workflow_ancestors])) display_list = [spawn_node.unified_job_template] + workflow_ancestors - job.job_explanation = _( + job.job_explanation = gettext_noop( "Workflow Job spawned from workflow could not start because it " "would result in recursion (spawn order, most recent first: {})" ).format(', '.join(['<{}>'.format(tmp) for tmp in display_list])) @@ -123,8 +123,8 @@ class TaskManager(): job.id, spawn_node.unified_job_template.pk, [wa.pk for wa in workflow_ancestors])) if not job._resources_sufficient_for_launch(): can_start = False - job.job_explanation = _("Job spawned from workflow could not start because it " - "was missing a related resource such as project or inventory") + job.job_explanation = gettext_noop("Job spawned from workflow could not start because it " + "was missing a related resource such as project or inventory") if can_start: if workflow_job.start_args: start_args = json.loads(decrypt_field(workflow_job, 'start_args')) @@ -132,8 +132,8 @@ class TaskManager(): start_args = {} can_start = job.signal_start(**start_args) if not can_start: - job.job_explanation = _("Job spawned from workflow could not start because it " - "was not in the right state or required manual credentials") + job.job_explanation = gettext_noop("Job spawned from workflow could not start because it " + "was not in the right state or required manual credentials") if not can_start: job.status = 'failed' job.save(update_fields=['status', 'job_explanation']) @@ -173,7 +173,7 @@ class TaskManager(): workflow_job.status = new_status if reason: logger.info(reason) - workflow_job.job_explanation = _("No error handling paths found, marking workflow as failed") + workflow_job.job_explanation = gettext_noop("No error handling paths found, marking workflow as failed") update_fields.append('job_explanation') workflow_job.start_args = '' # blank field to remove encrypted passwords workflow_job.save(update_fields=update_fields) diff --git a/awx/main/tasks.py b/awx/main/tasks.py index d2bf089822..b84c93d924 100644 --- a/awx/main/tasks.py +++ b/awx/main/tasks.py @@ -31,7 +31,7 @@ from django.db.models.fields.related import ForeignKey from django.utils.timezone import now, timedelta from django.utils.encoding import smart_str from django.contrib.auth.models import User -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import ugettext_lazy as _, gettext_noop from django.core.cache import cache from django.core.exceptions import ObjectDoesNotExist @@ -558,7 +558,8 @@ def awx_periodic_scheduler(): continue if not can_start: new_unified_job.status = 'failed' - new_unified_job.job_explanation = "Scheduled job could not start because it was not in the right state or required manual credentials" + new_unified_job.job_explanation = gettext_noop("Scheduled job could not start because it \ + was not in the right state or required manual credentials") new_unified_job.save(update_fields=['status', 'job_explanation']) new_unified_job.websocket_emit_status("failed") emit_channel_notification('schedules-changed', dict(id=schedule.id, group_name="schedules")) diff --git a/awx/main/tests/functional/models/test_notifications.py b/awx/main/tests/functional/models/test_notifications.py index 795a3696b5..4208843969 100644 --- a/awx/main/tests/functional/models/test_notifications.py +++ b/awx/main/tests/functional/models/test_notifications.py @@ -105,7 +105,10 @@ class TestJobNotificationMixin(object): assert isinstance(obj[key], dict) check_structure(expected_structure[key], obj[key]) else: - assert isinstance(obj[key], expected_structure[key]) + if key == 'job_explanation': + assert isinstance(str(obj[key]), expected_structure[key]) + else: + assert isinstance(obj[key], expected_structure[key]) kwargs = {} if JobClass is InventoryUpdate: kwargs['inventory_source'] = inventory_source