Compare commits

..

17 Commits

Author SHA1 Message Date
Pierre Ducroquet 01f74d5c4b redo search tokens creation
gitea/wcs/pipeline/head There was a failure building this commit Details
2024-02-29 13:11:12 +01:00
Pierre Ducroquet 4dd193988a also index SearchableFormDef 2024-02-29 13:11:12 +01:00
Pierre Ducroquet 1cfa7f7512 favor a perfect match 2024-02-29 13:11:12 +01:00
Pierre Ducroquet 561ae38d0c crude id and phone protection 2024-02-29 13:11:12 +01:00
Pierre Ducroquet ddf73d2127 fix migration 2024-02-29 13:11:12 +01:00
Pierre Ducroquet 0219846cd5 use wcs_tsquery everywhere 2024-02-29 13:11:12 +01:00
Pierre Ducroquet ed4ecaf8c6 fix 2024-02-29 13:11:12 +01:00
Pierre Ducroquet 2e5310b86d don't rely on PG14 2024-02-29 13:11:12 +01:00
Pierre Ducroquet ea3afc4d47 wcs_search_tokens: last part, search function using our tokens (#86527) 2024-02-29 13:11:12 +01:00
Pierre Ducroquet b90fb36db2 wcs_search_tokens: add purge cron job (#86527) 2024-02-29 13:11:12 +01:00
Pierre Ducroquet a86fa662c4 wcs_search_tokens: first part, create and maintain table (#86527) 2024-02-29 13:11:12 +01:00
Frédéric Péters 822010b131 translation update
gitea/wcs/pipeline/head This commit looks good Details
2024-02-29 10:53:30 +01:00
Frédéric Péters f355b9ca02 workflows: add option to get document model file using a template (#69689)
gitea/wcs/pipeline/head This commit looks good Details
2024-02-29 10:44:22 +01:00
Frédéric Péters 389a9bd165 portfolio: do not use publisher from request in afterjob (#74899)
gitea/wcs/pipeline/head This commit looks good Details
2024-02-29 10:43:53 +01:00
Valentin Deniaud 104c1c903a workflow_tests: add missing gettext calls (#87565)
gitea/wcs/pipeline/head This commit looks good Details
2024-02-28 17:25:13 +01:00
Valentin Deniaud 47c6188a40 workflow_tests: consider only jumps in skip time action (#87563)
gitea/wcs/pipeline/head This commit looks good Details
2024-02-28 16:12:10 +01:00
Valentin Deniaud e08aaca460 tests: add missing wipe for testdef (#87563) 2024-02-28 15:55:24 +01:00
12 changed files with 252 additions and 85 deletions

View File

@ -118,7 +118,8 @@ def test_workflow_inspect_page(pub):
resp = app.get('/backoffice/workflows/%s/inspect' % workflow.id)
assert (
'<span class="parameter">Model:</span> '
'<span class="parameter">Model:</span> File</li>'
'<li class="parameter-model_file">'
'<a href="status/st3/items/_export_to/?file=model_file">test.odt</a></li>'
) in resp.text

View File

@ -35,6 +35,8 @@ def pub():
BlockDef.wipe()
WebserviceResponse.wipe()
NamedWsCall.wipe()
TestResult.wipe()
TestDef.wipe()
return pub

View File

@ -230,6 +230,11 @@ def test_workflow_tests_automatic_jump_timeout(pub):
jump.timeout = 120 * 60 # 2 hours
jump.condition = {'type': 'django', 'value': 'form_receipt_datetime|age_in_days >= 1'}
sendmail = new_status.add_action('sendmail')
sendmail.to = ['test@example.org']
sendmail.subject = 'In new status'
sendmail.body = 'xxx'
workflow.store()
formdef = FormDef()

View File

@ -14,6 +14,7 @@ from webtest import Radio, Upload
from wcs import sessions
from wcs.blocks import BlockDef
from wcs.carddef import CardDef
from wcs.fields import (
BlockField,
BoolField,
@ -754,3 +755,80 @@ def test_workflows_edit_export_to_model_action_check_template(pub):
model_content = zip_out_fp.getvalue()
resp.form['model_file'] = Upload('test.odt', model_content)
resp.form.submit('submit').follow() # success
def test_export_to_model_from_template(pub):
CardDef.wipe()
carddef = CardDef()
carddef.name = 'card'
carddef.fields = [
FileField(id='1', label='File', varname='file'),
StringField(id='2', label='String', varname='string'),
]
carddef.store()
template_filename = os.path.join(os.path.dirname(__file__), '..', 'template.odt')
with open(template_filename, 'rb') as fd:
template = fd.read()
upload = QuixoteUpload('/foo/template.odt', content_type='application/octet-stream')
upload.fp = io.BytesIO()
upload.fp.write(template)
upload.fp.seek(0)
carddata = carddef.data_class()()
carddata.data = {'1': upload, '2': 'blah'}
carddata.just_created()
carddata.store()
wf = Workflow(name='test_export_to_model_from_template')
wf.backoffice_fields_formdef = WorkflowBackofficeFieldsFormDef(wf)
wf.backoffice_fields_formdef.fields = [
FileField(id='bo1', label='bo field 1', varname='backoffice_file1'),
]
st1 = wf.add_status('Status1')
wf.store()
formdef = FormDef()
formdef.name = 'foo-export'
formdef.fields = [
StringField(id='1', label='String', varname='string'),
]
formdef.workflow_id = wf.id
formdef.store()
formdata = formdef.data_class()()
formdata.data = {}
formdata.just_created()
formdata.store()
pub.substitutions.feed(formdata)
item = ExportToModel()
item.method = 'non-interactive'
item.convert_to_pdf = False
item.model_file_mode = 'template'
item.model_file_template = '{{cards|objects:"card"|first|get:"form_var_file" }}'
item.parent = st1
item.backoffice_filefield_id = 'bo1'
item.perform(formdata)
assert 'bo1' in formdata.data
fbo1 = formdata.data['bo1']
assert fbo1.base_filename == 'template.odt'
assert fbo1.content_type == 'application/octet-stream'
with zipfile.ZipFile(fbo1.get_file()) as zfile:
assert b'foo-export' in zfile.read('content.xml')
pub.loggederror_class.wipe()
item.model_file_template = '{{cards|objects:"card"|first|get:"form_var_string" }}'
formdata.data = {}
item.perform(formdata)
assert 'bo1' not in formdata.data
assert pub.loggederror_class.count() == 1
assert pub.loggederror_class.select()[0].summary == 'Invalid value obtained for model file (\'blah\')'
pub.loggederror_class.wipe()
item.model_file_template = '{% if foo %}{{ foo }}{% end %}' # invalid template
formdata.data = {}
item.perform(formdata)
assert 'bo1' not in formdata.data
assert pub.loggederror_class.count() == 1
assert pub.loggederror_class.select()[0].summary == 'Failed to evaluate template for action'

View File

@ -126,7 +126,12 @@ class FileField(WidgetField):
value = value.get_value() # unbox
if hasattr(value, 'base_filename'):
upload = PicklableUpload(value.base_filename, value.content_type or 'application/octet-stream')
upload.receive([value.get_content()])
if hasattr(value, 'get_content'):
upload.receive([value.get_content()])
else:
# native quixote Upload object
upload.receive([value.fp.read()])
value.fp.seek(0)
return upload
from wcs.workflows import NamedAttachmentsSubstitutionProxy

View File

@ -4,8 +4,8 @@ msgid ""
msgstr ""
"Project-Id-Version: wcs 0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-02-27 18:05+0100\n"
"PO-Revision-Date: 2024-02-27 18:05+0100\n"
"POT-Creation-Date: 2024-02-29 10:53+0100\n"
"PO-Revision-Date: 2024-02-29 10:53+0100\n"
"Last-Translator: Thomas Noël <tnoel@entrouvert.com>\n"
"Language-Team: french\n"
"Language: fr\n"
@ -313,7 +313,7 @@ msgstr "Nouveau bloc de champs"
#: admin/blocks.py admin/categories.py admin/comment_templates.py
#: admin/data_sources.py admin/forms.py admin/mail_templates.py
#: admin/settings.py admin/tests.py admin/workflows.py admin/wscalls.py
#: backoffice/data_management.py backoffice/i18n.py
#: backoffice/data_management.py backoffice/i18n.py wf/export_to_model.py
msgid "File"
msgstr "Fichier"
@ -1738,6 +1738,7 @@ msgid "Django Expression"
msgstr "Expression Django"
#: admin/logged_errors.py backoffice/management.py qommon/form.py
#: wf/export_to_model.py
msgid "Template"
msgstr "Gabarit"
@ -10027,7 +10028,7 @@ msgstr "Pas encore de résultats des tests."
#: templates/wcs/backoffice/test-webservice-responses.html
#: wf/assign_carddata.py wf/create_formdata.py wf/redirect_to_url.py
#: wf/roles.py
#: wf/roles.py workflow_tests.py
msgid "not configured"
msgstr "non configurée"
@ -11248,6 +11249,10 @@ msgstr "Création de document"
msgid "with model named %(file_name)s of %(size)s"
msgstr "avec le modèle %(file_name)s de %(size)s"
#: wf/export_to_model.py
msgid "with model from template"
msgstr "avec un modèle issu dun gabarit"
#: wf/export_to_model.py
msgid "no model set"
msgstr "aucun modèle défini"
@ -11276,10 +11281,6 @@ msgstr "Interactive (bouton)"
msgid "Non interactive"
msgstr "Non interactive"
#: wf/export_to_model.py
msgid "Available variables"
msgstr "Variables disponibles"
#: wf/export_to_model.py
msgid ""
"You can use variables in your model using the {{variable}} syntax, available "
@ -11288,13 +11289,17 @@ msgstr ""
"Vous pouvez utiliser des variables dans votre modèle avec la syntaxe "
"{{variable}}. Les variables disponibles dépendent du formulaire."
#: wf/export_to_model.py
msgid "Model"
msgstr "Modèle"
#: wf/export_to_model.py
msgid "Current value"
msgstr "Valeur actuelle"
#: wf/export_to_model.py
msgid "Model"
msgstr "Modèle"
msgid "Template to obtain model file"
msgstr "Gabarit pour obtenir le ficher servant de modèle"
#: wf/export_to_model.py
msgid "Convert generated file to PDF"
@ -11333,6 +11338,15 @@ msgstr "Erreur dans un gabarit lors de la création de document"
msgid "Error in template: %s"
msgstr "erreur dans le document modèle : %s"
#: wf/export_to_model.py
msgid "Failed to evaluate template for action"
msgstr "Erreur à lévaluation du gabarit pour laction"
#: wf/export_to_model.py
#, python-format
msgid "Invalid value obtained for model file (%r)"
msgstr "Valeur invalide obtenue pour le fichier modèle (%r)"
#: wf/external_workflow.py
#, python-format
msgid "Running external actions on \"%(label)s\" (%(count)s processed)"
@ -11927,6 +11941,11 @@ msgstr "Statut de la demande quand lerreur sest produite : %s"
msgid "Simulate click on action button"
msgstr "Clic sur un bouton daction"
#: workflow_tests.py
#, python-format
msgid "Click on \"%s\""
msgstr "Clic sur « %s »"
#: workflow_tests.py
#, python-format
msgid "Button \"%s\" is not displayed."
@ -11944,6 +11963,11 @@ msgstr "Texte du bouton"
msgid "Assert form status"
msgstr "Vérifier le statut de la demande"
#: workflow_tests.py
#, python-format
msgid "Status is \"%s\""
msgstr "Le statut est « %s »"
#: workflow_tests.py
#, python-format
msgid ""

View File

@ -62,13 +62,12 @@ class fargo_post_json_async:
def push_document(user, filename, stream):
if not user:
return
publisher = get_publisher()
payload = {}
if user.name_identifiers:
payload['user_nameid'] = force_str(user.name_identifiers[0], 'ascii')
elif user.email:
payload['user_email'] = force_str(user.email, 'ascii')
payload['origin'] = urllib.parse.urlparse(publisher.get_frontoffice_url()).netloc
payload['origin'] = urllib.parse.urlparse(get_publisher().get_frontoffice_url()).netloc
payload['file_name'] = filename
stream.seek(0)
payload['file_b64_content'] = force_str(base64.b64encode(stream.read()))
@ -78,9 +77,10 @@ def push_document(user, filename, stream):
status = 0
status, payload = async_post()
if status != 200:
publisher.record_error(
get_publisher().record_error(
_(
'file %(filename)r failed to be pushed to portfolio of %(display_name)r [status: %(status)d, payload: %(payload)r]'
'file %(filename)r failed to be pushed to portfolio of %(display_name)r '
'[status: %(status)d, payload: %(payload)r]'
)
% {
'filename': filename,

View File

@ -3131,3 +3131,11 @@ ul.objects-list.single-links li a.link-action-icon.duplicate {
content: "\f24d"; /* clone */
}
}
div[role="tabpanel"] > div.infonotice:first-child {
margin-top: 0;
}
form div.widget[data-widget-name="model_file_mode"] {
margin-bottom: 0;
}

View File

@ -1685,7 +1685,7 @@ def init_global_table(conn=None, cur=None):
def init_search_tokens(conn=None, cur=None):
own_cur = False
if not cur:
if cur is None:
own_cur = True
conn, cur = get_connection_and_cursor()

View File

@ -34,7 +34,7 @@ from quixote.errors import TraversalError
from quixote.html import htmltext
from quixote.http_request import Upload
from wcs.fields import CommentField, PageField, SubtitleField, TitleField
from wcs.fields import FileField
from wcs.portfolio import has_portfolio, push_document
from wcs.workflows import (
AttachmentEvolutionPart,
@ -51,6 +51,7 @@ from ..qommon.form import (
CheckboxWidget,
ComputedExpressionWidget,
FileWidget,
HtmlWidget,
RadiobuttonsWidget,
SingleSelectWidget,
StringWidget,
@ -198,24 +199,6 @@ class TemplatingError(TraversalError):
self.description = description
def get_varnames(fields):
"""Extract variable names for helping people fill their templates.
Prefer to variable name to the numeric field name.
"""
varnames = []
for field in fields:
if isinstance(field, (SubtitleField, TitleField, CommentField, PageField)):
continue
# add it as f$n$
label = field.label
if field.varname:
varnames.append(('var_%s' % field.varname, label))
else:
varnames.append(('f%s' % field.id, label))
return varnames
class ExportToModelDirectory(Directory):
_q_exports = ['']
@ -224,21 +207,22 @@ class ExportToModelDirectory(Directory):
self.wfstatusitem = wfstatusitem
def _q_index(self):
if not self.wfstatusitem.model_file:
if not (self.wfstatusitem.model_file or self.wfstatusitem.model_file_template):
raise TemplatingError(_('No model defined for this action'))
response = get_response()
model_file = self.wfstatusitem.get_model_file()
if self.wfstatusitem.convert_to_pdf:
response.content_type = 'application/pdf'
else:
response.content_type = self.wfstatusitem.model_file.content_type
response.content_type = model_file.content_type
response.set_header('location', '..')
filename = self.wfstatusitem.get_filename()
filename = self.wfstatusitem.get_filename(model_file)
if self.wfstatusitem.convert_to_pdf:
filename = filename.rsplit('.', 1)[0] + '.pdf'
if response.content_type != 'text/html':
response.set_header('content-disposition', 'attachment; filename="%s"' % filename)
return self.wfstatusitem.apply_template_to_formdata(self.formdata).read()
return self.wfstatusitem.apply_template_to_formdata(self.formdata, model_file).read()
class UploadValidationError(Exception):
@ -279,7 +263,9 @@ class ExportToModel(WorkflowStatusItem):
waitpoint = True
label = None
model_file_mode = 'file' # or 'template'
model_file = None
model_file_template = None
attach_to_history = False
directory_class = ExportToModelDirectory
by = ['_receiver']
@ -291,19 +277,26 @@ class ExportToModel(WorkflowStatusItem):
backoffice_filefield_id = None
def get_line_details(self):
if self.model_file:
if self.model_file and self.model_file_mode == 'file':
return _('with model named %(file_name)s of %(size)s') % {
'file_name': self.model_file.base_filename,
'size': filesizeformat(self.model_file.size),
}
elif self.model_file_template and self.model_file_mode == 'template':
return _('with model from template')
else:
return _('no model set')
def is_interactive(self):
return bool(self.method == 'interactive')
def has_configured_model_file(self):
return (self.model_file_mode == 'file' and self.model_file) or (
self.model_file_mode == 'template' and self.model_file_template
)
def fill_form(self, form, formdata, user, **kwargs):
if self.method != 'interactive' or not self.model_file:
if self.method != 'interactive' or not self.has_configured_model_file():
return
label = self.label
if not label:
@ -317,7 +310,7 @@ class ExportToModel(WorkflowStatusItem):
def submit_form(self, form, formdata, user, evo):
if self.method != 'interactive':
return
if not self.model_file:
if not self.has_configured_model_file():
return
if form.get_submit() == 'button%s' % self.id:
if not evo.comment:
@ -387,7 +380,7 @@ class ExportToModel(WorkflowStatusItem):
upload.fp.seek(0)
def get_parameters(self):
parameters = ('model_file',)
parameters = ('model_file_mode', 'model_file', 'model_file_template')
if transform_to_pdf is not None:
parameters += ('convert_to_pdf',)
parameters += ('varname', 'backoffice_filefield_id', 'attach_to_history')
@ -402,6 +395,10 @@ class ExportToModel(WorkflowStatusItem):
parameters.remove('by')
parameters.remove('label')
parameters.remove('backoffice_info_text')
if self.model_file_mode == 'file':
parameters.remove('model_file_template')
elif self.model_file_mode == 'template':
parameters.remove('model_file')
return parameters
def add_parameters_widgets(self, form, parameters, prefix='', formdef=None, **kwargs):
@ -410,46 +407,66 @@ class ExportToModel(WorkflowStatusItem):
methods = collections.OrderedDict(
[('interactive', _('Interactive (button)')), ('non-interactive', _('Non interactive'))]
)
if 'model_file' in parameters:
ids = (self.get_workflow().id, self.parent.id, self.id)
if formdef:
hint = htmltext('%s: <ul class="varnames">') % _('Available variables')
varnames = get_varnames(formdef.fields)
for pair in varnames:
hint += (
htmltext('<li><tt class="varname">{{ %s }}</tt> <label>%s</label></span></li>') % pair
)
hint += htmltext('</ul>')
ids = (formdef.id,) + ids
filename = 'export_to_model-%s-%s-%s-%s.upload' % ids
else:
hint = _(
if 'model_file_mode' in parameters:
form.add(
HtmlWidget,
name='note',
title=htmltext('<div class="infonotice">%s</div>')
% _(
'You can use variables in your model using '
'the {{variable}} syntax, available variables '
'depends on the form.'
)
filename = 'export_to_model-%s-%s-%s.upload' % ids
),
)
form.add(
RadiobuttonsWidget,
'%smodel_file_mode' % prefix,
title=_('Model'),
options=[('file', _('File'), 'file'), ('template', _('Template'), 'template')],
value=self.model_file_mode,
default_value=self.__class__.model_file_mode,
attrs={'data-dynamic-display-parent': 'true'},
extra_css_class='widget-inline-radio',
)
if 'model_file' in parameters:
ids = (self.get_workflow().id, self.parent.id, self.id)
filename = 'export_to_model-%s-%s-%s.upload' % ids
widget_name = '%smodel_file' % prefix
if formdef and formdef.workflow_options and formdef.workflow_options.get(widget_name) is not None:
value = formdef.workflow_options.get(widget_name)
else:
value = self.model_file
if value:
hint_prefix = htmltext('<div>%s: <a href="?file=%s">%s</a></div>') % (
hint = htmltext('<div>%s: <a href="?file=%s">%s</a></div>') % (
_('Current value'),
widget_name,
value.base_filename,
)
hint = hint_prefix + force_str(hint)
else:
hint = None
form.add(
ModelFileWidget,
widget_name,
directory='models',
filename=filename,
title=_('Model'),
hint=hint,
validation=self.model_file_content_validation,
value=value,
attrs={
'data-dynamic-display-child-of': '%smodel_file_mode' % prefix,
'data-dynamic-display-value': 'file',
},
)
if 'model_file_template' in parameters:
form.add(
ComputedExpressionWidget,
name='%smodel_file_template' % prefix,
title=_('Template to obtain model file'),
value=self.model_file_template,
attrs={
'data-dynamic-display-child-of': '%smodel_file_mode' % prefix,
'data-dynamic-display-value': 'template',
},
)
if 'convert_to_pdf' in parameters:
form.add(
@ -563,12 +580,12 @@ class ExportToModel(WorkflowStatusItem):
self.model_file.base_filename,
)
def get_filename(self):
def get_filename(self, model_file):
filename = None
if self.filename:
filename = self.compute(self.filename)
if not filename:
filename = self.model_file.base_filename
filename = model_file.base_filename
filename = filename.replace('/', '-')
return filename
@ -577,14 +594,14 @@ class ExportToModel(WorkflowStatusItem):
directory_name = property(get_directory_name)
def apply_template_to_formdata(self, formdata):
kind = self.model_file_validation(self.model_file)
def apply_template_to_formdata(self, formdata, model_file):
kind = self.model_file_validation(model_file)
if kind == 'rtf' and not get_publisher().has_site_option('disable-rtf-support'):
outstream = self.apply_rtf_template_to_formdata(formdata)
outstream = self.apply_rtf_template_to_formdata(formdata, model_file)
elif kind == 'opendocument':
outstream = self.apply_od_template_to_formdata(formdata)
outstream = self.apply_od_template_to_formdata(formdata, model_file)
elif kind == 'xml':
outstream = self.apply_text_template_to_formdata(formdata)
outstream = self.apply_text_template_to_formdata(formdata, model_file)
else:
raise Exception('unsupported model kind %r' % kind)
if kind == 'xml':
@ -606,17 +623,17 @@ class ExportToModel(WorkflowStatusItem):
return transform_to_pdf(outstream)
return outstream
def apply_text_template_to_formdata(self, formdata):
def apply_text_template_to_formdata(self, formdata, model_file):
return io.BytesIO(
force_bytes(
template_on_formdata(
formdata,
self.model_file.get_file().read().decode(errors='surrogateescape'),
model_file.get_file().read().decode(errors='surrogateescape'),
)
)
)
def apply_rtf_template_to_formdata(self, formdata):
def apply_rtf_template_to_formdata(self, formdata, model_file):
try:
# force ezt_only=True because an RTF file may contain {{ characters
# and would be seen as a Django template
@ -624,7 +641,7 @@ class ExportToModel(WorkflowStatusItem):
force_bytes(
template_on_formdata(
formdata,
force_str(self.model_file.get_file().read()),
force_str(model_file.get_file().read()),
ezt_format=ezt.FORMAT_RTF,
ezt_only=True,
)
@ -636,7 +653,7 @@ class ExportToModel(WorkflowStatusItem):
)
raise TemplatingError(_('Error in template: %s') % str(e))
def apply_od_template_to_formdata(self, formdata):
def apply_od_template_to_formdata(self, formdata, model_file):
context = get_formdata_template_context(formdata)
def process_styles(root):
@ -750,7 +767,7 @@ class ExportToModel(WorkflowStatusItem):
node.tail = current_tail
outstream = io.BytesIO()
transform_opendocument(self.model_file.get_file(), outstream, process_root)
transform_opendocument(model_file.get_file(), outstream, process_root)
outstream.seek(0)
return outstream
@ -892,12 +909,38 @@ class ExportToModel(WorkflowStatusItem):
return
self.perform_real(formdata, formdata.evolution[-1])
def get_model_file(self):
if self.model_file_mode == 'file':
return self.model_file
with get_publisher().complex_data():
try:
model_file = self.compute(
self.model_file_template, allow_complex=True, record_errors=False, raises=True
)
except Exception as e:
get_publisher().record_error(
_('Failed to evaluate template for action'), exception=e, status_item=self
)
return None
model_file = get_publisher().get_cached_complex_data(model_file)
try:
model_file = FileField.convert_value_from_anything(model_file)
except ValueError:
get_publisher().record_error(
_('Invalid value obtained for model file (%r)') % model_file, status_item=self
)
return None
return model_file
def perform_real(self, formdata, evo):
if not self.model_file:
if not self.has_configured_model_file():
return
outstream = self.apply_template_to_formdata(formdata)
filename = self.get_filename()
content_type = self.model_file.content_type
model_file = self.get_model_file()
if not model_file:
return
outstream = self.apply_template_to_formdata(formdata, model_file)
filename = self.get_filename(model_file)
content_type = model_file.content_type
if self.convert_to_pdf:
filename = filename.rsplit('.', 1)[0] + '.pdf'
content_type = 'application/pdf'

View File

@ -160,7 +160,7 @@ class WorkflowTestAction(XmlStorableObject):
def render_as_line(self):
for field, dummy in self.XML_NODES:
if field not in self.optional_fields and not getattr(self, field):
return 'not configured'
return _('not configured')
return self.details_label
@ -179,7 +179,7 @@ class ButtonClick(WorkflowTestAction):
@property
def details_label(self):
return 'Click on "%s"' % self.button_name
return _('Click on "%s"') % self.button_name
def perform(self, formdata, user):
status = formdata.get_status()
@ -227,7 +227,7 @@ class AssertStatus(WorkflowTestAction):
@property
def details_label(self):
return 'Status is "%s"' % self.status_name
return _('Status is "%s"') % self.status_name
def perform(self, formdata, user):
status = formdata.get_status()
@ -347,7 +347,7 @@ class SkipTime(WorkflowTestAction):
if hasattr(item, 'has_valid_timeout') and item.has_valid_timeout():
jump_actions.append(item)
delay = wf.jump.get_min_jumps_delay(status.items)
delay = wf.jump.get_min_jumps_delay(jump_actions)
if formdata.last_update_time > formdata.frozen_receipt_time - datetime.timedelta(seconds=delay):
return

View File

@ -3176,7 +3176,8 @@ class WorkflowStatusItem(XmlSerialisable):
if not widget:
continue
r += htmltext('<li class="parameter-%s">' % parameter)
r += htmltext('<span class="parameter">%s</span> ') % _('%s:') % widget.get_title()
if widget.get_title():
r += htmltext('<span class="parameter">%s</span> ') % _('%s:') % widget.get_title()
r += self.get_parameter_view_value(widget, parameter)
r += htmltext('</li>')
r += htmltext('</ul>')