general: add and use a lazy gettext function (#51289)

This commit is contained in:
Frédéric Péters 2021-05-15 15:34:16 +02:00
parent 1565f3abe2
commit 0da7edeab8
72 changed files with 646 additions and 587 deletions

View File

@ -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')

View File

@ -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

View File

@ -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('<div class="bo-block">')
r += htmltext('<h3>%s</h3>') % _(self.usage_title)
r += htmltext('<h3>%s</h3>') % self.usage_title
r += htmltext('<ul>')
for formdef in formdefs:
r += htmltext('<li><a href="../../%s/">') % str(formdef.id)
r += formdef.name
r += htmltext('</a></li>')
if not formdefs:
r += htmltext('<li>%s</li>') % _(self.empty_message)
r += htmltext('<li>%s</li>') % self.empty_message
r += htmltext('</ul>')
r += htmltext('</div>')
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('<a class="new-item" href="new" rel="popup">%s</a>') % _('New Category')
r += htmltext('</span>')
r += htmltext('</div>')
r += htmltext('<div class="explanation bo-block"><p>%s</p></div>') % _(self.category_explanation)
r += htmltext('<div class="explanation bo-block"><p>%s</p></div>') % self.category_explanation
categories = self.category_class.select()
r += htmltext('<ul class="biglist sortable" id="category-list">')
self.category_class.sort_by_position(categories)
@ -273,4 +273,4 @@ class CardDefCategoriesDirectory(CategoriesDirectory):
category_class = CardDefCategory
category_ui_class = CardDefCategoryUI
category_page_class = CardDefCategoryPage
category_explanation = N_('Categories are used to sort the different card models.')
category_explanation = _('Categories are used to sort the different card models.')

View File

@ -27,7 +27,7 @@ from wcs import fields
from wcs.admin import utils
from wcs.fields import get_field_options
from wcs.formdef import FormDef
from wcs.qommon import N_, _, errors, get_cfg, misc
from wcs.qommon import _, errors, get_cfg, misc
from wcs.qommon.admin.menu import command_icon
from wcs.qommon.backoffice.menu import html_top
from wcs.qommon.form import CheckboxWidget, Form, HtmlWidget, SingleSelectWidget, StringWidget
@ -218,7 +218,7 @@ class FieldsDirectory(Directory):
blacklisted_types = []
page_id = None
field_var_prefix = '..._'
readonly_message = N_('The fields are readonly.')
readonly_message = _('The fields are readonly.')
support_import = True
@ -323,7 +323,7 @@ class FieldsDirectory(Directory):
if field.required:
required = ''
else:
required = ' - ' + _('optional')
required = ' - %s' % _('optional')
r += htmltext('<span class="optional">%s</span>') % required
if getattr(field, 'condition', None):
r += htmltext(' - <span class="condition">%s</span>') % _('depending on condition')
@ -348,8 +348,8 @@ class FieldsDirectory(Directory):
r += htmltext('</ul>')
if self.objectdef.is_readonly():
get_response().filter['sidebar'] = htmltext('<div class="infonotice"><p>%s</p></div>') % _(
self.readonly_message
get_response().filter['sidebar'] = (
htmltext('<div class="infonotice"><p>%s</p></div>') % 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),
},
}

View File

@ -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('<p>%s</p>' % _(self.category_empty_choice)))
form.widgets.append(HtmlWidget('<p>%s</p>' % 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('<p>%s</p>' % check_count_message))
else:
form.widgets.append(HtmlWidget('<p>%s</p>' % _(self.delete_message)))
form.widgets.append(HtmlWidget('<p>%s</p>' % 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('<h2>%s %s</h2>') % (_(self.delete_title), self.formdef.name)
r += htmltext('<h2>%s %s</h2>') % (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('<h2>%s</h2>') % _('Overwrite')
r += htmltext('<p>%s</p>') % _(self.overwrite_message)
r += htmltext('<p>%s</p>') % 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('<h2>%s</h2>') % _(self.import_title)
r += htmltext('<p>%s</p>') % _(self.import_paragraph)
r += htmltext('<h2>%s</h2>') % self.import_title
r += htmltext('<p>%s</p>') % 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)

View File

@ -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())

View File

@ -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,

View File

@ -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)

View File

@ -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):

View File

@ -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. '
)

View File

@ -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.'
)

View File

@ -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('<input type="hidden" name="order_by" value="%s"/>') % order_by
if get_publisher().is_using_postgresql():
r += htmltext('<h3>%s</h3>') % _(self.search_label)
r += htmltext('<h3>%s</h3>') % self.search_label
if get_request().form.get('q'):
q = force_text(get_request().form.get('q'))
r += htmltext('<input class="inline-input" name="q" value="%s">') % 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('</p>')
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;
</script>'''
% {
'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

View File

@ -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):

View File

@ -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)

View File

@ -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

View File

@ -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'))

View File

@ -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())

View File

@ -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

View File

@ -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)

View File

@ -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):

View File

@ -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):

View File

@ -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=_(
'''<h3>Verification</h3>
<p>
@ -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,

View File

@ -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

View File

@ -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,
)

View File

@ -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

View File

@ -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('<h3>%s</h3>') % 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('<ul>')
for email_key in keys:
email_values = self.emails_dict[email_key]
r += htmltext('<li><a href="%s">%s</a></li>') % (email_key, _(email_values['description']))
r += htmltext('<li><a href="%s">%s</a></li>') % (email_key, email_values['description'])
r += htmltext('</ul>')
r += htmltext('<p>')
@ -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'],
)

View File

@ -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:

View File

@ -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 = '<p>%s</p>' % 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('<h3>%s</h3>') % 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('<ul>')
for text_key in keys:
text_values = self.texts_dict[text_key]
r += htmltext('<li><a href="%s">%s</a></li>') % (text_key, _(text_values['description']))
r += htmltext('<li><a href="%s">%s</a></li>') % (text_key, text_values['description'])
r += htmltext('</ul>')
r += htmltext('<p>')
@ -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')))

View File

@ -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

View File

@ -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)

View File

@ -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('<span title="%s" class="required">*</span>') % _('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('</button>')
htmltag('button', name=self.name, value=value, **self.attrs)
+ str(self.label)
+ htmltext('</button>')
)
@ -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('<div class="infonotice">%s</div>' % _(self.info))
return htmltext('<div class="infonotice">%s</div>' % 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('<br>')
kwargs['hint'] = str(kwargs['hint']) + htmltext('<br>')
else:
kwargs['hint'] = ''
kwargs['hint'] += htmltext(_('Usable units of time: %s.')) % ', '.join(timewords())
@ -629,7 +627,7 @@ class CheckboxWidget(QuixoteCheckboxWidget):
# custom style.
return (
htmltext('<label %s>%s<span>' % (data_attrs, checkbox))
+ inline_title
+ str(inline_title)
+ htmltext('</span></label>')
)
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,

View File

@ -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)

View File

@ -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

View File

@ -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('<h2>%s</h2>') % self.title
fc_callback = pub.get_frontoffice_url() + '/ident/fc/callback'
@ -268,7 +268,7 @@ class MethodAdminDirectory(Directory):
'<table class="franceconnect-attrs"><thead>' '<tr><th>%s</th><th>%s</th></tr></thead><tbody>'
) % (_('Attribute'), _('Description'))
for attribute, description in self.KNOWN_ATTRIBUTES:
r += htmltext('<tr><td><code>%s</code></td><td>%s</td></tr>') % (attribute, _(description))
r += htmltext('<tr><td><code>%s</code></td><td>%s</td></tr>') % (attribute, description)
r += htmltext('</tbody></table></div>')
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:

