From 0da7edeab81f52b4960c42091bf9905691a20154 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20P=C3=A9ters?= Date: Sat, 15 May 2021 15:34:16 +0200 Subject: [PATCH] general: add and use a lazy gettext function (#51289) --- tests/test_rootdirectory.py | 4 + wcs/admin/blocks.py | 4 +- wcs/admin/categories.py | 20 +-- wcs/admin/fields.py | 12 +- wcs/admin/forms.py | 84 +++++---- wcs/admin/settings.py | 27 +-- wcs/admin/users.py | 12 +- wcs/admin/workflows.py | 15 +- wcs/api.py | 4 +- wcs/backoffice/cards.py | 26 +-- wcs/backoffice/data_management.py | 8 +- wcs/backoffice/management.py | 34 ++-- wcs/backoffice/root.py | 22 +-- wcs/blocks.py | 10 +- wcs/carddef.py | 20 +-- wcs/categories.py | 8 +- wcs/data_sources.py | 4 +- wcs/fields.py | 52 +++--- wcs/formdata.py | 22 +-- wcs/formdef.py | 56 +++--- wcs/forms/actions.py | 6 +- wcs/forms/root.py | 48 ++--- wcs/logged_errors.py | 2 +- wcs/portfolio.py | 4 +- wcs/qommon/__init__.py | 5 +- wcs/qommon/admin/emails.py | 26 ++- wcs/qommon/admin/menu.py | 12 +- wcs/qommon/admin/texts.py | 14 +- wcs/qommon/afterjobs.py | 22 ++- wcs/qommon/errors.py | 14 +- wcs/qommon/form.py | 91 +++++----- wcs/qommon/http_response.py | 4 +- wcs/qommon/humantime.py | 17 +- wcs/qommon/ident/franceconnect.py | 50 +++--- wcs/qommon/ident/idp.py | 18 +- wcs/qommon/ident/password.py | 168 +++++++++--------- wcs/qommon/misc.py | 7 +- wcs/qommon/myspace.py | 4 +- wcs/qommon/publisher.py | 22 ++- wcs/qommon/sessions.py | 3 + wcs/qommon/substitution.py | 2 +- wcs/qommon/template.py | 2 +- wcs/root.py | 10 +- wcs/snapshots.py | 4 +- wcs/sql.py | 3 + .../wcs/backoffice/logged-error.html | 2 +- wcs/templates/wcs/backoffice/processing.html | 2 +- wcs/users.py | 8 +- wcs/wf/aggregation_email.py | 4 +- wcs/wf/anonymise.py | 4 +- wcs/wf/attachment.py | 4 +- wcs/wf/backoffice_fields.py | 6 +- wcs/wf/create_carddata.py | 12 +- wcs/wf/create_formdata.py | 20 +-- wcs/wf/criticality.py | 4 +- wcs/wf/dispatch.py | 4 +- wcs/wf/edit_carddata.py | 6 +- wcs/wf/export_to_model.py | 8 +- wcs/wf/external_workflow.py | 10 +- wcs/wf/form.py | 6 +- wcs/wf/geolocate.py | 4 +- wcs/wf/jump.py | 9 +- wcs/wf/notification.py | 4 +- wcs/wf/profile.py | 6 +- wcs/wf/redirect_to_url.py | 4 +- wcs/wf/register_comment.py | 4 +- wcs/wf/remove.py | 4 +- wcs/wf/resubmit.py | 4 +- wcs/wf/roles.py | 10 +- wcs/wf/timeout_jump.py | 4 +- wcs/wf/wscall.py | 8 +- wcs/workflows.py | 70 ++++---- 72 files changed, 646 insertions(+), 587 deletions(-) diff --git a/tests/test_rootdirectory.py b/tests/test_rootdirectory.py index f72bf0347..6343e4cd9 100644 --- a/tests/test_rootdirectory.py +++ b/tests/test_rootdirectory.py @@ -183,3 +183,7 @@ def test_jquery_debug_mode(): pub.write_cfg() resp = get_app(pub).get('/category1/test-formdef-1/') assert 'jquery.js' in resp.text + + +def test_i18n_js(): + get_app(pub).get('/i18n.js') diff --git a/wcs/admin/blocks.py b/wcs/admin/blocks.py index dde032a58..4da250ac0 100644 --- a/wcs/admin/blocks.py +++ b/wcs/admin/blocks.py @@ -22,7 +22,7 @@ from wcs.admin import utils from wcs.admin.fields import FieldDefPage, FieldsDirectory from wcs.backoffice.snapshots import SnapshotsDirectory from wcs.blocks import BlockDef, BlockdefImportError -from wcs.qommon import N_, _, misc, template +from wcs.qommon import _, misc, template from wcs.qommon.backoffice.menu import html_top from wcs.qommon.form import FileWidget, Form, HtmlWidget, StringWidget @@ -48,7 +48,7 @@ class BlockDirectory(FieldsDirectory): field_def_page_class = BlockFieldDefPage blacklisted_types = ['page', 'table', 'table-select', 'tablerows', 'ranked-items', 'blocks', 'computed'] support_import = False - readonly_message = N_('This block of fields is readonly.') + readonly_message = _('This block of fields is readonly.') def __init__(self, section, *args, **kwargs): self.section = section diff --git a/wcs/admin/categories.py b/wcs/admin/categories.py index b5bb1f622..33c6a7c95 100644 --- a/wcs/admin/categories.py +++ b/wcs/admin/categories.py @@ -21,7 +21,7 @@ from quixote.html import TemplateIO, htmltext from wcs.carddef import CardDef from wcs.categories import CardDefCategory, Category from wcs.formdef import FormDef -from wcs.qommon import N_, _ +from wcs.qommon import _ from wcs.qommon.backoffice.menu import html_top from wcs.qommon.form import Form, HtmlWidget, StringWidget, WysiwygTextWidget @@ -83,8 +83,8 @@ class CategoryPage(Directory): category_class = Category category_ui_class = CategoryUI formdef_class = FormDef - usage_title = N_('Forms in this category') - empty_message = N_('no form associated to this category') + usage_title = _('Forms in this category') + empty_message = _('no form associated to this category') _q_exports = ['', 'edit', 'delete', 'description'] def __init__(self, component): @@ -112,14 +112,14 @@ class CategoryPage(Directory): formdefs = self.formdef_class.select(order_by='name') formdefs = [x for x in formdefs if x.category_id == self.category.id] r += htmltext('
') - r += htmltext('

%s

') % _(self.usage_title) + r += htmltext('

%s

') % self.usage_title r += htmltext('') r += htmltext('
') return r.getvalue() @@ -192,8 +192,8 @@ class CardDefCategoryPage(CategoryPage): category_class = CardDefCategory category_ui_class = CardDefCategoryUI formdef_class = CardDef - usage_title = N_('Card models in this category') - empty_message = N_('no card model associated to this category') + usage_title = _('Card models in this category') + empty_message = _('no card model associated to this category') class CategoriesDirectory(Directory): @@ -201,7 +201,7 @@ class CategoriesDirectory(Directory): category_class = Category category_ui_class = CategoryUI category_page_class = CategoryPage - category_explanation = N_('Categories are used to sort the different forms.') + category_explanation = _('Categories are used to sort the different forms.') def _q_index(self): get_response().add_javascript(['jquery.js', 'jquery-ui.js', 'biglist.js', 'qommon.wysiwyg.js']) @@ -214,7 +214,7 @@ class CategoriesDirectory(Directory): r += htmltext('%s') % _('New Category') r += htmltext('') r += htmltext('') - r += htmltext('

%s

') % _(self.category_explanation) + r += htmltext('

%s

') % self.category_explanation categories = self.category_class.select() r += htmltext('') if self.objectdef.is_readonly(): - get_response().filter['sidebar'] = htmltext('

%s

') % _( - self.readonly_message + get_response().filter['sidebar'] = ( + htmltext('

%s

') % self.readonly_message ) if hasattr(self.objectdef, 'snapshot_object'): get_response().filter['sidebar'] += utils.snapshot_info_block( @@ -472,7 +472,7 @@ class FieldsDirectory(Directory): { 'success': 'ok', 'additional-action': { - 'message': _('Also move the fields of the page'), + 'message': str(_('Also move the fields of the page')), 'url': 'move_page_fields?fields=%s&page=%s' % (';'.join(page_field_ids), dropped_element), }, } diff --git a/wcs/admin/forms.py b/wcs/admin/forms.py index fa61d58e7..586c42258 100644 --- a/wcs/admin/forms.py +++ b/wcs/admin/forms.py @@ -32,7 +32,7 @@ from wcs.carddef import CardDef from wcs.categories import Category from wcs.formdef import DRAFTS_DEFAULT_LIFESPAN, FormDef, FormdefImportError, FormdefImportRecoverableError from wcs.forms.root import qrcode -from wcs.qommon import N_, _, force_str, get_logger, misc, template +from wcs.qommon import _, force_str, get_logger, misc, template from wcs.qommon.afterjobs import AfterJob from wcs.qommon.backoffice.menu import html_top from wcs.qommon.errors import TraversalError @@ -169,7 +169,7 @@ class AdminFieldDefPage(FieldDefPage): class AdminFieldsDirectory(FieldsDirectory): field_def_page_class = AdminFieldDefPage field_var_prefix = 'form_var_' - readonly_message = N_('This form is readonly.') + readonly_message = _('This form is readonly.') def index_bottom(self): if self.objectdef.is_readonly(): @@ -189,7 +189,7 @@ class AdminFieldsDirectory(FieldsDirectory): class OptionsDirectory(Directory): category_class = Category - category_empty_choice = N_('Select a category for this form') + category_empty_choice = _('Select a category for this form') _q_exports = [ 'confirmation', 'only_allow_one', @@ -341,7 +341,7 @@ class OptionsDirectory(Directory): def category(self): categories = get_categories(self.category_class) form = Form(enctype='multipart/form-data') - form.widgets.append(HtmlWidget('

%s

' % _(self.category_empty_choice))) + form.widgets.append(HtmlWidget('

%s

' % self.category_empty_choice)) form.add( SingleSelectWidget, 'category_id', @@ -573,10 +573,10 @@ class FormDefPage(Directory): formdef_default_workflow = '_default' options_directory_class = OptionsDirectory - delete_message = N_('You are about to irrevocably delete this form.') - delete_title = N_('Deleting Form:') - overwrite_message = N_('You can replace this form by uploading a file ' 'or by pointing to a form URL.') - overwrite_success_message = N_( + delete_message = _('You are about to irrevocably delete this form.') + delete_title = _('Deleting Form:') + overwrite_message = _('You can replace this form by uploading a file ' 'or by pointing to a form URL.') + overwrite_success_message = _( 'The form has been successfully overwritten. ' 'Do note it kept its existing address and role and workflow parameters.' ) @@ -834,7 +834,7 @@ class FormDefPage(Directory): except KeyError: # removed role ? roles.append(_('Unknown role (%s)') % x) - value = htmltext(', ').join(roles) + value = htmltext(', ').join([str(x) for x in roles]) else: value = C_('roles|None') return value @@ -845,7 +845,7 @@ class FormDefPage(Directory): auth_contexts = get_publisher().get_supported_authentication_contexts() value += ' (%s)' % ', '.join( [ - auth_contexts.get(x) + str(auth_contexts.get(x)) for x in self.formdef.required_authentication_contexts if auth_contexts.get(x) ] @@ -1180,11 +1180,11 @@ class FormDefPage(Directory): def duplicate(self): self.formdefui.formdef.id = None original_name = self.formdefui.formdef.name - self.formdefui.formdef.name = self.formdefui.formdef.name + _(' (copy)') + self.formdefui.formdef.name = '%s %s' % (self.formdefui.formdef.name, _('(copy)')) formdef_names = [x.name for x in self.formdef_class.select(lightweight=True)] no = 2 while self.formdefui.formdef.name in formdef_names: - self.formdefui.formdef.name = _('%(name)s (copy %(no)d)') % {'name': original_name, 'no': no} + self.formdefui.formdef.name = str(_('%(name)s (copy %(no)d)')) % {'name': original_name, 'no': no} no += 1 self.formdefui.formdef.url_name = None self.formdefui.formdef.table_name = None @@ -1212,16 +1212,16 @@ class FormDefPage(Directory): if check_count_message: form.widgets.append(HtmlWidget('

%s

' % check_count_message)) else: - form.widgets.append(HtmlWidget('

%s

' % _(self.delete_message))) + form.widgets.append(HtmlWidget('

%s

' % self.delete_message)) form.add_submit('delete', _('Delete')) form.add_submit('cancel', _('Cancel')) if form.get_widget('cancel').parse() or (form.is_submitted() and check_count_message): return redirect('..') if not form.is_submitted() or form.has_errors(): get_response().breadcrumb.append(('delete', _('Delete'))) - self.html_top(title=_(self.delete_title)) + self.html_top(title=self.delete_title) r = TemplateIO(html=True) - r += htmltext('

%s %s

') % (_(self.delete_title), self.formdef.name) + r += htmltext('

%s %s

') % (self.delete_title, self.formdef.name) r += form.render() return r.getvalue() else: @@ -1249,7 +1249,7 @@ class FormDefPage(Directory): self.html_top(title=_('Overwrite')) r = TemplateIO(html=True) r += htmltext('

%s

') % _('Overwrite') - r += htmltext('

%s

') % _(self.overwrite_message) + r += htmltext('

%s

') % self.overwrite_message r += form.render() return r.getvalue() @@ -1320,7 +1320,7 @@ class FormDefPage(Directory): new_formdef.roles = self.formdef.roles self.formdef = new_formdef self.formdef.store(comment=_('Overwritten')) - get_session().message = ('info', _(self.overwrite_success_message)) + get_session().message = ('info', str(self.overwrite_success_message)) return redirect('.') def get_incompatible_field_ids(self, new_formdef): @@ -1539,9 +1539,12 @@ class FormDefPage(Directory): count = self.formdef.data_class().count() archiver = Archiver(self.formdef) if count > 100: # Arbitrary threshold - job = get_response().add_after_job(str(N_('Archiving forms')), archiver.archive) - job.done_action_url = self.formdef.get_admin_url() + 'archive?job=%s' % job.id - job.done_action_label = _('Download Archive') + job = get_response().add_after_job( + _('Archiving forms'), + archiver.archive, + done_action_url=self.formdef.get_admin_url() + 'archive?job=%(job_id)s', + done_action_label=_('Download Archive'), + ) job.store() return redirect(job.get_processing_url()) else: @@ -1632,9 +1635,12 @@ class FormDefPage(Directory): count = self.formdef.data_class().count() anonymiser = Anonymiser(self.formdef, status_ids, before_date) if count > 100: # Arbitrary threshold - job = get_response().add_after_job(str(N_('Anonymising forms')), anonymiser.anonymise) - job.done_action_url = self.formdef.get_admin_url() - job.done_action_label = _('Back') + job = get_response().add_after_job( + _('Anonymising forms'), + anonymiser.anonymise, + done_action_url=self.formdef.get_admin_url(), + done_action_label=_('Back'), + ) job.store() return redirect(job.get_processing_url()) else: @@ -1739,15 +1745,15 @@ class FormsDirectory(AccessControlled, Directory): formdef_page_class = FormDefPage formdef_ui_class = FormDefUI - top_title = N_('Forms') - import_title = N_('Import Form') - import_submit_label = N_('Import Form') - import_paragraph = N_('You can install a new form by uploading a file ' 'or by pointing to the form URL.') - import_loading_error_message = N_('Error loading form (%s).') - import_success_message = N_( + top_title = _('Forms') + import_title = _('Import Form') + import_submit_label = _('Import Form') + import_paragraph = _('You can install a new form by uploading a file ' 'or by pointing to the form URL.') + import_loading_error_message = _('Error loading form (%s).') + import_success_message = _( 'This form has been successfully imported. ' 'Do note it is disabled by default.' ) - import_error_message = N_( + import_error_message = _( 'Imported form contained errors and has been automatically fixed, ' 'you should nevertheless check everything is ok. ' 'Do note it is disabled by default.' @@ -1761,7 +1767,7 @@ class FormsDirectory(AccessControlled, Directory): return super()._q_traverse(path) def _q_index(self): - self.html_top(title=_(self.top_title)) + self.html_top(title=self.top_title) r = TemplateIO(html=True) get_response().add_javascript(['jquery.js', 'widget_list.js']) r += self.form_actions() @@ -1865,7 +1871,7 @@ class FormsDirectory(AccessControlled, Directory): form.add(FileWidget, 'file', title=_('File'), required=False) form.add(UrlWidget, 'url', title=_('Address'), required=False, size=50) - form.add_submit('submit', _(self.import_submit_label)) + form.add_submit('submit', self.import_submit_label) form.add_submit('cancel', _('Cancel')) if form.get_submit() == 'cancel': @@ -1878,10 +1884,10 @@ class FormsDirectory(AccessControlled, Directory): pass get_response().breadcrumb.append(('import', _('Import'))) - self.html_top(title=_(self.import_title)) + self.html_top(title=self.import_title) r = TemplateIO(html=True) - r += htmltext('

%s

') % _(self.import_title) - r += htmltext('

%s

') % _(self.import_paragraph) + r += htmltext('

%s

') % self.import_title + r += htmltext('

%s

') % self.import_paragraph r += form.render() return r.getvalue() @@ -1894,7 +1900,7 @@ class FormsDirectory(AccessControlled, Directory): try: fp = misc.urlopen(url) except misc.ConnectionError as e: - form.set_error('url', _(self.import_loading_error_message) % str(e)) + form.set_error('url', self.import_loading_error_message % str(e)) raise ValueError() else: form.set_error('file', _('You have to enter a file or a URL.')) @@ -1904,11 +1910,11 @@ class FormsDirectory(AccessControlled, Directory): try: try: formdef = self.formdef_class.import_from_xml(fp) - get_session().message = ('info', _(self.import_success_message)) + get_session().message = ('info', str(self.import_success_message)) except FormdefImportRecoverableError as e: fp.seek(0) formdef = self.formdef_class.import_from_xml(fp, fix_on_error=True) - get_session().message = ('info', _(self.import_error_message)) + get_session().message = ('info', str(self.import_error_message)) except FormdefImportError as e: error = True reason = _(e.msg) % e.msg_args @@ -1936,7 +1942,7 @@ class FormsDirectory(AccessControlled, Directory): class UpdateDigestAfterJob(AfterJob): - label = N_('Updating digests') + label = _('Updating digests') def __init__(self, formdef): super().__init__(formdef_class=formdef.__class__, formdef_id=formdef.id) diff --git a/wcs/admin/settings.py b/wcs/admin/settings.py index 5396f620f..a6a028d9e 100644 --- a/wcs/admin/settings.py +++ b/wcs/admin/settings.py @@ -38,7 +38,7 @@ from wcs.blocks import BlockDef from wcs.carddef import CardDef from wcs.data_sources import NamedDataSource from wcs.formdef import FormDef -from wcs.qommon import N_, _, errors, get_cfg, ident, misc, template +from wcs.qommon import _, errors, get_cfg, ident, misc, template from wcs.qommon.admin.cfg import cfg_submit from wcs.qommon.admin.emails import EmailsDirectory from wcs.qommon.admin.logger import LoggerDirectory @@ -706,13 +706,13 @@ class SettingsDirectory(QommonSettingsDirectory): permission_keys = [] admin_sections = [ - ('forms', N_('Forms')), - ('cards', N_('Card Models')), - ('workflows', N_('Workflows')), - ('users', N_('Users')), - ('roles', N_('Roles')), - ('categories', N_('Categories')), - ('settings', N_('Settings')), + ('forms', _('Forms')), + ('cards', _('Card Models')), + ('workflows', _('Workflows')), + ('users', _('Users')), + ('roles', _('Roles')), + ('categories', _('Categories')), + ('settings', _('Settings')), ] for k, v in admin_sections: if k == 'cards' and not StudioDirectory.is_visible(): @@ -1128,10 +1128,13 @@ class SettingsDirectory(QommonSettingsDirectory): exporter = Exporter(dirs, settings=form.get_widget('settings').parse()) - job = get_response().add_after_job(N_('Exporting site settings'), exporter.export) - job.done_action_url = get_request().get_url() + '?download=%s' % job.id - job.done_action_label = _('Download Export') - job.done_button_attributes = {'download': 'export.wcs'} + job = get_response().add_after_job( + _('Exporting site settings'), + exporter.export, + done_action_url=get_request().get_url() + '?download=%(job_id)s', + done_action_label=_('Download Export'), + done_button_attributes={'download': 'export.wcs'}, + ) job.store() return redirect(job.get_processing_url()) diff --git a/wcs/admin/users.py b/wcs/admin/users.py index 7c4e31cdb..440049e54 100644 --- a/wcs/admin/users.py +++ b/wcs/admin/users.py @@ -19,7 +19,7 @@ from quixote.directory import Directory from quixote.html import TemplateIO, htmltext import wcs.qommon.storage as st -from wcs.qommon import N_, _, errors, force_str, get_cfg, ident, misc +from wcs.qommon import _, errors, force_str, get_cfg, ident, misc from wcs.qommon.admin.emails import EmailsDirectory from wcs.qommon.admin.menu import error_page from wcs.qommon.backoffice.listing import pagination_links @@ -537,11 +537,11 @@ class UsersDirectory(Directory): EmailsDirectory.register( 'email_with_token', - N_('Identification token'), - N_('Available variables: token, token_url, sitename'), - category=N_('Identification'), - default_subject=N_('Access to [sitename]'), - default_body=N_( + _('Identification token'), + _('Available variables: token, token_url, sitename'), + category=_('Identification'), + default_subject=_('Access to [sitename]'), + default_body=_( '''\ Hello, diff --git a/wcs/admin/workflows.py b/wcs/admin/workflows.py index a3f40e724..a8b026854 100644 --- a/wcs/admin/workflows.py +++ b/wcs/admin/workflows.py @@ -32,7 +32,7 @@ from wcs.backoffice.studio import StudioDirectory from wcs.carddef import CardDef from wcs.formdata import Evolution from wcs.formdef import FormDef -from wcs.qommon import N_, _, errors, force_str, get_logger, misc, template +from wcs.qommon import _, errors, force_str, get_logger, misc, template from wcs.qommon.admin.menu import command_icon from wcs.qommon.backoffice.menu import html_top from wcs.qommon.form import ( @@ -356,7 +356,7 @@ class WorkflowItemPage(Directory): if not self.workflow.is_readonly(): submit_label = _('Submit') if hasattr(self.item, 'submit_button_label'): - submit_label = _(self.item.submit_button_label) + submit_label = self.item.submit_button_label form.add_submit('submit', submit_label) form.add_submit('cancel', _('Cancel')) @@ -955,7 +955,7 @@ class WorkflowVariablesFieldsDirectory(FieldsDirectory): support_import = False blacklisted_types = ['page', 'blocks', 'computed'] field_var_prefix = 'form_option_' - readonly_message = N_('This workflow is readonly.') + readonly_message = _('This workflow is readonly.') def index_top(self): r = TemplateIO(html=True) @@ -978,7 +978,7 @@ class WorkflowBackofficeFieldsDirectory(FieldsDirectory): blacklisted_types = ['page', 'blocks', 'computed'] blacklisted_attributes = ['condition'] field_var_prefix = 'form_var_' - readonly_message = N_('This workflow is readonly.') + readonly_message = _('This workflow is readonly.') def index_top(self): r = TemplateIO(html=True) @@ -1746,11 +1746,14 @@ class WorkflowPage(Directory): def duplicate(self): self.workflow_ui.workflow.id = None original_name = self.workflow_ui.workflow.name - self.workflow_ui.workflow.name = self.workflow_ui.workflow.name + _(' (copy)') + self.workflow_ui.workflow.name = '%s %s' % (self.workflow_ui.workflow.name, _('(copy)')) workflow_names = [x.name for x in Workflow.select()] no = 2 while self.workflow_ui.workflow.name in workflow_names: - self.workflow_ui.workflow.name = _('%(name)s (copy %(no)d)') % {'name': original_name, 'no': no} + self.workflow_ui.workflow.name = str(_('%(name)s (copy %(no)d)')) % { + 'name': original_name, + 'no': no, + } no += 1 self.workflow_ui.workflow.store() return redirect('../%s/' % self.workflow_ui.workflow.id) diff --git a/wcs/api.py b/wcs/api.py index d7e4b1bbe..1f9854cd1 100644 --- a/wcs/api.py +++ b/wcs/api.py @@ -1261,7 +1261,7 @@ def validate_expression(request, *args, **kwargs): if expression and re.match(r'^=.*\[[a-zA-Z_]\w*\]', expression): hint['klass'] = 'warning' hint['msg'] = _('Make sure you want a Python expression, not a simple template string.') - return HttpResponse(json.dumps(hint), content_type='application/json') + return JsonResponse(hint) def validate_condition(request, *args, **kwargs): @@ -1274,7 +1274,7 @@ def validate_condition(request, *args, **kwargs): except ValidationError as e: hint['klass'] = 'error' hint['msg'] = str(e) - return HttpResponse(json.dumps(hint), content_type='application/json') + return JsonResponse(hint) def provisionning(request): diff --git a/wcs/backoffice/cards.py b/wcs/backoffice/cards.py index 8430c0f10..944b675d1 100644 --- a/wcs/backoffice/cards.py +++ b/wcs/backoffice/cards.py @@ -28,7 +28,7 @@ from wcs.carddef import CardDef from wcs.categories import CardDefCategory from wcs.workflows import Workflow -from ..qommon import N_, _ +from ..qommon import _ from ..qommon.misc import C_ from ..qommon.storage import NotEqual, Null @@ -40,7 +40,7 @@ class CardDefUI(FormDefUI): class CardDefOptionsDirectory(OptionsDirectory): category_class = CardDefCategory - category_empty_choice = N_('Select a category for this card model') + category_empty_choice = _('Select a category for this card model') class CardDefPage(FormDefPage): @@ -51,12 +51,12 @@ class CardDefPage(FormDefPage): options_directory_class = CardDefOptionsDirectory - delete_message = N_('You are about to irrevocably delete this card model.') - delete_title = N_('Deleting Card Model:') - overwrite_message = N_( + delete_message = _('You are about to irrevocably delete this card model.') + delete_title = _('Deleting Card Model:') + overwrite_message = _( 'You can replace this card model by uploading a file ' 'or by pointing to a form URL.' ) - overwrite_success_message = N_( + overwrite_success_message = _( 'The card model has been successfully overwritten. ' 'Do note it kept its existing address and role and workflow parameters.' ) @@ -250,15 +250,15 @@ class CardsDirectory(FormsDirectory): formdef_page_class = CardDefPage formdef_ui_class = CardDefUI - top_title = N_('Card Models') - import_title = N_('Import Card Model') - import_submit_label = N_('Import Card Model') - import_paragraph = N_( + top_title = _('Card Models') + import_title = _('Import Card Model') + import_submit_label = _('Import Card Model') + import_paragraph = _( 'You can install a new card model by uploading a file ' 'or by pointing to the card model URL.' ) - import_loading_error_message = N_('Error loading card model (%s).') - import_success_message = N_('This card model has been successfully imported. ') - import_error_message = N_( + import_loading_error_message = _('Error loading card model (%s).') + import_success_message = _('This card model has been successfully imported. ') + import_error_message = _( 'Imported card model contained errors and has been automatically fixed, ' 'you should nevertheless check everything is ok. ' ) diff --git a/wcs/backoffice/data_management.py b/wcs/backoffice/data_management.py index 578cc3a61..ae3cef0ed 100644 --- a/wcs/backoffice/data_management.py +++ b/wcs/backoffice/data_management.py @@ -26,7 +26,7 @@ from wcs import fields from wcs.carddef import CardDef from wcs.categories import CardDefCategory -from ..qommon import N_, _, errors, template +from ..qommon import _, errors, template from ..qommon.afterjobs import AfterJob from ..qommon.backoffice.menu import html_top from ..qommon.form import FileWidget, Form @@ -99,7 +99,7 @@ class CardPage(FormPage): ] admin_permission = 'cards' formdef_class = CardDef - search_label = N_('Search in card content') + search_label = _('Search in card content') @property def add(self): @@ -331,8 +331,8 @@ class CardFillPage(FormFillPage): class CardBackOfficeStatusPage(FormBackOfficeStatusPage): form_page_class = CardFillPage - sidebar_recorded_message = N_('The card has been recorded on %(date)s with the number %(number)s.') - sidebar_recorded_by_agent_message = N_( + sidebar_recorded_message = _('The card has been recorded on %(date)s with the number %(number)s.') + sidebar_recorded_by_agent_message = _( 'The card has been recorded on %(date)s with the number %(number)s by %(agent)s.' ) diff --git a/wcs/backoffice/management.py b/wcs/backoffice/management.py index edc2d1595..f5823b370 100644 --- a/wcs/backoffice/management.py +++ b/wcs/backoffice/management.py @@ -44,7 +44,7 @@ from wcs.roles import logged_users_role from wcs.variables import LazyFieldVar from wcs.workflows import WorkflowStatusItem, template_on_formdata -from ..qommon import N_, _, emails, errors, ezt, force_str, get_cfg, get_logger, misc, ngettext, ods, sms +from ..qommon import _, emails, errors, ezt, force_str, get_cfg, get_logger, misc, ngettext, ods, sms from ..qommon.admin.emails import EmailsDirectory from ..qommon.admin.menu import command_icon from ..qommon.afterjobs import AfterJob @@ -127,7 +127,7 @@ def geojson_formdatas(formdatas, geoloc_key='base', fields=None): 'url': formdata_backoffice_url, 'status_name': str(htmlescape(status.name)), 'status_colour': '#%s' % status_colour, - 'view_label': _('View'), + 'view_label': force_text(_('View')), }, 'geometry': { 'type': 'Point', @@ -1060,7 +1060,7 @@ class ManagementDirectory(Directory): FakeField('status', 'status', _('Status')), ] get_response().set_content_type('application/json') - return json.dumps(geojson_formdatas(formdatas, fields=fields)) + return json.dumps(geojson_formdatas(formdatas, fields=fields), cls=misc.JSONEncoder) def map(self): if not get_publisher().is_using_postgresql(): @@ -1107,7 +1107,7 @@ class FormPage(Directory): use_default_view = False admin_permission = 'forms' formdef_class = FormDef - search_label = N_('Search in form content') + search_label = _('Search in form content') WCS_SYNC_EXPORT_LIMIT = 100 # Arbitrary threshold def __init__(self, component=None, formdef=None, view=None, update_breadcrumbs=True): @@ -1568,7 +1568,7 @@ class FormPage(Directory): r += htmltext('') % order_by if get_publisher().is_using_postgresql(): - r += htmltext('

%s

') % _(self.search_label) + r += htmltext('

%s

') % self.search_label if get_request().form.get('q'): q = force_text(get_request().form.get('q')) r += htmltext('') % force_str(q) @@ -2331,7 +2331,7 @@ class FormPage(Directory): fields, selected_filter, user=user, query=query, criterias=criterias )[0] - return json.dumps(geojson_formdatas(items, fields=fields)) + return json.dumps(geojson_formdatas(items, fields=fields), cls=misc.JSONEncoder) def ics(self): if get_request().has_anonymised_data_api_restriction(): @@ -2716,8 +2716,8 @@ class FormBackOfficeStatusPage(FormStatusPage): ] form_page_class = FormFillPage - sidebar_recorded_message = N_('The form has been recorded on %(date)s with the number %(number)s.') - sidebar_recorded_by_agent_message = N_( + sidebar_recorded_message = _('The form has been recorded on %(date)s with the number %(number)s.') + sidebar_recorded_by_agent_message = _( 'The form has been recorded on %(date)s with the number %(number)s by %(agent)s.' ) @@ -2811,13 +2811,13 @@ class FormBackOfficeStatusPage(FormStatusPage): agent_user = get_publisher().user_class.get(formdata.submission_agent_id, ignore_errors=True) if agent_user: - r += _(self.sidebar_recorded_by_agent_message) % { + r += self.sidebar_recorded_by_agent_message % { 'date': tm, 'number': formdata.get_display_id(), 'agent': agent_user.get_display_name(), } else: - r += _(self.sidebar_recorded_message) % {'date': tm, 'number': formdata.get_display_id()} + r += self.sidebar_recorded_message % {'date': tm, 'number': formdata.get_display_id()} r += htmltext('

') try: status_colour = formdata.get_status().colour @@ -3441,7 +3441,7 @@ class FakeField: def __init__(self, id, type_, label, addable=True): self.id = id self.type = type_ - self.label = label + self.label = force_text(label) self.fake = True self.varname = id.replace('-', '_') self.store_display_value = None @@ -3545,10 +3545,10 @@ var month_line = %(month_line)s; var year_line = %(year_line)s; ''' % { - 'weekday_line': json.dumps(weekday_totals), - 'hour_line': json.dumps(hour_totals), - 'month_line': json.dumps(monthly_totals), - 'year_line': json.dumps(yearly_totals), + 'weekday_line': json.dumps(weekday_totals, cls=misc.JSONEncoder), + 'hour_line': json.dumps(hour_totals, cls=misc.JSONEncoder), + 'month_line': json.dumps(monthly_totals, cls=misc.JSONEncoder), + 'year_line': json.dumps(yearly_totals, cls=misc.JSONEncoder), } ) @@ -3735,7 +3735,7 @@ class MassActionAfterJob(AfterJob): class CsvExportAfterJob(AfterJob): - label = N_('Exporting forms in CSV') + label = _('Exporting to CSV file') def __init__(self, formdef, **kwargs): super().__init__(formdef_class=formdef.__class__, formdef_id=formdef.id, **kwargs) @@ -3799,6 +3799,8 @@ class CsvExportAfterJob(AfterJob): class OdsExportAfterJob(CsvExportAfterJob): + label = _('Exporting to ODS file') + def __init__(self, formdef, **kwargs): super().__init__(formdef=formdef, **kwargs) self.file_name = '%s.ods' % formdef.url_name diff --git a/wcs/backoffice/root.py b/wcs/backoffice/root.py index 73d79292e..a4bb6066c 100644 --- a/wcs/backoffice/root.py +++ b/wcs/backoffice/root.py @@ -28,7 +28,7 @@ import wcs.admin.users import wcs.admin.workflows from wcs.formdef import FormDef -from ..qommon import N_, _, errors, get_cfg, misc, template +from ..qommon import _, errors, get_cfg, misc, template from ..qommon.afterjobs import AfterJob from ..qommon.backoffice import BackofficeRootDirectory from ..qommon.backoffice.menu import html_top @@ -54,16 +54,16 @@ class RootDirectory(BackofficeRootDirectory): submission = SubmissionDirectory() menu_items = [ - ('submission/', N_('Submission')), - ('management/', N_('Management')), - ('data/', N_('Cards'), {'check_display_function': studio.is_visible}), - ('studio/', N_('Studio'), {'check_display_function': studio.is_visible}), - ('forms/', N_('Forms Workshop'), {'sub': True}), - ('cards/', N_('Card Models'), {'sub': True, 'check_display_function': studio.is_visible}), - ('workflows/', N_('Workflows Workshop'), {'sub': True}), - ('users/', N_('Users'), {'check_display_function': roles.is_visible}), - ('roles/', N_('Roles'), {'check_display_function': roles.is_visible}), - ('settings/', N_('Settings')), + ('submission/', _('Submission')), + ('management/', _('Management')), + ('data/', _('Cards'), {'check_display_function': studio.is_visible}), + ('studio/', _('Studio'), {'check_display_function': studio.is_visible}), + ('forms/', _('Forms Workshop'), {'sub': True}), + ('cards/', _('Card Models'), {'sub': True, 'check_display_function': studio.is_visible}), + ('workflows/', _('Workflows Workshop'), {'sub': True}), + ('users/', _('Users'), {'check_display_function': roles.is_visible}), + ('roles/', _('Roles'), {'check_display_function': roles.is_visible}), + ('settings/', _('Settings')), ] def _q_traverse(self, path): diff --git a/wcs/blocks.py b/wcs/blocks.py index 7934b0c08..934efa6ec 100644 --- a/wcs/blocks.py +++ b/wcs/blocks.py @@ -21,7 +21,7 @@ from quixote import get_publisher, get_request from quixote.html import htmltag, htmltext from . import data_sources, fields -from .qommon import N_, _, misc +from .qommon import _, misc from .qommon.form import CompositeWidget, WidgetList from .qommon.storage import StorableObject from .qommon.template import Template @@ -136,7 +136,7 @@ class BlockDef(StorableObject): unknown_datasources.add(data_source.get('type')) if unknown_datasources: raise BlockdefImportError( - N_('Unknown datasources'), details=', '.join(sorted(unknown_datasources)) + _('Unknown datasources'), details=', '.join(sorted(unknown_datasources)) ) return blockdef @@ -146,7 +146,7 @@ class BlockDef(StorableObject): charset = 'utf-8' blockdef = cls() if tree.find('name') is None or not tree.find('name').text: - raise BlockdefImportError(N_('Missing name')) + raise BlockdefImportError(_('Missing name')) # if the tree we get is actually a ElementTree for real, we get its # root element and go on happily. @@ -154,7 +154,7 @@ class BlockDef(StorableObject): tree = tree.getroot() if tree.tag != cls.xml_root_node: - raise BlockdefImportError(N_('Unexpected root node')) + raise BlockdefImportError(_('Unexpected root node')) if include_id and tree.attrib.get('id'): blockdef.id = tree.attrib.get('id') @@ -169,7 +169,7 @@ class BlockDef(StorableObject): try: field_o = fields.get_field_class_by_type(field.findtext('type'))() except KeyError: - raise BlockdefImportError(N_('Unknown field type'), details=field.findtext('type')) + raise BlockdefImportError(_('Unknown field type'), details=field.findtext('type')) field_o.init_with_xml(field, charset, include_id=True) blockdef.fields.append(field_o) diff --git a/wcs/carddef.py b/wcs/carddef.py index 5cd8e90a8..ab1d2eed5 100644 --- a/wcs/carddef.py +++ b/wcs/carddef.py @@ -23,7 +23,7 @@ from wcs.carddata import CardData from wcs.categories import CardDefCategory from wcs.formdef import FormDef, get_formdefs_of_all_kinds -from .qommon import N_, _, misc +from .qommon import _, force_text, misc from .qommon.storage import Equal, ILike, NotEqual from .qommon.template import Template @@ -36,8 +36,8 @@ class CardDef(FormDef): data_sql_prefix = 'carddata' pickle_module_name = 'carddef' xml_root_node = 'carddef' - verbose_name = N_('Card model') - verbose_name_plural = N_('Card models') + verbose_name = _('Card model') + verbose_name_plural = _('Card models') confirmation = False @@ -93,19 +93,19 @@ class CardDef(FormDef): from wcs.wf.remove import RemoveWorkflowStatusItem from wcs.workflows import ChoiceWorkflowStatusItem, EditableWorkflowStatusItem, Workflow - workflow = Workflow(name=_('Default (cards)')) + workflow = Workflow(name=force_text(_('Default (cards)'))) workflow.id = '_carddef_default' workflow.roles = { - '_viewer': _('Viewer'), - '_editor': _('Editor'), + '_viewer': force_text(_('Viewer')), + '_editor': force_text(_('Editor')), } - status = workflow.add_status(_('Recorded'), 'recorded') - deleted_status = workflow.add_status(_('Deleted'), 'deleted') + status = workflow.add_status(force_text(_('Recorded')), 'recorded') + deleted_status = workflow.add_status(force_text(_('Deleted')), 'deleted') editable = EditableWorkflowStatusItem() editable.id = '_editable' editable.by = ['_editor'] - editable.label = _('Edit Card') + editable.label = force_text(_('Edit Card')) editable.status = status.id editable.parent = status status.items.append(editable) @@ -113,7 +113,7 @@ class CardDef(FormDef): action_delete = ChoiceWorkflowStatusItem() action_delete.id = '_action_delete' action_delete.by = ['_editor'] - action_delete.label = _('Delete Card') + action_delete.label = force_text(_('Delete Card')) action_delete.status = deleted_status.id action_delete.require_confirmation = True action_delete.parent = status diff --git a/wcs/categories.py b/wcs/categories.py index 76b9afb52..1d03b19b1 100644 --- a/wcs/categories.py +++ b/wcs/categories.py @@ -17,7 +17,7 @@ from quixote import get_publisher from quixote.html import htmltext -from .qommon import N_ +from .qommon import _ from .qommon.misc import simplify from .qommon.storage import StorableObject from .qommon.substitution import Substitutions @@ -128,6 +128,6 @@ class CardDefCategory(Category): XML_NODES = [('name', 'str'), ('url_name', 'str'), ('description', 'str'), ('position', 'int')] -Substitutions.register('category_name', category=N_('General'), comment=N_('Category Name')) -Substitutions.register('category_description', category=N_('General'), comment=N_('Category Description')) -Substitutions.register('category_id', category=N_('General'), comment=N_('Category Identifier')) +Substitutions.register('category_name', category=_('General'), comment=_('Category Name')) +Substitutions.register('category_description', category=_('General'), comment=_('Category Description')) +Substitutions.register('category_id', category=_('General'), comment=_('Category Identifier')) diff --git a/wcs/data_sources.py b/wcs/data_sources.py index 2135de4ae..7b25ecb47 100644 --- a/wcs/data_sources.py +++ b/wcs/data_sources.py @@ -26,7 +26,7 @@ from quixote import get_publisher, get_request, get_session from quixote.html import TemplateIO from .api_utils import sign_url_auto_orig -from .qommon import N_, _, force_str, get_logger, misc +from .qommon import _, force_str, get_logger, misc from .qommon.afterjobs import AfterJob from .qommon.cron import CronJob from .qommon.form import CompositeWidget, OptGroup, SingleSelectWidget, StringWidget @@ -916,7 +916,7 @@ def build_agenda_datasources(publisher): class RefreshAgendas(AfterJob): - label = N_('Refreshing agendas') + label = _('Refreshing agendas') def execute(self): build_agenda_datasources(get_publisher()) diff --git a/wcs/fields.py b/wcs/fields.py index 8b1fa8cad..cdb9680a5 100644 --- a/wcs/fields.py +++ b/wcs/fields.py @@ -201,7 +201,7 @@ class PrefillSelectionWidget(CompositeWidget): attrs={ 'data-dynamic-display-child-of': 'prefill$type', 'data-dynamic-display-value-in': '|'.join( - [x[1] for x in options if x[0] not in ('none', 'geolocation')] + [str(x[1]) for x in options if x[0] not in ('none', 'geolocation')] ), 'inline_title': _('Locked'), }, @@ -268,7 +268,7 @@ class Field: pass def get_type_label(self): - return _(self.description) + return self.description @property def include_in_listing(self): @@ -822,7 +822,7 @@ def register_field_class(klass): class TitleField(Field): key = 'title' - description = N_('Title') + description = _('Title') html_tag = 'h3' display_locations = ['validation', 'summary'] @@ -885,7 +885,7 @@ register_field_class(TitleField) class SubtitleField(TitleField): key = 'subtitle' - description = N_('Subtitle') + description = _('Subtitle') html_tag = 'h4' @@ -894,7 +894,7 @@ register_field_class(SubtitleField) class CommentField(Field): key = 'comment' - description = N_('Comment') + description = _('Comment') display_locations = [] def get_text(self): @@ -995,7 +995,7 @@ def is_datasource_advanced(value): class StringField(WidgetField): key = 'string' - description = N_('Text (line)') + description = _('Text (line)') widget_class = WcsExtraStringWidget size = None @@ -1091,7 +1091,7 @@ register_field_class(StringField) class TextField(WidgetField): key = 'text' - description = N_('Long Text') + description = _('Long Text') widget_class = TextWidget cols = None @@ -1156,7 +1156,7 @@ register_field_class(TextField) class EmailField(WidgetField): key = 'email' - description = N_('Email') + description = _('Email') widget_class = EmailWidget @@ -1181,7 +1181,7 @@ register_field_class(EmailField) class BoolField(WidgetField): key = 'bool' - description = N_('Check Box (single choice)') + description = _('Check Box (single choice)') allow_complex = True widget_class = CheckboxWidget @@ -1202,9 +1202,9 @@ class BoolField(WidgetField): def get_view_value(self, value, **kwargs): if value is True or value == 'True': - return _('Yes') + return str(_('Yes')) elif value is False or value == 'False': - return _('No') + return str(_('No')) else: return '' @@ -1278,7 +1278,7 @@ register_field_class(BoolField) class FileField(WidgetField): key = 'file' - description = N_('File Upload') + description = _('File Upload') allow_complex = True document_type = None @@ -1497,7 +1497,7 @@ class FileField(WidgetField): # self.file_type is a combination of file type, we create a # virtual one from them if parts and len(parts) > 1: - label = ', '.join(parts) + label = ', '.join([str(x) for x in parts]) else: label = ','.join(file_type) self.document_type = { @@ -1534,7 +1534,7 @@ register_field_class(FileField) class DateField(WidgetField): key = 'date' - description = N_('Date') + description = _('Date') widget_class = DateWidget minimum_date = None @@ -1807,7 +1807,7 @@ class ItemFieldMixin: class ItemField(WidgetField, MapOptionsMixin, ItemFieldMixin): key = 'item' - description = N_('List') + description = _('List') allow_complex = True items = [] @@ -2163,7 +2163,7 @@ register_field_class(ItemField) class ItemsField(WidgetField, ItemFieldMixin): key = 'items' - description = N_('Multiple choice list') + description = _('Multiple choice list') allow_complex = True items = [] @@ -2501,7 +2501,7 @@ class PageCondition(Condition): class PageField(Field): key = 'page' - description = N_('Page') + description = _('Page') post_conditions = None @@ -2595,7 +2595,7 @@ register_field_class(PageField) class TableField(WidgetField): key = 'table' - description = N_('Table') + description = _('Table') allow_complex = True rows = None @@ -2764,7 +2764,7 @@ register_field_class(TableField) class TableSelectField(TableField): key = 'table-select' - description = N_('Table of Lists') + description = _('Table of Lists') allow_complex = True items = None @@ -2813,7 +2813,7 @@ register_field_class(TableSelectField) class TableRowsField(WidgetField): key = 'tablerows' - description = N_('Table with rows') + description = _('Table with rows') allow_complex = True total_row = True @@ -2956,7 +2956,7 @@ register_field_class(TableRowsField) class MapField(WidgetField, MapOptionsMixin): key = 'map' - description = N_('Map') + description = _('Map') default_position = None init_with_geoloc = False @@ -3054,7 +3054,7 @@ register_field_class(MapField) class RankedItemsField(WidgetField): key = 'ranked-items' - description = N_('Ranked Items') + description = _('Ranked Items') allow_complex = True items = [] @@ -3141,7 +3141,7 @@ register_field_class(RankedItemsField) class PasswordField(WidgetField): key = 'password' - description = N_('Password') + description = _('Password') min_length = 0 max_length = 0 @@ -3465,13 +3465,13 @@ def get_field_options(blacklisted_types): if klass.key in blacklisted_types: continue if issubclass(klass, WidgetField): - widgets.append((klass.key, _(klass.description), klass.key)) + widgets.append((klass.key, klass.description, klass.key)) else: - non_widgets.append((klass.key, _(klass.description), klass.key)) + non_widgets.append((klass.key, klass.description, klass.key)) options = widgets + [('', '—', '')] + non_widgets # add computed field in its own "section" - options.extend([('', '—', ''), (ComputedField.key, _(ComputedField.description), ComputedField.key)]) + options.extend([('', '—', ''), (ComputedField.key, ComputedField.description, ComputedField.key)]) if get_publisher().has_site_option('fields-blocks') and ( not blacklisted_types or 'blocks' not in blacklisted_types diff --git a/wcs/formdata.py b/wcs/formdata.py index 59fcf1b04..f0b0e9b5e 100644 --- a/wcs/formdata.py +++ b/wcs/formdata.py @@ -25,7 +25,7 @@ import time from quixote import get_publisher, get_request, get_session from quixote.http_request import Upload -from .qommon import N_, _, misc +from .qommon import _, misc from .qommon.evalutils import make_datetime from .qommon.publisher import get_cfg from .qommon.storage import Contains, Intersects, Null, StorableObject @@ -1421,14 +1421,14 @@ class FormData(StorableObject): self.__dict__ = dict -Substitutions.register('form_receipt_date', category=N_('Form'), comment=N_('Form Receipt Date')) -Substitutions.register('form_receipt_time', category=N_('Form'), comment=N_('Form Receipt Time')) -Substitutions.register('form_number', category=N_('Form'), comment=N_('Form Number')) -Substitutions.register('form_details', category=N_('Form'), comment=N_('Form Details')) -Substitutions.register('form_url', category=N_('Form'), comment=N_('Form URL')) -Substitutions.register('form_url_backoffice', category=N_('Form'), comment=N_('Form URL (backoffice)')) -Substitutions.register('form_status_url', category=N_('Form'), comment=N_('Form Status URL')) -Substitutions.register('form_tracking_code', category=N_('Form'), comment=N_('Form Tracking Code')) -Substitutions.register('form_user_display_name', category=N_('Form'), comment=N_('Form Submitter Name')) -Substitutions.register('form_user_email', category=N_('Form'), comment=N_('Form Submitter Email')) +Substitutions.register('form_receipt_date', category=_('Form'), comment=_('Form Receipt Date')) +Substitutions.register('form_receipt_time', category=_('Form'), comment=_('Form Receipt Time')) +Substitutions.register('form_number', category=_('Form'), comment=_('Form Number')) +Substitutions.register('form_details', category=_('Form'), comment=_('Form Details')) +Substitutions.register('form_url', category=_('Form'), comment=_('Form URL')) +Substitutions.register('form_url_backoffice', category=_('Form'), comment=_('Form URL (backoffice)')) +Substitutions.register('form_status_url', category=_('Form'), comment=_('Form Status URL')) +Substitutions.register('form_tracking_code', category=_('Form'), comment=_('Form Tracking Code')) +Substitutions.register('form_user_display_name', category=_('Form'), comment=_('Form Submitter Name')) +Substitutions.register('form_user_email', category=_('Form'), comment=_('Form Submitter Email')) Substitutions.register_dynamic_source(FormData) diff --git a/wcs/formdef.py b/wcs/formdef.py index 7d94c2794..1003ab9a0 100644 --- a/wcs/formdef.py +++ b/wcs/formdef.py @@ -35,7 +35,7 @@ from quixote.http_request import Upload from . import data_sources, fields from .categories import Category from .formdata import FormData -from .qommon import N_, PICKLE_KWARGS, _, force_str, get_cfg +from .qommon import PICKLE_KWARGS, _, force_str, get_cfg from .qommon.admin.emails import EmailsDirectory from .qommon.cron import CronJob from .qommon.form import Form, HtmlWidget, UploadedFile @@ -84,8 +84,8 @@ class FormDef(StorableObject): data_sql_prefix = 'formdata' pickle_module_name = 'formdef' xml_root_node = 'formdef' - verbose_name = N_('Form') - verbose_name_plural = N_('Forms') + verbose_name = _('Form') + verbose_name_plural = _('Forms') name = None description = None @@ -916,7 +916,7 @@ class FormDef(StorableObject): try: field_o = fields.get_field_class_by_type(field.get('type'))() except KeyError: - raise FormdefImportError(N_('Unknown field type'), details=field.findtext('type')) + raise FormdefImportError(_('Unknown field type'), details=field.findtext('type')) field_o.init_with_json(field, include_id=True) if not field_o.id: # this assumes all fields will have id, or none of them @@ -1100,7 +1100,7 @@ class FormDef(StorableObject): known_field_ids = set() for field in formdef.fields: if field.id in known_field_ids: - raise FormdefImportRecoverableError(N_('Duplicated field identifiers')) + raise FormdefImportRecoverableError(_('Duplicated field identifiers')) known_field_ids.add(field.id) return formdef @@ -1116,7 +1116,7 @@ class FormDef(StorableObject): assert charset == 'utf-8' formdef = cls() if tree.find('name') is None or not tree.find('name').text: - raise FormdefImportError(N_('Missing name')) + raise FormdefImportError(_('Missing name')) # if the tree we get is actually a ElementTree for real, we get its # root element and go on happily. @@ -1124,7 +1124,7 @@ class FormDef(StorableObject): tree = tree.getroot() if tree.tag != cls.xml_root_node: - raise FormdefImportError(N_('Unexpected root node')) + raise FormdefImportError(_('Unexpected root node')) if include_id and tree.attrib.get('id'): formdef.id = tree.attrib.get('id') @@ -1145,7 +1145,7 @@ class FormDef(StorableObject): try: field_o = fields.get_field_class_by_type(field.findtext('type'))() except KeyError: - raise FormdefImportError(N_('Unknown field type'), details=field.findtext('type')) + raise FormdefImportError(_('Unknown field type'), details=field.findtext('type')) field_o.init_with_xml(field, charset, include_id=True) if fix_on_error or not field_o.id: # this assumes all fields will have id, or none of them @@ -1299,7 +1299,7 @@ class FormDef(StorableObject): if unknown_datasources: raise FormdefImportError( - N_('Unknown datasources'), details=', '.join(sorted(unknown_datasources)) + _('Unknown datasources'), details=', '.join(sorted(unknown_datasources)) ) return formdef @@ -1340,7 +1340,7 @@ class FormDef(StorableObject): else: details.append('%s' % field.get_rst_view_value(value, indent=' ')) details.append('') - return '\n'.join(details) + return '\n'.join([str(x) for x in details]) def get_submitter_email(self, formdata): users_cfg = get_cfg('users', {}) @@ -1434,7 +1434,7 @@ class FormDef(StorableObject): details.append(' %s' % formdata.get_status_label()) if evo.comment: details.append('\n%s\n' % evo.comment) - return '\n\n----\n\n' + '\n'.join(details) + return '\n\n----\n\n' + '\n'.join([str(x) for x in details]) def is_of_concern_for_role_id(self, role_id): if not self.workflow_roles: @@ -1618,11 +1618,11 @@ class FormDef(StorableObject): EmailsDirectory.register( 'new_user', - N_('Notification of creation to user'), + _('Notification of creation to user'), enabled=False, - category=N_('Workflow'), - default_subject=N_('New form ({{ form_name }})'), - default_body=N_( + category=_('Workflow'), + default_subject=_('New form ({{ form_name }})'), + default_body=_( '''\ Hello, @@ -1642,10 +1642,10 @@ For reference, here are the details: EmailsDirectory.register( 'change_user', - N_('Notification of change to user'), - category=N_('Workflow'), - default_subject=N_('Form status change ({{ form_name }})'), - default_body=N_( + _('Notification of change to user'), + category=_('Workflow'), + default_subject=_('Form status change ({{ form_name }})'), + default_body=_( '''\ Hello, @@ -1669,11 +1669,11 @@ You can consult it with this link: {{ form_url }} EmailsDirectory.register( 'new_receiver', - N_('Notification of creation to receiver'), + _('Notification of creation to receiver'), enabled=False, - category=N_('Workflow'), - default_subject=N_('New form ({{ form_name }})'), - default_body=N_( + category=_('Workflow'), + default_subject=_('New form ({{ form_name }})'), + default_body=_( '''\ Hello, @@ -1692,10 +1692,10 @@ For reference, here are the details: EmailsDirectory.register( 'change_receiver', - N_('Notification of change to receiver'), - category=N_('Workflow'), - default_subject=N_('Form status change ({{ form_name }})'), - default_body=N_( + _('Notification of change to receiver'), + category=_('Workflow'), + default_subject=_('Form status change ({{ form_name }})'), + default_body=_( '''\ Hello, @@ -1715,7 +1715,7 @@ Status of the form just changed (from "{{ form_previous_status }}" to "{{ form_s ), ) -Substitutions.register('form_name', category=N_('Form'), comment=N_('Form Name')) +Substitutions.register('form_name', category=_('Form'), comment=_('Form Name')) def clean_drafts(publisher): diff --git a/wcs/forms/actions.py b/wcs/forms/actions.py index 3544d25fe..3a56292a6 100644 --- a/wcs/forms/actions.py +++ b/wcs/forms/actions.py @@ -22,14 +22,14 @@ from wcs.formdef import FormDef from wcs.forms.common import FormTemplateMixin from wcs.wf.jump import jump_and_perform -from ..qommon import N_, errors, misc, template, tokens +from ..qommon import _, errors, misc, template, tokens from ..qommon.form import Form class MissingOrExpiredToken(PublishError): status_code = 404 - title = N_('Error') - description = N_('This action link has already been used or has expired.') + title = _('Error') + description = _('This action link has already been used or has expired.') class ActionsDirectory(Directory): diff --git a/wcs/forms/root.py b/wcs/forms/root.py index bd2c930e9..764cccab4 100644 --- a/wcs/forms/root.py +++ b/wcs/forms/root.py @@ -44,7 +44,7 @@ from wcs.roles import logged_users_role from wcs.variables import LazyFormDef from wcs.workflows import Workflow, WorkflowBackofficeFieldsFormDef, WorkflowStatusItem -from ..qommon import N_, _, emails, errors, get_cfg, get_logger, misc, template +from ..qommon import _, emails, errors, get_cfg, get_logger, misc, template from ..qommon.admin.emails import EmailsDirectory from ..qommon.form import CheckboxWidget, EmailWidget, Form, HiddenErrorWidget, HtmlWidget, StringWidget from ..qommon.template import TemplateError @@ -1962,14 +1962,14 @@ class PublicFormStatusPage(FormStatusPage): ) -TextsDirectory.register('welcome-logged', N_('Welcome text on home page for logged users')) +TextsDirectory.register('welcome-logged', _('Welcome text on home page for logged users')) -TextsDirectory.register('welcome-unlogged', N_('Welcome text on home page for unlogged users')) +TextsDirectory.register('welcome-unlogged', _('Welcome text on home page for unlogged users')) TextsDirectory.register( 'captcha-page', - N_('Explanation text before the CAPTCHA'), - default=N_( + _('Explanation text before the CAPTCHA'), + default=_( '''

Verification

@@ -1981,44 +1981,44 @@ In order to submit the form you need to complete this simple question. TextsDirectory.register( 'form-recorded', - N_('Message when a form has been recorded'), - category=N_('Forms'), - default=N_( - 'The form has been recorded on {{ form_receipt_datetime }} with the number {{ form_number }}.' - ), + _('Message when a form has been recorded'), + category=_('Forms'), + default=_('The form has been recorded on {{ form_receipt_datetime }} with the number {{ form_number }}.'), ) TextsDirectory.register( 'form-recorded-allow-one', - N_('Message when a form has been recorded, and the form is set to only allow one per user'), - category=N_('Forms'), - default=N_('The form has been recorded on {{ form_receipt_datetime }}.'), + _('Message when a form has been recorded, and the form is set to only allow one per user'), + category=_('Forms'), + default=_('The form has been recorded on {{ form_receipt_datetime }}.'), ) TextsDirectory.register( 'check-before-submit', - N_('Message when a form is displayed before validation'), - category=N_('Forms'), - default=N_('Check values then click submit.'), + _('Message when a form is displayed before validation'), + category=_('Forms'), + default=_('Check values then click submit.'), ) TextsDirectory.register( 'tracking-code-email-dialog', - N_('Message in tracking code popup dialog'), - category=N_('Forms'), - default=N_('You can get a reminder of the tracking code by email.'), + _('Message in tracking code popup dialog'), + category=_('Forms'), + default=_('You can get a reminder of the tracking code by email.'), ) TextsDirectory.register( - 'tracking-code-short-text', N_('Short text in the tracking code box'), category=N_('Forms') + 'tracking-code-short-text', + _('Short text in the tracking code box'), + category=_('Forms'), ) EmailsDirectory.register( 'tracking-code-reminder', - N_('Tracking Code'), - category=N_('Miscellaneous'), - default_subject=N_('Tracking Code reminder'), - default_body=N_( + _('Tracking Code'), + category=_('Miscellaneous'), + default_subject=_('Tracking Code reminder'), + default_body=_( '''\ Hello, diff --git a/wcs/logged_errors.py b/wcs/logged_errors.py index fcc25f46f..1dd7fb1a5 100644 --- a/wcs/logged_errors.py +++ b/wcs/logged_errors.py @@ -59,7 +59,7 @@ class LoggedError: exception=None, ): error = cls() - error.summary = error_summary + error.summary = str(error_summary) error.traceback = plain_error_msg error.expression = expression error.expression_type = expression_type diff --git a/wcs/portfolio.py b/wcs/portfolio.py index 52354daf7..f76899a2b 100644 --- a/wcs/portfolio.py +++ b/wcs/portfolio.py @@ -25,7 +25,7 @@ from quixote.html import TemplateIO, htmltext from wcs.api_utils import get_secret_and_orig, sign_url -from .qommon import N_, get_logger +from .qommon import _, get_logger from .qommon.misc import http_post_request, json_loads, urlopen @@ -84,7 +84,7 @@ def push_document(user, filename, stream): if get_response(): get_response().add_after_job( - N_('Sending file %(filename)s in portfolio of %(user_name)s') + _('Sending file %(filename)s in portfolio of %(user_name)s') % {'filename': filename, 'user_name': user.display_name}, afterjob, ) diff --git a/wcs/qommon/__init__.py b/wcs/qommon/__init__.py index 3d33d46a2..abfd22085 100644 --- a/wcs/qommon/__init__.py +++ b/wcs/qommon/__init__.py @@ -21,6 +21,7 @@ import threading import django.apps from django.conf import settings from django.utils.encoding import force_text +from django.utils.functional import lazy from quixote import get_publisher try: @@ -37,7 +38,7 @@ force_str = force_text PICKLE_KWARGS = {'encoding': 'bytes', 'fix_imports': True} -def _(message): +def gettext(message): pub = get_publisher() if pub is None: return message @@ -55,6 +56,8 @@ def N_(x): return x +_ = lazy(gettext, str) + from . import publisher # noqa pylint: disable=wrong-import-position from .publisher import get_cfg # noqa pylint: disable=wrong-import-position from .publisher import get_logger # noqa pylint: disable=wrong-import-position diff --git a/wcs/qommon/admin/emails.py b/wcs/qommon/admin/emails.py index 6b022b2a6..ef70f06b4 100644 --- a/wcs/qommon/admin/emails.py +++ b/wcs/qommon/admin/emails.py @@ -61,9 +61,7 @@ class EmailsDirectory(Directory): emails_cfg = get_cfg('emails', {}) cfg_key = 'email-%s' % email_key default_subject = cls.emails_dict[email_key].get('default_subject') - if default_subject: - default_subject = _(default_subject) - real_subject = emails_cfg.get(cfg_key + '_subject') or default_subject + real_subject = emails_cfg.get(cfg_key + '_subject') or str(default_subject) return real_subject @classmethod @@ -71,9 +69,7 @@ class EmailsDirectory(Directory): emails_cfg = get_cfg('emails', {}) cfg_key = 'email-%s' % email_key default_body = cls.emails_dict[email_key].get('default_body') - if default_body: - default_body = _(default_body) - real_body = emails_cfg.get(cfg_key) or default_body + real_body = emails_cfg.get(cfg_key) or str(default_body) return real_body def options(self): @@ -193,7 +189,7 @@ class EmailsDirectory(Directory): categories = {} for k, v in self.emails_dict.items(): if v.get('category'): - translated_category = _(v.get('category')) + translated_category = v.get('category') else: translated_category = _('Miscellaneous') if translated_category not in categories: @@ -205,11 +201,11 @@ class EmailsDirectory(Directory): r += htmltext('

%s

') % category_key keys = categories.get(category_key) - keys.sort(key=lambda x: _(self.emails_dict[x]['description'])) + keys.sort(key=lambda x: self.emails_dict[x]['description']) r += htmltext('') r += htmltext('

') @@ -221,8 +217,8 @@ class EmailsDirectory(Directory): emails_cfg = get_cfg('emails', {}) cfg_key = 'email-%s' % email_key - default_subject = _(self.emails_dict[email_key].get('default_subject')) - default_body = _(self.emails_dict[email_key].get('default_body')) + default_subject = self.emails_dict[email_key].get('default_subject') + default_body = self.emails_dict[email_key].get('default_body') displayed_subject = emails_cfg.get(cfg_key + '_subject') or default_subject displayed_body = emails_cfg.get(cfg_key) or default_body @@ -273,8 +269,8 @@ class EmailsDirectory(Directory): if check_template and not check_template(template): return False - default_subject = _(self.emails_dict[email_key].get('default_subject')) - default_body = _(self.emails_dict[email_key].get('default_body')) + default_subject = self.emails_dict[email_key].get('default_subject') + default_body = self.emails_dict[email_key].get('default_body') if template == default_body: template = None @@ -298,8 +294,8 @@ class EmailsDirectory(Directory): return self.email( component, - _(self.emails_dict[component]['description']), - _(self.emails_dict[component]['hint']), + self.emails_dict[component]['description'], + self.emails_dict[component]['hint'], self.emails_dict[component]['enabled'], ) diff --git a/wcs/qommon/admin/menu.py b/wcs/qommon/admin/menu.py index 90b16a7d3..45f8106a4 100644 --- a/wcs/qommon/admin/menu.py +++ b/wcs/qommon/admin/menu.py @@ -19,7 +19,7 @@ import re from quixote import get_publisher, get_request from quixote.html import TemplateIO, htmltext -from .. import N_, _ +from .. import _ from ..backoffice.menu import html_top @@ -113,16 +113,16 @@ def get_vc_version(): def command_icon(url, type, label=None, popup=False): labels = { - 'edit': N_('Edit'), - 'remove': N_('Remove'), - 'duplicate': N_('Duplicate'), - 'view': N_('View'), + 'edit': _('Edit'), + 'remove': _('Remove'), + 'duplicate': _('Duplicate'), + 'view': _('View'), } if label: klass = 'button' else: klass = '' - label = _(labels[type]) + label = labels[type] root_url = get_publisher().get_application_static_files_root_url() rel = '' if popup: diff --git a/wcs/qommon/admin/texts.py b/wcs/qommon/admin/texts.py index 99ee3d8a2..e043eeca8 100644 --- a/wcs/qommon/admin/texts.py +++ b/wcs/qommon/admin/texts.py @@ -41,7 +41,7 @@ class TextsDirectory(Directory): if os.path.exists(filepath): return htmltext(open(filepath).read()) return '' - text = _(default) + text = str(default) # make sure translation is applied if not text.startswith('<'): text = '

%s

' % text @@ -79,7 +79,7 @@ class TextsDirectory(Directory): categories = {} for k, v in self.texts_dict.items(): if v.get('category'): - translated_category = _(v.get('category')) + translated_category = v.get('category') else: translated_category = _('Miscellaneous') if translated_category not in categories: @@ -91,11 +91,11 @@ class TextsDirectory(Directory): r += htmltext('

%s

') % category_key keys = categories.get(category_key) - keys.sort(key=lambda x: _(self.texts_dict[x]['description'])) + keys.sort(key=lambda x: self.texts_dict[x]['description']) r += htmltext('') r += htmltext('

') @@ -108,9 +108,7 @@ class TextsDirectory(Directory): cfg_key = 'text-%s' % text_key default_text = self.texts_dict.get(text_key, {}).get('default') - if default_text: - default_text = _(default_text) - else: + if not default_text: filepath = os.path.join(get_publisher().DATA_DIR, 'texts', '%s.html' % text_key) if os.path.exists(str(filepath)): default_text = open(str(filepath)).read() @@ -176,7 +174,7 @@ class TextsDirectory(Directory): return None hint = self.texts_dict[component]['hint'] - return self.text(component, _(self.texts_dict[component]['description']), hint and _(hint)) + return self.text(component, self.texts_dict[component]['description'], hint) def _q_traverse(self, path): get_response().breadcrumb.append(('texts/', _('Texts'))) diff --git a/wcs/qommon/afterjobs.py b/wcs/qommon/afterjobs.py index 571ba30d1..8b8560d1f 100644 --- a/wcs/qommon/afterjobs.py +++ b/wcs/qommon/afterjobs.py @@ -22,7 +22,7 @@ import uuid from quixote import get_publisher, get_response from quixote.directory import Directory -from . import N_, _, errors +from . import N_, _, errors, force_text from .storage import StorableObject @@ -36,8 +36,8 @@ class AfterJobStatusDirectory(Directory): response = get_response() response.set_content_type('text/plain') if not job.completion_status: - return job.status + '|' + _(job.status) - return job.status + '|' + _(job.status) + ' ' + job.completion_status + return job.status + '|' + str(_(job.status)) + return job.status + '|' + str(_(job.status)) + ' ' + job.completion_status class AfterJob(StorableObject): @@ -55,12 +55,24 @@ class AfterJob(StorableObject): def __init__(self, label=None, cmd=None, **kwargs): super().__init__(id=str(uuid.uuid4())) if label: - self.label = label + self.label = force_text(label) + self.done_action_label_arg = kwargs.pop('done_action_label', None) + self.done_action_url_arg = kwargs.pop('done_action_url', None) + self.done_button_attributes_arg = kwargs.pop('done_button_attributes', None) self.creation_time = time.time() self.job_cmd = cmd self.status = N_('registered') self.kwargs = kwargs + def done_action_label(self): + return self.done_action_label_arg + + def done_action_url(self): + return self.done_action_url_arg % {'job_id': self.id} + + def done_action_attributes(self): + return self.done_button_attributes_arg + def run(self, spool=False): if self.completion_time: return @@ -94,6 +106,8 @@ class AfterJob(StorableObject): self.store() def __getstate__(self): + if getattr(self, 'done_action_label_arg', None): + self.done_action_label_arg = force_text(self.done_action_label_arg) if not isinstance(self.job_cmd, str): obj_dict = self.__dict__.copy() obj_dict['job_cmd'] = None diff --git a/wcs/qommon/errors.py b/wcs/qommon/errors.py index 0a6b53089..12e3bd220 100644 --- a/wcs/qommon/errors.py +++ b/wcs/qommon/errors.py @@ -21,7 +21,7 @@ from quixote import get_publisher from quixote.errors import AccessError, TraversalError from quixote.html import TemplateIO, htmltext -from . import N_, template +from . import _, template class AccessForbiddenError(AccessError): @@ -59,7 +59,7 @@ class AccessUnauthorizedError(AccessForbiddenError): return AccessForbiddenError.render(self) if self.public_msg: - session.message = ('error', self.public_msg) + session.message = ('error', str(self.public_msg)) login_url = get_publisher().get_root_url() + 'login/' login_url += '?' + urllib.parse.urlencode({'next': request.get_frontoffice_url()}) return quixote.redirect(login_url) @@ -132,8 +132,8 @@ class SMSError(Exception): pass -TraversalError.title = N_('Page not found') -TraversalError.description = N_( +TraversalError.title = _('Page not found') +TraversalError.description = _( "The requested link does not exist on this site. If " "you arrived here by following a link from an external " "page, please inform that page's maintainer." @@ -141,9 +141,7 @@ TraversalError.description = N_( def format_publish_error(exc): - from . import _ - if getattr(exc, 'public_msg', None): - return template.error_page(exc.format(), _(exc.title)) + return template.error_page(exc.format(), exc.title) else: - return template.error_page(_(exc.description), _(exc.title)) + return template.error_page(exc.description, exc.title) diff --git a/wcs/qommon/form.py b/wcs/qommon/form.py index c36dd5764..cf8bc185a 100644 --- a/wcs/qommon/form.py +++ b/wcs/qommon/form.py @@ -69,7 +69,7 @@ from quixote.html import TemplateIO, htmlescape, htmltag, htmltext, stringify from wcs.conditions import Condition, ValidationError from ..portfolio import has_portfolio -from . import N_, _, force_str, misc, ngettext +from . import _, force_str, misc, ngettext from .humantime import humanduration2seconds, seconds2humanduration, timewords from .misc import HAS_PDFTOPPM, json_loads, strftime from .publisher import get_cfg @@ -79,17 +79,10 @@ from .template_utils import render_block_to_string from .upload_storage import PicklableUpload # noqa pylint: disable=unused-import from .upload_storage import UploadStorageError -Widget.REQUIRED_ERROR = N_('required field') +Widget.REQUIRED_ERROR = _('required field') get_error_orig = Widget.get_error -def get_i18n_error(self, request=None): - error = get_error_orig(self, request) - if error == Widget.REQUIRED_ERROR: - return _(error) - return error - - def is_prefilled(self): if hasattr(self, 'prefilled'): return self.prefilled @@ -99,6 +92,7 @@ def is_prefilled(self): def render_title(self, title): if title: + title = str(title) # apply translation if required if self.required: title += htmltext('*') % _('This field is required.') attrs = { @@ -178,7 +172,6 @@ def render_widget_content(self): return htmltext(force_str(render_block_to_string(template_names, 'widget-content', context))) -Widget.get_error = get_i18n_error Widget.render = render Widget.cleanup = None Widget.render_error = render_error @@ -211,7 +204,9 @@ class SubmitWidget(quixote.form.widget.SubmitWidget): self.attrs['formnovalidate'] = 'formnovalidate' value = htmlescape(self.label) if self.label else None return ( - htmltag('button', name=self.name, value=value, **self.attrs) + self.label + htmltext('') + htmltag('button', name=self.name, value=value, **self.attrs) + + str(self.label) + + htmltext('') ) @@ -254,13 +249,13 @@ Widget.transfer_form_value = transfer_form_value class Form(QuixoteForm): - TOKEN_NOTICE = N_( + TOKEN_NOTICE = _( "The form you have submitted is invalid. Most " "likely it has been successfully submitted once " "already. Please review the form data " "and submit the form again." ) - ERROR_NOTICE = N_("There were errors processing your form. See below for details.") + ERROR_NOTICE = _("There were errors processing your form. See below for details.") info = None captcha = None @@ -295,6 +290,9 @@ class Form(QuixoteForm): widget.advanced = advanced return widget + def set_error(self, name, error): + super().set_error(name, force_text(error)) + def remove(self, name): widget = self._names.get(name) if widget: @@ -344,13 +342,13 @@ class Form(QuixoteForm): return r.getvalue() def _render_info_notice(self): - return htmltext('

%s
' % _(self.info)) + return htmltext('
%s
' % self.info) def _render_error_notice(self): errors = [] classnames = ['errornotice'] if self.has_errors(): - errors.append(_(QuixoteForm._render_error_notice(self))) + errors.append(QuixoteForm._render_error_notice(self)) if self.global_error_messages: errors.extend(self.global_error_messages) classnames.append('global-errors') @@ -531,7 +529,7 @@ class DurationWidget(StringWidget): if value: value = seconds2humanduration(int(value)) if 'hint' in kwargs: - kwargs['hint'] += htmltext('
') + kwargs['hint'] = str(kwargs['hint']) + htmltext('
') else: kwargs['hint'] = '' kwargs['hint'] += htmltext(_('Usable units of time: %s.')) % ', '.join(timewords()) @@ -629,7 +627,7 @@ class CheckboxWidget(QuixoteCheckboxWidget): # custom style. return ( htmltext('') ) return checkbox @@ -1040,64 +1038,75 @@ class ValidationWidget(CompositeWidget): ( 'digits', { - 'title': N_('Digits'), + 'title': _('Digits'), 'regex': r'\d+', - 'error_message': N_('Only digits are allowed'), + 'error_message': _('Only digits are allowed'), 'html_inputmode': 'numeric', }, ), ( 'phone', { - 'title': N_('Phone Number'), + 'title': _('Phone Number'), 'regex': r'\+?[-\(\)\d\.\s/]+', - 'error_message': N_('Invalid phone number'), + 'error_message': _('Invalid phone number'), 'html_input_type': 'tel', }, ), ( 'phone-fr', { - 'title': N_('Phone Number (France)'), + 'title': _('Phone Number (France)'), 'function': 'validate_phone_fr', - 'error_message': N_('Invalid phone number'), + 'error_message': _('Invalid phone number'), 'html_input_type': 'tel', }, ), ( 'zipcode-fr', { - 'title': N_('Zip Code (France)'), + 'title': _('Zip Code (France)'), 'regex': r'\d{5}', - 'error_message': N_('Invalid zip code'), + 'error_message': _('Invalid zip code'), 'html_inputmode': 'numeric', }, ), ( 'siren-fr', { - 'title': N_('SIREN Code (France)'), + 'title': _('SIREN Code (France)'), 'function': 'validate_siren', - 'error_message': N_('Invalid SIREN code'), + 'error_message': _('Invalid SIREN code'), 'html_inputmode': 'numeric', }, ), ( 'siret-fr', { - 'title': N_('SIRET Code (France)'), + 'title': _('SIRET Code (France)'), 'function': 'validate_siret', - 'error_message': N_('Invalid SIRET code'), + 'error_message': _('Invalid SIRET code'), 'html_inputmode': 'numeric', }, ), ( 'nir-fr', - {'title': N_('NIR (France)'), 'error_message': N_('Invalid NIR'), 'function': 'validate_nir'}, + { + 'title': _('NIR (France)'), + 'error_message': _('Invalid NIR'), + 'function': 'validate_nir', + }, ), - ('iban', {'title': N_('IBAN'), 'function': 'validate_iban', 'error_message': N_('Invalid IBAN')}), - ('regex', {'title': N_('Regular Expression')}), - ('django', {'title': N_('Django Condition')}), + ( + 'iban', + { + 'title': _('IBAN'), + 'function': 'validate_iban', + 'error_message': _('Invalid IBAN'), + }, + ), + ('regex', {'title': _('Regular Expression')}), + ('django', {'title': _('Django Condition')}), ] ) @@ -1106,7 +1115,7 @@ class ValidationWidget(CompositeWidget): if not value: value = {} - options = [(None, _('None'))] + [(x, _(y['title'])) for x, y in self.validation_methods.items()] + options = [(None, _('None'))] + [(x, y['title']) for x, y in self.validation_methods.items()] self.add( SingleSelectWidget, @@ -1150,7 +1159,7 @@ class ValidationWidget(CompositeWidget): attrs={ 'data-dynamic-display-child-of': 'validation$type', 'data-dynamic-display-value-in': '|'.join( - [validation_labels.get('regex'), validation_labels.get('django')] + [str(validation_labels.get('regex')), str(validation_labels.get('django'))] ), }, ) @@ -1212,7 +1221,7 @@ class ValidationWidget(CompositeWidget): return validation.get('error_message') validation_method = cls.validation_methods.get(validation['type']) if validation_method and 'error_message' in validation_method: - return _(validation_method['error_message']) + return validation_method['error_message'] @classmethod def get_validation_pattern(cls, validation): @@ -1600,9 +1609,9 @@ class CaptchaWidget(CompositeWidget): # don't get twice the same number b = random.randint(2, 9) if mode == 'arithmetic-simple': - operator = random.choice([N_('plus'), N_('minus')]) + operator = random.choice([_('plus'), _('minus')]) else: - operator = random.choice([N_('times'), N_('plus'), N_('minus')]) + operator = random.choice([_('times'), _('plus'), _('minus')]) if operator == 'times': answer = a * b elif operator == 'plus': @@ -1614,7 +1623,7 @@ class CaptchaWidget(CompositeWidget): self.question = _('What is the result of %(a)d %(op)s %(b)d?') % { 'a': a, 'b': b, - 'op': _(operator), + 'op': operator, } self.hint = kwargs.get('hint') if self.hint is None: @@ -1656,7 +1665,7 @@ class WidgetList(quixote.form.widget.WidgetList): ): if add_element_label == 'Add row': - add_element_label = _('Add row') + add_element_label = str(_('Add row')) CompositeWidget.__init__(self, name, value=value, **kwargs) self.element_type = element_type @@ -1769,7 +1778,7 @@ class WidgetDict(quixote.form.widget.WidgetDict): ): if add_element_label == 'Add row': - add_element_label = _('Add row') + add_element_label = str(_('Add row')) quixote.form.widget.WidgetDict.__init__( self, diff --git a/wcs/qommon/http_response.py b/wcs/qommon/http_response.py index 0a3013890..409227042 100644 --- a/wcs/qommon/http_response.py +++ b/wcs/qommon/http_response.py @@ -185,13 +185,13 @@ class HTTPResponse(quixote.http_response.HTTPResponse): ] ) - def add_after_job(self, label_or_instance, cmd=None, fire_and_forget=False): + def add_after_job(self, label_or_instance, cmd=None, fire_and_forget=False, **kwargs): if not self.after_jobs: self.after_jobs = [] if isinstance(label_or_instance, AfterJob): job = label_or_instance else: - job = AfterJob(label=label_or_instance, cmd=cmd) + job = AfterJob(label=label_or_instance, cmd=cmd, **kwargs) if fire_and_forget: job.id = None self.after_jobs.append(job) diff --git a/wcs/qommon/humantime.py b/wcs/qommon/humantime.py index 50535b58c..5b6c21bd5 100644 --- a/wcs/qommon/humantime.py +++ b/wcs/qommon/humantime.py @@ -16,7 +16,7 @@ import re -from . import N_, _, ngettext +from . import _, ngettext _minute = 60 _hour = 60 * 60 @@ -34,12 +34,12 @@ def list2human(stringlist): _humandurations = ( - ((N_("day"), N_("days")), _day), - ((N_("hour"), N_("hours")), _hour), - ((N_("month"), N_("months")), _month), - ((N_("year"), N_("years")), _year), - ((N_("minute"), N_("minutes")), _minute), - ((N_("second"), N_("seconds")), 1), + ((_("day"), _("days")), _day), + ((_("hour"), _("hours")), _hour), + ((_("month"), _("months")), _month), + ((_("year"), _("years")), _year), + ((_("minute"), _("minutes")), _minute), + ((_("second"), _("seconds")), 1), ) @@ -48,7 +48,7 @@ def timewords(): result = [] for words, dummy in _humandurations: for word in words: - result.append(_(word)) + result.append(str(word)) # str() to force translation return result @@ -58,7 +58,6 @@ def humanduration2seconds(humanduration): seconds = 0 for words, quantity in _humandurations: for word in words: - word = _(word) m = re.search(r"(\d+)\s*\b%s\b" % word, humanduration) if m: seconds = seconds + int(m.group(1)) * quantity diff --git a/wcs/qommon/ident/franceconnect.py b/wcs/qommon/ident/franceconnect.py index fcc062eb3..780087828 100644 --- a/wcs/qommon/ident/franceconnect.py +++ b/wcs/qommon/ident/franceconnect.py @@ -29,7 +29,7 @@ from quixote.html import TemplateIO, htmltext from wcs.formdata import flatten_dict from wcs.workflows import WorkflowStatusItem -from .. import N_, _, get_cfg, get_logger, template +from .. import _, get_cfg, get_logger, template from ..backoffice.menu import html_top from ..form import ( CompositeWidget, @@ -42,7 +42,7 @@ from ..form import ( from ..misc import http_get_page, http_post_request, json_loads from .base import AuthMethod -ADMIN_TITLE = N_('FranceConnect') +ADMIN_TITLE = _('FranceConnect') # XXX: make an OIDC auth method that FranceConnect would inherit from @@ -122,13 +122,13 @@ class MethodDirectory(Directory): class MethodAdminDirectory(Directory): title = ADMIN_TITLE - label = N_('Configure FranceConnect identification method') + label = _('Configure FranceConnect identification method') _q_exports = [''] PLATFORMS = [ { - 'name': N_('Development citizens'), + 'name': _('Development citizens'), 'slug': 'dev-particulier', 'authorization_url': 'https://fcp.integ01.dev-franceconnect.fr/api/v1/authorize', 'token_url': 'https://fcp.integ01.dev-franceconnect.fr/api/v1/token', @@ -136,7 +136,7 @@ class MethodAdminDirectory(Directory): 'logout_url': 'https://fcp.integ01.dev-franceconnect.fr/api/v1/logout', }, { - 'name': N_('Development enterprise'), + 'name': _('Development enterprise'), 'slug': 'dev-entreprise', 'authorization_url': 'https://fce.integ01.dev-franceconnect.fr/api/v1/authorize', 'token_url': 'https://fce.integ01.dev-franceconnect.fr/api/v1/token', @@ -144,7 +144,7 @@ class MethodAdminDirectory(Directory): 'logout_url': 'https://fce.integ01.dev-franceconnect.fr/api/v1/logout', }, { - 'name': N_('Production citizens'), + 'name': _('Production citizens'), 'slug': 'prod-particulier', 'authorization_url': 'https://app.franceconnect.gouv.fr/api/v1/authorize', 'token_url': 'https://app.franceconnect.gouv.fr/api/v1/token', @@ -154,22 +154,22 @@ class MethodAdminDirectory(Directory): ] CONFIG = [ - ('client_id', N_('Client ID')), - ('client_secret', N_('Client secret')), - ('platform', N_('Platform')), - ('scopes', N_('Scopes')), - ('user_field_mappings', N_('User field mappings')), + ('client_id', _('Client ID')), + ('client_secret', _('Client secret')), + ('platform', _('Platform')), + ('scopes', _('Scopes')), + ('user_field_mappings', _('User field mappings')), ] KNOWN_ATTRIBUTES = [ - ('given_name', N_('first names separated by spaces')), - ('family_name', N_('birth\'s last name')), - ('birthdate', N_('birthdate formatted as YYYY-MM-DD')), - ('gender', N_('gender \'male\' for men, and \'female\' for women')), - ('birthplace', N_('INSEE code of the place of birth')), - ('birthcountry', N_('INSEE code of the country of birth')), - ('email', N_('email')), - ('siret', N_('SIRET or SIREN number of the enterprise')), + ('given_name', _('first names separated by spaces')), + ('family_name', _('birth\'s last name')), + ('birthdate', _('birthdate formatted as YYYY-MM-DD')), + ('gender', _('gender \'male\' for men, and \'female\' for women')), + ('birthplace', _('INSEE code of the place of birth')), + ('birthcountry', _('INSEE code of the country of birth')), + ('email', _('email')), + ('siret', _('SIRET or SIREN number of the enterprise')), # Note: FranceConnect website also refer to adress and phones attributes # but we don't know what must be expected of their value. ] @@ -189,7 +189,7 @@ class MethodAdminDirectory(Directory): widget = UserFieldMappingTableWidget elif key == 'platform': widget = SingleSelectWidget - kwargs['options'] = [(platform['slug'], _(platform['name'])) for platform in cls.PLATFORMS] + kwargs['options'] = [(platform['slug'], platform['name']) for platform in cls.PLATFORMS] elif key == 'scopes': default = 'identite_pivot address email phones' hint = _( @@ -202,7 +202,7 @@ class MethodAdminDirectory(Directory): form.add( widget, key, - title=_(title), + title=title, hint=hint, required=True, value=instance.get(key, default), @@ -233,7 +233,7 @@ class MethodAdminDirectory(Directory): if 'submit' in get_request().form and form.is_submitted() and not form.has_errors(): return self.submit(form) - html_top('settings', title=_(self.title)) + html_top('settings', title=self.title) r = TemplateIO(html=True) r += htmltext('

%s

') % self.title fc_callback = pub.get_frontoffice_url() + '/ident/fc/callback' @@ -268,7 +268,7 @@ class MethodAdminDirectory(Directory): '' '' ) % (_('Attribute'), _('Description')) for attribute, description in self.KNOWN_ATTRIBUTES: - r += htmltext('') % (attribute, _(description)) + r += htmltext('') % (attribute, description) r += htmltext('
%s%s
%s%s
%s%s
') return r.getvalue() @@ -444,7 +444,7 @@ class FCAuthMethod(AuthMethod): user.set_attributes_from_formdata(user.form_data) AUTHORIZATION_REQUEST_ERRORS = { - 'access_denied': N_('user did not authorize login'), + 'access_denied': _('user did not authorize login'), } def callback(self): @@ -465,7 +465,7 @@ class FCAuthMethod(AuthMethod): if error: # we log only errors whose user is not responsible msg = self.AUTHORIZATION_REQUEST_ERRORS.get(error) - logger.error(_('FranceConnect authentication failed: %s'), _(msg) if msg else error) + logger.error(_('FranceConnect authentication failed: %s'), msg if msg else error) return redirect(next_url) access_token, id_token = self.get_access_token(request.form['code']) if not access_token: diff --git a/wcs/qommon/ident/idp.py b/wcs/qommon/ident/idp.py index 1c788e2aa..c9280e0d8 100644 --- a/wcs/qommon/ident/idp.py +++ b/wcs/qommon/ident/idp.py @@ -29,7 +29,7 @@ from quixote import get_publisher, get_request, get_response, get_session, redir from quixote.directory import Directory from quixote.html import TemplateIO, htmltext -from .. import N_, _, errors, get_cfg, get_logger, misc, saml2utils, template, x509utils +from .. import _, errors, get_cfg, get_logger, misc, saml2utils, template, x509utils from ..admin.menu import command_icon from ..backoffice.menu import html_top from ..form import ( @@ -47,7 +47,7 @@ from ..storage import atomic_write from ..tokens import Token from .base import AuthMethod -ADMIN_TITLE = N_('SAML2') +ADMIN_TITLE = _('SAML2') def is_idp_managing_user_attributes(): @@ -214,16 +214,16 @@ class MethodDirectory(Directory): class AdminIDPDir(Directory): - title = N_('Identity Providers') + title = _('Identity Providers') _q_exports = ['', 'new', 'new_remote'] def _q_traverse(self, path): - get_response().breadcrumb.append(('idp/', _(self.title))) + get_response().breadcrumb.append(('idp/', self.title)) return Directory._q_traverse(self, path) def _q_index(self): - html_top('settings', title=_(self.title)) + html_top('settings', title=self.title) r = TemplateIO(html=True) r += htmltext('

%s

') % _('Identity Providers') r += htmltext('