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() pub.write_cfg()
resp = get_app(pub).get('/category1/test-formdef-1/') resp = get_app(pub).get('/category1/test-formdef-1/')
assert 'jquery.js' in resp.text 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.admin.fields import FieldDefPage, FieldsDirectory
from wcs.backoffice.snapshots import SnapshotsDirectory from wcs.backoffice.snapshots import SnapshotsDirectory
from wcs.blocks import BlockDef, BlockdefImportError 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.backoffice.menu import html_top
from wcs.qommon.form import FileWidget, Form, HtmlWidget, StringWidget from wcs.qommon.form import FileWidget, Form, HtmlWidget, StringWidget
@ -48,7 +48,7 @@ class BlockDirectory(FieldsDirectory):
field_def_page_class = BlockFieldDefPage field_def_page_class = BlockFieldDefPage
blacklisted_types = ['page', 'table', 'table-select', 'tablerows', 'ranked-items', 'blocks', 'computed'] blacklisted_types = ['page', 'table', 'table-select', 'tablerows', 'ranked-items', 'blocks', 'computed']
support_import = False 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): def __init__(self, section, *args, **kwargs):
self.section = section self.section = section

View File

@ -21,7 +21,7 @@ from quixote.html import TemplateIO, htmltext
from wcs.carddef import CardDef from wcs.carddef import CardDef
from wcs.categories import CardDefCategory, Category from wcs.categories import CardDefCategory, Category
from wcs.formdef import FormDef 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.backoffice.menu import html_top
from wcs.qommon.form import Form, HtmlWidget, StringWidget, WysiwygTextWidget from wcs.qommon.form import Form, HtmlWidget, StringWidget, WysiwygTextWidget
@ -83,8 +83,8 @@ class CategoryPage(Directory):
category_class = Category category_class = Category
category_ui_class = CategoryUI category_ui_class = CategoryUI
formdef_class = FormDef formdef_class = FormDef
usage_title = N_('Forms in this category') usage_title = _('Forms in this category')
empty_message = N_('no form associated to this category') empty_message = _('no form associated to this category')
_q_exports = ['', 'edit', 'delete', 'description'] _q_exports = ['', 'edit', 'delete', 'description']
def __init__(self, component): def __init__(self, component):
@ -112,14 +112,14 @@ class CategoryPage(Directory):
formdefs = self.formdef_class.select(order_by='name') formdefs = self.formdef_class.select(order_by='name')
formdefs = [x for x in formdefs if x.category_id == self.category.id] formdefs = [x for x in formdefs if x.category_id == self.category.id]
r += htmltext('<div class="bo-block">') 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>') r += htmltext('<ul>')
for formdef in formdefs: for formdef in formdefs:
r += htmltext('<li><a href="../../%s/">') % str(formdef.id) r += htmltext('<li><a href="../../%s/">') % str(formdef.id)
r += formdef.name r += formdef.name
r += htmltext('</a></li>') r += htmltext('</a></li>')
if not formdefs: if not formdefs:
r += htmltext('<li>%s</li>') % _(self.empty_message) r += htmltext('<li>%s</li>') % self.empty_message
r += htmltext('</ul>') r += htmltext('</ul>')
r += htmltext('</div>') r += htmltext('</div>')
return r.getvalue() return r.getvalue()
@ -192,8 +192,8 @@ class CardDefCategoryPage(CategoryPage):
category_class = CardDefCategory category_class = CardDefCategory
category_ui_class = CardDefCategoryUI category_ui_class = CardDefCategoryUI
formdef_class = CardDef formdef_class = CardDef
usage_title = N_('Card models in this category') usage_title = _('Card models in this category')
empty_message = N_('no card model associated to this category') empty_message = _('no card model associated to this category')
class CategoriesDirectory(Directory): class CategoriesDirectory(Directory):
@ -201,7 +201,7 @@ class CategoriesDirectory(Directory):
category_class = Category category_class = Category
category_ui_class = CategoryUI category_ui_class = CategoryUI
category_page_class = CategoryPage 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): def _q_index(self):
get_response().add_javascript(['jquery.js', 'jquery-ui.js', 'biglist.js', 'qommon.wysiwyg.js']) 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('<a class="new-item" href="new" rel="popup">%s</a>') % _('New Category')
r += htmltext('</span>') r += htmltext('</span>')
r += htmltext('</div>') 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() categories = self.category_class.select()
r += htmltext('<ul class="biglist sortable" id="category-list">') r += htmltext('<ul class="biglist sortable" id="category-list">')
self.category_class.sort_by_position(categories) self.category_class.sort_by_position(categories)
@ -273,4 +273,4 @@ class CardDefCategoriesDirectory(CategoriesDirectory):
category_class = CardDefCategory category_class = CardDefCategory
category_ui_class = CardDefCategoryUI category_ui_class = CardDefCategoryUI
category_page_class = CardDefCategoryPage 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.admin import utils
from wcs.fields import get_field_options from wcs.fields import get_field_options
from wcs.formdef import FormDef 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.admin.menu import command_icon
from wcs.qommon.backoffice.menu import html_top from wcs.qommon.backoffice.menu import html_top
from wcs.qommon.form import CheckboxWidget, Form, HtmlWidget, SingleSelectWidget, StringWidget from wcs.qommon.form import CheckboxWidget, Form, HtmlWidget, SingleSelectWidget, StringWidget
@ -218,7 +218,7 @@ class FieldsDirectory(Directory):
blacklisted_types = [] blacklisted_types = []
page_id = None page_id = None
field_var_prefix = '..._' field_var_prefix = '..._'
readonly_message = N_('The fields are readonly.') readonly_message = _('The fields are readonly.')
support_import = True support_import = True
@ -323,7 +323,7 @@ class FieldsDirectory(Directory):
if field.required: if field.required:
required = '' required = ''
else: else:
required = ' - ' + _('optional') required = ' - %s' % _('optional')
r += htmltext('<span class="optional">%s</span>') % required r += htmltext('<span class="optional">%s</span>') % required
if getattr(field, 'condition', None): if getattr(field, 'condition', None):
r += htmltext(' - <span class="condition">%s</span>') % _('depending on condition') r += htmltext(' - <span class="condition">%s</span>') % _('depending on condition')
@ -348,8 +348,8 @@ class FieldsDirectory(Directory):
r += htmltext('</ul>') r += htmltext('</ul>')
if self.objectdef.is_readonly(): if self.objectdef.is_readonly():
get_response().filter['sidebar'] = htmltext('<div class="infonotice"><p>%s</p></div>') % _( get_response().filter['sidebar'] = (
self.readonly_message htmltext('<div class="infonotice"><p>%s</p></div>') % self.readonly_message
) )
if hasattr(self.objectdef, 'snapshot_object'): if hasattr(self.objectdef, 'snapshot_object'):
get_response().filter['sidebar'] += utils.snapshot_info_block( get_response().filter['sidebar'] += utils.snapshot_info_block(
@ -472,7 +472,7 @@ class FieldsDirectory(Directory):
{ {
'success': 'ok', 'success': 'ok',
'additional-action': { '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), '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.categories import Category
from wcs.formdef import DRAFTS_DEFAULT_LIFESPAN, FormDef, FormdefImportError, FormdefImportRecoverableError from wcs.formdef import DRAFTS_DEFAULT_LIFESPAN, FormDef, FormdefImportError, FormdefImportRecoverableError
from wcs.forms.root import qrcode 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.afterjobs import AfterJob
from wcs.qommon.backoffice.menu import html_top from wcs.qommon.backoffice.menu import html_top
from wcs.qommon.errors import TraversalError from wcs.qommon.errors import TraversalError
@ -169,7 +169,7 @@ class AdminFieldDefPage(FieldDefPage):
class AdminFieldsDirectory(FieldsDirectory): class AdminFieldsDirectory(FieldsDirectory):
field_def_page_class = AdminFieldDefPage field_def_page_class = AdminFieldDefPage
field_var_prefix = 'form_var_' field_var_prefix = 'form_var_'
readonly_message = N_('This form is readonly.') readonly_message = _('This form is readonly.')
def index_bottom(self): def index_bottom(self):
if self.objectdef.is_readonly(): if self.objectdef.is_readonly():
@ -189,7 +189,7 @@ class AdminFieldsDirectory(FieldsDirectory):
class OptionsDirectory(Directory): class OptionsDirectory(Directory):
category_class = Category category_class = Category
category_empty_choice = N_('Select a category for this form') category_empty_choice = _('Select a category for this form')
_q_exports = [ _q_exports = [
'confirmation', 'confirmation',
'only_allow_one', 'only_allow_one',
@ -341,7 +341,7 @@ class OptionsDirectory(Directory):
def category(self): def category(self):
categories = get_categories(self.category_class) categories = get_categories(self.category_class)
form = Form(enctype='multipart/form-data') 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( form.add(
SingleSelectWidget, SingleSelectWidget,
'category_id', 'category_id',
@ -573,10 +573,10 @@ class FormDefPage(Directory):
formdef_default_workflow = '_default' formdef_default_workflow = '_default'
options_directory_class = OptionsDirectory options_directory_class = OptionsDirectory
delete_message = N_('You are about to irrevocably delete this form.') delete_message = _('You are about to irrevocably delete this form.')
delete_title = N_('Deleting Form:') delete_title = _('Deleting Form:')
overwrite_message = N_('You can replace this form by uploading a file ' 'or by pointing to a form URL.') overwrite_message = _('You can replace this form by uploading a file ' 'or by pointing to a form URL.')
overwrite_success_message = N_( overwrite_success_message = _(
'The form has been successfully overwritten. ' 'The form has been successfully overwritten. '
'Do note it kept its existing address and role and workflow parameters.' 'Do note it kept its existing address and role and workflow parameters.'
) )
@ -834,7 +834,7 @@ class FormDefPage(Directory):
except KeyError: except KeyError:
# removed role ? # removed role ?
roles.append(_('Unknown role (%s)') % x) roles.append(_('Unknown role (%s)') % x)
value = htmltext(', ').join(roles) value = htmltext(', ').join([str(x) for x in roles])
else: else:
value = C_('roles|None') value = C_('roles|None')
return value return value
@ -845,7 +845,7 @@ class FormDefPage(Directory):
auth_contexts = get_publisher().get_supported_authentication_contexts() auth_contexts = get_publisher().get_supported_authentication_contexts()
value += ' (%s)' % ', '.join( value += ' (%s)' % ', '.join(
[ [
auth_contexts.get(x) str(auth_contexts.get(x))
for x in self.formdef.required_authentication_contexts for x in self.formdef.required_authentication_contexts
if auth_contexts.get(x) if auth_contexts.get(x)
] ]
@ -1180,11 +1180,11 @@ class FormDefPage(Directory):
def duplicate(self): def duplicate(self):
self.formdefui.formdef.id = None self.formdefui.formdef.id = None
original_name = self.formdefui.formdef.name 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)] formdef_names = [x.name for x in self.formdef_class.select(lightweight=True)]
no = 2 no = 2
while self.formdefui.formdef.name in formdef_names: 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 no += 1
self.formdefui.formdef.url_name = None self.formdefui.formdef.url_name = None
self.formdefui.formdef.table_name = None self.formdefui.formdef.table_name = None
@ -1212,16 +1212,16 @@ class FormDefPage(Directory):
if check_count_message: if check_count_message:
form.widgets.append(HtmlWidget('<p>%s</p>' % check_count_message)) form.widgets.append(HtmlWidget('<p>%s</p>' % check_count_message))
else: 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('delete', _('Delete'))
form.add_submit('cancel', _('Cancel')) form.add_submit('cancel', _('Cancel'))
if form.get_widget('cancel').parse() or (form.is_submitted() and check_count_message): if form.get_widget('cancel').parse() or (form.is_submitted() and check_count_message):
return redirect('..') return redirect('..')
if not form.is_submitted() or form.has_errors(): if not form.is_submitted() or form.has_errors():
get_response().breadcrumb.append(('delete', _('Delete'))) 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 = 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() r += form.render()
return r.getvalue() return r.getvalue()
else: else:
@ -1249,7 +1249,7 @@ class FormDefPage(Directory):
self.html_top(title=_('Overwrite')) self.html_top(title=_('Overwrite'))
r = TemplateIO(html=True) r = TemplateIO(html=True)
r += htmltext('<h2>%s</h2>') % _('Overwrite') 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() r += form.render()
return r.getvalue() return r.getvalue()
@ -1320,7 +1320,7 @@ class FormDefPage(Directory):
new_formdef.roles = self.formdef.roles new_formdef.roles = self.formdef.roles
self.formdef = new_formdef self.formdef = new_formdef
self.formdef.store(comment=_('Overwritten')) self.formdef.store(comment=_('Overwritten'))
get_session().message = ('info', _(self.overwrite_success_message)) get_session().message = ('info', str(self.overwrite_success_message))
return redirect('.') return redirect('.')
def get_incompatible_field_ids(self, new_formdef): def get_incompatible_field_ids(self, new_formdef):
@ -1539,9 +1539,12 @@ class FormDefPage(Directory):
count = self.formdef.data_class().count() count = self.formdef.data_class().count()
archiver = Archiver(self.formdef) archiver = Archiver(self.formdef)
if count > 100: # Arbitrary threshold if count > 100: # Arbitrary threshold
job = get_response().add_after_job(str(N_('Archiving forms')), archiver.archive) job = get_response().add_after_job(
job.done_action_url = self.formdef.get_admin_url() + 'archive?job=%s' % job.id _('Archiving forms'),
job.done_action_label = _('Download Archive') archiver.archive,
done_action_url=self.formdef.get_admin_url() + 'archive?job=%(job_id)s',
done_action_label=_('Download Archive'),
)
job.store() job.store()
return redirect(job.get_processing_url()) return redirect(job.get_processing_url())
else: else:
@ -1632,9 +1635,12 @@ class FormDefPage(Directory):
count = self.formdef.data_class().count() count = self.formdef.data_class().count()
anonymiser = Anonymiser(self.formdef, status_ids, before_date) anonymiser = Anonymiser(self.formdef, status_ids, before_date)
if count > 100: # Arbitrary threshold if count > 100: # Arbitrary threshold
job = get_response().add_after_job(str(N_('Anonymising forms')), anonymiser.anonymise) job = get_response().add_after_job(
job.done_action_url = self.formdef.get_admin_url() _('Anonymising forms'),
job.done_action_label = _('Back') anonymiser.anonymise,
done_action_url=self.formdef.get_admin_url(),
done_action_label=_('Back'),
)
job.store() job.store()
return redirect(job.get_processing_url()) return redirect(job.get_processing_url())
else: else:
@ -1739,15 +1745,15 @@ class FormsDirectory(AccessControlled, Directory):
formdef_page_class = FormDefPage formdef_page_class = FormDefPage
formdef_ui_class = FormDefUI formdef_ui_class = FormDefUI
top_title = N_('Forms') top_title = _('Forms')
import_title = N_('Import Form') import_title = _('Import Form')
import_submit_label = N_('Import Form') import_submit_label = _('Import Form')
import_paragraph = N_('You can install a new form by uploading a file ' 'or by pointing to the form URL.') import_paragraph = _('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_loading_error_message = _('Error loading form (%s).')
import_success_message = N_( import_success_message = _(
'This form has been successfully imported. ' 'Do note it is disabled by default.' '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, ' 'Imported form contained errors and has been automatically fixed, '
'you should nevertheless check everything is ok. ' 'you should nevertheless check everything is ok. '
'Do note it is disabled by default.' 'Do note it is disabled by default.'
@ -1761,7 +1767,7 @@ class FormsDirectory(AccessControlled, Directory):
return super()._q_traverse(path) return super()._q_traverse(path)
def _q_index(self): def _q_index(self):
self.html_top(title=_(self.top_title)) self.html_top(title=self.top_title)
r = TemplateIO(html=True) r = TemplateIO(html=True)
get_response().add_javascript(['jquery.js', 'widget_list.js']) get_response().add_javascript(['jquery.js', 'widget_list.js'])
r += self.form_actions() r += self.form_actions()
@ -1865,7 +1871,7 @@ class FormsDirectory(AccessControlled, Directory):
form.add(FileWidget, 'file', title=_('File'), required=False) form.add(FileWidget, 'file', title=_('File'), required=False)
form.add(UrlWidget, 'url', title=_('Address'), required=False, size=50) 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')) form.add_submit('cancel', _('Cancel'))
if form.get_submit() == 'cancel': if form.get_submit() == 'cancel':
@ -1878,10 +1884,10 @@ class FormsDirectory(AccessControlled, Directory):
pass pass
get_response().breadcrumb.append(('import', _('Import'))) 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 = TemplateIO(html=True)
r += htmltext('<h2>%s</h2>') % _(self.import_title) r += htmltext('<h2>%s</h2>') % self.import_title
r += htmltext('<p>%s</p>') % _(self.import_paragraph) r += htmltext('<p>%s</p>') % self.import_paragraph
r += form.render() r += form.render()
return r.getvalue() return r.getvalue()
@ -1894,7 +1900,7 @@ class FormsDirectory(AccessControlled, Directory):
try: try:
fp = misc.urlopen(url) fp = misc.urlopen(url)
except misc.ConnectionError as e: 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() raise ValueError()
else: else:
form.set_error('file', _('You have to enter a file or a URL.')) form.set_error('file', _('You have to enter a file or a URL.'))
@ -1904,11 +1910,11 @@ class FormsDirectory(AccessControlled, Directory):
try: try:
try: try:
formdef = self.formdef_class.import_from_xml(fp) 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: except FormdefImportRecoverableError as e:
fp.seek(0) fp.seek(0)
formdef = self.formdef_class.import_from_xml(fp, fix_on_error=True) 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: except FormdefImportError as e:
error = True error = True
reason = _(e.msg) % e.msg_args reason = _(e.msg) % e.msg_args
@ -1936,7 +1942,7 @@ class FormsDirectory(AccessControlled, Directory):
class UpdateDigestAfterJob(AfterJob): class UpdateDigestAfterJob(AfterJob):
label = N_('Updating digests') label = _('Updating digests')
def __init__(self, formdef): def __init__(self, formdef):
super().__init__(formdef_class=formdef.__class__, formdef_id=formdef.id) 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.carddef import CardDef
from wcs.data_sources import NamedDataSource from wcs.data_sources import NamedDataSource
from wcs.formdef import FormDef 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.cfg import cfg_submit
from wcs.qommon.admin.emails import EmailsDirectory from wcs.qommon.admin.emails import EmailsDirectory
from wcs.qommon.admin.logger import LoggerDirectory from wcs.qommon.admin.logger import LoggerDirectory
@ -706,13 +706,13 @@ class SettingsDirectory(QommonSettingsDirectory):
permission_keys = [] permission_keys = []
admin_sections = [ admin_sections = [
('forms', N_('Forms')), ('forms', _('Forms')),
('cards', N_('Card Models')), ('cards', _('Card Models')),
('workflows', N_('Workflows')), ('workflows', _('Workflows')),
('users', N_('Users')), ('users', _('Users')),
('roles', N_('Roles')), ('roles', _('Roles')),
('categories', N_('Categories')), ('categories', _('Categories')),
('settings', N_('Settings')), ('settings', _('Settings')),
] ]
for k, v in admin_sections: for k, v in admin_sections:
if k == 'cards' and not StudioDirectory.is_visible(): if k == 'cards' and not StudioDirectory.is_visible():
@ -1128,10 +1128,13 @@ class SettingsDirectory(QommonSettingsDirectory):
exporter = Exporter(dirs, settings=form.get_widget('settings').parse()) exporter = Exporter(dirs, settings=form.get_widget('settings').parse())
job = get_response().add_after_job(N_('Exporting site settings'), exporter.export) job = get_response().add_after_job(
job.done_action_url = get_request().get_url() + '?download=%s' % job.id _('Exporting site settings'),
job.done_action_label = _('Download Export') exporter.export,
job.done_button_attributes = {'download': 'export.wcs'} done_action_url=get_request().get_url() + '?download=%(job_id)s',
done_action_label=_('Download Export'),
done_button_attributes={'download': 'export.wcs'},
)
job.store() job.store()
return redirect(job.get_processing_url()) return redirect(job.get_processing_url())

View File

@ -19,7 +19,7 @@ from quixote.directory import Directory
from quixote.html import TemplateIO, htmltext from quixote.html import TemplateIO, htmltext
import wcs.qommon.storage as st 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.emails import EmailsDirectory
from wcs.qommon.admin.menu import error_page from wcs.qommon.admin.menu import error_page
from wcs.qommon.backoffice.listing import pagination_links from wcs.qommon.backoffice.listing import pagination_links
@ -537,11 +537,11 @@ class UsersDirectory(Directory):
EmailsDirectory.register( EmailsDirectory.register(
'email_with_token', 'email_with_token',
N_('Identification token'), _('Identification token'),
N_('Available variables: token, token_url, sitename'), _('Available variables: token, token_url, sitename'),
category=N_('Identification'), category=_('Identification'),
default_subject=N_('Access to [sitename]'), default_subject=_('Access to [sitename]'),
default_body=N_( default_body=_(
'''\ '''\
Hello, Hello,

View File

@ -32,7 +32,7 @@ from wcs.backoffice.studio import StudioDirectory
from wcs.carddef import CardDef from wcs.carddef import CardDef
from wcs.formdata import Evolution from wcs.formdata import Evolution
from wcs.formdef import FormDef 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.admin.menu import command_icon
from wcs.qommon.backoffice.menu import html_top from wcs.qommon.backoffice.menu import html_top
from wcs.qommon.form import ( from wcs.qommon.form import (
@ -356,7 +356,7 @@ class WorkflowItemPage(Directory):
if not self.workflow.is_readonly(): if not self.workflow.is_readonly():
submit_label = _('Submit') submit_label = _('Submit')
if hasattr(self.item, 'submit_button_label'): 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('submit', submit_label)
form.add_submit('cancel', _('Cancel')) form.add_submit('cancel', _('Cancel'))
@ -955,7 +955,7 @@ class WorkflowVariablesFieldsDirectory(FieldsDirectory):
support_import = False support_import = False
blacklisted_types = ['page', 'blocks', 'computed'] blacklisted_types = ['page', 'blocks', 'computed']
field_var_prefix = 'form_option_' field_var_prefix = 'form_option_'
readonly_message = N_('This workflow is readonly.') readonly_message = _('This workflow is readonly.')
def index_top(self): def index_top(self):
r = TemplateIO(html=True) r = TemplateIO(html=True)
@ -978,7 +978,7 @@ class WorkflowBackofficeFieldsDirectory(FieldsDirectory):
blacklisted_types = ['page', 'blocks', 'computed'] blacklisted_types = ['page', 'blocks', 'computed']
blacklisted_attributes = ['condition'] blacklisted_attributes = ['condition']
field_var_prefix = 'form_var_' field_var_prefix = 'form_var_'
readonly_message = N_('This workflow is readonly.') readonly_message = _('This workflow is readonly.')
def index_top(self): def index_top(self):
r = TemplateIO(html=True) r = TemplateIO(html=True)
@ -1746,11 +1746,14 @@ class WorkflowPage(Directory):
def duplicate(self): def duplicate(self):
self.workflow_ui.workflow.id = None self.workflow_ui.workflow.id = None
original_name = self.workflow_ui.workflow.name 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()] workflow_names = [x.name for x in Workflow.select()]
no = 2 no = 2
while self.workflow_ui.workflow.name in workflow_names: 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 no += 1
self.workflow_ui.workflow.store() self.workflow_ui.workflow.store()
return redirect('../%s/' % self.workflow_ui.workflow.id) 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): if expression and re.match(r'^=.*\[[a-zA-Z_]\w*\]', expression):
hint['klass'] = 'warning' hint['klass'] = 'warning'
hint['msg'] = _('Make sure you want a Python expression, not a simple template string.') 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): def validate_condition(request, *args, **kwargs):
@ -1274,7 +1274,7 @@ def validate_condition(request, *args, **kwargs):
except ValidationError as e: except ValidationError as e:
hint['klass'] = 'error' hint['klass'] = 'error'
hint['msg'] = str(e) hint['msg'] = str(e)
return HttpResponse(json.dumps(hint), content_type='application/json') return JsonResponse(hint)
def provisionning(request): def provisionning(request):

View File

@ -28,7 +28,7 @@ from wcs.carddef import CardDef
from wcs.categories import CardDefCategory from wcs.categories import CardDefCategory
from wcs.workflows import Workflow from wcs.workflows import Workflow
from ..qommon import N_, _ from ..qommon import _
from ..qommon.misc import C_ from ..qommon.misc import C_
from ..qommon.storage import NotEqual, Null from ..qommon.storage import NotEqual, Null
@ -40,7 +40,7 @@ class CardDefUI(FormDefUI):
class CardDefOptionsDirectory(OptionsDirectory): class CardDefOptionsDirectory(OptionsDirectory):
category_class = CardDefCategory 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): class CardDefPage(FormDefPage):
@ -51,12 +51,12 @@ class CardDefPage(FormDefPage):
options_directory_class = CardDefOptionsDirectory options_directory_class = CardDefOptionsDirectory
delete_message = N_('You are about to irrevocably delete this card model.') delete_message = _('You are about to irrevocably delete this card model.')
delete_title = N_('Deleting Card Model:') delete_title = _('Deleting Card Model:')
overwrite_message = N_( overwrite_message = _(
'You can replace this card model by uploading a file ' 'or by pointing to a form URL.' '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. ' 'The card model has been successfully overwritten. '
'Do note it kept its existing address and role and workflow parameters.' 'Do note it kept its existing address and role and workflow parameters.'
) )
@ -250,15 +250,15 @@ class CardsDirectory(FormsDirectory):
formdef_page_class = CardDefPage formdef_page_class = CardDefPage
formdef_ui_class = CardDefUI formdef_ui_class = CardDefUI
top_title = N_('Card Models') top_title = _('Card Models')
import_title = N_('Import Card Model') import_title = _('Import Card Model')
import_submit_label = N_('Import Card Model') import_submit_label = _('Import Card Model')
import_paragraph = N_( import_paragraph = _(
'You can install a new card model by uploading a file ' 'or by pointing to the card model URL.' '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_loading_error_message = _('Error loading card model (%s).')
import_success_message = N_('This card model has been successfully imported. ') import_success_message = _('This card model has been successfully imported. ')
import_error_message = N_( import_error_message = _(
'Imported card model contained errors and has been automatically fixed, ' 'Imported card model contained errors and has been automatically fixed, '
'you should nevertheless check everything is ok. ' 'you should nevertheless check everything is ok. '
) )

View File

@ -26,7 +26,7 @@ from wcs import fields
from wcs.carddef import CardDef from wcs.carddef import CardDef
from wcs.categories import CardDefCategory from wcs.categories import CardDefCategory
from ..qommon import N_, _, errors, template from ..qommon import _, errors, template
from ..qommon.afterjobs import AfterJob from ..qommon.afterjobs import AfterJob
from ..qommon.backoffice.menu import html_top from ..qommon.backoffice.menu import html_top
from ..qommon.form import FileWidget, Form from ..qommon.form import FileWidget, Form
@ -99,7 +99,7 @@ class CardPage(FormPage):
] ]
admin_permission = 'cards' admin_permission = 'cards'
formdef_class = CardDef formdef_class = CardDef
search_label = N_('Search in card content') search_label = _('Search in card content')
@property @property
def add(self): def add(self):
@ -331,8 +331,8 @@ class CardFillPage(FormFillPage):
class CardBackOfficeStatusPage(FormBackOfficeStatusPage): class CardBackOfficeStatusPage(FormBackOfficeStatusPage):
form_page_class = CardFillPage form_page_class = CardFillPage
sidebar_recorded_message = N_('The card has been recorded on %(date)s with the number %(number)s.') sidebar_recorded_message = _('The card has been recorded on %(date)s with the number %(number)s.')
sidebar_recorded_by_agent_message = N_( sidebar_recorded_by_agent_message = _(
'The card has been recorded on %(date)s with the number %(number)s by %(agent)s.' '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.variables import LazyFieldVar
from wcs.workflows import WorkflowStatusItem, template_on_formdata 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.emails import EmailsDirectory
from ..qommon.admin.menu import command_icon from ..qommon.admin.menu import command_icon
from ..qommon.afterjobs import AfterJob from ..qommon.afterjobs import AfterJob
@ -127,7 +127,7 @@ def geojson_formdatas(formdatas, geoloc_key='base', fields=None):
'url': formdata_backoffice_url, 'url': formdata_backoffice_url,
'status_name': str(htmlescape(status.name)), 'status_name': str(htmlescape(status.name)),
'status_colour': '#%s' % status_colour, 'status_colour': '#%s' % status_colour,
'view_label': _('View'), 'view_label': force_text(_('View')),
}, },
'geometry': { 'geometry': {
'type': 'Point', 'type': 'Point',
@ -1060,7 +1060,7 @@ class ManagementDirectory(Directory):
FakeField('status', 'status', _('Status')), FakeField('status', 'status', _('Status')),
] ]
get_response().set_content_type('application/json') 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): def map(self):
if not get_publisher().is_using_postgresql(): if not get_publisher().is_using_postgresql():
@ -1107,7 +1107,7 @@ class FormPage(Directory):
use_default_view = False use_default_view = False
admin_permission = 'forms' admin_permission = 'forms'
formdef_class = FormDef formdef_class = FormDef
search_label = N_('Search in form content') search_label = _('Search in form content')
WCS_SYNC_EXPORT_LIMIT = 100 # Arbitrary threshold WCS_SYNC_EXPORT_LIMIT = 100 # Arbitrary threshold
def __init__(self, component=None, formdef=None, view=None, update_breadcrumbs=True): 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 r += htmltext('<input type="hidden" name="order_by" value="%s"/>') % order_by
if get_publisher().is_using_postgresql(): 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'): if get_request().form.get('q'):
q = force_text(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) 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 fields, selected_filter, user=user, query=query, criterias=criterias
)[0] )[0]
return json.dumps(geojson_formdatas(items, fields=fields)) return json.dumps(geojson_formdatas(items, fields=fields), cls=misc.JSONEncoder)
def ics(self): def ics(self):
if get_request().has_anonymised_data_api_restriction(): if get_request().has_anonymised_data_api_restriction():
@ -2716,8 +2716,8 @@ class FormBackOfficeStatusPage(FormStatusPage):
] ]
form_page_class = FormFillPage form_page_class = FormFillPage
sidebar_recorded_message = N_('The form has been recorded on %(date)s with the number %(number)s.') sidebar_recorded_message = _('The form has been recorded on %(date)s with the number %(number)s.')
sidebar_recorded_by_agent_message = N_( sidebar_recorded_by_agent_message = _(
'The form has been recorded on %(date)s with the number %(number)s by %(agent)s.' '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) agent_user = get_publisher().user_class.get(formdata.submission_agent_id, ignore_errors=True)
if agent_user: if agent_user:
r += _(self.sidebar_recorded_by_agent_message) % { r += self.sidebar_recorded_by_agent_message % {
'date': tm, 'date': tm,
'number': formdata.get_display_id(), 'number': formdata.get_display_id(),
'agent': agent_user.get_display_name(), 'agent': agent_user.get_display_name(),
} }
else: 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>') r += htmltext('</p>')
try: try:
status_colour = formdata.get_status().colour status_colour = formdata.get_status().colour
@ -3441,7 +3441,7 @@ class FakeField:
def __init__(self, id, type_, label, addable=True): def __init__(self, id, type_, label, addable=True):
self.id = id self.id = id
self.type = type_ self.type = type_
self.label = label self.label = force_text(label)
self.fake = True self.fake = True
self.varname = id.replace('-', '_') self.varname = id.replace('-', '_')
self.store_display_value = None self.store_display_value = None
@ -3545,10 +3545,10 @@ var month_line = %(month_line)s;
var year_line = %(year_line)s; var year_line = %(year_line)s;
</script>''' </script>'''
% { % {
'weekday_line': json.dumps(weekday_totals), 'weekday_line': json.dumps(weekday_totals, cls=misc.JSONEncoder),
'hour_line': json.dumps(hour_totals), 'hour_line': json.dumps(hour_totals, cls=misc.JSONEncoder),
'month_line': json.dumps(monthly_totals), 'month_line': json.dumps(monthly_totals, cls=misc.JSONEncoder),
'year_line': json.dumps(yearly_totals), 'year_line': json.dumps(yearly_totals, cls=misc.JSONEncoder),
} }
) )
@ -3735,7 +3735,7 @@ class MassActionAfterJob(AfterJob):
class CsvExportAfterJob(AfterJob): class CsvExportAfterJob(AfterJob):
label = N_('Exporting forms in CSV') label = _('Exporting to CSV file')
def __init__(self, formdef, **kwargs): def __init__(self, formdef, **kwargs):
super().__init__(formdef_class=formdef.__class__, formdef_id=formdef.id, **kwargs) super().__init__(formdef_class=formdef.__class__, formdef_id=formdef.id, **kwargs)
@ -3799,6 +3799,8 @@ class CsvExportAfterJob(AfterJob):
class OdsExportAfterJob(CsvExportAfterJob): class OdsExportAfterJob(CsvExportAfterJob):
label = _('Exporting to ODS file')
def __init__(self, formdef, **kwargs): def __init__(self, formdef, **kwargs):
super().__init__(formdef=formdef, **kwargs) super().__init__(formdef=formdef, **kwargs)
self.file_name = '%s.ods' % formdef.url_name self.file_name = '%s.ods' % formdef.url_name

View File

@ -28,7 +28,7 @@ import wcs.admin.users
import wcs.admin.workflows import wcs.admin.workflows
from wcs.formdef import FormDef 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.afterjobs import AfterJob
from ..qommon.backoffice import BackofficeRootDirectory from ..qommon.backoffice import BackofficeRootDirectory
from ..qommon.backoffice.menu import html_top from ..qommon.backoffice.menu import html_top
@ -54,16 +54,16 @@ class RootDirectory(BackofficeRootDirectory):
submission = SubmissionDirectory() submission = SubmissionDirectory()
menu_items = [ menu_items = [
('submission/', N_('Submission')), ('submission/', _('Submission')),
('management/', N_('Management')), ('management/', _('Management')),
('data/', N_('Cards'), {'check_display_function': studio.is_visible}), ('data/', _('Cards'), {'check_display_function': studio.is_visible}),
('studio/', N_('Studio'), {'check_display_function': studio.is_visible}), ('studio/', _('Studio'), {'check_display_function': studio.is_visible}),
('forms/', N_('Forms Workshop'), {'sub': True}), ('forms/', _('Forms Workshop'), {'sub': True}),
('cards/', N_('Card Models'), {'sub': True, 'check_display_function': studio.is_visible}), ('cards/', _('Card Models'), {'sub': True, 'check_display_function': studio.is_visible}),
('workflows/', N_('Workflows Workshop'), {'sub': True}), ('workflows/', _('Workflows Workshop'), {'sub': True}),
('users/', N_('Users'), {'check_display_function': roles.is_visible}), ('users/', _('Users'), {'check_display_function': roles.is_visible}),
('roles/', N_('Roles'), {'check_display_function': roles.is_visible}), ('roles/', _('Roles'), {'check_display_function': roles.is_visible}),
('settings/', N_('Settings')), ('settings/', _('Settings')),
] ]
def _q_traverse(self, path): 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 quixote.html import htmltag, htmltext
from . import data_sources, fields from . import data_sources, fields
from .qommon import N_, _, misc from .qommon import _, misc
from .qommon.form import CompositeWidget, WidgetList from .qommon.form import CompositeWidget, WidgetList
from .qommon.storage import StorableObject from .qommon.storage import StorableObject
from .qommon.template import Template from .qommon.template import Template
@ -136,7 +136,7 @@ class BlockDef(StorableObject):
unknown_datasources.add(data_source.get('type')) unknown_datasources.add(data_source.get('type'))
if unknown_datasources: if unknown_datasources:
raise BlockdefImportError( raise BlockdefImportError(
N_('Unknown datasources'), details=', '.join(sorted(unknown_datasources)) _('Unknown datasources'), details=', '.join(sorted(unknown_datasources))
) )
return blockdef return blockdef
@ -146,7 +146,7 @@ class BlockDef(StorableObject):
charset = 'utf-8' charset = 'utf-8'
blockdef = cls() blockdef = cls()
if tree.find('name') is None or not tree.find('name').text: 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 # if the tree we get is actually a ElementTree for real, we get its
# root element and go on happily. # root element and go on happily.
@ -154,7 +154,7 @@ class BlockDef(StorableObject):
tree = tree.getroot() tree = tree.getroot()
if tree.tag != cls.xml_root_node: 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'): if include_id and tree.attrib.get('id'):
blockdef.id = tree.attrib.get('id') blockdef.id = tree.attrib.get('id')
@ -169,7 +169,7 @@ class BlockDef(StorableObject):
try: try:
field_o = fields.get_field_class_by_type(field.findtext('type'))() field_o = fields.get_field_class_by_type(field.findtext('type'))()
except KeyError: 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) field_o.init_with_xml(field, charset, include_id=True)
blockdef.fields.append(field_o) blockdef.fields.append(field_o)

View File

@ -23,7 +23,7 @@ from wcs.carddata import CardData
from wcs.categories import CardDefCategory from wcs.categories import CardDefCategory
from wcs.formdef import FormDef, get_formdefs_of_all_kinds 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.storage import Equal, ILike, NotEqual
from .qommon.template import Template from .qommon.template import Template
@ -36,8 +36,8 @@ class CardDef(FormDef):
data_sql_prefix = 'carddata' data_sql_prefix = 'carddata'
pickle_module_name = 'carddef' pickle_module_name = 'carddef'
xml_root_node = 'carddef' xml_root_node = 'carddef'
verbose_name = N_('Card model') verbose_name = _('Card model')
verbose_name_plural = N_('Card models') verbose_name_plural = _('Card models')
confirmation = False confirmation = False
@ -93,19 +93,19 @@ class CardDef(FormDef):
from wcs.wf.remove import RemoveWorkflowStatusItem from wcs.wf.remove import RemoveWorkflowStatusItem
from wcs.workflows import ChoiceWorkflowStatusItem, EditableWorkflowStatusItem, Workflow from wcs.workflows import ChoiceWorkflowStatusItem, EditableWorkflowStatusItem, Workflow
workflow = Workflow(name=_('Default (cards)')) workflow = Workflow(name=force_text(_('Default (cards)')))
workflow.id = '_carddef_default' workflow.id = '_carddef_default'
workflow.roles = { workflow.roles = {
'_viewer': _('Viewer'), '_viewer': force_text(_('Viewer')),
'_editor': _('Editor'), '_editor': force_text(_('Editor')),
} }
status = workflow.add_status(_('Recorded'), 'recorded') status = workflow.add_status(force_text(_('Recorded')), 'recorded')
deleted_status = workflow.add_status(_('Deleted'), 'deleted') deleted_status = workflow.add_status(force_text(_('Deleted')), 'deleted')
editable = EditableWorkflowStatusItem() editable = EditableWorkflowStatusItem()
editable.id = '_editable' editable.id = '_editable'
editable.by = ['_editor'] editable.by = ['_editor']
editable.label = _('Edit Card') editable.label = force_text(_('Edit Card'))
editable.status = status.id editable.status = status.id
editable.parent = status editable.parent = status
status.items.append(editable) status.items.append(editable)
@ -113,7 +113,7 @@ class CardDef(FormDef):
action_delete = ChoiceWorkflowStatusItem() action_delete = ChoiceWorkflowStatusItem()
action_delete.id = '_action_delete' action_delete.id = '_action_delete'
action_delete.by = ['_editor'] action_delete.by = ['_editor']
action_delete.label = _('Delete Card') action_delete.label = force_text(_('Delete Card'))
action_delete.status = deleted_status.id action_delete.status = deleted_status.id
action_delete.require_confirmation = True action_delete.require_confirmation = True
action_delete.parent = status action_delete.parent = status

View File

@ -17,7 +17,7 @@
from quixote import get_publisher from quixote import get_publisher
from quixote.html import htmltext from quixote.html import htmltext
from .qommon import N_ from .qommon import _
from .qommon.misc import simplify from .qommon.misc import simplify
from .qommon.storage import StorableObject from .qommon.storage import StorableObject
from .qommon.substitution import Substitutions from .qommon.substitution import Substitutions
@ -128,6 +128,6 @@ class CardDefCategory(Category):
XML_NODES = [('name', 'str'), ('url_name', 'str'), ('description', 'str'), ('position', 'int')] 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_name', category=_('General'), comment=_('Category Name'))
Substitutions.register('category_description', category=N_('General'), comment=N_('Category Description')) Substitutions.register('category_description', category=_('General'), comment=_('Category Description'))
Substitutions.register('category_id', category=N_('General'), comment=N_('Category Identifier')) 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 quixote.html import TemplateIO
from .api_utils import sign_url_auto_orig 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.afterjobs import AfterJob
from .qommon.cron import CronJob from .qommon.cron import CronJob
from .qommon.form import CompositeWidget, OptGroup, SingleSelectWidget, StringWidget from .qommon.form import CompositeWidget, OptGroup, SingleSelectWidget, StringWidget
@ -916,7 +916,7 @@ def build_agenda_datasources(publisher):
class RefreshAgendas(AfterJob): class RefreshAgendas(AfterJob):
label = N_('Refreshing agendas') label = _('Refreshing agendas')
def execute(self): def execute(self):
build_agenda_datasources(get_publisher()) build_agenda_datasources(get_publisher())

View File

@ -201,7 +201,7 @@ class PrefillSelectionWidget(CompositeWidget):
attrs={ attrs={
'data-dynamic-display-child-of': 'prefill$type', 'data-dynamic-display-child-of': 'prefill$type',
'data-dynamic-display-value-in': '|'.join( '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'), 'inline_title': _('Locked'),
}, },
@ -268,7 +268,7 @@ class Field:
pass pass
def get_type_label(self): def get_type_label(self):
return _(self.description) return self.description
@property @property
def include_in_listing(self): def include_in_listing(self):
@ -822,7 +822,7 @@ def register_field_class(klass):
class TitleField(Field): class TitleField(Field):
key = 'title' key = 'title'
description = N_('Title') description = _('Title')
html_tag = 'h3' html_tag = 'h3'
display_locations = ['validation', 'summary'] display_locations = ['validation', 'summary']
@ -885,7 +885,7 @@ register_field_class(TitleField)
class SubtitleField(TitleField): class SubtitleField(TitleField):
key = 'subtitle' key = 'subtitle'
description = N_('Subtitle') description = _('Subtitle')
html_tag = 'h4' html_tag = 'h4'
@ -894,7 +894,7 @@ register_field_class(SubtitleField)
class CommentField(Field): class CommentField(Field):
key = 'comment' key = 'comment'
description = N_('Comment') description = _('Comment')
display_locations = [] display_locations = []
def get_text(self): def get_text(self):
@ -995,7 +995,7 @@ def is_datasource_advanced(value):
class StringField(WidgetField): class StringField(WidgetField):
key = 'string' key = 'string'
description = N_('Text (line)') description = _('Text (line)')
widget_class = WcsExtraStringWidget widget_class = WcsExtraStringWidget
size = None size = None
@ -1091,7 +1091,7 @@ register_field_class(StringField)
class TextField(WidgetField): class TextField(WidgetField):
key = 'text' key = 'text'
description = N_('Long Text') description = _('Long Text')
widget_class = TextWidget widget_class = TextWidget
cols = None cols = None
@ -1156,7 +1156,7 @@ register_field_class(TextField)
class EmailField(WidgetField): class EmailField(WidgetField):
key = 'email' key = 'email'
description = N_('Email') description = _('Email')
widget_class = EmailWidget widget_class = EmailWidget
@ -1181,7 +1181,7 @@ register_field_class(EmailField)
class BoolField(WidgetField): class BoolField(WidgetField):
key = 'bool' key = 'bool'
description = N_('Check Box (single choice)') description = _('Check Box (single choice)')
allow_complex = True allow_complex = True
widget_class = CheckboxWidget widget_class = CheckboxWidget
@ -1202,9 +1202,9 @@ class BoolField(WidgetField):
def get_view_value(self, value, **kwargs): def get_view_value(self, value, **kwargs):
if value is True or value == 'True': if value is True or value == 'True':
return _('Yes') return str(_('Yes'))
elif value is False or value == 'False': elif value is False or value == 'False':
return _('No') return str(_('No'))
else: else:
return '' return ''
@ -1278,7 +1278,7 @@ register_field_class(BoolField)
class FileField(WidgetField): class FileField(WidgetField):
key = 'file' key = 'file'
description = N_('File Upload') description = _('File Upload')
allow_complex = True allow_complex = True
document_type = None document_type = None
@ -1497,7 +1497,7 @@ class FileField(WidgetField):
# self.file_type is a combination of file type, we create a # self.file_type is a combination of file type, we create a
# virtual one from them # virtual one from them
if parts and len(parts) > 1: if parts and len(parts) > 1:
label = ', '.join(parts) label = ', '.join([str(x) for x in parts])
else: else:
label = ','.join(file_type) label = ','.join(file_type)
self.document_type = { self.document_type = {
@ -1534,7 +1534,7 @@ register_field_class(FileField)
class DateField(WidgetField): class DateField(WidgetField):
key = 'date' key = 'date'
description = N_('Date') description = _('Date')
widget_class = DateWidget widget_class = DateWidget
minimum_date = None minimum_date = None
@ -1807,7 +1807,7 @@ class ItemFieldMixin:
class ItemField(WidgetField, MapOptionsMixin, ItemFieldMixin): class ItemField(WidgetField, MapOptionsMixin, ItemFieldMixin):
key = 'item' key = 'item'
description = N_('List') description = _('List')
allow_complex = True allow_complex = True
items = [] items = []
@ -2163,7 +2163,7 @@ register_field_class(ItemField)
class ItemsField(WidgetField, ItemFieldMixin): class ItemsField(WidgetField, ItemFieldMixin):
key = 'items' key = 'items'
description = N_('Multiple choice list') description = _('Multiple choice list')
allow_complex = True allow_complex = True
items = [] items = []
@ -2501,7 +2501,7 @@ class PageCondition(Condition):
class PageField(Field): class PageField(Field):
key = 'page' key = 'page'
description = N_('Page') description = _('Page')
post_conditions = None post_conditions = None
@ -2595,7 +2595,7 @@ register_field_class(PageField)
class TableField(WidgetField): class TableField(WidgetField):
key = 'table' key = 'table'
description = N_('Table') description = _('Table')
allow_complex = True allow_complex = True
rows = None rows = None
@ -2764,7 +2764,7 @@ register_field_class(TableField)
class TableSelectField(TableField): class TableSelectField(TableField):
key = 'table-select' key = 'table-select'
description = N_('Table of Lists') description = _('Table of Lists')
allow_complex = True allow_complex = True
items = None items = None
@ -2813,7 +2813,7 @@ register_field_class(TableSelectField)
class TableRowsField(WidgetField): class TableRowsField(WidgetField):
key = 'tablerows' key = 'tablerows'
description = N_('Table with rows') description = _('Table with rows')
allow_complex = True allow_complex = True
total_row = True total_row = True
@ -2956,7 +2956,7 @@ register_field_class(TableRowsField)
class MapField(WidgetField, MapOptionsMixin): class MapField(WidgetField, MapOptionsMixin):
key = 'map' key = 'map'
description = N_('Map') description = _('Map')
default_position = None default_position = None
init_with_geoloc = False init_with_geoloc = False
@ -3054,7 +3054,7 @@ register_field_class(MapField)
class RankedItemsField(WidgetField): class RankedItemsField(WidgetField):
key = 'ranked-items' key = 'ranked-items'
description = N_('Ranked Items') description = _('Ranked Items')
allow_complex = True allow_complex = True
items = [] items = []
@ -3141,7 +3141,7 @@ register_field_class(RankedItemsField)
class PasswordField(WidgetField): class PasswordField(WidgetField):
key = 'password' key = 'password'
description = N_('Password') description = _('Password')
min_length = 0 min_length = 0
max_length = 0 max_length = 0
@ -3465,13 +3465,13 @@ def get_field_options(blacklisted_types):
if klass.key in blacklisted_types: if klass.key in blacklisted_types:
continue continue
if issubclass(klass, WidgetField): if issubclass(klass, WidgetField):
widgets.append((klass.key, _(klass.description), klass.key)) widgets.append((klass.key, klass.description, klass.key))
else: else:
non_widgets.append((klass.key, _(klass.description), klass.key)) non_widgets.append((klass.key, klass.description, klass.key))
options = widgets + [('', '', '')] + non_widgets options = widgets + [('', '', '')] + non_widgets
# add computed field in its own "section" # 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 ( if get_publisher().has_site_option('fields-blocks') and (
not blacklisted_types or 'blocks' not in blacklisted_types 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 import get_publisher, get_request, get_session
from quixote.http_request import Upload from quixote.http_request import Upload
from .qommon import N_, _, misc from .qommon import _, misc
from .qommon.evalutils import make_datetime from .qommon.evalutils import make_datetime
from .qommon.publisher import get_cfg from .qommon.publisher import get_cfg
from .qommon.storage import Contains, Intersects, Null, StorableObject from .qommon.storage import Contains, Intersects, Null, StorableObject
@ -1421,14 +1421,14 @@ class FormData(StorableObject):
self.__dict__ = dict self.__dict__ = dict
Substitutions.register('form_receipt_date', category=N_('Form'), comment=N_('Form Receipt Date')) Substitutions.register('form_receipt_date', category=_('Form'), comment=_('Form Receipt Date'))
Substitutions.register('form_receipt_time', category=N_('Form'), comment=N_('Form Receipt Time')) Substitutions.register('form_receipt_time', category=_('Form'), comment=_('Form Receipt Time'))
Substitutions.register('form_number', category=N_('Form'), comment=N_('Form Number')) Substitutions.register('form_number', category=_('Form'), comment=_('Form Number'))
Substitutions.register('form_details', category=N_('Form'), comment=N_('Form Details')) Substitutions.register('form_details', category=_('Form'), comment=_('Form Details'))
Substitutions.register('form_url', category=N_('Form'), comment=N_('Form URL')) Substitutions.register('form_url', category=_('Form'), comment=_('Form URL'))
Substitutions.register('form_url_backoffice', category=N_('Form'), comment=N_('Form URL (backoffice)')) Substitutions.register('form_url_backoffice', category=_('Form'), comment=_('Form URL (backoffice)'))
Substitutions.register('form_status_url', category=N_('Form'), comment=N_('Form Status URL')) Substitutions.register('form_status_url', category=_('Form'), comment=_('Form Status URL'))
Substitutions.register('form_tracking_code', category=N_('Form'), comment=N_('Form Tracking Code')) Substitutions.register('form_tracking_code', category=_('Form'), comment=_('Form Tracking Code'))
Substitutions.register('form_user_display_name', category=N_('Form'), comment=N_('Form Submitter Name')) Substitutions.register('form_user_display_name', category=_('Form'), comment=_('Form Submitter Name'))
Substitutions.register('form_user_email', category=N_('Form'), comment=N_('Form Submitter Email')) Substitutions.register('form_user_email', category=_('Form'), comment=_('Form Submitter Email'))
Substitutions.register_dynamic_source(FormData) Substitutions.register_dynamic_source(FormData)

View File

@ -35,7 +35,7 @@ from quixote.http_request import Upload
from . import data_sources, fields from . import data_sources, fields
from .categories import Category from .categories import Category
from .formdata import FormData 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.admin.emails import EmailsDirectory
from .qommon.cron import CronJob from .qommon.cron import CronJob
from .qommon.form import Form, HtmlWidget, UploadedFile from .qommon.form import Form, HtmlWidget, UploadedFile
@ -84,8 +84,8 @@ class FormDef(StorableObject):
data_sql_prefix = 'formdata' data_sql_prefix = 'formdata'
pickle_module_name = 'formdef' pickle_module_name = 'formdef'
xml_root_node = 'formdef' xml_root_node = 'formdef'
verbose_name = N_('Form') verbose_name = _('Form')
verbose_name_plural = N_('Forms') verbose_name_plural = _('Forms')
name = None name = None
description = None description = None
@ -916,7 +916,7 @@ class FormDef(StorableObject):
try: try:
field_o = fields.get_field_class_by_type(field.get('type'))() field_o = fields.get_field_class_by_type(field.get('type'))()
except KeyError: 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) field_o.init_with_json(field, include_id=True)
if not field_o.id: if not field_o.id:
# this assumes all fields will have id, or none of them # this assumes all fields will have id, or none of them
@ -1100,7 +1100,7 @@ class FormDef(StorableObject):
known_field_ids = set() known_field_ids = set()
for field in formdef.fields: for field in formdef.fields:
if field.id in known_field_ids: if field.id in known_field_ids:
raise FormdefImportRecoverableError(N_('Duplicated field identifiers')) raise FormdefImportRecoverableError(_('Duplicated field identifiers'))
known_field_ids.add(field.id) known_field_ids.add(field.id)
return formdef return formdef
@ -1116,7 +1116,7 @@ class FormDef(StorableObject):
assert charset == 'utf-8' assert charset == 'utf-8'
formdef = cls() formdef = cls()
if tree.find('name') is None or not tree.find('name').text: 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 # if the tree we get is actually a ElementTree for real, we get its
# root element and go on happily. # root element and go on happily.
@ -1124,7 +1124,7 @@ class FormDef(StorableObject):
tree = tree.getroot() tree = tree.getroot()
if tree.tag != cls.xml_root_node: 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'): if include_id and tree.attrib.get('id'):
formdef.id = tree.attrib.get('id') formdef.id = tree.attrib.get('id')
@ -1145,7 +1145,7 @@ class FormDef(StorableObject):
try: try:
field_o = fields.get_field_class_by_type(field.findtext('type'))() field_o = fields.get_field_class_by_type(field.findtext('type'))()
except KeyError: 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) field_o.init_with_xml(field, charset, include_id=True)
if fix_on_error or not field_o.id: if fix_on_error or not field_o.id:
# this assumes all fields will have id, or none of them # this assumes all fields will have id, or none of them
@ -1299,7 +1299,7 @@ class FormDef(StorableObject):
if unknown_datasources: if unknown_datasources:
raise FormdefImportError( raise FormdefImportError(
N_('Unknown datasources'), details=', '.join(sorted(unknown_datasources)) _('Unknown datasources'), details=', '.join(sorted(unknown_datasources))
) )
return formdef return formdef
@ -1340,7 +1340,7 @@ class FormDef(StorableObject):
else: else:
details.append('%s' % field.get_rst_view_value(value, indent=' ')) details.append('%s' % field.get_rst_view_value(value, indent=' '))
details.append('') details.append('')
return '\n'.join(details) return '\n'.join([str(x) for x in details])
def get_submitter_email(self, formdata): def get_submitter_email(self, formdata):
users_cfg = get_cfg('users', {}) users_cfg = get_cfg('users', {})
@ -1434,7 +1434,7 @@ class FormDef(StorableObject):
details.append(' %s' % formdata.get_status_label()) details.append(' %s' % formdata.get_status_label())
if evo.comment: if evo.comment:
details.append('\n%s\n' % 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): def is_of_concern_for_role_id(self, role_id):
if not self.workflow_roles: if not self.workflow_roles:
@ -1618,11 +1618,11 @@ class FormDef(StorableObject):
EmailsDirectory.register( EmailsDirectory.register(
'new_user', 'new_user',
N_('Notification of creation to user'), _('Notification of creation to user'),
enabled=False, enabled=False,
category=N_('Workflow'), category=_('Workflow'),
default_subject=N_('New form ({{ form_name }})'), default_subject=_('New form ({{ form_name }})'),
default_body=N_( default_body=_(
'''\ '''\
Hello, Hello,
@ -1642,10 +1642,10 @@ For reference, here are the details:
EmailsDirectory.register( EmailsDirectory.register(
'change_user', 'change_user',
N_('Notification of change to user'), _('Notification of change to user'),
category=N_('Workflow'), category=_('Workflow'),
default_subject=N_('Form status change ({{ form_name }})'), default_subject=_('Form status change ({{ form_name }})'),
default_body=N_( default_body=_(
'''\ '''\
Hello, Hello,
@ -1669,11 +1669,11 @@ You can consult it with this link: {{ form_url }}
EmailsDirectory.register( EmailsDirectory.register(
'new_receiver', 'new_receiver',
N_('Notification of creation to receiver'), _('Notification of creation to receiver'),
enabled=False, enabled=False,
category=N_('Workflow'), category=_('Workflow'),
default_subject=N_('New form ({{ form_name }})'), default_subject=_('New form ({{ form_name }})'),
default_body=N_( default_body=_(
'''\ '''\
Hello, Hello,
@ -1692,10 +1692,10 @@ For reference, here are the details:
EmailsDirectory.register( EmailsDirectory.register(
'change_receiver', 'change_receiver',
N_('Notification of change to receiver'), _('Notification of change to receiver'),
category=N_('Workflow'), category=_('Workflow'),
default_subject=N_('Form status change ({{ form_name }})'), default_subject=_('Form status change ({{ form_name }})'),
default_body=N_( default_body=_(
'''\ '''\
Hello, 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): def clean_drafts(publisher):

View File

@ -22,14 +22,14 @@ from wcs.formdef import FormDef
from wcs.forms.common import FormTemplateMixin from wcs.forms.common import FormTemplateMixin
from wcs.wf.jump import jump_and_perform 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 from ..qommon.form import Form
class MissingOrExpiredToken(PublishError): class MissingOrExpiredToken(PublishError):
status_code = 404 status_code = 404
title = N_('Error') title = _('Error')
description = N_('This action link has already been used or has expired.') description = _('This action link has already been used or has expired.')
class ActionsDirectory(Directory): class ActionsDirectory(Directory):

View File

@ -44,7 +44,7 @@ from wcs.roles import logged_users_role
from wcs.variables import LazyFormDef from wcs.variables import LazyFormDef
from wcs.workflows import Workflow, WorkflowBackofficeFieldsFormDef, WorkflowStatusItem 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.admin.emails import EmailsDirectory
from ..qommon.form import CheckboxWidget, EmailWidget, Form, HiddenErrorWidget, HtmlWidget, StringWidget from ..qommon.form import CheckboxWidget, EmailWidget, Form, HiddenErrorWidget, HtmlWidget, StringWidget
from ..qommon.template import TemplateError 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( TextsDirectory.register(
'captcha-page', 'captcha-page',
N_('Explanation text before the CAPTCHA'), _('Explanation text before the CAPTCHA'),
default=N_( default=_(
'''<h3>Verification</h3> '''<h3>Verification</h3>
<p> <p>
@ -1981,44 +1981,44 @@ In order to submit the form you need to complete this simple question.
TextsDirectory.register( TextsDirectory.register(
'form-recorded', 'form-recorded',
N_('Message when a form has been recorded'), _('Message when a form has been recorded'),
category=N_('Forms'), category=_('Forms'),
default=N_( default=_('The form has been recorded on {{ form_receipt_datetime }} with the number {{ form_number }}.'),
'The form has been recorded on {{ form_receipt_datetime }} with the number {{ form_number }}.'
),
) )
TextsDirectory.register( TextsDirectory.register(
'form-recorded-allow-one', 'form-recorded-allow-one',
N_('Message when a form has been recorded, and the form is set to only allow one per user'), _('Message when a form has been recorded, and the form is set to only allow one per user'),
category=N_('Forms'), category=_('Forms'),
default=N_('The form has been recorded on {{ form_receipt_datetime }}.'), default=_('The form has been recorded on {{ form_receipt_datetime }}.'),
) )
TextsDirectory.register( TextsDirectory.register(
'check-before-submit', 'check-before-submit',
N_('Message when a form is displayed before validation'), _('Message when a form is displayed before validation'),
category=N_('Forms'), category=_('Forms'),
default=N_('Check values then click submit.'), default=_('Check values then click submit.'),
) )
TextsDirectory.register( TextsDirectory.register(
'tracking-code-email-dialog', 'tracking-code-email-dialog',
N_('Message in tracking code popup dialog'), _('Message in tracking code popup dialog'),
category=N_('Forms'), category=_('Forms'),
default=N_('You can get a reminder of the tracking code by email.'), default=_('You can get a reminder of the tracking code by email.'),
) )
TextsDirectory.register( 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( EmailsDirectory.register(
'tracking-code-reminder', 'tracking-code-reminder',
N_('Tracking Code'), _('Tracking Code'),
category=N_('Miscellaneous'), category=_('Miscellaneous'),
default_subject=N_('Tracking Code reminder'), default_subject=_('Tracking Code reminder'),
default_body=N_( default_body=_(
'''\ '''\
Hello, Hello,

View File

@ -59,7 +59,7 @@ class LoggedError:
exception=None, exception=None,
): ):
error = cls() error = cls()
error.summary = error_summary error.summary = str(error_summary)
error.traceback = plain_error_msg error.traceback = plain_error_msg
error.expression = expression error.expression = expression
error.expression_type = expression_type 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 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 from .qommon.misc import http_post_request, json_loads, urlopen
@ -84,7 +84,7 @@ def push_document(user, filename, stream):
if get_response(): if get_response():
get_response().add_after_job( 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}, % {'filename': filename, 'user_name': user.display_name},
afterjob, afterjob,
) )

View File

@ -21,6 +21,7 @@ import threading
import django.apps import django.apps
from django.conf import settings from django.conf import settings
from django.utils.encoding import force_text from django.utils.encoding import force_text
from django.utils.functional import lazy
from quixote import get_publisher from quixote import get_publisher
try: try:
@ -37,7 +38,7 @@ force_str = force_text
PICKLE_KWARGS = {'encoding': 'bytes', 'fix_imports': True} PICKLE_KWARGS = {'encoding': 'bytes', 'fix_imports': True}
def _(message): def gettext(message):
pub = get_publisher() pub = get_publisher()
if pub is None: if pub is None:
return message return message
@ -55,6 +56,8 @@ def N_(x):
return x return x
_ = lazy(gettext, str)
from . import publisher # noqa pylint: disable=wrong-import-position from . import publisher # noqa pylint: disable=wrong-import-position
from .publisher import get_cfg # 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 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', {}) emails_cfg = get_cfg('emails', {})
cfg_key = 'email-%s' % email_key cfg_key = 'email-%s' % email_key
default_subject = cls.emails_dict[email_key].get('default_subject') default_subject = cls.emails_dict[email_key].get('default_subject')
if default_subject: real_subject = emails_cfg.get(cfg_key + '_subject') or str(default_subject)
default_subject = _(default_subject)
real_subject = emails_cfg.get(cfg_key + '_subject') or default_subject
return real_subject return real_subject
@classmethod @classmethod
@ -71,9 +69,7 @@ class EmailsDirectory(Directory):
emails_cfg = get_cfg('emails', {}) emails_cfg = get_cfg('emails', {})
cfg_key = 'email-%s' % email_key cfg_key = 'email-%s' % email_key
default_body = cls.emails_dict[email_key].get('default_body') default_body = cls.emails_dict[email_key].get('default_body')
if default_body: real_body = emails_cfg.get(cfg_key) or str(default_body)
default_body = _(default_body)
real_body = emails_cfg.get(cfg_key) or default_body
return real_body return real_body
def options(self): def options(self):
@ -193,7 +189,7 @@ class EmailsDirectory(Directory):
categories = {} categories = {}
for k, v in self.emails_dict.items(): for k, v in self.emails_dict.items():
if v.get('category'): if v.get('category'):
translated_category = _(v.get('category')) translated_category = v.get('category')
else: else:
translated_category = _('Miscellaneous') translated_category = _('Miscellaneous')
if translated_category not in categories: if translated_category not in categories:
@ -205,11 +201,11 @@ class EmailsDirectory(Directory):
r += htmltext('<h3>%s</h3>') % category_key r += htmltext('<h3>%s</h3>') % category_key
keys = categories.get(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>') r += htmltext('<ul>')
for email_key in keys: for email_key in keys:
email_values = self.emails_dict[email_key] 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('</ul>')
r += htmltext('<p>') r += htmltext('<p>')
@ -221,8 +217,8 @@ class EmailsDirectory(Directory):
emails_cfg = get_cfg('emails', {}) emails_cfg = get_cfg('emails', {})
cfg_key = 'email-%s' % email_key cfg_key = 'email-%s' % email_key
default_subject = _(self.emails_dict[email_key].get('default_subject')) default_subject = self.emails_dict[email_key].get('default_subject')
default_body = _(self.emails_dict[email_key].get('default_body')) default_body = self.emails_dict[email_key].get('default_body')
displayed_subject = emails_cfg.get(cfg_key + '_subject') or default_subject displayed_subject = emails_cfg.get(cfg_key + '_subject') or default_subject
displayed_body = emails_cfg.get(cfg_key) or default_body 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): if check_template and not check_template(template):
return False return False
default_subject = _(self.emails_dict[email_key].get('default_subject')) default_subject = self.emails_dict[email_key].get('default_subject')
default_body = _(self.emails_dict[email_key].get('default_body')) default_body = self.emails_dict[email_key].get('default_body')
if template == default_body: if template == default_body:
template = None template = None
@ -298,8 +294,8 @@ class EmailsDirectory(Directory):
return self.email( return self.email(
component, component,
_(self.emails_dict[component]['description']), self.emails_dict[component]['description'],
_(self.emails_dict[component]['hint']), self.emails_dict[component]['hint'],
self.emails_dict[component]['enabled'], self.emails_dict[component]['enabled'],
) )

View File

@ -19,7 +19,7 @@ import re
from quixote import get_publisher, get_request from quixote import get_publisher, get_request
from quixote.html import TemplateIO, htmltext from quixote.html import TemplateIO, htmltext
from .. import N_, _ from .. import _
from ..backoffice.menu import html_top from ..backoffice.menu import html_top
@ -113,16 +113,16 @@ def get_vc_version():
def command_icon(url, type, label=None, popup=False): def command_icon(url, type, label=None, popup=False):
labels = { labels = {
'edit': N_('Edit'), 'edit': _('Edit'),
'remove': N_('Remove'), 'remove': _('Remove'),
'duplicate': N_('Duplicate'), 'duplicate': _('Duplicate'),
'view': N_('View'), 'view': _('View'),
} }
if label: if label:
klass = 'button' klass = 'button'
else: else:
klass = '' klass = ''
label = _(labels[type]) label = labels[type]
root_url = get_publisher().get_application_static_files_root_url() root_url = get_publisher().get_application_static_files_root_url()
rel = '' rel = ''
if popup: if popup:

View File

@ -41,7 +41,7 @@ class TextsDirectory(Directory):
if os.path.exists(filepath): if os.path.exists(filepath):
return htmltext(open(filepath).read()) return htmltext(open(filepath).read())
return '' return ''
text = _(default) text = str(default) # make sure translation is applied
if not text.startswith('<'): if not text.startswith('<'):
text = '<p>%s</p>' % text text = '<p>%s</p>' % text
@ -79,7 +79,7 @@ class TextsDirectory(Directory):
categories = {} categories = {}
for k, v in self.texts_dict.items(): for k, v in self.texts_dict.items():
if v.get('category'): if v.get('category'):
translated_category = _(v.get('category')) translated_category = v.get('category')
else: else:
translated_category = _('Miscellaneous') translated_category = _('Miscellaneous')
if translated_category not in categories: if translated_category not in categories:
@ -91,11 +91,11 @@ class TextsDirectory(Directory):
r += htmltext('<h3>%s</h3>') % category_key r += htmltext('<h3>%s</h3>') % category_key
keys = categories.get(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>') r += htmltext('<ul>')
for text_key in keys: for text_key in keys:
text_values = self.texts_dict[text_key] 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('</ul>')
r += htmltext('<p>') r += htmltext('<p>')
@ -108,9 +108,7 @@ class TextsDirectory(Directory):
cfg_key = 'text-%s' % text_key cfg_key = 'text-%s' % text_key
default_text = self.texts_dict.get(text_key, {}).get('default') default_text = self.texts_dict.get(text_key, {}).get('default')
if default_text: if not default_text:
default_text = _(default_text)
else:
filepath = os.path.join(get_publisher().DATA_DIR, 'texts', '%s.html' % text_key) filepath = os.path.join(get_publisher().DATA_DIR, 'texts', '%s.html' % text_key)
if os.path.exists(str(filepath)): if os.path.exists(str(filepath)):
default_text = open(str(filepath)).read() default_text = open(str(filepath)).read()
@ -176,7 +174,7 @@ class TextsDirectory(Directory):
return None return None
hint = self.texts_dict[component]['hint'] 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): def _q_traverse(self, path):
get_response().breadcrumb.append(('texts/', _('Texts'))) get_response().breadcrumb.append(('texts/', _('Texts')))

View File

@ -22,7 +22,7 @@ import uuid
from quixote import get_publisher, get_response from quixote import get_publisher, get_response
from quixote.directory import Directory from quixote.directory import Directory
from . import N_, _, errors from . import N_, _, errors, force_text
from .storage import StorableObject from .storage import StorableObject
@ -36,8 +36,8 @@ class AfterJobStatusDirectory(Directory):
response = get_response() response = get_response()
response.set_content_type('text/plain') response.set_content_type('text/plain')
if not job.completion_status: if not job.completion_status:
return job.status + '|' + _(job.status) return job.status + '|' + str(_(job.status))
return job.status + '|' + _(job.status) + ' ' + job.completion_status return job.status + '|' + str(_(job.status)) + ' ' + job.completion_status
class AfterJob(StorableObject): class AfterJob(StorableObject):
@ -55,12 +55,24 @@ class AfterJob(StorableObject):
def __init__(self, label=None, cmd=None, **kwargs): def __init__(self, label=None, cmd=None, **kwargs):
super().__init__(id=str(uuid.uuid4())) super().__init__(id=str(uuid.uuid4()))
if label: 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.creation_time = time.time()
self.job_cmd = cmd self.job_cmd = cmd
self.status = N_('registered') self.status = N_('registered')
self.kwargs = kwargs 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): def run(self, spool=False):
if self.completion_time: if self.completion_time:
return return
@ -94,6 +106,8 @@ class AfterJob(StorableObject):
self.store() self.store()
def __getstate__(self): 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): if not isinstance(self.job_cmd, str):
obj_dict = self.__dict__.copy() obj_dict = self.__dict__.copy()
obj_dict['job_cmd'] = None obj_dict['job_cmd'] = None

View File

@ -21,7 +21,7 @@ from quixote import get_publisher
from quixote.errors import AccessError, TraversalError from quixote.errors import AccessError, TraversalError
from quixote.html import TemplateIO, htmltext from quixote.html import TemplateIO, htmltext
from . import N_, template from . import _, template
class AccessForbiddenError(AccessError): class AccessForbiddenError(AccessError):
@ -59,7 +59,7 @@ class AccessUnauthorizedError(AccessForbiddenError):
return AccessForbiddenError.render(self) return AccessForbiddenError.render(self)
if self.public_msg: 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 = get_publisher().get_root_url() + 'login/'
login_url += '?' + urllib.parse.urlencode({'next': request.get_frontoffice_url()}) login_url += '?' + urllib.parse.urlencode({'next': request.get_frontoffice_url()})
return quixote.redirect(login_url) return quixote.redirect(login_url)
@ -132,8 +132,8 @@ class SMSError(Exception):
pass pass
TraversalError.title = N_('Page not found') TraversalError.title = _('Page not found')
TraversalError.description = N_( TraversalError.description = _(
"The requested link does not exist on this site. If " "The requested link does not exist on this site. If "
"you arrived here by following a link from an external " "you arrived here by following a link from an external "
"page, please inform that page's maintainer." "page, please inform that page's maintainer."
@ -141,9 +141,7 @@ TraversalError.description = N_(
def format_publish_error(exc): def format_publish_error(exc):
from . import _
if getattr(exc, 'public_msg', None): if getattr(exc, 'public_msg', None):
return template.error_page(exc.format(), _(exc.title)) return template.error_page(exc.format(), exc.title)
else: 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 wcs.conditions import Condition, ValidationError
from ..portfolio import has_portfolio 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 .humantime import humanduration2seconds, seconds2humanduration, timewords
from .misc import HAS_PDFTOPPM, json_loads, strftime from .misc import HAS_PDFTOPPM, json_loads, strftime
from .publisher import get_cfg 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 PicklableUpload # noqa pylint: disable=unused-import
from .upload_storage import UploadStorageError from .upload_storage import UploadStorageError
Widget.REQUIRED_ERROR = N_('required field') Widget.REQUIRED_ERROR = _('required field')
get_error_orig = Widget.get_error 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): def is_prefilled(self):
if hasattr(self, 'prefilled'): if hasattr(self, 'prefilled'):
return self.prefilled return self.prefilled
@ -99,6 +92,7 @@ def is_prefilled(self):
def render_title(self, title): def render_title(self, title):
if title: if title:
title = str(title) # apply translation if required
if self.required: if self.required:
title += htmltext('<span title="%s" class="required">*</span>') % _('This field is required.') title += htmltext('<span title="%s" class="required">*</span>') % _('This field is required.')
attrs = { attrs = {
@ -178,7 +172,6 @@ def render_widget_content(self):
return htmltext(force_str(render_block_to_string(template_names, 'widget-content', context))) return htmltext(force_str(render_block_to_string(template_names, 'widget-content', context)))
Widget.get_error = get_i18n_error
Widget.render = render Widget.render = render
Widget.cleanup = None Widget.cleanup = None
Widget.render_error = render_error Widget.render_error = render_error
@ -211,7 +204,9 @@ class SubmitWidget(quixote.form.widget.SubmitWidget):
self.attrs['formnovalidate'] = 'formnovalidate' self.attrs['formnovalidate'] = 'formnovalidate'
value = htmlescape(self.label) if self.label else None value = htmlescape(self.label) if self.label else None
return ( 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): class Form(QuixoteForm):
TOKEN_NOTICE = N_( TOKEN_NOTICE = _(
"The form you have submitted is invalid. Most " "The form you have submitted is invalid. Most "
"likely it has been successfully submitted once " "likely it has been successfully submitted once "
"already. Please review the form data " "already. Please review the form data "
"and submit the form again." "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 info = None
captcha = None captcha = None
@ -295,6 +290,9 @@ class Form(QuixoteForm):
widget.advanced = advanced widget.advanced = advanced
return widget return widget
def set_error(self, name, error):
super().set_error(name, force_text(error))
def remove(self, name): def remove(self, name):
widget = self._names.get(name) widget = self._names.get(name)
if widget: if widget:
@ -344,13 +342,13 @@ class Form(QuixoteForm):
return r.getvalue() return r.getvalue()
def _render_info_notice(self): 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): def _render_error_notice(self):
errors = [] errors = []
classnames = ['errornotice'] classnames = ['errornotice']
if self.has_errors(): if self.has_errors():
errors.append(_(QuixoteForm._render_error_notice(self))) errors.append(QuixoteForm._render_error_notice(self))
if self.global_error_messages: if self.global_error_messages:
errors.extend(self.global_error_messages) errors.extend(self.global_error_messages)
classnames.append('global-errors') classnames.append('global-errors')
@ -531,7 +529,7 @@ class DurationWidget(StringWidget):
if value: if value:
value = seconds2humanduration(int(value)) value = seconds2humanduration(int(value))
if 'hint' in kwargs: if 'hint' in kwargs:
kwargs['hint'] += htmltext('<br>') kwargs['hint'] = str(kwargs['hint']) + htmltext('<br>')
else: else:
kwargs['hint'] = '' kwargs['hint'] = ''
kwargs['hint'] += htmltext(_('Usable units of time: %s.')) % ', '.join(timewords()) kwargs['hint'] += htmltext(_('Usable units of time: %s.')) % ', '.join(timewords())
@ -629,7 +627,7 @@ class CheckboxWidget(QuixoteCheckboxWidget):
# custom style. # custom style.
return ( return (
htmltext('<label %s>%s<span>' % (data_attrs, checkbox)) htmltext('<label %s>%s<span>' % (data_attrs, checkbox))
+ inline_title + str(inline_title)
+ htmltext('</span></label>') + htmltext('</span></label>')
) )
return checkbox return checkbox
@ -1040,64 +1038,75 @@ class ValidationWidget(CompositeWidget):
( (
'digits', 'digits',
{ {
'title': N_('Digits'), 'title': _('Digits'),
'regex': r'\d+', 'regex': r'\d+',
'error_message': N_('Only digits are allowed'), 'error_message': _('Only digits are allowed'),
'html_inputmode': 'numeric', 'html_inputmode': 'numeric',
}, },
), ),
( (
'phone', 'phone',
{ {
'title': N_('Phone Number'), 'title': _('Phone Number'),
'regex': r'\+?[-\(\)\d\.\s/]+', 'regex': r'\+?[-\(\)\d\.\s/]+',
'error_message': N_('Invalid phone number'), 'error_message': _('Invalid phone number'),
'html_input_type': 'tel', 'html_input_type': 'tel',
}, },
), ),
( (
'phone-fr', 'phone-fr',
{ {
'title': N_('Phone Number (France)'), 'title': _('Phone Number (France)'),
'function': 'validate_phone_fr', 'function': 'validate_phone_fr',
'error_message': N_('Invalid phone number'), 'error_message': _('Invalid phone number'),
'html_input_type': 'tel', 'html_input_type': 'tel',
}, },
), ),
( (
'zipcode-fr', 'zipcode-fr',
{ {
'title': N_('Zip Code (France)'), 'title': _('Zip Code (France)'),
'regex': r'\d{5}', 'regex': r'\d{5}',
'error_message': N_('Invalid zip code'), 'error_message': _('Invalid zip code'),
'html_inputmode': 'numeric', 'html_inputmode': 'numeric',
}, },
), ),
( (
'siren-fr', 'siren-fr',
{ {
'title': N_('SIREN Code (France)'), 'title': _('SIREN Code (France)'),
'function': 'validate_siren', 'function': 'validate_siren',
'error_message': N_('Invalid SIREN code'), 'error_message': _('Invalid SIREN code'),
'html_inputmode': 'numeric', 'html_inputmode': 'numeric',
}, },
), ),
( (
'siret-fr', 'siret-fr',
{ {
'title': N_('SIRET Code (France)'), 'title': _('SIRET Code (France)'),
'function': 'validate_siret', 'function': 'validate_siret',
'error_message': N_('Invalid SIRET code'), 'error_message': _('Invalid SIRET code'),
'html_inputmode': 'numeric', 'html_inputmode': 'numeric',
}, },
), ),
( (
'nir-fr', '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')}), 'iban',
('django', {'title': N_('Django Condition')}), {
'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: if not value:
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( self.add(
SingleSelectWidget, SingleSelectWidget,
@ -1150,7 +1159,7 @@ class ValidationWidget(CompositeWidget):
attrs={ attrs={
'data-dynamic-display-child-of': 'validation$type', 'data-dynamic-display-child-of': 'validation$type',
'data-dynamic-display-value-in': '|'.join( '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') return validation.get('error_message')
validation_method = cls.validation_methods.get(validation['type']) validation_method = cls.validation_methods.get(validation['type'])
if validation_method and 'error_message' in validation_method: if validation_method and 'error_message' in validation_method:
return _(validation_method['error_message']) return validation_method['error_message']
@classmethod @classmethod
def get_validation_pattern(cls, validation): def get_validation_pattern(cls, validation):
@ -1600,9 +1609,9 @@ class CaptchaWidget(CompositeWidget):
# don't get twice the same number # don't get twice the same number
b = random.randint(2, 9) b = random.randint(2, 9)
if mode == 'arithmetic-simple': if mode == 'arithmetic-simple':
operator = random.choice([N_('plus'), N_('minus')]) operator = random.choice([_('plus'), _('minus')])
else: else:
operator = random.choice([N_('times'), N_('plus'), N_('minus')]) operator = random.choice([_('times'), _('plus'), _('minus')])
if operator == 'times': if operator == 'times':
answer = a * b answer = a * b
elif operator == 'plus': elif operator == 'plus':
@ -1614,7 +1623,7 @@ class CaptchaWidget(CompositeWidget):
self.question = _('What is the result of %(a)d %(op)s %(b)d?') % { self.question = _('What is the result of %(a)d %(op)s %(b)d?') % {
'a': a, 'a': a,
'b': b, 'b': b,
'op': _(operator), 'op': operator,
} }
self.hint = kwargs.get('hint') self.hint = kwargs.get('hint')
if self.hint is None: if self.hint is None:
@ -1656,7 +1665,7 @@ class WidgetList(quixote.form.widget.WidgetList):
): ):
if add_element_label == 'Add row': if add_element_label == 'Add row':
add_element_label = _('Add row') add_element_label = str(_('Add row'))
CompositeWidget.__init__(self, name, value=value, **kwargs) CompositeWidget.__init__(self, name, value=value, **kwargs)
self.element_type = element_type self.element_type = element_type
@ -1769,7 +1778,7 @@ class WidgetDict(quixote.form.widget.WidgetDict):
): ):
if add_element_label == 'Add row': if add_element_label == 'Add row':
add_element_label = _('Add row') add_element_label = str(_('Add row'))
quixote.form.widget.WidgetDict.__init__( quixote.form.widget.WidgetDict.__init__(
self, 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: if not self.after_jobs:
self.after_jobs = [] self.after_jobs = []
if isinstance(label_or_instance, AfterJob): if isinstance(label_or_instance, AfterJob):
job = label_or_instance job = label_or_instance
else: else:
job = AfterJob(label=label_or_instance, cmd=cmd) job = AfterJob(label=label_or_instance, cmd=cmd, **kwargs)
if fire_and_forget: if fire_and_forget:
job.id = None job.id = None
self.after_jobs.append(job) self.after_jobs.append(job)

View File

@ -16,7 +16,7 @@
import re import re
from . import N_, _, ngettext from . import _, ngettext
_minute = 60 _minute = 60
_hour = 60 * 60 _hour = 60 * 60
@ -34,12 +34,12 @@ def list2human(stringlist):
_humandurations = ( _humandurations = (
((N_("day"), N_("days")), _day), ((_("day"), _("days")), _day),
((N_("hour"), N_("hours")), _hour), ((_("hour"), _("hours")), _hour),
((N_("month"), N_("months")), _month), ((_("month"), _("months")), _month),
((N_("year"), N_("years")), _year), ((_("year"), _("years")), _year),
((N_("minute"), N_("minutes")), _minute), ((_("minute"), _("minutes")), _minute),
((N_("second"), N_("seconds")), 1), ((_("second"), _("seconds")), 1),
) )
@ -48,7 +48,7 @@ def timewords():
result = [] result = []
for words, dummy in _humandurations: for words, dummy in _humandurations:
for word in words: for word in words:
result.append(_(word)) result.append(str(word)) # str() to force translation
return result return result
@ -58,7 +58,6 @@ def humanduration2seconds(humanduration):
seconds = 0 seconds = 0
for words, quantity in _humandurations: for words, quantity in _humandurations:
for word in words: for word in words:
word = _(word)
m = re.search(r"(\d+)\s*\b%s\b" % word, humanduration) m = re.search(r"(\d+)\s*\b%s\b" % word, humanduration)
if m: if m:
seconds = seconds + int(m.group(1)) * quantity 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.formdata import flatten_dict
from wcs.workflows import WorkflowStatusItem 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 ..backoffice.menu import html_top
from ..form import ( from ..form import (
CompositeWidget, CompositeWidget,
@ -42,7 +42,7 @@ from ..form import (
from ..misc import http_get_page, http_post_request, json_loads from ..misc import http_get_page, http_post_request, json_loads
from .base import AuthMethod from .base import AuthMethod
ADMIN_TITLE = N_('FranceConnect') ADMIN_TITLE = _('FranceConnect')
# XXX: make an OIDC auth method that FranceConnect would inherit from # XXX: make an OIDC auth method that FranceConnect would inherit from
@ -122,13 +122,13 @@ class MethodDirectory(Directory):
class MethodAdminDirectory(Directory): class MethodAdminDirectory(Directory):
title = ADMIN_TITLE title = ADMIN_TITLE
label = N_('Configure FranceConnect identification method') label = _('Configure FranceConnect identification method')
_q_exports = [''] _q_exports = ['']
PLATFORMS = [ PLATFORMS = [
{ {
'name': N_('Development citizens'), 'name': _('Development citizens'),
'slug': 'dev-particulier', 'slug': 'dev-particulier',
'authorization_url': 'https://fcp.integ01.dev-franceconnect.fr/api/v1/authorize', 'authorization_url': 'https://fcp.integ01.dev-franceconnect.fr/api/v1/authorize',
'token_url': 'https://fcp.integ01.dev-franceconnect.fr/api/v1/token', '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', 'logout_url': 'https://fcp.integ01.dev-franceconnect.fr/api/v1/logout',
}, },
{ {
'name': N_('Development enterprise'), 'name': _('Development enterprise'),
'slug': 'dev-entreprise', 'slug': 'dev-entreprise',
'authorization_url': 'https://fce.integ01.dev-franceconnect.fr/api/v1/authorize', 'authorization_url': 'https://fce.integ01.dev-franceconnect.fr/api/v1/authorize',
'token_url': 'https://fce.integ01.dev-franceconnect.fr/api/v1/token', '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', 'logout_url': 'https://fce.integ01.dev-franceconnect.fr/api/v1/logout',
}, },
{ {
'name': N_('Production citizens'), 'name': _('Production citizens'),
'slug': 'prod-particulier', 'slug': 'prod-particulier',
'authorization_url': 'https://app.franceconnect.gouv.fr/api/v1/authorize', 'authorization_url': 'https://app.franceconnect.gouv.fr/api/v1/authorize',
'token_url': 'https://app.franceconnect.gouv.fr/api/v1/token', 'token_url': 'https://app.franceconnect.gouv.fr/api/v1/token',
@ -154,22 +154,22 @@ class MethodAdminDirectory(Directory):
] ]
CONFIG = [ CONFIG = [
('client_id', N_('Client ID')), ('client_id', _('Client ID')),
('client_secret', N_('Client secret')), ('client_secret', _('Client secret')),
('platform', N_('Platform')), ('platform', _('Platform')),
('scopes', N_('Scopes')), ('scopes', _('Scopes')),
('user_field_mappings', N_('User field mappings')), ('user_field_mappings', _('User field mappings')),
] ]
KNOWN_ATTRIBUTES = [ KNOWN_ATTRIBUTES = [
('given_name', N_('first names separated by spaces')), ('given_name', _('first names separated by spaces')),
('family_name', N_('birth\'s last name')), ('family_name', _('birth\'s last name')),
('birthdate', N_('birthdate formatted as YYYY-MM-DD')), ('birthdate', _('birthdate formatted as YYYY-MM-DD')),
('gender', N_('gender \'male\' for men, and \'female\' for women')), ('gender', _('gender \'male\' for men, and \'female\' for women')),
('birthplace', N_('INSEE code of the place of birth')), ('birthplace', _('INSEE code of the place of birth')),
('birthcountry', N_('INSEE code of the country of birth')), ('birthcountry', _('INSEE code of the country of birth')),
('email', N_('email')), ('email', _('email')),
('siret', N_('SIRET or SIREN number of the enterprise')), ('siret', _('SIRET or SIREN number of the enterprise')),
# Note: FranceConnect website also refer to adress and phones attributes # Note: FranceConnect website also refer to adress and phones attributes
# but we don't know what must be expected of their value. # but we don't know what must be expected of their value.
] ]
@ -189,7 +189,7 @@ class MethodAdminDirectory(Directory):
widget = UserFieldMappingTableWidget widget = UserFieldMappingTableWidget
elif key == 'platform': elif key == 'platform':
widget = SingleSelectWidget 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': elif key == 'scopes':
default = 'identite_pivot address email phones' default = 'identite_pivot address email phones'
hint = _( hint = _(
@ -202,7 +202,7 @@ class MethodAdminDirectory(Directory):
form.add( form.add(
widget, widget,
key, key,
title=_(title), title=title,
hint=hint, hint=hint,
required=True, required=True,
value=instance.get(key, default), 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(): if 'submit' in get_request().form and form.is_submitted() and not form.has_errors():
return self.submit(form) return self.submit(form)
html_top('settings', title=_(self.title)) html_top('settings', title=self.title)
r = TemplateIO(html=True) r = TemplateIO(html=True)
r += htmltext('<h2>%s</h2>') % self.title r += htmltext('<h2>%s</h2>') % self.title
fc_callback = pub.get_frontoffice_url() + '/ident/fc/callback' 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>' '<table class="franceconnect-attrs"><thead>' '<tr><th>%s</th><th>%s</th></tr></thead><tbody>'
) % (_('Attribute'), _('Description')) ) % (_('Attribute'), _('Description'))
for attribute, description in self.KNOWN_ATTRIBUTES: 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>') r += htmltext('</tbody></table></div>')
return r.getvalue() return r.getvalue()
@ -444,7 +444,7 @@ class FCAuthMethod(AuthMethod):
user.set_attributes_from_formdata(user.form_data) user.set_attributes_from_formdata(user.form_data)
AUTHORIZATION_REQUEST_ERRORS = { AUTHORIZATION_REQUEST_ERRORS = {
'access_denied': N_('user did not authorize login'), 'access_denied': _('user did not authorize login'),
} }
def callback(self): def callback(self):
@ -465,7 +465,7 @@ class FCAuthMethod(AuthMethod):
if error: if error:
# we log only errors whose user is not responsible # we log only errors whose user is not responsible
msg = self.AUTHORIZATION_REQUEST_ERRORS.get(error) 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) return redirect(next_url)
access_token, id_token = self.get_access_token(request.form['code']) access_token, id_token = self.get_access_token(request.form['code'])
if not access_token: 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.directory import Directory
from quixote.html import TemplateIO, htmltext 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 ..admin.menu import command_icon
from ..backoffice.menu import html_top from ..backoffice.menu import html_top
from ..form import ( from ..form import (
@ -47,7 +47,7 @@ from ..storage import atomic_write
from ..tokens import Token from ..tokens import Token
from .base import AuthMethod from .base import AuthMethod
ADMIN_TITLE = N_('SAML2') ADMIN_TITLE = _('SAML2')
def is_idp_managing_user_attributes(): def is_idp_managing_user_attributes():
@ -214,16 +214,16 @@ class MethodDirectory(Directory):
class AdminIDPDir(Directory): class AdminIDPDir(Directory):
title = N_('Identity Providers') title = _('Identity Providers')
_q_exports = ['', 'new', 'new_remote'] _q_exports = ['', 'new', 'new_remote']
def _q_traverse(self, path): 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) return Directory._q_traverse(self, path)
def _q_index(self): def _q_index(self):
html_top('settings', title=_(self.title)) html_top('settings', title=self.title)
r = TemplateIO(html=True) r = TemplateIO(html=True)
r += htmltext('<h2>%s</h2>') % _('Identity Providers') r += htmltext('<h2>%s</h2>') % _('Identity Providers')
r += htmltext('<ul id="nav-idp-admin">\n') r += htmltext('<ul id="nav-idp-admin">\n')
@ -790,18 +790,18 @@ class AdminIDPUI(Directory):
class MethodAdminDirectory(Directory): class MethodAdminDirectory(Directory):
title = ADMIN_TITLE title = ADMIN_TITLE
label = N_('Configure SAML identification method') label = _('Configure SAML identification method')
_q_exports = ['', 'sp', 'idp', 'identities'] _q_exports = ['', 'sp', 'idp', 'identities']
idp = AdminIDPDir() idp = AdminIDPDir()
def _q_traverse(self, path): 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) return Directory._q_traverse(self, path)
def _q_index(self): def _q_index(self):
html_top('settings', title=_(self.title)) html_top('settings', title=self.title)
r = TemplateIO(html=True) r = TemplateIO(html=True)
r += htmltext('<h2>SAML 2.0</h2>') r += htmltext('<h2>SAML 2.0</h2>')
r += htmltext('<dl> <dt><a href="sp">%s</a></dt> <dd>%s</dd>') % ( r += htmltext('<dl> <dt><a href="sp">%s</a></dt> <dd>%s</dd>') % (
@ -1185,7 +1185,7 @@ class MethodUserDirectory(Directory):
class IdPAuthMethod(AuthMethod): class IdPAuthMethod(AuthMethod):
key = 'idp' key = 'idp'
description = N_('SAML identity provider') description = _('SAML identity provider')
method_directory = MethodDirectory method_directory = MethodDirectory
method_admin_directory = MethodAdminDirectory method_admin_directory = MethodAdminDirectory
method_user_directory = MethodUserDirectory method_user_directory = MethodUserDirectory

View File

@ -23,7 +23,7 @@ from quixote.html import TemplateIO, htmltext
from wcs.qommon.admin.texts import TextsDirectory 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 storage as st
from .. import template, tokens from .. import template, tokens
from ..admin.emails import EmailsDirectory from ..admin.emails import EmailsDirectory
@ -680,21 +680,21 @@ class MethodDirectory(Directory):
emails.custom_template_email('password-subscription-notification', data, user.email) emails.custom_template_email('password-subscription-notification', data, user.email)
ADMIN_TITLE = N_('Username / Password') ADMIN_TITLE = _('Username / Password')
class MethodAdminDirectory(Directory): class MethodAdminDirectory(Directory):
title = ADMIN_TITLE title = ADMIN_TITLE
label = N_('Configure username/password identification method') label = _('Configure username/password identification method')
_q_exports = ['', 'passwords', 'identities'] _q_exports = ['', 'passwords', 'identities']
def _q_index(self): def _q_index(self):
html_top('settings', title=_(ADMIN_TITLE)) html_top('settings', title=ADMIN_TITLE)
get_response().breadcrumb.append(('password/', _(self.title))) get_response().breadcrumb.append(('password/', ADMIN_TITLE))
r = TemplateIO(html=True) r = TemplateIO(html=True)
r += htmltext('<h2>%s</h2>') % _(ADMIN_TITLE) r += htmltext('<h2>%s</h2>') % ADMIN_TITLE
r += get_session().display_message() r += get_session().display_message()
@ -958,7 +958,7 @@ class MethodUserDirectory(Directory):
return actions return actions
def email(self): def email(self):
html_top('users', title=_(ADMIN_TITLE)) html_top('users', title=ADMIN_TITLE)
r = TemplateIO(html=True) r = TemplateIO(html=True)
get_response().breadcrumb.append(('email', 'Email Password')) get_response().breadcrumb.append(('email', 'Email Password'))
r += htmltext('<h2>%s</h2>') % _('Email Password') r += htmltext('<h2>%s</h2>') % _('Email Password')
@ -1065,11 +1065,11 @@ class PasswordAuthMethod(AuthMethod):
EmailsDirectory.register( EmailsDirectory.register(
'password-subscription-notification', 'password-subscription-notification',
N_('Subscription notification for password account'), _('Subscription notification for password account'),
N_('Available variables: email, website, token_url, token, admin_email, username, password'), _('Available variables: email, website, token_url, token, admin_email, username, password'),
category=N_('Identification'), category=_('Identification'),
default_subject=N_('Subscription Confirmation'), default_subject=_('Subscription Confirmation'),
default_body=N_( default_body=_(
'''\ '''\
We have received a request for subscription of your email address, We have received a request for subscription of your email address,
"[email]", to the [website] web site. "[email]", to the [website] web site.
@ -1089,11 +1089,11 @@ to [admin_email].
EmailsDirectory.register( EmailsDirectory.register(
'change-password-request', 'change-password-request',
N_('Request for password change'), _('Request for password change'),
N_('Available variables: change_url, cancel_url, token, time'), _('Available variables: change_url, cancel_url, token, time'),
category=N_('Identification'), category=_('Identification'),
default_subject=N_('Change Password Request'), default_subject=_('Change Password Request'),
default_body=N_( default_body=_(
"""\ """\
You have (or someone impersonating you has) requested to change your You have (or someone impersonating you has) requested to change your
password. To complete the change, visit the following link: 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( EmailsDirectory.register(
'new-generated-password', 'new-generated-password',
N_('New generated password'), _('New generated password'),
N_('Available variables: username, password, hostname'), _('Available variables: username, password, hostname'),
category=N_('Identification'), category=_('Identification'),
default_subject=N_('Your new password'), default_subject=_('Your new password'),
default_body=N_( default_body=_(
'''\ '''\
Hello, Hello,
@ -1134,11 +1134,11 @@ account details:
EmailsDirectory.register( EmailsDirectory.register(
'new-account-approved', 'new-account-approved',
N_('Approval of new account'), _('Approval of new account'),
N_('Available variables: username, password'), _('Available variables: username, password'),
category=N_('Identification'), category=_('Identification'),
default_subject=N_('Your account has been approved'), default_subject=_('Your account has been approved'),
default_body=N_( default_body=_(
'''\ '''\
Your account has been approved. Your account has been approved.
@ -1152,11 +1152,11 @@ Account details:
EmailsDirectory.register( EmailsDirectory.register(
'warning-about-unused-account', 'warning-about-unused-account',
N_('Warning about unusued account'), _('Warning about unusued account'),
N_('Available variables: username'), _('Available variables: username'),
category=N_('Identification'), category=_('Identification'),
default_subject=N_('Your account is unused'), default_subject=_('Your account is unused'),
default_body=N_( default_body=_(
'''\ '''\
Your account ([username]) is not being used. Your account ([username]) is not being used.
''' '''
@ -1165,11 +1165,11 @@ Your account ([username]) is not being used.
EmailsDirectory.register( EmailsDirectory.register(
'notification-of-removed-account', 'notification-of-removed-account',
N_('Notification of removal of unused account'), _('Notification of removal of unused account'),
N_('Available variables: username'), _('Available variables: username'),
category=N_('Identification'), category=_('Identification'),
default_subject=N_('Your account has been removed'), default_subject=_('Your account has been removed'),
default_body=N_( default_body=_(
'''\ '''\
Your account ([username]) was not being used, it has therefore been removed. 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( EmailsDirectory.register(
'new-registration-admin-notification', 'new-registration-admin-notification',
N_('Notification of new registration to administrators'), _('Notification of new registration to administrators'),
N_('Available variables: hostname, email_as_username, username'), _('Available variables: hostname, email_as_username, username'),
category=N_('Identification'), category=_('Identification'),
default_subject=N_('New Registration'), default_subject=_('New Registration'),
default_body=N_( default_body=_(
'''\ '''\
Hello, Hello,
@ -1196,11 +1196,11 @@ A new account has been created on [hostname].
EmailsDirectory.register( EmailsDirectory.register(
'new-account-generated-password', 'new-account-generated-password',
N_('Welcome email, with generated password'), _('Welcome email, with generated password'),
N_('Available variables: hostname, username, password, email_as_username'), _('Available variables: hostname, username, password, email_as_username'),
category=N_('Identification'), category=_('Identification'),
default_subject=N_('Welcome to [hostname]'), default_subject=_('Welcome to [hostname]'),
default_body=N_( default_body=_(
'''\ '''\
Welcome to [hostname], Welcome to [hostname],
@ -1211,11 +1211,11 @@ Your password is: [password]
EmailsDirectory.register( EmailsDirectory.register(
'password-email-create-anew', 'password-email-create-anew',
N_('Email with a new password for the user'), _('Email with a new password for the user'),
N_('Available variables: hostname, name, username, password'), _('Available variables: hostname, name, username, password'),
category=N_('Identification'), category=_('Identification'),
default_subject=N_('Your new password for [hostname]'), default_subject=_('Your new password for [hostname]'),
default_body=N_( default_body=_(
'''\ '''\
Hello [name], Hello [name],
@ -1226,11 +1226,11 @@ Here is your new password for [hostname]: [password]
EmailsDirectory.register( EmailsDirectory.register(
'password-email-current', 'password-email-current',
N_('Email with current password for the user'), _('Email with current password for the user'),
N_('Available variables: hostname, name, username, password'), _('Available variables: hostname, name, username, password'),
category=N_('Identification'), category=_('Identification'),
default_subject=N_('Your password for [hostname]'), default_subject=_('Your password for [hostname]'),
default_body=N_( default_body=_(
'''\ '''\
Hello [name], Hello [name],
@ -1242,9 +1242,9 @@ Here is your password for [hostname]: [password]
TextsDirectory.register( TextsDirectory.register(
'account-created', 'account-created',
N_('Text when account confirmed by user'), _('Text when account confirmed by user'),
category=N_('Identification'), category=_('Identification'),
default=N_( default=_(
'''<p> '''<p>
Your account has been created. Your account has been created.
</p>''' </p>'''
@ -1253,9 +1253,9 @@ Your account has been created.
TextsDirectory.register( TextsDirectory.register(
'password-forgotten-token-sent', 'password-forgotten-token-sent',
N_('Text when an email with a change password token has been sent'), _('Text when an email with a change password token has been sent'),
category=N_('Identification'), category=_('Identification'),
default=N_( default=_(
'''<p> '''<p>
A token for changing your password has been emailed to you. Follow the instructions in that email to change your password. A token for changing your password has been emailed to you. Follow the instructions in that email to change your password.
</p> </p>
@ -1267,9 +1267,9 @@ A token for changing your password has been emailed to you. Follow the instructi
TextsDirectory.register( TextsDirectory.register(
'new-password-sent-by-email', 'new-password-sent-by-email',
N_('Text when new password has been sent'), _('Text when new password has been sent'),
category=N_('Identification'), category=_('Identification'),
default=N_( default=_(
'''<p> '''<p>
Your new password has been sent to you by email. Your new password has been sent to you by email.
</p> </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( TextsDirectory.register(
'password-forgotten-link', 'password-forgotten-link',
N_('Text on login page, linking to the forgotten password request page'), _('Text on login page, linking to the forgotten password request page'),
category=N_('Identification'), category=_('Identification'),
default=N_( default=_(
'''<p> '''<p>
If you have an account, but have forgotten your password, you should go 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 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( TextsDirectory.register(
'password-forgotten-enter-username', 'password-forgotten-enter-username',
N_('Text on forgotten password request page'), _('Text on forgotten password request page'),
category=N_('Identification'), category=_('Identification'),
default=N_( default=_(
'''<p> '''<p>
If you have an account, but have forgotten your password, enter your user name If you have an account, but have forgotten your password, enter your user name
below and submit a request to change your password. below and submit a request to change your password.
@ -1308,10 +1308,10 @@ below and submit a request to change your password.
TextsDirectory.register( TextsDirectory.register(
'password-account-link-to-register-page', 'password-account-link-to-register-page',
N_('Text linking the login page to the account creation page'), _('Text linking the login page to the account creation page'),
hint=N_('Available variable: register_url'), hint=_('Available variable: register_url'),
category=N_('Identification'), category=_('Identification'),
default=N_( default=_(
'''<p> '''<p>
If you do not have an account, you should go to the <a href="[register_url]"> If you do not have an account, you should go to the <a href="[register_url]">
New Account page</a>. New Account page</a>.
@ -1321,22 +1321,22 @@ New Account page</a>.
TextsDirectory.register( TextsDirectory.register(
'invalid-password-token', 'invalid-password-token',
N_('Text when an invalid password token is used'), _('Text when an invalid password token is used'),
category=N_('Identification'), category=_('Identification'),
default=N_( default=_(
'''<p> '''<p>
Sorry, the token you used is invalid, or has already been used. Sorry, the token you used is invalid, or has already been used.
</p>''' </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( TextsDirectory.register(
'email-sent-confirm-creation', 'email-sent-confirm-creation',
N_('Text when a mail for confirmation of an account creation has been sent'), _('Text when a mail for confirmation of an account creation has been sent'),
category=N_('Identification'), category=_('Identification'),
default=N_('An email has been sent to you so you can confirm your account creation.'), 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()), '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 # Let the base class default method raise the TypeError
return json.JSONEncoder.default(self, obj) 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-Origin', get_request().get_environ('HTTP_ORIGIN'))
get_response().set_header('Access-Control-Allow-Credentials', 'true') get_response().set_header('Access-Control-Allow-Credentials', 'true')
get_response().set_header('Access-Control-Allow-Headers', 'x-requested-with') 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'): for variable in ('jsonpCallback', 'callback'):
if variable in get_request().form: if variable in get_request().form:
get_response().set_content_type('application/javascript') get_response().set_content_type('application/javascript')
@ -1002,6 +1006,7 @@ def get_document_types(current_document_type):
document_types.update(get_cfg('filetypes', {})) document_types.update(get_cfg('filetypes', {}))
for key, document_type in document_types.items(): for key, document_type in document_types.items():
document_type['id'] = key document_type['id'] = key
document_type['label'] = str(document_type['label'])
# add current file type if it does not exist anymore in the settings # add current file type if it does not exist anymore in the settings
cur_dt = current_document_type cur_dt = current_document_type
if cur_dt and cur_dt['id'] not in document_types: 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 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 .form import Form, HtmlWidget, PasswordWidget
from .ident.password import check_password from .ident.password import check_password
from .ident.password_accounts import PasswordAccount from .ident.password_accounts import PasswordAccount
@ -194,4 +194,4 @@ class MyspaceDirectory(Directory):
return form.render() 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 django.utils.encoding import force_bytes, force_text
from quixote.publish import Publisher, get_publisher, get_request, get_response 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 .cron import CronJob
from .http_request import HTTPRequest from .http_request import HTTPRequest
from .http_response import AfterJob, HTTPResponse from .http_response import AfterJob, HTTPResponse
@ -185,7 +185,13 @@ class QommonPublisher(Publisher):
request.response.headers['WWW-Authenticate'] = 'Basic realm="%s"' % exc.realm request.response.headers['WWW-Authenticate'] = 'Basic realm="%s"' % exc.realm
if request.is_json(): if request.is_json():
request.response.set_content_type('application/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): if isinstance(exc, errors.TraversalError):
raise Http404() raise Http404()
output = self.format_publish_error(exc) output = self.format_publish_error(exc)
@ -1003,9 +1009,9 @@ def get_publisher_class():
return builtins.__dict__.get('__publisher_class') return builtins.__dict__.get('__publisher_class')
Substitutions.register('site_name', category=N_('General'), comment=N_('Site Name')) Substitutions.register('site_name', category=_('General'), comment=_('Site Name'))
Substitutions.register('site_theme', category=N_('General'), comment=N_('Current Theme Name')) Substitutions.register('site_theme', category=_('General'), comment=_('Current Theme Name'))
Substitutions.register('site_url', category=N_('General'), comment=N_('Site URL')) Substitutions.register('site_url', category=_('General'), comment=_('Site URL'))
Substitutions.register('site_url_backoffice', category=N_('General'), comment=N_('Site URL (backoffice)')) Substitutions.register('site_url_backoffice', category=_('General'), comment=_('Site URL (backoffice)'))
Substitutions.register('today', category=N_('General'), comment=N_('Current Date')) Substitutions.register('today', category=_('General'), comment=_('Current Date'))
Substitutions.register('now', category=N_('General'), comment=N_('Current Date & Time')) Substitutions.register('now', category=_('General'), comment=_('Current Date & Time'))

View File

@ -153,6 +153,9 @@ class Session(QommonSession, CaptchaSession, StorableObject):
return odict return odict
def store(self, *args, **kwargs): 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__) current_dict = copy.copy(self.__dict__)
orig_dict = current_dict.pop('__orig_dict__', {}) orig_dict = current_dict.pop('__orig_dict__', {})
current_dict.pop('_access_time', None) current_dict.pop('_access_time', None)

View File

@ -144,7 +144,7 @@ class Substitutions:
% (_('Category'), _('Variable'), _('Comment')) % (_('Category'), _('Variable'), _('Comment'))
) )
r += htmltext('<tbody>') 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: for dynamic_source in cls.dynamic_sources:
vars.extend(dynamic_source.get_substitution_variables_list()) vars.extend(dynamic_source.get_substitution_variables_list())
vars.sort() vars.sort()

View File

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

View File

@ -33,7 +33,7 @@ from .forms import root
from .forms.actions import ActionsDirectory from .forms.actions import ActionsDirectory
from .forms.preview import PreviewDirectory from .forms.preview import PreviewDirectory
from .myspace import MyspaceDirectory 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.afterjobs import AfterJobStatusDirectory
from .qommon.form import Form, RadiobuttonsWidget from .qommon.form import Form, RadiobuttonsWidget
from .qommon.pages import PagesDirectory from .qommon.pages import PagesDirectory
@ -92,7 +92,7 @@ class LoginDirectory(Directory):
RadiobuttonsWidget, RadiobuttonsWidget,
'method', 'method',
options=[ 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/>'), delim=htmltext('<br/>'),
) )
@ -157,7 +157,7 @@ class RegisterDirectory(Directory):
RadiobuttonsWidget, RadiobuttonsWidget,
'method', 'method',
options=[ 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/>'), delim=htmltext('<br/>'),
) )
@ -344,7 +344,7 @@ class RootDirectory(Directory):
# they would propose the returned json content for download if # they would propose the returned json content for download if
# it was served with the appropriate content type :/ # it was served with the appropriate content type :/
get_response().set_content_type('text/plain') get_response().set_content_type('text/plain')
return json.dumps(results) return json.dumps(results, cls=misc.JSONEncoder)
def feed_substitution_parts(self): def feed_substitution_parts(self):
get_publisher().substitutions.feed(get_session()) get_publisher().substitutions.feed(get_session())
@ -446,7 +446,7 @@ class RootDirectory(Directory):
'email_domain_suggest': _('Did you want to write'), 'email_domain_suggest': _('Did you want to write'),
'email_domain_fix': _('Apply fix'), '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 admin = None
backoffice = None backoffice = None

View File

@ -25,7 +25,7 @@ from wcs.qommon.storage import Null
class UnknownUser: class UnknownUser:
def __str__(self): def __str__(self):
return _('unknown user') return str(_('unknown user'))
class Snapshot: class Snapshot:
@ -53,7 +53,7 @@ class Snapshot:
if get_session(): if get_session():
obj.user_id = get_session().user obj.user_id = get_session().user
obj.serialization = ET.tostring(instance.export_to_xml(include_id=True)).decode('utf-8') 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 obj.label = label
latest = cls.get_latest(obj.object_type, obj.object_id) latest = cls.get_latest(obj.object_type, obj.object_id)
if label is not None or latest is None or obj.serialization != latest.serialization: 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 @guard_postgres
def store(self): def store(self):
if self.message:
# escape lazy gettext
self.message = (self.message[0], str(self.message[1]))
sql_dict = { sql_dict = {
'id': self.id, 'id': self.id,
'session_data': bytearray(pickle.dumps(self.__dict__, protocol=2)), '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 "Latest occurence:" %} {{ error.latest_occurence_timestamp }}</li>
<li>{% trans "Count:" %} {{ error.occurences_count }}</li> <li>{% trans "Count:" %} {{ error.occurences_count }}</li>
{% if formdef %} {% 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 %} {% endif %}
{% if workflow %} {% if workflow %}
<li>{% trans "Workflow:" %} <a href="{{ workflow.get_admin_url }}">{{ workflow.name }}</a> <li>{% trans "Workflow:" %} <a href="{{ workflow.get_admin_url }}">{{ workflow.name }}</a>

View File

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

View File

@ -20,7 +20,7 @@ from quixote import get_publisher
import wcs.qommon.storage as st import wcs.qommon.storage as st
from .qommon import N_, _, get_cfg from .qommon import _, get_cfg
from .qommon.misc import simplify from .qommon.misc import simplify
from .qommon.storage import StorableObject from .qommon.storage import StorableObject
from .qommon.substitution import Substitutions, invalidate_substitution_cache from .qommon.substitution import Substitutions, invalidate_substitution_cache
@ -315,7 +315,9 @@ class User(StorableObject):
Substitutions.register( 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) 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 wcs.workflows import WorkflowStatusItem, register_item_class
from ..qommon import N_, _, emails from ..qommon import _, emails
from ..qommon.cron import CronJob from ..qommon.cron import CronJob
from ..qommon.form import SingleSelectWidget, WidgetList from ..qommon.form import SingleSelectWidget, WidgetList
from ..qommon.publisher import get_publisher_class from ..qommon.publisher import get_publisher_class
@ -26,7 +26,7 @@ from ..qommon.storage import StorableObject
class AggregationEmailWorkflowStatusItem(WorkflowStatusItem): class AggregationEmailWorkflowStatusItem(WorkflowStatusItem):
description = N_('Daily Summary Email') description = _('Daily Summary Email')
key = 'aggregationemail' key = 'aggregationemail'
category = 'interaction' category = 'interaction'
ok_in_global_action = False ok_in_global_action = False

View File

@ -14,12 +14,12 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program; if not, see <http://www.gnu.org/licenses/>. # 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 from wcs.workflows import WorkflowStatusItem, register_item_class
class AnonymiseWorkflowStatusItem(WorkflowStatusItem): class AnonymiseWorkflowStatusItem(WorkflowStatusItem):
description = N_('Anonymisation') description = _('Anonymisation')
key = 'anonymise' key = 'anonymise'
category = 'formdata-action' 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.portfolio import has_portfolio, push_document
from wcs.workflows import AttachmentEvolutionPart, WorkflowStatusItem, register_item_class from wcs.workflows import AttachmentEvolutionPart, WorkflowStatusItem, register_item_class
from ..qommon import N_, _ from ..qommon import _
from ..qommon.errors import TraversalError from ..qommon.errors import TraversalError
from ..qommon.form import ( from ..qommon.form import (
CheckboxWidget, CheckboxWidget,
@ -87,7 +87,7 @@ def form_attachment(self):
class AddAttachmentWorkflowStatusItem(WorkflowStatusItem): class AddAttachmentWorkflowStatusItem(WorkflowStatusItem):
description = N_('Attachment') description = _('Attachment')
key = 'addattachment' key = 'addattachment'
category = 'interaction' category = 'interaction'
endpoint = False endpoint = False

View File

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

View File

@ -17,7 +17,7 @@
from quixote import get_publisher from quixote import get_publisher
from wcs.carddef import CardDef from wcs.carddef import CardDef
from wcs.qommon import N_ from wcs.qommon import _
from wcs.wf.create_formdata import CreateFormdataWorkflowStatusItem, LinkedFormdataEvolutionPart from wcs.wf.create_formdata import CreateFormdataWorkflowStatusItem, LinkedFormdataEvolutionPart
from wcs.workflows import register_item_class from wcs.workflows import register_item_class
@ -27,7 +27,7 @@ class LinkedCardDataEvolutionPart(LinkedFormdataEvolutionPart):
class CreateCarddataWorkflowStatusItem(CreateFormdataWorkflowStatusItem): class CreateCarddataWorkflowStatusItem(CreateFormdataWorkflowStatusItem):
description = N_('Create Card Data') description = _('Create Card Data')
key = 'create_carddata' key = 'create_carddata'
category = 'formdata-action' category = 'formdata-action'
ok_in_global_action = True ok_in_global_action = True
@ -35,10 +35,10 @@ class CreateCarddataWorkflowStatusItem(CreateFormdataWorkflowStatusItem):
formdef_class = CardDef formdef_class = CardDef
evolution_part_class = LinkedCardDataEvolutionPart evolution_part_class = LinkedCardDataEvolutionPart
formdef_label = N_('Card') formdef_label = _('Card')
mappings_label = N_('Mappings to new card fields') mappings_label = _('Mappings to new card fields')
varname_hint = N_('This is used to get linked card in expressions.') varname_hint = _('This is used to get linked card in expressions.')
user_association_option_label = N_('User to associate to card') user_association_option_label = _('User to associate to card')
@classmethod @classmethod
def is_available(cls, workflow=None): 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 quixote.html import htmltext
from wcs.formdef import FormDef from wcs.formdef import FormDef
from wcs.qommon import N_, _ from wcs.qommon import _
from wcs.qommon.form import ( from wcs.qommon.form import (
CheckboxWidget, CheckboxWidget,
CompositeWidget, CompositeWidget,
@ -204,7 +204,7 @@ class LazyFormDataLinks:
class CreateFormdataWorkflowStatusItem(WorkflowStatusItem): class CreateFormdataWorkflowStatusItem(WorkflowStatusItem):
description = N_('New Form Creation') description = _('New Form Creation')
key = 'create_formdata' key = 'create_formdata'
category = 'formdata-action' category = 'formdata-action'
support_substitution_variables = True support_substitution_variables = True
@ -213,11 +213,11 @@ class CreateFormdataWorkflowStatusItem(WorkflowStatusItem):
evolution_part_class = LinkedFormdataEvolutionPart evolution_part_class = LinkedFormdataEvolutionPart
formdef_slug = None formdef_slug = None
formdef_label = N_('Form') formdef_label = _('Form')
mappings_label = N_('Mappings to new form fields') mappings_label = _('Mappings to new form fields')
accept_empty_value = False accept_empty_value = False
varname_hint = N_('This is used to get linked forms in expressions.') varname_hint = _('This is used to get linked forms in expressions.')
user_association_option_label = N_('User to associate to form') user_association_option_label = _('User to associate to form')
draft = False draft = False
backoffice_submission = False backoffice_submission = False
@ -261,7 +261,7 @@ class CreateFormdataWorkflowStatusItem(WorkflowStatusItem):
form.add( form.add(
SingleSelectWidget, SingleSelectWidget,
'%sformdef_slug' % prefix, '%sformdef_slug' % prefix,
title=_(self.formdef_label), title=self.formdef_label,
value=self.formdef_slug, value=self.formdef_slug,
options=list_forms, options=list_forms,
) )
@ -281,7 +281,7 @@ class CreateFormdataWorkflowStatusItem(WorkflowStatusItem):
form.add( form.add(
RadiobuttonsWidget, RadiobuttonsWidget,
'%suser_association_mode' % prefix, '%suser_association_mode' % prefix,
title=_(self.user_association_option_label), title=self.user_association_option_label,
options=[ options=[
(None, _('None'), 'none'), (None, _('None'), 'none'),
('keep-user', _('Keep Current User'), 'keep-user'), ('keep-user', _('Keep Current User'), 'keep-user'),
@ -319,7 +319,7 @@ class CreateFormdataWorkflowStatusItem(WorkflowStatusItem):
widget = form.add( widget = form.add(
MappingsWidget, MappingsWidget,
'%smappings' % prefix, '%smappings' % prefix,
title=_(self.mappings_label), title=self.mappings_label,
accept_empty_value=self.accept_empty_value, accept_empty_value=self.accept_empty_value,
to_formdef=formdef, to_formdef=formdef,
value=self.mappings, value=self.mappings,
@ -334,7 +334,7 @@ class CreateFormdataWorkflowStatusItem(WorkflowStatusItem):
'%svarname' % prefix, '%svarname' % prefix,
title=_('Identifier'), title=_('Identifier'),
value=self.varname, value=self.varname,
hint=_(self.varname_hint), hint=self.varname_hint,
advanced=not (bool(self.varname)), advanced=not (bool(self.varname)),
) )
if 'map_fields_by_varname' in parameters and formdef: if 'map_fields_by_varname' in parameters and formdef:

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -28,14 +28,14 @@ from quixote import get_publisher
from wcs.workflows import WorkflowStatusItem, register_item_class 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.errors import ConnectionError
from ..qommon.form import CheckboxWidget, ComputedExpressionWidget, RadiobuttonsWidget from ..qommon.form import CheckboxWidget, ComputedExpressionWidget, RadiobuttonsWidget
from ..qommon.misc import http_get_page, normalize_geolocation from ..qommon.misc import http_get_page, normalize_geolocation
class GeolocateWorkflowStatusItem(WorkflowStatusItem): class GeolocateWorkflowStatusItem(WorkflowStatusItem):
description = N_('Geolocation') description = _('Geolocation')
key = 'geolocate' key = 'geolocate'
category = 'formdata-action' 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.conditions import Condition
from wcs.workflows import Workflow, WorkflowGlobalAction, WorkflowStatusJumpItem, register_item_class 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.cron import CronJob
from ..qommon.form import ComputedExpressionWidget, SingleSelectWidget, StringWidget, WidgetList from ..qommon.form import ComputedExpressionWidget, SingleSelectWidget, StringWidget, WidgetList
from ..qommon.humantime import humanduration2seconds, seconds2humanduration, timewords from ..qommon.humantime import humanduration2seconds, seconds2humanduration, timewords
@ -110,7 +110,7 @@ class TriggerDirectory(Directory):
class JumpWorkflowStatusItem(WorkflowStatusJumpItem): class JumpWorkflowStatusItem(WorkflowStatusJumpItem):
description = N_('Automatic Jump') description = _('Automatic Jump')
key = 'jump' key = 'jump'
by = [] by = []
@ -167,7 +167,10 @@ class JumpWorkflowStatusItem(WorkflowStatusJumpItem):
reasons.append(_('timeout')) reasons.append(_('timeout'))
if reasons: 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: else:
return wf_status[0].name 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 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.form import ComputedExpressionWidget, SingleSelectWidget, StringWidget, TextWidget, WidgetList
from ..qommon.template import TemplateError from ..qommon.template import TemplateError
from .wscall import WebserviceCallStatusItem from .wscall import WebserviceCallStatusItem
class SendNotificationWorkflowStatusItem(WebserviceCallStatusItem): class SendNotificationWorkflowStatusItem(WebserviceCallStatusItem):
description = N_('User Notification') description = _('User Notification')
key = 'notification' key = 'notification'
category = 'interaction' category = 'interaction'
support_substitution_variables = True 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.api_utils import MissingSecret, get_secret_and_orig, sign_url
from wcs.workflows import WorkflowStatusItem, XmlSerialisable, register_item_class 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.form import CompositeWidget, ComputedExpressionWidget, SingleSelectWidget, WidgetListAsTable
from ..qommon.ident.idp import is_idp_managing_user_attributes from ..qommon.ident.idp import is_idp_managing_user_attributes
from ..qommon.misc import JSONEncoder, http_patch_request from ..qommon.misc import JSONEncoder, http_patch_request
@ -102,7 +102,7 @@ class FieldNode(XmlSerialisable):
class UpdateUserProfileStatusItem(WorkflowStatusItem): class UpdateUserProfileStatusItem(WorkflowStatusItem):
description = N_('User Profile Update') description = _('User Profile Update')
key = 'update_user_profile' key = 'update_user_profile'
category = 'user-action' category = 'user-action'
@ -211,7 +211,7 @@ class UpdateUserProfileStatusItem(WorkflowStatusItem):
get_logger().error('failed to update profile for user %r', user) get_logger().error('failed to update profile for user %r', user)
if get_request(): 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: else:
after_job() after_job()

View File

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

View File

@ -26,7 +26,7 @@ from wcs.workflows import (
template_on_formdata, 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.form import SingleSelectWidget, TextWidget, WidgetList
from ..qommon.template import TemplateError from ..qommon.template import TemplateError
@ -80,7 +80,7 @@ class JournalEvolutionPart:
class RegisterCommenterWorkflowStatusItem(WorkflowStatusItem): class RegisterCommenterWorkflowStatusItem(WorkflowStatusItem):
description = N_('History Message') description = _('History Message')
key = 'register-comment' key = 'register-comment'
category = 'interaction' 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 wcs.workflows import AbortActionException, WorkflowStatusItem, register_item_class
from ..qommon import N_, _ from ..qommon import _
class RemoveWorkflowStatusItem(WorkflowStatusItem): class RemoveWorkflowStatusItem(WorkflowStatusItem):
description = N_('Deletion') description = _('Deletion')
key = 'remove' key = 'remove'
category = 'formdata-action' 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.qommon.form import SingleSelectWidget, StringWidget, WidgetList, WysiwygTextWidget
from wcs.workflows import WorkflowStatusItem, register_item_class from wcs.workflows import WorkflowStatusItem, register_item_class
from ..qommon import N_, _ from ..qommon import _
class ResubmitWorkflowStatusItem(WorkflowStatusItem): class ResubmitWorkflowStatusItem(WorkflowStatusItem):
description = N_('Resubmission') description = _('Resubmission')
key = 'resubmit' key = 'resubmit'
category = 'formdata-action' category = 'formdata-action'
endpoint = False 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.roles import get_user_roles
from wcs.workflows import WorkflowStatusItem, register_item_class from wcs.workflows import WorkflowStatusItem, register_item_class
from ..qommon import N_, _ from ..qommon import _
from ..qommon.form import SingleSelectWidgetWithOther from ..qommon.form import SingleSelectWidgetWithOther
from ..qommon.ident.idp import is_idp_managing_user_attributes from ..qommon.ident.idp import is_idp_managing_user_attributes
from ..qommon.misc import http_delete_request, http_post_request from ..qommon.misc import http_delete_request, http_post_request
@ -47,7 +47,7 @@ def sign_ws_url(url):
class AddRoleWorkflowStatusItem(WorkflowStatusItem): class AddRoleWorkflowStatusItem(WorkflowStatusItem):
description = N_('Role Addition') description = _('Role Addition')
key = 'add_role' key = 'add_role'
category = 'user-action' category = 'user-action'
@ -116,7 +116,7 @@ class AddRoleWorkflowStatusItem(WorkflowStatusItem):
get_logger().error('failed to add role %r to user %r', role, user) get_logger().error('failed to add role %r to user %r', role, user)
if get_request(): if get_request():
get_response().add_after_job(str(N_('Adding role')), after_job) get_response().add_after_job(_('Adding role'), after_job)
else: else:
after_job() after_job()
@ -125,7 +125,7 @@ register_item_class(AddRoleWorkflowStatusItem)
class RemoveRoleWorkflowStatusItem(WorkflowStatusItem): class RemoveRoleWorkflowStatusItem(WorkflowStatusItem):
description = N_('Role Removal') description = _('Role Removal')
key = 'remove_role' key = 'remove_role'
category = 'user-action' category = 'user-action'
@ -187,7 +187,7 @@ class RemoveRoleWorkflowStatusItem(WorkflowStatusItem):
get_logger().error('failed to remove role %r from user %r', role, user) get_logger().error('failed to remove role %r from user %r', role, user)
if get_request(): if get_request():
get_response().add_after_job(str(N_('Removing role')), after_job) get_response().add_after_job(_('Removing role'), after_job)
else: else:
after_job() after_job()

View File

@ -14,13 +14,13 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program; if not, see <http://www.gnu.org/licenses/>. # 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 from .jump import JumpWorkflowStatusItem, register_item_class
class TimeoutWorkflowStatusItem(JumpWorkflowStatusItem): class TimeoutWorkflowStatusItem(JumpWorkflowStatusItem):
description = N_('Change Status on Timeout') description = _('Change Status on Timeout')
key = 'timeout' key = 'timeout'
ok_in_global_action = False 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 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.errors import ConnectionError
from ..qommon.form import ( from ..qommon.form import (
CheckboxWidget, CheckboxWidget,
@ -110,7 +110,7 @@ class JournalWsCallErrorPart:
class WebserviceCallStatusItem(WorkflowStatusItem): class WebserviceCallStatusItem(WorkflowStatusItem):
description = N_('Webservice') description = _('Webservice')
key = 'webservice_call' key = 'webservice_call'
category = 'interaction' category = 'interaction'
support_substitution_variables = True support_substitution_variables = True
@ -248,7 +248,7 @@ class WebserviceCallStatusItem(WorkflowStatusItem):
attrs={ attrs={
'data-dynamic-display-child-of': '%smethod' % prefix, 'data-dynamic-display-child-of': '%smethod' % prefix,
'data-dynamic-display-value-in': '|'.join( '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={ attrs={
'data-dynamic-display-child-of': '%smethod' % prefix, 'data-dynamic-display-child-of': '%smethod' % prefix,
'data-dynamic-display-value-in': '|'.join( '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 .formdata import Evolution
from .formdef import FormDef, FormdefImportError from .formdef import FormDef, FormdefImportError
from .mail_templates import MailTemplate 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 ( from .qommon.form import (
CheckboxWidget, CheckboxWidget,
ComputedExpressionWidget, ComputedExpressionWidget,
@ -393,7 +393,7 @@ class Workflow(StorableObject):
StorableObject.__init__(self) StorableObject.__init__(self)
self.name = name self.name = name
self.possible_status = [] self.possible_status = []
self.roles = {'_receiver': _('Recipient')} self.roles = {'_receiver': force_text(_('Recipient'))}
self.global_actions = [] self.global_actions = []
self.criticality_levels = [] self.criticality_levels = []
@ -401,7 +401,7 @@ class Workflow(StorableObject):
changed = False changed = False
if 'roles' not in self.__dict__ or self.roles is None: if 'roles' not in self.__dict__ or self.roles is None:
self.roles = {'_receiver': _('Recipient')} self.roles = {'_receiver': force_text(_('Recipient'))}
changed = True changed = True
for status in self.possible_status: for status in self.possible_status:
@ -457,7 +457,7 @@ class Workflow(StorableObject):
form.rebuild() form.rebuild()
if get_response(): 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: else:
update() update()
@ -670,7 +670,7 @@ class Workflow(StorableObject):
charset = get_publisher().site_charset charset = get_publisher().site_charset
workflow = cls() workflow = cls()
if tree.find('name') is None or not tree.find('name').text: 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 # if the tree we get is actually a ElementTree for real, we get its
# root element and go on happily. # root element and go on happily.
@ -678,7 +678,7 @@ class Workflow(StorableObject):
tree = tree.getroot() tree = tree.getroot()
if tree.tag != 'workflow': if tree.tag != 'workflow':
raise WorkflowImportError(N_('Not a workflow')) raise WorkflowImportError(_('Not a workflow'))
if include_id and tree.attrib.get('id'): if include_id and tree.attrib.get('id'):
workflow.id = tree.attrib.get('id') workflow.id = tree.attrib.get('id')
@ -807,18 +807,20 @@ class Workflow(StorableObject):
def get_default_workflow(cls): def get_default_workflow(cls):
from .qommon.admin.emails import EmailsDirectory 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.id = '_default'
workflow.roles = {'_receiver': _('Recipient')} workflow.roles = {'_receiver': force_text(_('Recipient'))}
just_submitted_status = workflow.add_status(_('Just Submitted'), 'just_submitted') just_submitted_status = workflow.add_status(force_text(_('Just Submitted')), 'just_submitted')
just_submitted_status.visibility = ['_receiver'] 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' new_status.colour = '66FF00'
rejected_status = workflow.add_status(_('Rejected'), 'rejected') rejected_status = workflow.add_status(force_text(_('Rejected')), 'rejected')
rejected_status.colour = 'FF3300' rejected_status.colour = 'FF3300'
accepted_status = workflow.add_status(_('Accepted'), 'accepted') accepted_status = workflow.add_status(force_text(_('Accepted')), 'accepted')
accepted_status.colour = '66CCFF' accepted_status.colour = '66CCFF'
finished_status = workflow.add_status(_('Finished'), 'finished') finished_status = workflow.add_status(force_text(_('Finished')), 'finished')
finished_status.colour = 'CCCCCC' finished_status.colour = 'CCCCCC'
commentable = CommentableWorkflowStatusItem() commentable = CommentableWorkflowStatusItem()
@ -906,7 +908,7 @@ class Workflow(StorableObject):
accept = ChoiceWorkflowStatusItem() accept = ChoiceWorkflowStatusItem()
accept.id = '_accept' accept.id = '_accept'
accept.label = _('Accept') accept.label = force_text(_('Accept'))
accept.by = ['_receiver'] accept.by = ['_receiver']
accept.status = accepted_status.id accept.status = accepted_status.id
accept.parent = new_status accept.parent = new_status
@ -914,7 +916,7 @@ class Workflow(StorableObject):
reject = ChoiceWorkflowStatusItem() reject = ChoiceWorkflowStatusItem()
reject.id = '_reject' reject.id = '_reject'
reject.label = _('Reject') reject.label = force_text(_('Reject'))
reject.by = ['_receiver'] reject.by = ['_receiver']
reject.status = rejected_status.id reject.status = rejected_status.id
reject.parent = new_status reject.parent = new_status
@ -922,7 +924,7 @@ class Workflow(StorableObject):
finish = ChoiceWorkflowStatusItem() finish = ChoiceWorkflowStatusItem()
finish.id = '_finish' finish.id = '_finish'
finish.label = _('Finish') finish.label = force_text(_('Finish'))
finish.by = ['_receiver'] finish.by = ['_receiver']
finish.status = finished_status.id finish.status = finished_status.id
finish.parent = accepted_status finish.parent = accepted_status
@ -1074,7 +1076,7 @@ class XmlSerialisable:
# if the roles are managed by the idp, don't try further. # 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: 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 # and if there's no match, create a new role
role = get_publisher().role_class() role = get_publisher().role_class()
@ -1916,7 +1918,7 @@ class WorkflowStatusItem(XmlSerialisable):
return changed return changed
def render_as_line(self): def render_as_line(self):
label = _(self.description) label = self.description
details = self.get_line_details() details = self.get_line_details()
if details: if details:
label += ' (%s)' % 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 if not targets and formdata: # do not log in presentation context: formdata is needed
message = _( message = _(
'reference to invalid status %(target)s in status %(status)s, ' 'action %(status_item)s' '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) get_publisher().record_error(message, formdata=formdata, status_item=self)
return targets return targets
@ -2212,9 +2214,9 @@ class WorkflowStatusItem(XmlSerialisable):
roles = self.parent.parent.render_list_of_roles(self.by) roles = self.parent.parent.render_list_of_roles(self.by)
label += ' %s %s' % (_('by'), roles) label += ' %s %s' % (_('by'), roles)
if getattr(self, 'status', None) == '_previous': if getattr(self, 'status', None) == '_previous':
label += ' ' + _('(to last marker)') label += ' ' + str(_('(to last marker)'))
if getattr(self, 'set_marker_on_status', False): if getattr(self, 'set_marker_on_status', False):
label += ' ' + _('(and set marker)') label += ' ' + str(_('(and set marker)'))
else: else:
label = self.render_as_line() label = self.render_as_line()
return label return label
@ -2281,7 +2283,7 @@ class WorkflowStatusItem(XmlSerialisable):
value = xml_node_text(elem) value = xml_node_text(elem)
mail_template = MailTemplate.get_by_slug(value) mail_template = MailTemplate.get_by_slug(value)
if not mail_template: 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 self.mail_template = value
return return
@ -2471,7 +2473,7 @@ def register_item_class(klass):
class CommentableWorkflowStatusItem(WorkflowStatusItem): class CommentableWorkflowStatusItem(WorkflowStatusItem):
description = N_('Comment') description = _('Comment')
key = 'commentable' key = 'commentable'
category = 'interaction' category = 'interaction'
endpoint = False endpoint = False
@ -2542,11 +2544,11 @@ class CommentableWorkflowStatusItem(WorkflowStatusItem):
super().add_parameters_widgets(form, parameters, prefix=prefix, formdef=formdef, **kwargs) super().add_parameters_widgets(form, parameters, prefix=prefix, formdef=formdef, **kwargs)
if 'label' in parameters: if 'label' in parameters:
if self.label is None: if self.label is None:
self.label = _('Comment') self.label = str(_('Comment'))
form.add(StringWidget, '%slabel' % prefix, size=40, title=_('Label'), value=self.label) form.add(StringWidget, '%slabel' % prefix, size=40, title=_('Label'), value=self.label)
if 'button_label' in parameters: if 'button_label' in parameters:
if self.button_label == 0: if self.button_label == 0:
self.button_label = _('Add Comment') self.button_label = str(_('Add Comment'))
form.add( form.add(
StringWidget, StringWidget,
'%sbutton_label' % prefix, '%sbutton_label' % prefix,
@ -2610,7 +2612,7 @@ register_item_class(CommentableWorkflowStatusItem)
class ChoiceWorkflowStatusItem(WorkflowStatusJumpItem): class ChoiceWorkflowStatusItem(WorkflowStatusJumpItem):
description = N_('Manual Jump') description = _('Manual Jump')
key = 'choice' key = 'choice'
endpoint = False endpoint = False
waitpoint = True waitpoint = True
@ -2642,7 +2644,7 @@ class ChoiceWorkflowStatusItem(WorkflowStatusJumpItem):
if self.label and to_status: if self.label and to_status:
more = '' more = ''
if self.set_marker_on_status: if self.set_marker_on_status:
more += ' ' + _('(and set marker)') more += ' ' + str(_('(and set marker)'))
if self.by: if self.by:
return _('"%(label)s", to %(to)s, by %(by)s%(more)s') % { return _('"%(label)s", to %(to)s, by %(by)s%(more)s') % {
'label': self.get_label(), 'label': self.get_label(),
@ -2749,7 +2751,7 @@ register_item_class(ChoiceWorkflowStatusItem)
class JumpOnSubmitWorkflowStatusItem(WorkflowStatusJumpItem): class JumpOnSubmitWorkflowStatusItem(WorkflowStatusJumpItem):
description = N_('On Submit Jump') description = _('On Submit Jump')
key = 'jumponsubmit' key = 'jumponsubmit'
ok_in_global_action = False ok_in_global_action = False
@ -2777,7 +2779,7 @@ register_item_class(JumpOnSubmitWorkflowStatusItem)
class SendmailWorkflowStatusItem(WorkflowStatusItem): class SendmailWorkflowStatusItem(WorkflowStatusItem):
description = N_('Email') description = _('Email')
key = 'sendmail' key = 'sendmail'
category = 'interaction' category = 'interaction'
support_substitution_variables = True support_substitution_variables = True
@ -3061,7 +3063,7 @@ def template_on_context(context=None, template=None, **kwargs):
class SendSMSWorkflowStatusItem(WorkflowStatusItem): class SendSMSWorkflowStatusItem(WorkflowStatusItem):
description = N_('SMS') description = _('SMS')
key = 'sendsms' key = 'sendsms'
category = 'interaction' category = 'interaction'
support_substitution_variables = True support_substitution_variables = True
@ -3129,7 +3131,7 @@ register_item_class(SendSMSWorkflowStatusItem)
class DisplayMessageWorkflowStatusItem(WorkflowStatusItem): class DisplayMessageWorkflowStatusItem(WorkflowStatusItem):
description = N_('Alert') description = _('Alert')
key = 'displaymsg' key = 'displaymsg'
category = 'interaction' category = 'interaction'
support_substitution_variables = True support_substitution_variables = True
@ -3150,7 +3152,7 @@ class DisplayMessageWorkflowStatusItem(WorkflowStatusItem):
parts.append(_('with actions')) parts.append(_('with actions'))
if self.to: if self.to:
parts.append(_('for %s') % self.render_list_of_roles(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'): def get_message(self, filled, position='top'):
if not (self.message and self.position == position and filled.is_for_current_user(self.to)): 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): class RedirectToStatusWorkflowStatusItem(WorkflowStatusItem):
description = N_('Status Page Redirection') description = _('Status Page Redirection')
key = 'redirectstatus' key = 'redirectstatus'
ok_in_global_action = False ok_in_global_action = False
@ -3262,7 +3264,7 @@ class RedirectToStatusWorkflowStatusItem(WorkflowStatusItem):
class EditableWorkflowStatusItem(WorkflowStatusItem): class EditableWorkflowStatusItem(WorkflowStatusItem):
description = N_('Edition') description = _('Edition')
key = 'editable' key = 'editable'
category = 'formdata-action' category = 'formdata-action'
endpoint = False endpoint = False