View File

@ -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('<h2>%s</h2>') % _('Identity Providers')
r += htmltext('<ul id="nav-idp-admin">\n')
@ -790,18 +790,18 @@ class AdminIDPUI(Directory):
class MethodAdminDirectory(Directory):
title = ADMIN_TITLE
label = N_('Configure SAML identification method')
label = _('Configure SAML identification method')
_q_exports = ['', 'sp', 'idp', 'identities']
idp = AdminIDPDir()
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('<h2>SAML 2.0</h2>')
r += htmltext('<dl> <dt><a href="sp">%s</a></dt> <dd>%s</dd>') % (
@ -1185,7 +1185,7 @@ class MethodUserDirectory(Directory):
class IdPAuthMethod(AuthMethod):
key = 'idp'
description = N_('SAML identity provider')
description = _('SAML identity provider')
method_directory = MethodDirectory
method_admin_directory = MethodAdminDirectory
method_user_directory = MethodUserDirectory

View File

@ -23,7 +23,7 @@ from quixote.html import TemplateIO, htmltext
from wcs.qommon.admin.texts import TextsDirectory
from .. import N_, _, emails, errors, get_cfg, get_logger, misc, ngettext
from .. import _, emails, errors, get_cfg, get_logger, misc, ngettext
from .. import storage as st
from .. import template, tokens
from ..admin.emails import EmailsDirectory
@ -680,21 +680,21 @@ class MethodDirectory(Directory):
emails.custom_template_email('password-subscription-notification', data, user.email)
ADMIN_TITLE = N_('Username / Password')
ADMIN_TITLE = _('Username / Password')
class MethodAdminDirectory(Directory):
title = ADMIN_TITLE
label = N_('Configure username/password identification method')
label = _('Configure username/password identification method')
_q_exports = ['', 'passwords', 'identities']
def _q_index(self):
html_top('settings', title=_(ADMIN_TITLE))
get_response().breadcrumb.append(('password/', _(self.title)))
html_top('settings', title=ADMIN_TITLE)
get_response().breadcrumb.append(('password/', ADMIN_TITLE))
r = TemplateIO(html=True)
r += htmltext('<h2>%s</h2>') % _(ADMIN_TITLE)
r += htmltext('<h2>%s</h2>') % ADMIN_TITLE
r += get_session().display_message()
@ -958,7 +958,7 @@ class MethodUserDirectory(Directory):
return actions
def email(self):
html_top('users', title=_(ADMIN_TITLE))
html_top('users', title=ADMIN_TITLE)
r = TemplateIO(html=True)
get_response().breadcrumb.append(('email', 'Email Password'))
r += htmltext('<h2>%s</h2>') % _('Email Password')
@ -1065,11 +1065,11 @@ class PasswordAuthMethod(AuthMethod):
EmailsDirectory.register(
'password-subscription-notification',
N_('Subscription notification for password account'),
N_('Available variables: email, website, token_url, token, admin_email, username, password'),
category=N_('Identification'),
default_subject=N_('Subscription Confirmation'),
default_body=N_(
_('Subscription notification for password account'),
_('Available variables: email, website, token_url, token, admin_email, username, password'),
category=_('Identification'),
default_subject=_('Subscription Confirmation'),
default_body=_(
'''\
We have received a request for subscription of your email address,
"[email]", to the [website] web site.
@ -1089,11 +1089,11 @@ to [admin_email].
EmailsDirectory.register(
'change-password-request',
N_('Request for password change'),
N_('Available variables: change_url, cancel_url, token, time'),
category=N_('Identification'),
default_subject=N_('Change Password Request'),
default_body=N_(
_('Request for password change'),
_('Available variables: change_url, cancel_url, token, time'),
category=_('Identification'),
default_subject=_('Change Password Request'),
default_body=_(
"""\
You have (or someone impersonating you has) requested to change your
password. To complete the change, visit the following link:
@ -1114,11 +1114,11 @@ If you do nothing, the request will lapse after 3 days (precisely on
EmailsDirectory.register(
'new-generated-password',
N_('New generated password'),
N_('Available variables: username, password, hostname'),
category=N_('Identification'),
default_subject=N_('Your new password'),
default_body=N_(
_('New generated password'),
_('Available variables: username, password, hostname'),
category=_('Identification'),
default_subject=_('Your new password'),
default_body=_(
'''\
Hello,
@ -1134,11 +1134,11 @@ account details:
EmailsDirectory.register(
'new-account-approved',
N_('Approval of new account'),
N_('Available variables: username, password'),
category=N_('Identification'),
default_subject=N_('Your account has been approved'),
default_body=N_(
_('Approval of new account'),
_('Available variables: username, password'),
category=_('Identification'),
default_subject=_('Your account has been approved'),
default_body=_(
'''\
Your account has been approved.
@ -1152,11 +1152,11 @@ Account details:
EmailsDirectory.register(
'warning-about-unused-account',
N_('Warning about unusued account'),
N_('Available variables: username'),
category=N_('Identification'),
default_subject=N_('Your account is unused'),
default_body=N_(
_('Warning about unusued account'),
_('Available variables: username'),
category=_('Identification'),
default_subject=_('Your account is unused'),
default_body=_(
'''\
Your account ([username]) is not being used.
'''
@ -1165,11 +1165,11 @@ Your account ([username]) is not being used.
EmailsDirectory.register(
'notification-of-removed-account',
N_('Notification of removal of unused account'),
N_('Available variables: username'),
category=N_('Identification'),
default_subject=N_('Your account has been removed'),
default_body=N_(
_('Notification of removal of unused account'),
_('Available variables: username'),
category=_('Identification'),
default_subject=_('Your account has been removed'),
default_body=_(
'''\
Your account ([username]) was not being used, it has therefore been removed.
'''
@ -1178,11 +1178,11 @@ Your account ([username]) was not being used, it has therefore been removed.
EmailsDirectory.register(
'new-registration-admin-notification',
N_('Notification of new registration to administrators'),
N_('Available variables: hostname, email_as_username, username'),
category=N_('Identification'),
default_subject=N_('New Registration'),
default_body=N_(
_('Notification of new registration to administrators'),
_('Available variables: hostname, email_as_username, username'),
category=_('Identification'),
default_subject=_('New Registration'),
default_body=_(
'''\
Hello,
@ -1196,11 +1196,11 @@ A new account has been created on [hostname].
EmailsDirectory.register(
'new-account-generated-password',
N_('Welcome email, with generated password'),
N_('Available variables: hostname, username, password, email_as_username'),
category=N_('Identification'),
default_subject=N_('Welcome to [hostname]'),
default_body=N_(
_('Welcome email, with generated password'),
_('Available variables: hostname, username, password, email_as_username'),
category=_('Identification'),
default_subject=_('Welcome to [hostname]'),
default_body=_(
'''\
Welcome to [hostname],
@ -1211,11 +1211,11 @@ Your password is: [password]
EmailsDirectory.register(
'password-email-create-anew',
N_('Email with a new password for the user'),
N_('Available variables: hostname, name, username, password'),
category=N_('Identification'),
default_subject=N_('Your new password for [hostname]'),
default_body=N_(
_('Email with a new password for the user'),
_('Available variables: hostname, name, username, password'),
category=_('Identification'),
default_subject=_('Your new password for [hostname]'),
default_body=_(
'''\
Hello [name],
@ -1226,11 +1226,11 @@ Here is your new password for [hostname]: [password]
EmailsDirectory.register(
'password-email-current',
N_('Email with current password for the user'),
N_('Available variables: hostname, name, username, password'),
category=N_('Identification'),
default_subject=N_('Your password for [hostname]'),
default_body=N_(
_('Email with current password for the user'),
_('Available variables: hostname, name, username, password'),
category=_('Identification'),
default_subject=_('Your password for [hostname]'),
default_body=_(
'''\
Hello [name],
@ -1242,9 +1242,9 @@ Here is your password for [hostname]: [password]
TextsDirectory.register(
'account-created',
N_('Text when account confirmed by user'),
category=N_('Identification'),
default=N_(
_('Text when account confirmed by user'),
category=_('Identification'),
default=_(
'''<p>
Your account has been created.
</p>'''
@ -1253,9 +1253,9 @@ Your account has been created.
TextsDirectory.register(
'password-forgotten-token-sent',
N_('Text when an email with a change password token has been sent'),
category=N_('Identification'),
default=N_(
_('Text when an email with a change password token has been sent'),
category=_('Identification'),
default=_(
'''<p>
A token for changing your password has been emailed to you. Follow the instructions in that email to change your password.
</p>
@ -1267,9 +1267,9 @@ A token for changing your password has been emailed to you. Follow the instructi
TextsDirectory.register(
'new-password-sent-by-email',
N_('Text when new password has been sent'),
category=N_('Identification'),
default=N_(
_('Text when new password has been sent'),
category=_('Identification'),
default=_(
'''<p>
Your new password has been sent to you by email.
</p>
@ -1279,13 +1279,13 @@ Your new password has been sent to you by email.
),
)
TextsDirectory.register('new-account', N_('Text on top of registration form'), category=N_('Identification'))
TextsDirectory.register('new-account', _('Text on top of registration form'), category=_('Identification'))
TextsDirectory.register(
'password-forgotten-link',
N_('Text on login page, linking to the forgotten password request page'),
category=N_('Identification'),
default=N_(
_('Text on login page, linking to the forgotten password request page'),
category=_('Identification'),
default=_(
'''<p>
If you have an account, but have forgotten your password, you should go
to the <a href="%(forgotten_url)s">Lost password page</a> and submit a request
@ -1296,9 +1296,9 @@ to change your password.
TextsDirectory.register(
'password-forgotten-enter-username',
N_('Text on forgotten password request page'),
category=N_('Identification'),
default=N_(
_('Text on forgotten password request page'),
category=_('Identification'),
default=_(
'''<p>
If you have an account, but have forgotten your password, enter your user name
below and submit a request to change your password.
@ -1308,10 +1308,10 @@ below and submit a request to change your password.
TextsDirectory.register(
'password-account-link-to-register-page',
N_('Text linking the login page to the account creation page'),
hint=N_('Available variable: register_url'),
category=N_('Identification'),
default=N_(
_('Text linking the login page to the account creation page'),
hint=_('Available variable: register_url'),
category=_('Identification'),
default=_(
'''<p>
If you do not have an account, you should go to the <a href="[register_url]">
New Account page</a>.
@ -1321,22 +1321,22 @@ New Account page</a>.
TextsDirectory.register(
'invalid-password-token',
N_('Text when an invalid password token is used'),
category=N_('Identification'),
default=N_(
_('Text when an invalid password token is used'),
category=_('Identification'),
default=_(
'''<p>
Sorry, the token you used is invalid, or has already been used.
</p>'''
),
)
TextsDirectory.register('top-of-login', N_('Text on top of the login page'), category=N_('Identification'))
TextsDirectory.register('top-of-login', _('Text on top of the login page'), category=_('Identification'))
TextsDirectory.register(
'email-sent-confirm-creation',
N_('Text when a mail for confirmation of an account creation has been sent'),
category=N_('Identification'),
default=N_('An email has been sent to you so you can confirm your account creation.'),
_('Text when a mail for confirmation of an account creation has been sent'),
category=_('Identification'),
default=_('An email has been sent to you so you can confirm your account creation.'),
)

View File

@ -600,6 +600,10 @@ class JSONEncoder(json.JSONEncoder):
'content': base64.b64encode(obj.get_content()),
}
if obj.__class__.__name__ == '__proxy__':
# lazy gettext
return str(obj)
# Let the base class default method raise the TypeError
return json.JSONEncoder.default(self, obj)
@ -620,7 +624,7 @@ def json_response(data):
get_response().set_header('Access-Control-Allow-Origin', get_request().get_environ('HTTP_ORIGIN'))
get_response().set_header('Access-Control-Allow-Credentials', 'true')
get_response().set_header('Access-Control-Allow-Headers', 'x-requested-with')
json_str = json.dumps(data)
json_str = json.dumps(data, cls=JSONEncoder)
for variable in ('jsonpCallback', 'callback'):
if variable in get_request().form:
get_response().set_content_type('application/javascript')
@ -1002,6 +1006,7 @@ def get_document_types(current_document_type):
document_types.update(get_cfg('filetypes', {}))
for key, document_type in document_types.items():
document_type['id'] = key
document_type['label'] = str(document_type['label'])
# add current file type if it does not exist anymore in the settings
cur_dt = current_document_type
if cur_dt and cur_dt['id'] not in document_types:

View File

@ -20,7 +20,7 @@ from quixote.html import TemplateIO, htmltext
from wcs.qommon.admin.texts import TextsDirectory
from . import N_, _, errors, get_cfg, template
from . import _, errors, get_cfg, template
from .form import Form, HtmlWidget, PasswordWidget
from .ident.password import check_password
from .ident.password_accounts import PasswordAccount
@ -194,4 +194,4 @@ class MyspaceDirectory(Directory):
return form.render()
TextsDirectory.register('top-of-profile', N_('Text on top of the profile page'))
TextsDirectory.register('top-of-profile', _('Text on top of the profile page'))

View File

@ -45,7 +45,7 @@ from django.utils import translation
from django.utils.encoding import force_bytes, force_text
from quixote.publish import Publisher, get_publisher, get_request, get_response
from . import N_, _, errors, force_str, logger, storage, template
from . import _, errors, force_str, logger, storage, template
from .cron import CronJob
from .http_request import HTTPRequest
from .http_response import AfterJob, HTTPResponse
@ -185,7 +185,13 @@ class QommonPublisher(Publisher):
request.response.headers['WWW-Authenticate'] = 'Basic realm="%s"' % exc.realm
if request.is_json():
request.response.set_content_type('application/json')
return json.dumps({'err': 1, 'err_class': exc.title, 'err_desc': exc.public_msg})
return json.dumps(
{
'err': 1,
'err_class': str(exc.title),
'err_desc': str(exc.public_msg) if exc.public_msg else None,
}
)
if isinstance(exc, errors.TraversalError):
raise Http404()
output = self.format_publish_error(exc)
@ -1003,9 +1009,9 @@ def get_publisher_class():
return builtins.__dict__.get('__publisher_class')
Substitutions.register('site_name', category=N_('General'), comment=N_('Site Name'))
Substitutions.register('site_theme', category=N_('General'), comment=N_('Current Theme Name'))
Substitutions.register('site_url', category=N_('General'), comment=N_('Site URL'))
Substitutions.register('site_url_backoffice', category=N_('General'), comment=N_('Site URL (backoffice)'))
Substitutions.register('today', category=N_('General'), comment=N_('Current Date'))
Substitutions.register('now', category=N_('General'), comment=N_('Current Date & Time'))
Substitutions.register('site_name', category=_('General'), comment=_('Site Name'))
Substitutions.register('site_theme', category=_('General'), comment=_('Current Theme Name'))
Substitutions.register('site_url', category=_('General'), comment=_('Site URL'))
Substitutions.register('site_url_backoffice', category=_('General'), comment=_('Site URL (backoffice)'))
Substitutions.register('today', category=_('General'), comment=_('Current Date'))
Substitutions.register('now', category=_('General'), comment=_('Current Date & Time'))

View File

@ -153,6 +153,9 @@ class Session(QommonSession, CaptchaSession, StorableObject):
return odict
def store(self, *args, **kwargs):
if self.message:
# escape lazy gettext
self.message = (self.message[0], str(self.message[1]))
current_dict = copy.copy(self.__dict__)
orig_dict = current_dict.pop('__orig_dict__', {})
current_dict.pop('_access_time', None)

View File

@ -144,7 +144,7 @@ class Substitutions:
% (_('Category'), _('Variable'), _('Comment'))
)
r += htmltext('<tbody>')
vars = [(_(y.get('category')), x, _(y.get('comment'))) for x, y in cls.substitutions_dict.items()]
vars = [(y.get('category'), x, y.get('comment')) for x, y in cls.substitutions_dict.items()]
for dynamic_source in cls.dynamic_sources:
vars.extend(dynamic_source.get_substitution_variables_list())
vars.sort()

View File

@ -458,7 +458,7 @@ def ezt_raises(exception, on_parse=False):
message = _('syntax error in ezt template: %s')
else:
message = _('failure to render ezt template: %s')
raise TemplateError(message % ' '.join(parts))
raise TemplateError(message % ' '.join([str(x) for x in parts]))
class Template:

View File

@ -33,7 +33,7 @@ from .forms import root
from .forms.actions import ActionsDirectory
from .forms.preview import PreviewDirectory
from .myspace import MyspaceDirectory
from .qommon import _, errors, get_cfg, get_logger, ident, saml2, template
from .qommon import _, errors, get_cfg, get_logger, ident, misc, saml2, template
from .qommon.afterjobs import AfterJobStatusDirectory
from .qommon.form import Form, RadiobuttonsWidget
from .qommon.pages import PagesDirectory
@ -92,7 +92,7 @@ class LoginDirectory(Directory):
RadiobuttonsWidget,
'method',
options=[
(x.key, _(x.description)) for x in ident.get_method_classes() if x.key in ident_methods
(x.key, x.description) for x in ident.get_method_classes() if x.key in ident_methods
],
delim=htmltext('<br/>'),
)
@ -157,7 +157,7 @@ class RegisterDirectory(Directory):
RadiobuttonsWidget,
'method',
options=[
(x.key, _(x.description)) for x in ident.get_method_classes() if x.key in ident_methods
(x.key, x.description) for x in ident.get_method_classes() if x.key in ident_methods
],
delim=htmltext('<br/>'),
)
@ -344,7 +344,7 @@ class RootDirectory(Directory):
# they would propose the returned json content for download if
# it was served with the appropriate content type :/
get_response().set_content_type('text/plain')
return json.dumps(results)
return json.dumps(results, cls=misc.JSONEncoder)
def feed_substitution_parts(self):
get_publisher().substitutions.feed(get_session())
@ -446,7 +446,7 @@ class RootDirectory(Directory):
'email_domain_suggest': _('Did you want to write'),
'email_domain_fix': _('Apply fix'),
}
return 'WCS_I18N = %s;\n' % json.dumps(strings)
return 'WCS_I18N = %s;\n' % json.dumps(strings, cls=misc.JSONEncoder)
admin = None
backoffice = None

View File

@ -25,7 +25,7 @@ from wcs.qommon.storage import Null
class UnknownUser:
def __str__(self):
return _('unknown user')
return str(_('unknown user'))
class Snapshot:
@ -53,7 +53,7 @@ class Snapshot:
if get_session():
obj.user_id = get_session().user
obj.serialization = ET.tostring(instance.export_to_xml(include_id=True)).decode('utf-8')
obj.comment = comment
obj.comment = str(comment) if comment else None
obj.label = label
latest = cls.get_latest(obj.object_type, obj.object_id)
if label is not None or latest is None or obj.serialization != latest.serialization:

View File

@ -2645,6 +2645,9 @@ class Session(SqlMixin, wcs.sessions.BasicSession):
@guard_postgres
def store(self):
if self.message:
# escape lazy gettext
self.message = (self.message[0], str(self.message[1]))
sql_dict = {
'id': self.id,
'session_data': bytearray(pickle.dumps(self.__dict__, protocol=2)),

View File

@ -11,7 +11,7 @@
<li>{% trans "Latest occurence:" %} {{ error.latest_occurence_timestamp }}</li>
<li>{% trans "Count:" %} {{ error.occurences_count }}</li>
{% if formdef %}
<li>{% trans formdef.verbose_name %}{% trans ":" %} <a href="{{ formdef.get_admin_url }}">{{ formdef.name }}</a></li>
<li>{{ formdef.verbose_name }}{% trans ":" %} <a href="{{ formdef.get_admin_url }}">{{ formdef.name }}</a></li>
{% endif %}
{% if workflow %}
<li>{% trans "Workflow:" %} <a href="{{ workflow.get_admin_url }}">{{ workflow.name }}</a>

View File

@ -6,7 +6,7 @@
{% block content %}
<div class="section">
<dl class="job-status">
<dt>{% trans job.label %}</dt>
<dt>{{ job.label }}</dt>
<dd><span class="afterjob" id="{{ job.id }}">{% trans job.status %}</span></dd>
</dl>
</div>

View File

@ -20,7 +20,7 @@ from quixote import get_publisher
import wcs.qommon.storage as st
from .qommon import N_, _, get_cfg
from .qommon import _, get_cfg
from .qommon.misc import simplify
from .qommon.storage import StorableObject
from .qommon.substitution import Substitutions, invalidate_substitution_cache
@ -315,7 +315,9 @@ class User(StorableObject):
Substitutions.register(
'session_user_display_name', category=N_('User'), comment=N_('Session User Display Name')
'session_user_display_name',
category=_('User'),
comment=_('Session User Display Name'),
)
Substitutions.register('session_user_email', category=N_('User'), comment=N_('Session User Email'))
Substitutions.register('session_user_email', category=_('User'), comment=_('Session User Email'))
Substitutions.register_dynamic_source(User)

View File

@ -18,7 +18,7 @@ from quixote import get_publisher
from wcs.workflows import WorkflowStatusItem, register_item_class
from ..qommon import N_, _, emails
from ..qommon import _, emails
from ..qommon.cron import CronJob
from ..qommon.form import SingleSelectWidget, WidgetList
from ..qommon.publisher import get_publisher_class
@ -26,7 +26,7 @@ from ..qommon.storage import StorableObject
class AggregationEmailWorkflowStatusItem(WorkflowStatusItem):
description = N_('Daily Summary Email')
description = _('Daily Summary Email')
key = 'aggregationemail'
category = 'interaction'
ok_in_global_action = False

View File

@ -14,12 +14,12 @@
# You should have received a copy of the GNU General Public License
# along with this program; if not, see <http://www.gnu.org/licenses/>.
from wcs.qommon import N_
from wcs.qommon import _
from wcs.workflows import WorkflowStatusItem, register_item_class
class AnonymiseWorkflowStatusItem(WorkflowStatusItem):
description = N_('Anonymisation')
description = _('Anonymisation')
key = 'anonymise'
category = 'formdata-action'

View File

@ -24,7 +24,7 @@ from wcs.forms.common import FileDirectory, FormStatusPage
from wcs.portfolio import has_portfolio, push_document
from wcs.workflows import AttachmentEvolutionPart, WorkflowStatusItem, register_item_class
from ..qommon import N_, _
from ..qommon import _
from ..qommon.errors import TraversalError
from ..qommon.form import (
CheckboxWidget,
@ -87,7 +87,7 @@ def form_attachment(self):
class AddAttachmentWorkflowStatusItem(WorkflowStatusItem):
description = N_('Attachment')
description = _('Attachment')
key = 'addattachment'
category = 'interaction'
endpoint = False

View File

@ -23,7 +23,7 @@ from wcs.fields import WidgetField
from wcs.wf.profile import FieldNode
from wcs.workflows import WorkflowStatusItem, register_item_class
from ..qommon import N_, _
from ..qommon import _
from ..qommon.form import (
CompositeWidget,
ComputedExpressionWidget,
@ -80,7 +80,7 @@ class SetBackofficeFieldsTableWidget(WidgetListAsTable):
class SetBackofficeFieldsWorkflowStatusItem(WorkflowStatusItem):
description = N_('Backoffice Data')
description = _('Backoffice Data')
key = 'set-backoffice-fields'
category = 'formdata-action'
@ -164,7 +164,7 @@ class SetBackofficeFieldsWorkflowStatusItem(WorkflowStatusItem):
except ValueError as e:
summary = _('Failed to convert %(class)s value to %(kind)s field (%(id)s)') % {
'class': type(new_value),
'kind': _(getattr(formdef_field, 'description', 'unknown')),
'kind': getattr(formdef_field, 'description', _('unknown')),
'id': field['field_id'],
}
expression_dict = self.get_expression(field['value'])

View File

@ -17,7 +17,7 @@
from quixote import get_publisher
from wcs.carddef import CardDef
from wcs.qommon import N_
from wcs.qommon import _
from wcs.wf.create_formdata import CreateFormdataWorkflowStatusItem, LinkedFormdataEvolutionPart
from wcs.workflows import register_item_class
@ -27,7 +27,7 @@ class LinkedCardDataEvolutionPart(LinkedFormdataEvolutionPart):
class CreateCarddataWorkflowStatusItem(CreateFormdataWorkflowStatusItem):
description = N_('Create Card Data')
description = _('Create Card Data')
key = 'create_carddata'
category = 'formdata-action'
ok_in_global_action = True
@ -35,10 +35,10 @@ class CreateCarddataWorkflowStatusItem(CreateFormdataWorkflowStatusItem):
formdef_class = CardDef
evolution_part_class = LinkedCardDataEvolutionPart
formdef_label = N_('Card')
mappings_label = N_('Mappings to new card fields')
varname_hint = N_('This is used to get linked card in expressions.')
user_association_option_label = N_('User to associate to card')
formdef_label = _('Card')
mappings_label = _('Mappings to new card fields')
varname_hint = _('This is used to get linked card in expressions.')
user_association_option_label = _('User to associate to card')
@classmethod
def is_available(cls, workflow=None):

View File

@ -22,7 +22,7 @@ from quixote import get_publisher, get_request, get_session
from quixote.html import htmltext
from wcs.formdef import FormDef
from wcs.qommon import N_, _
from wcs.qommon import _
from wcs.qommon.form import (
CheckboxWidget,
CompositeWidget,
@ -204,7 +204,7 @@ class LazyFormDataLinks:
class CreateFormdataWorkflowStatusItem(WorkflowStatusItem):
description = N_('New Form Creation')
description = _('New Form Creation')
key = 'create_formdata'
category = 'formdata-action'
support_substitution_variables = True
@ -213,11 +213,11 @@ class CreateFormdataWorkflowStatusItem(WorkflowStatusItem):
evolution_part_class = LinkedFormdataEvolutionPart
formdef_slug = None
formdef_label = N_('Form')
mappings_label = N_('Mappings to new form fields')
formdef_label = _('Form')
mappings_label = _('Mappings to new form fields')
accept_empty_value = False
varname_hint = N_('This is used to get linked forms in expressions.')
user_association_option_label = N_('User to associate to form')
varname_hint = _('This is used to get linked forms in expressions.')
user_association_option_label = _('User to associate to form')
draft = False
backoffice_submission = False
@ -261,7 +261,7 @@ class CreateFormdataWorkflowStatusItem(WorkflowStatusItem):
form.add(
SingleSelectWidget,
'%sformdef_slug' % prefix,
title=_(self.formdef_label),
title=self.formdef_label,
value=self.formdef_slug,
options=list_forms,
)
@ -281,7 +281,7 @@ class CreateFormdataWorkflowStatusItem(WorkflowStatusItem):
form.add(
RadiobuttonsWidget,
'%suser_association_mode' % prefix,
title=_(self.user_association_option_label),
title=self.user_association_option_label,
options=[
(None, _('None'), 'none'),
('keep-user', _('Keep Current User'), 'keep-user'),
@ -319,7 +319,7 @@ class CreateFormdataWorkflowStatusItem(WorkflowStatusItem):
widget = form.add(
MappingsWidget,
'%smappings' % prefix,
title=_(self.mappings_label),
title=self.mappings_label,
accept_empty_value=self.accept_empty_value,
to_formdef=formdef,
value=self.mappings,
@ -334,7 +334,7 @@ class CreateFormdataWorkflowStatusItem(WorkflowStatusItem):
'%svarname' % prefix,
title=_('Identifier'),
value=self.varname,
hint=_(self.varname_hint),
hint=self.varname_hint,
advanced=not (bool(self.varname)),
)
if 'map_fields_by_varname' in parameters and formdef:

View File

@ -17,7 +17,7 @@
from wcs.qommon.form import SingleSelectWidget
from wcs.workflows import WorkflowStatusItem, register_item_class
from ..qommon import N_, _
from ..qommon import _
MODE_INC = '1'
MODE_DEC = '2'
@ -25,7 +25,7 @@ MODE_SET = '3'
class ModifyCriticalityWorkflowStatusItem(WorkflowStatusItem):
description = N_('Criticality Levels')
description = _('Criticality Levels')
key = 'modify_criticality'
category = 'formdata-action'

View File

@ -23,7 +23,7 @@ from quixote.html import htmltext
from wcs.roles import get_user_roles
from wcs.workflows import WorkflowStatusItem, XmlSerialisable, get_role_name, register_item_class
from ..qommon import N_, _
from ..qommon import _
from ..qommon.form import (
CompositeWidget,
RadiobuttonsWidget,
@ -85,7 +85,7 @@ class RuleNode(XmlSerialisable):
class DispatchWorkflowStatusItem(WorkflowStatusItem):
description = N_('Function/Role Linking')
description = _('Function/Role Linking')
key = 'dispatch'
category = 'formdata-action'

View File

@ -16,7 +16,7 @@
from quixote import get_publisher
from wcs.qommon import N_, _
from wcs.qommon import _
from wcs.wf.create_carddata import CreateCarddataWorkflowStatusItem
from wcs.wf.external_workflow import ExternalWorkflowGlobalAction
from wcs.workflows import register_item_class
@ -27,8 +27,8 @@ class EditCarddataWorkflowStatusItem(CreateCarddataWorkflowStatusItem, ExternalW
key = 'edit_carddata'
mappings_label = _('Mappings to card fields')
accept_empty_value = True
automatic_targetting = N_('Action on linked cards')
manual_targetting = N_('Specify the identifier of the card on which the action will be applied')
automatic_targetting = _('Action on linked cards')
manual_targetting = _('Specify the identifier of the card on which the action will be applied')
@classmethod
def is_available(cls, workflow=None):

View File

@ -45,7 +45,7 @@ from wcs.workflows import (
template_on_formdata,
)
from ..qommon import N_, _, ezt, force_str, get_logger, misc
from ..qommon import _, ezt, force_str, get_logger, misc
from ..qommon.form import (
CheckboxWidget,
ComputedExpressionWidget,
@ -220,7 +220,7 @@ class ExportToModelDirectory(Directory):
class ExportToModel(WorkflowStatusItem):
description = N_('Document Creation')
description = _('Document Creation')
key = 'export_to_model'
category = 'formdata-action'
support_substitution_variables = True
@ -268,7 +268,7 @@ class ExportToModel(WorkflowStatusItem):
return
if form.get_submit() == 'button%s' % self.id:
if not evo.comment:
evo.comment = _('Form exported in a model')
evo.comment = str(_('Form exported in a model'))
self.perform_real(formdata, evo)
in_backoffice = get_request() and get_request().is_in_backoffice()
if self.attach_to_history:
@ -351,7 +351,7 @@ class ExportToModel(WorkflowStatusItem):
widget_name,
value.base_filename,
)
hint = hint_prefix + hint
hint = hint_prefix + force_text(hint)
form.add(
UploadWidget,
widget_name,

View File

@ -18,7 +18,7 @@ from quixote import get_publisher
from wcs.carddef import CardDef
from wcs.formdef import FormDef
from wcs.qommon import N_, _
from wcs.qommon import _
from wcs.qommon.form import ComputedExpressionWidget, Form, RadiobuttonsWidget, SingleSelectWidget
from wcs.workflows import (
Workflow,
@ -34,8 +34,8 @@ class ExternalWorkflowGlobalAction(WorkflowStatusItem):
description = _('External workflow')
key = 'external_workflow_global_action'
category = 'formdata-action'
automatic_targetting = N_('Action on linked cards/forms')
manual_targetting = N_('Specify the identifier of the card/form on which the action will be applied')
automatic_targetting = _('Action on linked cards/forms')
manual_targetting = _('Specify the identifier of the card/form on which the action will be applied')
slug = None
target_mode = None
@ -101,8 +101,8 @@ class ExternalWorkflowGlobalAction(WorkflowStatusItem):
if 'target_mode' in parameters:
target_modes = [
('all', _(self.automatic_targetting), 'all'),
('manual', _(self.manual_targetting), 'manual'),
('all', self.automatic_targetting, 'all'),
('manual', self.manual_targetting, 'manual'),
]
form.add(
RadiobuttonsWidget,

View File

@ -26,7 +26,7 @@ from wcs.forms.common import FileDirectory
from wcs.forms.root import FormPage
from wcs.workflows import RedisplayFormException, WorkflowStatusItem, register_item_class
from ..qommon import N_, _
from ..qommon import _
from ..qommon.form import SingleSelectWidget, VarnameWidget, WidgetList
@ -85,14 +85,14 @@ class WorkflowFormFieldsDirectory(FieldsDirectory):
class FormWorkflowStatusItem(WorkflowStatusItem):
description = N_('Form')
description = _('Form')
key = 'form'
category = 'interaction'
ok_in_global_action = False
endpoint = False
waitpoint = True
redirect_after_submit_url = 'fields/'
submit_button_label = N_('Submit and go to fields edition')
submit_button_label = _('Submit and go to fields edition')
by = []
formdef = None

View File

@ -28,14 +28,14 @@ from quixote import get_publisher
from wcs.workflows import WorkflowStatusItem, register_item_class
from ..qommon import N_, _, force_str, get_logger
from ..qommon import _, force_str, get_logger
from ..qommon.errors import ConnectionError
from ..qommon.form import CheckboxWidget, ComputedExpressionWidget, RadiobuttonsWidget
from ..qommon.misc import http_get_page, normalize_geolocation
class GeolocateWorkflowStatusItem(WorkflowStatusItem):
description = N_('Geolocation')
description = _('Geolocation')
key = 'geolocate'
category = 'formdata-action'

View File

@ -27,7 +27,7 @@ from wcs.api import get_user_from_api_query_string, is_url_signed
from wcs.conditions import Condition
from wcs.workflows import Workflow, WorkflowGlobalAction, WorkflowStatusJumpItem, register_item_class
from ..qommon import N_, _, errors, force_str
from ..qommon import _, errors, force_str
from ..qommon.cron import CronJob
from ..qommon.form import ComputedExpressionWidget, SingleSelectWidget, StringWidget, WidgetList
from ..qommon.humantime import humanduration2seconds, seconds2humanduration, timewords
@ -110,7 +110,7 @@ class TriggerDirectory(Directory):
class JumpWorkflowStatusItem(WorkflowStatusJumpItem):
description = N_('Automatic Jump')
description = _('Automatic Jump')
key = 'jump'
by = []
@ -167,7 +167,10 @@ class JumpWorkflowStatusItem(WorkflowStatusJumpItem):
reasons.append(_('timeout'))
if reasons:
return _('to %(name)s, %(reasons)s') % {'name': wf_status[0].name, 'reasons': ', '.join(reasons)}
return _('to %(name)s, %(reasons)s') % {
'name': wf_status[0].name,
'reasons': ', '.join([str(x) for x in reasons]),
}
else:
return wf_status[0].name

View File

@ -20,14 +20,14 @@ from quixote import get_publisher
from wcs.workflows import WorkflowStatusItem, register_item_class, template_on_formdata
from ..qommon import N_, _, get_logger
from ..qommon import _, get_logger
from ..qommon.form import ComputedExpressionWidget, SingleSelectWidget, StringWidget, TextWidget, WidgetList
from ..qommon.template import TemplateError
from .wscall import WebserviceCallStatusItem
class SendNotificationWorkflowStatusItem(WebserviceCallStatusItem):
description = N_('User Notification')
description = _('User Notification')
key = 'notification'
category = 'interaction'
support_substitution_variables = True

View File

@ -26,7 +26,7 @@ from quixote import get_publisher, get_request, get_response
from wcs.api_utils import MissingSecret, get_secret_and_orig, sign_url
from wcs.workflows import WorkflowStatusItem, XmlSerialisable, register_item_class
from ..qommon import N_, _
from ..qommon import _
from ..qommon.form import CompositeWidget, ComputedExpressionWidget, SingleSelectWidget, WidgetListAsTable
from ..qommon.ident.idp import is_idp_managing_user_attributes
from ..qommon.misc import JSONEncoder, http_patch_request
@ -102,7 +102,7 @@ class FieldNode(XmlSerialisable):
class UpdateUserProfileStatusItem(WorkflowStatusItem):
description = N_('User Profile Update')
description = _('User Profile Update')
key = 'update_user_profile'
category = 'user-action'
@ -211,7 +211,7 @@ class UpdateUserProfileStatusItem(WorkflowStatusItem):
get_logger().error('failed to update profile for user %r', user)
if get_request():
get_response().add_after_job(str(N_('Updating user profile')), after_job)
get_response().add_after_job(_('Updating user profile'), after_job)
else:
after_job()

View File

@ -16,12 +16,12 @@
from wcs.workflows import WorkflowStatusItem, register_item_class
from ..qommon import N_, _
from ..qommon import _
from ..qommon.form import ComputedExpressionWidget
class RedirectToUrlWorkflowStatusItem(WorkflowStatusItem):
description = N_('Web Redirection')
description = _('Web Redirection')
key = 'redirect_to_url'
category = 'formdata-action'
endpoint = False

View File

@ -26,7 +26,7 @@ from wcs.workflows import (
template_on_formdata,
)
from ..qommon import N_, _, ezt, get_logger
from ..qommon import _, ezt, get_logger
from ..qommon.form import SingleSelectWidget, TextWidget, WidgetList
from ..qommon.template import TemplateError
@ -80,7 +80,7 @@ class JournalEvolutionPart:
class RegisterCommenterWorkflowStatusItem(WorkflowStatusItem):
description = N_('History Message')
description = _('History Message')
key = 'register-comment'
category = 'interaction'

View File

@ -18,11 +18,11 @@ from quixote import get_publisher, get_request, get_response, get_session
from wcs.workflows import AbortActionException, WorkflowStatusItem, register_item_class
from ..qommon import N_, _
from ..qommon import _
class RemoveWorkflowStatusItem(WorkflowStatusItem):
description = N_('Deletion')
description = _('Deletion')
key = 'remove'
category = 'formdata-action'

View File

@ -20,11 +20,11 @@ from wcs.formdef import FormDef
from wcs.qommon.form import SingleSelectWidget, StringWidget, WidgetList, WysiwygTextWidget
from wcs.workflows import WorkflowStatusItem, register_item_class
from ..qommon import N_, _
from ..qommon import _
class ResubmitWorkflowStatusItem(WorkflowStatusItem):
description = N_('Resubmission')
description = _('Resubmission')
key = 'resubmit'
category = 'formdata-action'
endpoint = False

View File

@ -23,7 +23,7 @@ from wcs.api_utils import MissingSecret, get_secret_and_orig, sign_url
from wcs.roles import get_user_roles
from wcs.workflows import WorkflowStatusItem, register_item_class
from ..qommon import N_, _
from ..qommon import _
from ..qommon.form import SingleSelectWidgetWithOther
from ..qommon.ident.idp import is_idp_managing_user_attributes
from ..qommon.misc import http_delete_request, http_post_request
@ -47,7 +47,7 @@ def sign_ws_url(url):
class AddRoleWorkflowStatusItem(WorkflowStatusItem):
description = N_('Role Addition')
description = _('Role Addition')
key = 'add_role'
category = 'user-action'
@ -116,7 +116,7 @@ class AddRoleWorkflowStatusItem(WorkflowStatusItem):
get_logger().error('failed to add role %r to user %r', role, user)
if get_request():
get_response().add_after_job(str(N_('Adding role')), after_job)
get_response().add_after_job(_('Adding role'), after_job)
else:
after_job()
@ -125,7 +125,7 @@ register_item_class(AddRoleWorkflowStatusItem)
class RemoveRoleWorkflowStatusItem(WorkflowStatusItem):
description = N_('Role Removal')
description = _('Role Removal')
key = 'remove_role'
category = 'user-action'
@ -187,7 +187,7 @@ class RemoveRoleWorkflowStatusItem(WorkflowStatusItem):
get_logger().error('failed to remove role %r from user %r', role, user)
if get_request():
get_response().add_after_job(str(N_('Removing role')), after_job)
get_response().add_after_job(_('Removing role'), after_job)
else:
after_job()

View File

@ -14,13 +14,13 @@
# You should have received a copy of the GNU General Public License
# along with this program; if not, see <http://www.gnu.org/licenses/>.
from wcs.qommon import N_
from wcs.qommon import _
from .jump import JumpWorkflowStatusItem, register_item_class
class TimeoutWorkflowStatusItem(JumpWorkflowStatusItem):
description = N_('Change Status on Timeout')
description = _('Change Status on Timeout')
key = 'timeout'
ok_in_global_action = False

View File

@ -34,7 +34,7 @@ from wcs.workflows import (
)
from wcs.wscalls import call_webservice, get_app_error_code
from ..qommon import N_, _, force_str
from ..qommon import _, force_str
from ..qommon.errors import ConnectionError
from ..qommon.form import (
CheckboxWidget,
@ -110,7 +110,7 @@ class JournalWsCallErrorPart:
class WebserviceCallStatusItem(WorkflowStatusItem):
description = N_('Webservice')
description = _('Webservice')
key = 'webservice_call'
category = 'interaction'
support_substitution_variables = True
@ -248,7 +248,7 @@ class WebserviceCallStatusItem(WorkflowStatusItem):
attrs={
'data-dynamic-display-child-of': '%smethod' % prefix,
'data-dynamic-display-value-in': '|'.join(
[_(methods['POST']), _(methods['PUT']), _(methods['PATCH'])]
[str(_(methods['POST'])), str(_(methods['PUT'])), str(_(methods['PATCH']))]
),
},
)
@ -262,7 +262,7 @@ class WebserviceCallStatusItem(WorkflowStatusItem):
attrs={
'data-dynamic-display-child-of': '%smethod' % prefix,
'data-dynamic-display-value-in': '|'.join(
[_(methods['POST']), _(methods['PUT']), _(methods['PATCH'])]
[str(_(methods['POST'])), str(_(methods['PUT'])), str(_(methods['PATCH']))]
),
},
)

View File

@ -36,7 +36,7 @@ from .fields import FileField
from .formdata import Evolution
from .formdef import FormDef, FormdefImportError
from .mail_templates import MailTemplate
from .qommon import N_, _, emails, errors, ezt, force_str, get_cfg, get_logger, misc
from .qommon import _, emails, errors, ezt, force_str, get_cfg, get_logger, misc
from .qommon.form import (
CheckboxWidget,
ComputedExpressionWidget,
@ -393,7 +393,7 @@ class Workflow(StorableObject):
StorableObject.__init__(self)
self.name = name
self.possible_status = []
self.roles = {'_receiver': _('Recipient')}
self.roles = {'_receiver': force_text(_('Recipient'))}
self.global_actions = []
self.criticality_levels = []
@ -401,7 +401,7 @@ class Workflow(StorableObject):
changed = False
if 'roles' not in self.__dict__ or self.roles is None:
self.roles = {'_receiver': _('Recipient')}
self.roles = {'_receiver': force_text(_('Recipient'))}
changed = True
for status in self.possible_status:
@ -457,7 +457,7 @@ class Workflow(StorableObject):
form.rebuild()
if get_response():
get_response().add_after_job(N_('Reindexing cards and forms after workflow change'), update)
get_response().add_after_job(_('Reindexing cards and forms after workflow change'), update)
else:
update()
@ -670,7 +670,7 @@ class Workflow(StorableObject):
charset = get_publisher().site_charset
workflow = cls()
if tree.find('name') is None or not tree.find('name').text:
raise WorkflowImportError(N_('Missing name'))
raise WorkflowImportError(_('Missing name'))
# if the tree we get is actually a ElementTree for real, we get its
# root element and go on happily.
@ -678,7 +678,7 @@ class Workflow(StorableObject):
tree = tree.getroot()
if tree.tag != 'workflow':
raise WorkflowImportError(N_('Not a workflow'))
raise WorkflowImportError(_('Not a workflow'))
if include_id and tree.attrib.get('id'):
workflow.id = tree.attrib.get('id')
@ -807,18 +807,20 @@ class Workflow(StorableObject):
def get_default_workflow(cls):
from .qommon.admin.emails import EmailsDirectory
workflow = Workflow(name=_('Default'))
# force_text() is used on lazy gettext calls as the default workflow is used
# in tests as the basis for other ones and lazy gettext would fail pickling.
workflow = Workflow(name=force_text(_('Default')))
workflow.id = '_default'
workflow.roles = {'_receiver': _('Recipient')}
just_submitted_status = workflow.add_status(_('Just Submitted'), 'just_submitted')
workflow.roles = {'_receiver': force_text(_('Recipient'))}
just_submitted_status = workflow.add_status(force_text(_('Just Submitted')), 'just_submitted')
just_submitted_status.visibility = ['_receiver']
new_status = workflow.add_status(_('New'), 'new')
new_status = workflow.add_status(force_text(_('New')), 'new')
new_status.colour = '66FF00'
rejected_status = workflow.add_status(_('Rejected'), 'rejected')
rejected_status = workflow.add_status(force_text(_('Rejected')), 'rejected')
rejected_status.colour = 'FF3300'
accepted_status = workflow.add_status(_('Accepted'), 'accepted')
accepted_status = workflow.add_status(force_text(_('Accepted')), 'accepted')
accepted_status.colour = '66CCFF'
finished_status = workflow.add_status(_('Finished'), 'finished')
finished_status = workflow.add_status(force_text(_('Finished')), 'finished')
finished_status.colour = 'CCCCCC'
commentable = CommentableWorkflowStatusItem()
@ -906,7 +908,7 @@ class Workflow(StorableObject):
accept = ChoiceWorkflowStatusItem()
accept.id = '_accept'
accept.label = _('Accept')
accept.label = force_text(_('Accept'))
accept.by = ['_receiver']
accept.status = accepted_status.id
accept.parent = new_status
@ -914,7 +916,7 @@ class Workflow(StorableObject):
reject = ChoiceWorkflowStatusItem()
reject.id = '_reject'
reject.label = _('Reject')
reject.label = force_text(_('Reject'))
reject.by = ['_receiver']
reject.status = rejected_status.id
reject.parent = new_status
@ -922,7 +924,7 @@ class Workflow(StorableObject):
finish = ChoiceWorkflowStatusItem()
finish.id = '_finish'
finish.label = _('Finish')
finish.label = force_text(_('Finish'))
finish.by = ['_receiver']
finish.status = finished_status.id
finish.parent = accepted_status
@ -1074,7 +1076,7 @@ class XmlSerialisable:
# if the roles are managed by the idp, don't try further.
if get_publisher() and get_cfg('sp', {}).get('idp-manage-roles') is True:
raise WorkflowImportError(N_('Unknown referenced role (%s)'), (value,))
raise WorkflowImportError(_('Unknown referenced role (%s)'), (value,))
# and if there's no match, create a new role
role = get_publisher().role_class()
@ -1916,7 +1918,7 @@ class WorkflowStatusItem(XmlSerialisable):
return changed
def render_as_line(self):
label = _(self.description)
label = self.description
details = self.get_line_details()
if details:
label += ' (%s)' % details
@ -2199,7 +2201,7 @@ class WorkflowStatusItem(XmlSerialisable):
if not targets and formdata: # do not log in presentation context: formdata is needed
message = _(
'reference to invalid status %(target)s in status %(status)s, ' 'action %(status_item)s'
) % {'target': self.status, 'status': self.parent.name, 'status_item': _(self.description)}
) % {'target': self.status, 'status': self.parent.name, 'status_item': self.description}
get_publisher().record_error(message, formdata=formdata, status_item=self)
return targets
@ -2212,9 +2214,9 @@ class WorkflowStatusItem(XmlSerialisable):
roles = self.parent.parent.render_list_of_roles(self.by)
label += ' %s %s' % (_('by'), roles)
if getattr(self, 'status', None) == '_previous':
label += ' ' + _('(to last marker)')
label += ' ' + str(_('(to last marker)'))
if getattr(self, 'set_marker_on_status', False):
label += ' ' + _('(and set marker)')
label += ' ' + str(_('(and set marker)'))
else:
label = self.render_as_line()
return label
@ -2281,7 +2283,7 @@ class WorkflowStatusItem(XmlSerialisable):
value = xml_node_text(elem)
mail_template = MailTemplate.get_by_slug(value)
if not mail_template:
raise WorkflowImportError(N_('Unknown referenced mail template (%s)'), (value,))
raise WorkflowImportError(_('Unknown referenced mail template (%s)'), (value,))
self.mail_template = value
return
@ -2471,7 +2473,7 @@ def register_item_class(klass):
class CommentableWorkflowStatusItem(WorkflowStatusItem):
description = N_('Comment')
description = _('Comment')
key = 'commentable'
category = 'interaction'
endpoint = False
@ -2542,11 +2544,11 @@ class CommentableWorkflowStatusItem(WorkflowStatusItem):
super().add_parameters_widgets(form, parameters, prefix=prefix, formdef=formdef, **kwargs)
if 'label' in parameters:
if self.label is None:
self.label = _('Comment')
self.label = str(_('Comment'))
form.add(StringWidget, '%slabel' % prefix, size=40, title=_('Label'), value=self.label)
if 'button_label' in parameters:
if self.button_label == 0:
self.button_label = _('Add Comment')
self.button_label = str(_('Add Comment'))
form.add(
StringWidget,
'%sbutton_label' % prefix,
@ -2610,7 +2612,7 @@ register_item_class(CommentableWorkflowStatusItem)
class ChoiceWorkflowStatusItem(WorkflowStatusJumpItem):
description = N_('Manual Jump')
description = _('Manual Jump')
key = 'choice'
endpoint = False
waitpoint = True
@ -2642,7 +2644,7 @@ class ChoiceWorkflowStatusItem(WorkflowStatusJumpItem):
if self.label and to_status:
more = ''
if self.set_marker_on_status:
more += ' ' + _('(and set marker)')
more += ' ' + str(_('(and set marker)'))
if self.by:
return _('"%(label)s", to %(to)s, by %(by)s%(more)s') % {
'label': self.get_label(),
@ -2749,7 +2751,7 @@ register_item_class(ChoiceWorkflowStatusItem)
class JumpOnSubmitWorkflowStatusItem(WorkflowStatusJumpItem):
description = N_('On Submit Jump')
description = _('On Submit Jump')
key = 'jumponsubmit'
ok_in_global_action = False
@ -2777,7 +2779,7 @@ register_item_class(JumpOnSubmitWorkflowStatusItem)
class SendmailWorkflowStatusItem(WorkflowStatusItem):
description = N_('Email')
description = _('Email')
key = 'sendmail'
category = 'interaction'
support_substitution_variables = True
@ -3061,7 +3063,7 @@ def template_on_context(context=None, template=None, **kwargs):
class SendSMSWorkflowStatusItem(WorkflowStatusItem):
description = N_('SMS')
description = _('SMS')
key = 'sendsms'
category = 'interaction'
support_substitution_variables = True
@ -3129,7 +3131,7 @@ register_item_class(SendSMSWorkflowStatusItem)
class DisplayMessageWorkflowStatusItem(WorkflowStatusItem):
description = N_('Alert')
description = _('Alert')
key = 'displaymsg'
category = 'interaction'
support_substitution_variables = True
@ -3150,7 +3152,7 @@ class DisplayMessageWorkflowStatusItem(WorkflowStatusItem):
parts.append(_('with actions'))
if self.to:
parts.append(_('for %s') % self.render_list_of_roles(self.to))
return ', '.join(parts)
return ', '.join([str(x) for x in parts])
def get_message(self, filled, position='top'):
if not (self.message and self.position == position and filled.is_for_current_user(self.to)):
@ -3234,7 +3236,7 @@ register_item_class(DisplayMessageWorkflowStatusItem)
class RedirectToStatusWorkflowStatusItem(WorkflowStatusItem):
description = N_('Status Page Redirection')
description = _('Status Page Redirection')
key = 'redirectstatus'
ok_in_global_action = False
@ -3262,7 +3264,7 @@ class RedirectToStatusWorkflowStatusItem(WorkflowStatusItem):
class EditableWorkflowStatusItem(WorkflowStatusItem):
description = N_('Edition')
description = _('Edition')
key = 'editable'
category = 'formdata-action'
endpoint = False