backoffice: make most objects documentable (#19777)
This commit is contained in:
parent
3cd6f61a3c
commit
c82031b4d0
|
@ -685,3 +685,37 @@ def test_block_test_results(pub):
|
|||
resp.form['varname'] = 'test_3'
|
||||
resp = resp.form.submit('submit').follow()
|
||||
assert TestResult.count() == 1
|
||||
|
||||
|
||||
def test_block_documentation(pub):
|
||||
create_superuser(pub)
|
||||
|
||||
BlockDef.wipe()
|
||||
blockdef = FormDef()
|
||||
blockdef.name = 'block title'
|
||||
blockdef.fields = [fields.BoolField(id='1', label='Bool')]
|
||||
blockdef.store()
|
||||
|
||||
app = login(get_app(pub))
|
||||
|
||||
resp = app.get(blockdef.get_admin_url())
|
||||
assert resp.pyquery('.documentation[hidden]')
|
||||
resp = app.post_json(blockdef.get_admin_url() + 'update-documentation', {'content': '<p>doc</p>'})
|
||||
assert resp.json == {'err': 0, 'empty': False, 'changed': True}
|
||||
blockdef.refresh_from_storage()
|
||||
assert blockdef.documentation == '<p>doc</p>'
|
||||
resp = app.get(blockdef.get_admin_url())
|
||||
assert resp.pyquery('.documentation:not([hidden])')
|
||||
|
||||
resp = app.get(blockdef.get_admin_url() + 'fields/1/')
|
||||
assert resp.pyquery('.documentation[hidden]')
|
||||
assert resp.pyquery('#sidebar[hidden]')
|
||||
resp = app.post_json(
|
||||
blockdef.get_admin_url() + 'fields/1/update-documentation', {'content': '<p>doc</p>'}
|
||||
)
|
||||
assert resp.json == {'err': 0, 'empty': False, 'changed': True}
|
||||
blockdef.refresh_from_storage()
|
||||
assert blockdef.fields[0].documentation == '<p>doc</p>'
|
||||
resp = app.get(blockdef.get_admin_url() + 'fields/1/')
|
||||
assert resp.pyquery('.documentation:not([hidden])')
|
||||
assert resp.pyquery('#sidebar:not([hidden])')
|
||||
|
|
|
@ -1180,3 +1180,35 @@ def test_cards_management_options(pub):
|
|||
assert_option_display(resp, 'Templates', 'Custom')
|
||||
resp = resp.click('Management', href='options/management')
|
||||
assert resp.form['history_pane_default_mode'].value == 'expanded'
|
||||
|
||||
|
||||
def test_card_documentation(pub):
|
||||
create_superuser(pub)
|
||||
|
||||
CardDef.wipe()
|
||||
carddef = FormDef()
|
||||
carddef.name = 'card title'
|
||||
carddef.fields = [fields.BoolField(id='1', label='Bool')]
|
||||
carddef.store()
|
||||
|
||||
app = login(get_app(pub))
|
||||
|
||||
resp = app.get(carddef.get_admin_url())
|
||||
assert resp.pyquery('.documentation[hidden]')
|
||||
resp = app.post_json(carddef.get_admin_url() + 'update-documentation', {'content': '<p>doc</p>'})
|
||||
assert resp.json == {'err': 0, 'empty': False, 'changed': True}
|
||||
carddef.refresh_from_storage()
|
||||
assert carddef.documentation == '<p>doc</p>'
|
||||
resp = app.get(carddef.get_admin_url())
|
||||
assert resp.pyquery('.documentation:not([hidden])')
|
||||
|
||||
resp = app.get(carddef.get_admin_url() + 'fields/1/')
|
||||
assert resp.pyquery('.documentation[hidden]')
|
||||
assert resp.pyquery('#sidebar[hidden]')
|
||||
resp = app.post_json(carddef.get_admin_url() + 'fields/1/update-documentation', {'content': '<p>doc</p>'})
|
||||
assert resp.json == {'err': 0, 'empty': False, 'changed': True}
|
||||
carddef.refresh_from_storage()
|
||||
assert carddef.fields[0].documentation == '<p>doc</p>'
|
||||
resp = app.get(carddef.get_admin_url() + 'fields/1/')
|
||||
assert resp.pyquery('.documentation:not([hidden])')
|
||||
assert resp.pyquery('#sidebar:not([hidden])')
|
||||
|
|
|
@ -195,7 +195,6 @@ def test_data_sources_new(pub):
|
|||
resp = app.get('/backoffice/settings/data-sources/')
|
||||
resp = resp.click('New Data Source')
|
||||
resp.forms[0]['name'] = 'a new data source'
|
||||
resp.forms[0]['description'] = 'description of the data source'
|
||||
|
||||
resp.forms[0]['data_source$type'] = 'python'
|
||||
resp.forms[0]['data_source$value'] = repr([{'id': '1', 'text': 'un'}, {'id': '2', 'text': 'deux'}])
|
||||
|
@ -209,13 +208,11 @@ def test_data_sources_new(pub):
|
|||
assert 'Edit Data Source' in resp.text
|
||||
|
||||
assert NamedDataSource.get(1).name == 'a new data source'
|
||||
assert NamedDataSource.get(1).description == 'description of the data source'
|
||||
|
||||
# add a second one
|
||||
resp = app.get('/backoffice/settings/data-sources/')
|
||||
resp = resp.click('New Data Source')
|
||||
resp.forms[0]['name'] = 'an other data source'
|
||||
resp.forms[0]['description'] = 'description of the data source'
|
||||
resp.forms[0]['data_source$type'] = 'python'
|
||||
resp = resp.forms[0].submit('data_source$apply')
|
||||
resp.forms[0]['data_source$value'] = repr([{'id': '1', 'text': 'un'}, {'id': '2', 'text': 'deux'}])
|
||||
|
@ -408,7 +405,6 @@ def test_data_sources_category(pub):
|
|||
resp = app.get('/backoffice/settings/data-sources/categories/')
|
||||
resp = resp.click('New Category')
|
||||
resp.form['name'] = 'a new category'
|
||||
resp.form['description'] = 'description of the category'
|
||||
resp = resp.form.submit('submit')
|
||||
assert DataSourceCategory.count() == 1
|
||||
category = DataSourceCategory.select()[0]
|
||||
|
@ -440,7 +436,6 @@ def test_data_sources_category(pub):
|
|||
resp = app.get('/backoffice/settings/data-sources/categories/')
|
||||
resp = resp.click('New Category')
|
||||
resp.form['name'] = 'a second category'
|
||||
resp.form['description'] = 'description of the category'
|
||||
resp = resp.form.submit('submit')
|
||||
assert DataSourceCategory.count() == 2
|
||||
category2 = [x for x in DataSourceCategory.select() if x.id != category.id][0]
|
||||
|
@ -872,13 +867,10 @@ def test_data_sources_edit(pub):
|
|||
|
||||
resp = resp.click(href='edit')
|
||||
assert resp.forms[0]['name'].value == 'foobar'
|
||||
resp.forms[0]['description'] = 'data source description'
|
||||
resp = resp.forms[0].submit('submit')
|
||||
assert resp.location == 'http://example.net/backoffice/settings/data-sources/1/'
|
||||
resp = resp.follow()
|
||||
|
||||
assert NamedDataSource.get(1).description == 'data source description'
|
||||
|
||||
resp = app.get('/backoffice/settings/data-sources/1/edit')
|
||||
assert '>Data Attribute</label>' in resp.text
|
||||
assert '>Id Attribute</label>' in resp.text
|
||||
|
@ -1146,3 +1138,22 @@ def test_data_sources_agenda_refresh(mock_collect, pub, chrono_url):
|
|||
resp = resp.follow()
|
||||
assert 'Agendas will be updated in the background.' in resp.text
|
||||
assert NamedDataSource.count() == 2
|
||||
|
||||
|
||||
def test_datasource_documentation(pub):
|
||||
create_superuser(pub)
|
||||
|
||||
NamedDataSource.wipe()
|
||||
data_source = NamedDataSource(name='foobar')
|
||||
data_source.store()
|
||||
|
||||
app = login(get_app(pub))
|
||||
|
||||
resp = app.get(data_source.get_admin_url())
|
||||
assert resp.pyquery('.documentation[hidden]')
|
||||
resp = app.post_json(data_source.get_admin_url() + 'update-documentation', {'content': '<p>doc</p>'})
|
||||
assert resp.json == {'err': 0, 'empty': False, 'changed': True}
|
||||
data_source.refresh_from_storage()
|
||||
assert data_source.documentation == '<p>doc</p>'
|
||||
resp = app.get(data_source.get_admin_url())
|
||||
assert resp.pyquery('.documentation:not([hidden])')
|
||||
|
|
|
@ -5179,3 +5179,35 @@ def test_admin_form_sql_integrity_error(pub):
|
|||
== 'There are integrity errors in the database column types.'
|
||||
)
|
||||
assert resp.pyquery('.errornotice li').text() == 'String, expected: character varying, got: boolean.'
|
||||
|
||||
|
||||
def test_form_documentation(pub):
|
||||
create_superuser(pub)
|
||||
|
||||
FormDef.wipe()
|
||||
formdef = FormDef()
|
||||
formdef.name = 'form title'
|
||||
formdef.fields = [fields.BoolField(id='1', label='Bool')]
|
||||
formdef.store()
|
||||
|
||||
app = login(get_app(pub))
|
||||
|
||||
resp = app.get(formdef.get_admin_url())
|
||||
assert resp.pyquery('.documentation[hidden]')
|
||||
resp = app.post_json(formdef.get_admin_url() + 'update-documentation', {'content': '<p>doc</p>'})
|
||||
assert resp.json == {'err': 0, 'empty': False, 'changed': True}
|
||||
formdef.refresh_from_storage()
|
||||
assert formdef.documentation == '<p>doc</p>'
|
||||
resp = app.get(formdef.get_admin_url())
|
||||
assert resp.pyquery('.documentation:not([hidden])')
|
||||
|
||||
resp = app.get(formdef.get_admin_url() + 'fields/1/')
|
||||
assert resp.pyquery('.documentation[hidden]')
|
||||
assert resp.pyquery('#sidebar[hidden]')
|
||||
resp = app.post_json(formdef.get_admin_url() + 'fields/1/update-documentation', {'content': '<p>doc</p>'})
|
||||
assert resp.json == {'err': 0, 'empty': False, 'changed': True}
|
||||
formdef.refresh_from_storage()
|
||||
assert formdef.fields[0].documentation == '<p>doc</p>'
|
||||
resp = app.get(formdef.get_admin_url() + 'fields/1/')
|
||||
assert resp.pyquery('.documentation:not([hidden])')
|
||||
assert resp.pyquery('#sidebar:not([hidden])')
|
||||
|
|
|
@ -4429,3 +4429,112 @@ def test_workflow_test_results(pub):
|
|||
assert TestResult.count() == 2
|
||||
result = TestResult.select(order_by='id')[1]
|
||||
assert result.reason == 'Workflow: New status "new status"'
|
||||
|
||||
|
||||
def test_workflow_documentation(pub):
|
||||
create_superuser(pub)
|
||||
|
||||
Workflow.wipe()
|
||||
workflow = Workflow(name='Workflow One')
|
||||
status = workflow.add_status(name='New status')
|
||||
status.add_action('anonymise')
|
||||
workflow.backoffice_fields_formdef = WorkflowBackofficeFieldsFormDef(workflow)
|
||||
workflow.backoffice_fields_formdef.fields = [
|
||||
fields.StringField(id='bo234', label='bo field 1'),
|
||||
]
|
||||
workflow.variables_formdef = WorkflowVariablesFieldsFormDef(workflow=workflow)
|
||||
workflow.variables_formdef.fields = [
|
||||
fields.StringField(id='va123', label='bo field 1'),
|
||||
]
|
||||
global_action = workflow.add_global_action('action1')
|
||||
workflow.store()
|
||||
|
||||
app = login(get_app(pub))
|
||||
resp = app.get(workflow.get_admin_url())
|
||||
assert resp.pyquery('.documentation[hidden]')
|
||||
assert app.post_json(workflow.get_admin_url() + 'update-documentation', {}).json.get('err') == 1
|
||||
resp = app.post_json(workflow.get_admin_url() + 'update-documentation', {'content': ''})
|
||||
assert resp.json == {'err': 0, 'empty': True, 'changed': False}
|
||||
resp = app.post_json(workflow.get_admin_url() + 'update-documentation', {'content': '<p>doc</p>'})
|
||||
assert resp.json == {'err': 0, 'empty': False, 'changed': True}
|
||||
workflow.refresh_from_storage()
|
||||
assert workflow.documentation == '<p>doc</p>'
|
||||
|
||||
# check forbidden HTML is cleaned
|
||||
resp = app.post_json(
|
||||
workflow.get_admin_url() + 'update-documentation',
|
||||
{'content': '<p>iframe</p><iframe src="xx"></iframe>'},
|
||||
)
|
||||
assert resp.json == {'err': 0, 'empty': False, 'changed': True}
|
||||
workflow.refresh_from_storage()
|
||||
assert workflow.documentation == '<p>iframe</p>'
|
||||
|
||||
resp = app.get(workflow.get_admin_url())
|
||||
assert resp.pyquery('.documentation:not([hidden])')
|
||||
|
||||
resp = app.get(workflow.get_admin_url() + 'variables/fields/')
|
||||
assert resp.pyquery('.documentation[hidden]')
|
||||
resp = app.post_json(
|
||||
workflow.get_admin_url() + 'variables/fields/update-documentation', {'content': '<p>doc</p>'}
|
||||
)
|
||||
assert resp.json == {'err': 0, 'empty': False, 'changed': True}
|
||||
workflow.refresh_from_storage()
|
||||
assert workflow.variables_formdef.documentation == '<p>doc</p>'
|
||||
resp = app.get(workflow.get_admin_url() + 'variables/fields/')
|
||||
assert resp.pyquery('.documentation:not([hidden])')
|
||||
|
||||
resp = app.get(workflow.get_admin_url() + 'variables/fields/va123/')
|
||||
assert resp.pyquery('.documentation[hidden]')
|
||||
assert resp.pyquery('#sidebar[hidden]')
|
||||
resp = app.post_json(
|
||||
workflow.get_admin_url() + 'variables/fields/va123/update-documentation', {'content': '<p>doc</p>'}
|
||||
)
|
||||
assert resp.json == {'err': 0, 'empty': False, 'changed': True}
|
||||
workflow.refresh_from_storage()
|
||||
assert workflow.variables_formdef.fields[0].documentation == '<p>doc</p>'
|
||||
resp = app.get(workflow.get_admin_url() + 'variables/fields/va123/')
|
||||
assert resp.pyquery('.documentation:not([hidden])')
|
||||
assert resp.pyquery('#sidebar:not([hidden])')
|
||||
|
||||
resp = app.get(workflow.get_admin_url() + 'backoffice-fields/fields/')
|
||||
assert resp.pyquery('.documentation[hidden]')
|
||||
resp = app.post_json(
|
||||
workflow.get_admin_url() + 'backoffice-fields/fields/update-documentation', {'content': '<p>doc</p>'}
|
||||
)
|
||||
assert resp.json == {'err': 0, 'empty': False, 'changed': True}
|
||||
workflow.refresh_from_storage()
|
||||
assert workflow.backoffice_fields_formdef.documentation == '<p>doc</p>'
|
||||
resp = app.get(workflow.get_admin_url() + 'backoffice-fields/fields/')
|
||||
assert resp.pyquery('.documentation:not([hidden])')
|
||||
|
||||
resp = app.get(workflow.get_admin_url() + 'backoffice-fields/fields/bo234/')
|
||||
assert resp.pyquery('.documentation[hidden]')
|
||||
assert resp.pyquery('#sidebar[hidden]')
|
||||
resp = app.post_json(
|
||||
workflow.get_admin_url() + 'backoffice-fields/fields/bo234/update-documentation',
|
||||
{'content': '<p>doc</p>'},
|
||||
)
|
||||
assert resp.json == {'err': 0, 'empty': False, 'changed': True}
|
||||
workflow.refresh_from_storage()
|
||||
assert workflow.backoffice_fields_formdef.fields[0].documentation == '<p>doc</p>'
|
||||
resp = app.get(workflow.get_admin_url() + 'backoffice-fields/fields/bo234/')
|
||||
assert resp.pyquery('.documentation:not([hidden])')
|
||||
assert resp.pyquery('#sidebar:not([hidden])')
|
||||
|
||||
resp = app.get(global_action.get_admin_url())
|
||||
assert resp.pyquery('.documentation[hidden]')
|
||||
resp = app.post_json(global_action.get_admin_url() + 'update-documentation', {'content': '<p>doc</p>'})
|
||||
assert resp.json == {'err': 0, 'empty': False, 'changed': True}
|
||||
workflow.refresh_from_storage()
|
||||
assert workflow.global_actions[0].documentation == '<p>doc</p>'
|
||||
resp = app.get(global_action.get_admin_url())
|
||||
assert resp.pyquery('.documentation:not([hidden])')
|
||||
|
||||
resp = app.get(status.get_admin_url())
|
||||
assert resp.pyquery('.documentation[hidden]')
|
||||
resp = app.post_json(status.get_admin_url() + 'update-documentation', {'content': '<p>doc</p>'})
|
||||
assert resp.json == {'err': 0, 'empty': False, 'changed': True}
|
||||
workflow.refresh_from_storage()
|
||||
assert workflow.possible_status[0].documentation == '<p>doc</p>'
|
||||
resp = app.get(status.get_admin_url())
|
||||
assert resp.pyquery('.documentation:not([hidden])')
|
||||
|
|
|
@ -36,7 +36,6 @@ def teardown_module(module):
|
|||
def wscall():
|
||||
NamedWsCall.wipe()
|
||||
wscall = NamedWsCall(name='xxx')
|
||||
wscall.description = 'description'
|
||||
wscall.notify_on_errors = True
|
||||
wscall.record_on_errors = True
|
||||
wscall.request = {
|
||||
|
@ -68,7 +67,6 @@ def test_wscalls_new(pub, value):
|
|||
assert resp.form['notify_on_errors'].value is None
|
||||
assert resp.form['record_on_errors'].value == 'yes'
|
||||
resp.form['name'] = 'a new webservice call'
|
||||
resp.form['description'] = 'description'
|
||||
resp.form['notify_on_errors'] = value
|
||||
resp.form['record_on_errors'] = value
|
||||
resp.form['request$url'] = 'http://remote.example.net/json'
|
||||
|
@ -111,14 +109,12 @@ def test_wscalls_edit(pub, wscall):
|
|||
assert resp.form['notify_on_errors'].value == 'yes'
|
||||
assert resp.form['record_on_errors'].value == 'yes'
|
||||
assert 'slug' in resp.form.fields
|
||||
resp.form['description'] = 'bla bla bla'
|
||||
resp.form['notify_on_errors'] = False
|
||||
resp.form['record_on_errors'] = False
|
||||
resp = resp.form.submit('submit')
|
||||
assert resp.location == 'http://example.net/backoffice/settings/wscalls/xxx/'
|
||||
resp = resp.follow()
|
||||
|
||||
assert NamedWsCall.get('xxx').description == 'bla bla bla'
|
||||
assert NamedWsCall.get('xxx').notify_on_errors is False
|
||||
assert NamedWsCall.get('xxx').record_on_errors is False
|
||||
|
||||
|
@ -235,7 +231,6 @@ def test_wscalls_empty_param_values(pub):
|
|||
resp = app.get('/backoffice/settings/wscalls/')
|
||||
resp = resp.click('New webservice call')
|
||||
resp.form['name'] = 'a new webservice call'
|
||||
resp.form['description'] = 'description'
|
||||
resp.form['request$qs_data$element0key'] = 'foo'
|
||||
resp.form['request$post_data$element0key'] = 'bar'
|
||||
resp = resp.form.submit('submit').follow()
|
||||
|
@ -253,7 +248,6 @@ def test_wscalls_timeout(pub):
|
|||
resp = app.get('/backoffice/settings/wscalls/')
|
||||
resp = resp.click('New webservice call')
|
||||
resp.form['name'] = 'a new webservice call'
|
||||
resp.form['description'] = 'description'
|
||||
resp.form['request$timeout'] = 'plop'
|
||||
resp = resp.form.submit('submit')
|
||||
assert resp.pyquery('[data-widget-name="request$timeout"].widget-with-error')
|
||||
|
@ -300,3 +294,22 @@ def test_wscalls_usage(pub, wscall):
|
|||
formdef.store()
|
||||
resp = app.get(usage_url)
|
||||
assert 'No usage detected.' in resp.text
|
||||
|
||||
|
||||
def test_wscall_documentation(pub):
|
||||
create_superuser(pub)
|
||||
|
||||
NamedWsCall.wipe()
|
||||
wscall = NamedWsCall(name='foobar')
|
||||
wscall.store()
|
||||
|
||||
app = login(get_app(pub))
|
||||
|
||||
resp = app.get(wscall.get_admin_url())
|
||||
assert resp.pyquery('.documentation[hidden]')
|
||||
resp = app.post_json(wscall.get_admin_url() + 'update-documentation', {'content': '<p>doc</p>'})
|
||||
assert resp.json == {'err': 0, 'empty': False, 'changed': True}
|
||||
wscall.refresh_from_storage()
|
||||
assert wscall.documentation == '<p>doc</p>'
|
||||
resp = app.get(wscall.get_admin_url())
|
||||
assert resp.pyquery('.documentation:not([hidden])')
|
||||
|
|
|
@ -508,3 +508,40 @@ def test_comment_template_with_category(pub):
|
|||
export = ET.tostring(comment_template.export_to_xml(include_id=True))
|
||||
comment_template3 = CommentTemplate.import_from_xml_tree(ET.fromstring(export), include_id=True)
|
||||
assert comment_template3.category_id is None
|
||||
|
||||
|
||||
def test_comment_template_migration(pub):
|
||||
comment_template = CommentTemplate(name='test template')
|
||||
comment_template.description = 'hello'
|
||||
assert comment_template.migrate() is True
|
||||
assert not comment_template.description
|
||||
assert comment_template.documentation == 'hello'
|
||||
|
||||
|
||||
def test_comment_template_legacy_xml(pub):
|
||||
comment_template = CommentTemplate(name='test template')
|
||||
comment_template.documentation = 'hello'
|
||||
export = ET.tostring(export_to_indented_xml(comment_template))
|
||||
export = export.replace(b'documentation>', b'description>')
|
||||
|
||||
comment_template2 = CommentTemplate.import_from_xml_tree(ET.fromstring(export))
|
||||
comment_template2.store()
|
||||
comment_template2.refresh_from_storage()
|
||||
assert comment_template2.documentation
|
||||
|
||||
|
||||
def test_comment_template_documentation(pub, superuser):
|
||||
CommentTemplate.wipe()
|
||||
comment_template = CommentTemplate(name='foobar')
|
||||
comment_template.store()
|
||||
|
||||
app = login(get_app(pub))
|
||||
|
||||
resp = app.get(comment_template.get_admin_url())
|
||||
assert resp.pyquery('.documentation[hidden]')
|
||||
resp = app.post_json(comment_template.get_admin_url() + 'update-documentation', {'content': '<p>doc</p>'})
|
||||
assert resp.json == {'err': 0, 'empty': False, 'changed': True}
|
||||
comment_template.refresh_from_storage()
|
||||
assert comment_template.documentation == '<p>doc</p>'
|
||||
resp = app.get(comment_template.get_admin_url())
|
||||
assert resp.pyquery('.documentation:not([hidden])')
|
||||
|
|
|
@ -542,3 +542,40 @@ def test_mail_template_with_category(pub):
|
|||
export = ET.tostring(mail_template.export_to_xml(include_id=True))
|
||||
mail_template3 = MailTemplate.import_from_xml_tree(ET.fromstring(export), include_id=True)
|
||||
assert mail_template3.category_id is None
|
||||
|
||||
|
||||
def test_mail_template_migration(pub):
|
||||
mail_template = MailTemplate(name='test template')
|
||||
mail_template.description = 'hello'
|
||||
assert mail_template.migrate() is True
|
||||
assert not mail_template.description
|
||||
assert mail_template.documentation == 'hello'
|
||||
|
||||
|
||||
def test_mail_template_legacy_xml(pub):
|
||||
mail_template = MailTemplate(name='test template')
|
||||
mail_template.documentation = 'hello'
|
||||
export = ET.tostring(export_to_indented_xml(mail_template))
|
||||
export = export.replace(b'documentation>', b'description>')
|
||||
|
||||
mail_template2 = MailTemplate.import_from_xml_tree(ET.fromstring(export))
|
||||
mail_template2.store()
|
||||
mail_template2.refresh_from_storage()
|
||||
assert mail_template2.documentation
|
||||
|
||||
|
||||
def test_mail_template_documentation(pub, superuser):
|
||||
MailTemplate.wipe()
|
||||
mail_template = MailTemplate(name='foobar')
|
||||
mail_template.store()
|
||||
|
||||
app = login(get_app(pub))
|
||||
|
||||
resp = app.get(mail_template.get_admin_url())
|
||||
assert resp.pyquery('.documentation[hidden]')
|
||||
resp = app.post_json(mail_template.get_admin_url() + 'update-documentation', {'content': '<p>doc</p>'})
|
||||
assert resp.json == {'err': 0, 'empty': False, 'changed': True}
|
||||
mail_template.refresh_from_storage()
|
||||
assert mail_template.documentation == '<p>doc</p>'
|
||||
resp = app.get(mail_template.get_admin_url())
|
||||
assert resp.pyquery('.documentation:not([hidden])')
|
||||
|
|
|
@ -1101,3 +1101,38 @@ def test_import_root_node_error():
|
|||
excinfo.value.msg
|
||||
== 'Provided XML file is invalid, it starts with a <wrong_root_node> tag instead of <workflow>'
|
||||
)
|
||||
|
||||
|
||||
def test_documentation_attributes(pub):
|
||||
Workflow.wipe()
|
||||
workflow = Workflow(name='Workflow One')
|
||||
workflow.documentation = 'doc1'
|
||||
status = workflow.add_status(name='New status')
|
||||
status.documentation = 'doc2'
|
||||
action = status.add_action('anonymise')
|
||||
action.documentation = 'doc3'
|
||||
workflow.backoffice_fields_formdef = WorkflowBackofficeFieldsFormDef(workflow)
|
||||
workflow.backoffice_fields_formdef.documentation = 'doc4'
|
||||
workflow.backoffice_fields_formdef.fields = [
|
||||
StringField(id='bo234', label='bo field 1'),
|
||||
]
|
||||
workflow.backoffice_fields_formdef.fields[0].documentation = 'doc5'
|
||||
workflow.variables_formdef = WorkflowVariablesFieldsFormDef(workflow=workflow)
|
||||
workflow.variables_formdef.documentation = 'doc6'
|
||||
workflow.variables_formdef.fields = [
|
||||
StringField(id='va123', label='var field 1'),
|
||||
]
|
||||
workflow.variables_formdef.fields[0].documentation = 'doc7'
|
||||
global_action = workflow.add_global_action('action1')
|
||||
global_action.documentation = 'doc8'
|
||||
workflow.store()
|
||||
|
||||
wf2 = assert_import_export_works(workflow)
|
||||
assert wf2.documentation == 'doc1'
|
||||
assert wf2.possible_status[0].documentation == 'doc2'
|
||||
assert wf2.possible_status[0].items[0].documentation == 'doc3'
|
||||
assert wf2.backoffice_fields_formdef.documentation == 'doc4'
|
||||
assert wf2.backoffice_fields_formdef.fields[0].documentation == 'doc5'
|
||||
assert wf2.variables_formdef.documentation == 'doc6'
|
||||
assert wf2.variables_formdef.fields[0].documentation == 'doc7'
|
||||
assert wf2.global_actions[0].documentation == 'doc8'
|
||||
|
|
|
@ -55,6 +55,7 @@ class BlockDirectory(FieldsDirectory):
|
|||
'duplicate',
|
||||
('history', 'snapshots_dir'),
|
||||
'overwrite',
|
||||
('update-documentation', 'update_documentation'),
|
||||
]
|
||||
field_def_page_class = BlockFieldDefPage
|
||||
blacklisted_types = ['page', 'table', 'table-select', 'tablerows', 'ranked-items', 'blocks', 'computed']
|
||||
|
@ -113,11 +114,13 @@ class BlockDirectory(FieldsDirectory):
|
|||
r += htmltext('<li><a href="export">%s</a></li>') % _('Export')
|
||||
r += htmltext('<li><a href="delete" rel="popup">%s</a></li>') % _('Delete')
|
||||
r += htmltext('</ul>')
|
||||
r += self.get_documentable_button()
|
||||
r += htmltext('<a href="settings" rel="popup" role="button">%s</a>') % _('Settings')
|
||||
r += htmltext('</span>')
|
||||
r += htmltext('</div>')
|
||||
r += utils.last_modification_block(obj=self.objectdef)
|
||||
r += get_session().display_message()
|
||||
r += self.get_documentable_zone()
|
||||
|
||||
if not self.objectdef.fields:
|
||||
r += htmltext('<div class="infonotice">%s</div>') % _('There are not yet any fields defined.')
|
||||
|
|
|
@ -20,6 +20,7 @@ from quixote.html import TemplateIO, htmltext
|
|||
|
||||
from wcs.admin import utils
|
||||
from wcs.admin.categories import CommentTemplateCategoriesDirectory, get_categories
|
||||
from wcs.admin.documentable import DocumentableMixin
|
||||
from wcs.backoffice.applications import ApplicationsDirectory
|
||||
from wcs.backoffice.snapshots import SnapshotsDirectory
|
||||
from wcs.categories import CommentTemplateCategory
|
||||
|
@ -169,7 +170,7 @@ class CommentTemplatesDirectory(Directory):
|
|||
return redirect('%s/' % comment_template.id)
|
||||
|
||||
|
||||
class CommentTemplatePage(Directory):
|
||||
class CommentTemplatePage(Directory, DocumentableMixin):
|
||||
_q_exports = [
|
||||
'',
|
||||
'edit',
|
||||
|
@ -177,6 +178,7 @@ class CommentTemplatePage(Directory):
|
|||
'duplicate',
|
||||
'export',
|
||||
('history', 'snapshots_dir'),
|
||||
('update-documentation', 'update_documentation'),
|
||||
]
|
||||
do_not_call_in_templates = True
|
||||
|
||||
|
@ -187,6 +189,8 @@ class CommentTemplatePage(Directory):
|
|||
raise errors.TraversalError()
|
||||
get_response().breadcrumb.append((component + '/', self.comment_template.name))
|
||||
self.snapshots_dir = SnapshotsDirectory(self.comment_template)
|
||||
self.documented_object = self.comment_template
|
||||
self.documented_element = self.comment_template
|
||||
|
||||
def get_sidebar(self):
|
||||
r = TemplateIO(html=True)
|
||||
|
@ -247,14 +251,6 @@ class CommentTemplatePage(Directory):
|
|||
value=self.comment_template.category_id,
|
||||
)
|
||||
|
||||
form.add(
|
||||
TextWidget,
|
||||
'description',
|
||||
title=_('Description'),
|
||||
cols=80,
|
||||
rows=3,
|
||||
value=self.comment_template.description,
|
||||
)
|
||||
form.add(
|
||||
TextWidget,
|
||||
'comment',
|
||||
|
@ -307,7 +303,6 @@ class CommentTemplatePage(Directory):
|
|||
self.comment_template.name = name
|
||||
if form.get_widget('category_id'):
|
||||
self.comment_template.category_id = form.get_widget('category_id').parse()
|
||||
self.comment_template.description = form.get_widget('description').parse()
|
||||
self.comment_template.comment = form.get_widget('comment').parse()
|
||||
self.comment_template.attachments = form.get_widget('attachments').parse()
|
||||
if slug_widget:
|
||||
|
|
|
@ -20,6 +20,7 @@ from quixote.html import TemplateIO, htmltext
|
|||
|
||||
from wcs.admin import utils
|
||||
from wcs.admin.categories import DataSourceCategoriesDirectory, get_categories
|
||||
from wcs.admin.documentable import DocumentableMixin
|
||||
from wcs.backoffice.applications import ApplicationsDirectory
|
||||
from wcs.backoffice.snapshots import SnapshotsDirectory
|
||||
from wcs.carddef import CardDef
|
||||
|
@ -45,7 +46,6 @@ from wcs.qommon.form import (
|
|||
SingleSelectWidget,
|
||||
SlugWidget,
|
||||
StringWidget,
|
||||
TextWidget,
|
||||
WidgetDict,
|
||||
WidgetList,
|
||||
get_response,
|
||||
|
@ -73,14 +73,6 @@ class NamedDataSourceUI:
|
|||
options=category_options,
|
||||
value=self.datasource.category_id,
|
||||
)
|
||||
form.add(
|
||||
TextWidget,
|
||||
'description',
|
||||
title=_('Description'),
|
||||
cols=40,
|
||||
rows=5,
|
||||
value=self.datasource.description,
|
||||
)
|
||||
if not self.datasource or (
|
||||
self.datasource.type != 'wcs:users' and self.datasource.external != 'agenda_manual'
|
||||
):
|
||||
|
@ -297,7 +289,7 @@ class NamedDataSourceUI:
|
|||
self.datasource.store()
|
||||
|
||||
|
||||
class NamedDataSourcePage(Directory):
|
||||
class NamedDataSourcePage(Directory, DocumentableMixin):
|
||||
_q_exports = [
|
||||
'',
|
||||
'edit',
|
||||
|
@ -306,6 +298,7 @@ class NamedDataSourcePage(Directory):
|
|||
'duplicate',
|
||||
('history', 'snapshots_dir'),
|
||||
('preview-block', 'preview_block'),
|
||||
('update-documentation', 'update_documentation'),
|
||||
]
|
||||
do_not_call_in_templates = True
|
||||
|
||||
|
@ -319,6 +312,8 @@ class NamedDataSourcePage(Directory):
|
|||
self.datasource_ui = NamedDataSourceUI(self.datasource)
|
||||
get_response().breadcrumb.append((component + '/', self.datasource.name))
|
||||
self.snapshots_dir = SnapshotsDirectory(self.datasource)
|
||||
self.documented_object = self.datasource
|
||||
self.documented_element = self.datasource
|
||||
|
||||
def get_sidebar(self):
|
||||
r = TemplateIO(html=True)
|
||||
|
|
|
@ -0,0 +1,62 @@
|
|||
# w.c.s. - web application for online forms
|
||||
# Copyright (C) 2005-2024 Entr'ouvert
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
import json
|
||||
|
||||
from quixote import get_request, get_response
|
||||
from quixote.html import htmltext
|
||||
|
||||
from wcs.qommon import _, template
|
||||
from wcs.qommon.form import RichTextWidget
|
||||
|
||||
|
||||
class DocumentableMixin:
|
||||
def get_documentable_button(self):
|
||||
return htmltext(template.render('wcs/backoffice/includes/documentation-editor-link.html', {}))
|
||||
|
||||
def get_documentable_zone(self):
|
||||
return htmltext('<span class="actions">%s</span>') % template.render(
|
||||
'wcs/backoffice/includes/documentation.html',
|
||||
{'element': self.documented_element, 'object': self.documented_object},
|
||||
)
|
||||
|
||||
def update_documentation(self):
|
||||
get_request().ignore_session = True
|
||||
get_response().set_content_type('application/json')
|
||||
try:
|
||||
content = get_request().json['content']
|
||||
except (KeyError, TypeError):
|
||||
return json.dumps({'err': 1})
|
||||
content = RichTextWidget('').clean_html(content) or None
|
||||
changed = False
|
||||
if content != self.documented_element.documentation:
|
||||
changed = True
|
||||
self.documented_element.documentation = content
|
||||
self.documented_object.store(_('Documentation update'))
|
||||
return json.dumps(
|
||||
{'err': 0, 'empty': not bool(self.documented_element.documentation), 'changed': changed}
|
||||
)
|
||||
|
||||
|
||||
class DocumentableFieldMixin:
|
||||
def documentation_part(self):
|
||||
if not self.field.documentation:
|
||||
get_response().filter['sidebar_attrs'] = 'hidden'
|
||||
return template.render(
|
||||
'wcs/backoffice/includes/documentation.html',
|
||||
{'element': self.documented_element, 'object': self.documented_object},
|
||||
)
|
|
@ -26,18 +26,21 @@ from wcs.admin import utils
|
|||
from wcs.carddef import CardDef
|
||||
from wcs.fields import BlockField, get_field_options
|
||||
from wcs.formdef import FormDef, UpdateStatisticsDataAfterJob
|
||||
from wcs.qommon import _, errors, get_cfg, misc
|
||||
from wcs.qommon import _, errors, get_cfg, misc, template
|
||||
from wcs.qommon.admin.menu import command_icon
|
||||
from wcs.qommon.form import CheckboxWidget, Form, HtmlWidget, OptGroup, SingleSelectWidget, StringWidget
|
||||
from wcs.qommon.substitution import CompatibilityNamesDict
|
||||
|
||||
from .documentable import DocumentableFieldMixin, DocumentableMixin
|
||||
|
||||
class FieldDefPage(Directory):
|
||||
_q_exports = ['', 'delete', 'duplicate']
|
||||
|
||||
class FieldDefPage(Directory, DocumentableMixin, DocumentableFieldMixin):
|
||||
_q_exports = ['', 'delete', 'duplicate', ('update-documentation', 'update_documentation')]
|
||||
|
||||
large = False
|
||||
page_id = None
|
||||
blacklisted_attributes = []
|
||||
is_documentable = True
|
||||
|
||||
def __init__(self, objectdef, field_id):
|
||||
self.objectdef = objectdef
|
||||
|
@ -47,6 +50,8 @@ class FieldDefPage(Directory):
|
|||
raise errors.TraversalError()
|
||||
if not self.field.label:
|
||||
self.field.label = str(_('None'))
|
||||
self.documented_object = objectdef
|
||||
self.documented_element = self.field
|
||||
label = misc.ellipsize(self.field.unhtmled_label, 40)
|
||||
last_breadcrumb_url_part, last_breadcrumb_label = get_response().breadcrumb[-1]
|
||||
get_response().breadcrumb = get_response().breadcrumb[:-1]
|
||||
|
@ -67,7 +72,11 @@ class FieldDefPage(Directory):
|
|||
return form
|
||||
|
||||
def get_sidebar(self):
|
||||
return None
|
||||
if not self.is_documentable:
|
||||
return None
|
||||
r = TemplateIO(html=True)
|
||||
r += self.documentation_part()
|
||||
return r.getvalue()
|
||||
|
||||
def _q_index(self):
|
||||
form = self.form()
|
||||
|
@ -94,9 +103,15 @@ class FieldDefPage(Directory):
|
|||
get_response().set_title(self.objectdef.name)
|
||||
get_response().filter['sidebar'] = self.get_sidebar() # noqa pylint: disable=assignment-from-none
|
||||
r = TemplateIO(html=True)
|
||||
r += htmltext('<div id="appbar" class="field-edit">')
|
||||
r += htmltext('<h2 class="field-edit--title">%s</h2>') % misc.ellipsize(
|
||||
self.field.unhtmled_label, 80
|
||||
)
|
||||
if self.is_documentable:
|
||||
r += htmltext('<span class="actions">%s</span>') % template.render(
|
||||
'wcs/backoffice/includes/documentation-editor-link.html', {}
|
||||
)
|
||||
r += htmltext('</div>')
|
||||
if isinstance(self.field, BlockField):
|
||||
try:
|
||||
block_field = self.field.block
|
||||
|
@ -329,8 +344,15 @@ class FieldsPagesDirectory(Directory):
|
|||
return directory
|
||||
|
||||
|
||||
class FieldsDirectory(Directory):
|
||||
_q_exports = ['', 'update_order', 'move_page_fields', 'new', 'pages']
|
||||
class FieldsDirectory(Directory, DocumentableMixin):
|
||||
_q_exports = [
|
||||
'',
|
||||
'update_order',
|
||||
'move_page_fields',
|
||||
'new',
|
||||
'pages',
|
||||
('update-documentation', 'update_documentation'),
|
||||
]
|
||||
field_def_page_class = FieldDefPage
|
||||
blacklisted_types = []
|
||||
page_id = None
|
||||
|
@ -345,6 +367,8 @@ class FieldsDirectory(Directory):
|
|||
|
||||
def __init__(self, objectdef):
|
||||
self.objectdef = objectdef
|
||||
self.documented_object = self.objectdef
|
||||
self.documented_element = self.objectdef
|
||||
self.pages = FieldsPagesDirectory(self)
|
||||
|
||||
def _q_traverse(self, path):
|
||||
|
|
|
@ -69,6 +69,7 @@ from . import utils
|
|||
from .blocks import BlocksDirectory
|
||||
from .categories import CategoriesDirectory, get_categories
|
||||
from .data_sources import NamedDataSourcesDirectory
|
||||
from .documentable import DocumentableMixin
|
||||
from .fields import FieldDefPage, FieldsDirectory
|
||||
from .logged_errors import LoggedErrorsDirectory
|
||||
|
||||
|
@ -621,7 +622,7 @@ class WorkflowRoleDirectory(Directory):
|
|||
return redirect('..')
|
||||
|
||||
|
||||
class FormDefPage(Directory, TempfileDirectoryMixin):
|
||||
class FormDefPage(Directory, TempfileDirectoryMixin, DocumentableMixin):
|
||||
do_not_call_in_templates = True
|
||||
_q_exports = [
|
||||
'',
|
||||
|
@ -648,6 +649,7 @@ class FormDefPage(Directory, TempfileDirectoryMixin):
|
|||
('backoffice-submission-roles', 'backoffice_submission_roles'),
|
||||
('logged-errors', 'logged_errors_dir'),
|
||||
('history', 'snapshots_dir'),
|
||||
('update-documentation', 'update_documentation'),
|
||||
]
|
||||
|
||||
formdef_class = FormDef
|
||||
|
@ -691,6 +693,8 @@ class FormDefPage(Directory, TempfileDirectoryMixin):
|
|||
parent_dir=self, formdef_class=self.formdef_class, formdef_id=self.formdef.id
|
||||
)
|
||||
self.snapshots_dir = SnapshotsDirectory(self.formdef)
|
||||
self.documented_object = self.formdef
|
||||
self.documented_element = self.formdef
|
||||
|
||||
def add_option_line(self, link, label, current_value, popup=True):
|
||||
return htmltext(
|
||||
|
|
|
@ -20,6 +20,7 @@ from quixote.html import TemplateIO, htmltext
|
|||
|
||||
from wcs.admin import utils
|
||||
from wcs.admin.categories import MailTemplateCategoriesDirectory, get_categories
|
||||
from wcs.admin.documentable import DocumentableMixin
|
||||
from wcs.backoffice.applications import ApplicationsDirectory
|
||||
from wcs.backoffice.snapshots import SnapshotsDirectory
|
||||
from wcs.categories import MailTemplateCategory
|
||||
|
@ -167,7 +168,7 @@ class MailTemplatesDirectory(Directory):
|
|||
return redirect('%s/' % mail_template.id)
|
||||
|
||||
|
||||
class MailTemplatePage(Directory):
|
||||
class MailTemplatePage(Directory, DocumentableMixin):
|
||||
_q_exports = [
|
||||
'',
|
||||
'edit',
|
||||
|
@ -175,6 +176,7 @@ class MailTemplatePage(Directory):
|
|||
'duplicate',
|
||||
'export',
|
||||
('history', 'snapshots_dir'),
|
||||
('update-documentation', 'update_documentation'),
|
||||
]
|
||||
do_not_call_in_templates = True
|
||||
|
||||
|
@ -185,6 +187,8 @@ class MailTemplatePage(Directory):
|
|||
raise errors.TraversalError()
|
||||
get_response().breadcrumb.append((component + '/', self.mail_template.name))
|
||||
self.snapshots_dir = SnapshotsDirectory(self.mail_template)
|
||||
self.documented_object = self.mail_template
|
||||
self.documented_element = self.mail_template
|
||||
|
||||
def get_sidebar(self):
|
||||
r = TemplateIO(html=True)
|
||||
|
@ -242,15 +246,6 @@ class MailTemplatePage(Directory):
|
|||
options=category_options,
|
||||
value=self.mail_template.category_id,
|
||||
)
|
||||
|
||||
form.add(
|
||||
TextWidget,
|
||||
'description',
|
||||
title=_('Description'),
|
||||
cols=80,
|
||||
rows=3,
|
||||
value=self.mail_template.description,
|
||||
)
|
||||
form.add(
|
||||
StringWidget,
|
||||
'subject',
|
||||
|
@ -312,7 +307,6 @@ class MailTemplatePage(Directory):
|
|||
self.mail_template.name = name
|
||||
if form.get_widget('category_id'):
|
||||
self.mail_template.category_id = form.get_widget('category_id').parse()
|
||||
self.mail_template.description = form.get_widget('description').parse()
|
||||
self.mail_template.subject = form.get_widget('subject').parse()
|
||||
self.mail_template.body = form.get_widget('body').parse()
|
||||
self.mail_template.attachments = form.get_widget('attachments').parse()
|
||||
|
|
|
@ -131,6 +131,7 @@ authentication is unavailable. Lasso must be installed to use it.'
|
|||
|
||||
class UserFieldDefPage(FieldDefPage):
|
||||
blacklisted_attributes = ['condition']
|
||||
is_documentable = False
|
||||
|
||||
|
||||
class UserFieldsDirectory(FieldsDirectory):
|
||||
|
|
|
@ -67,6 +67,7 @@ from wcs.workflows import (
|
|||
from . import utils
|
||||
from .comment_templates import CommentTemplatesDirectory
|
||||
from .data_sources import NamedDataSourcesDirectory
|
||||
from .documentable import DocumentableFieldMixin, DocumentableMixin
|
||||
from .fields import FieldDefPage, FieldsDirectory
|
||||
from .logged_errors import LoggedErrorsDirectory
|
||||
from .mail_templates import MailTemplatesDirectory
|
||||
|
@ -440,8 +441,13 @@ class WorkflowUI:
|
|||
return workflow
|
||||
|
||||
|
||||
class WorkflowItemPage(Directory):
|
||||
_q_exports = ['', 'delete', 'copy']
|
||||
class WorkflowItemPage(Directory, DocumentableMixin):
|
||||
_q_exports = [
|
||||
'',
|
||||
'delete',
|
||||
'copy',
|
||||
('update-documentation', 'update_documentation'),
|
||||
]
|
||||
do_not_call_in_templates = True
|
||||
|
||||
def __init__(self, workflow, parent, component):
|
||||
|
@ -451,6 +457,8 @@ class WorkflowItemPage(Directory):
|
|||
raise errors.TraversalError()
|
||||
self.workflow = workflow
|
||||
self.parent = parent
|
||||
self.documented_object = self.workflow
|
||||
self.documented_element = self.item
|
||||
get_response().breadcrumb.append(('items/%s/' % component, self.item.description))
|
||||
|
||||
def _q_index(self):
|
||||
|
@ -495,6 +503,8 @@ class WorkflowItemPage(Directory):
|
|||
context = {
|
||||
'view': self,
|
||||
'html_form': form,
|
||||
'workflow': self.workflow,
|
||||
'has_sidebar': True,
|
||||
'action': self.item,
|
||||
'get_substitution_html_table': get_publisher().substitutions.get_substitution_html_table,
|
||||
}
|
||||
|
@ -673,7 +683,7 @@ class GlobalActionItemsDir(ToChildDirectory):
|
|||
klass = WorkflowItemPage
|
||||
|
||||
|
||||
class WorkflowStatusPage(Directory):
|
||||
class WorkflowStatusPage(Directory, DocumentableMixin):
|
||||
_q_exports = [
|
||||
'',
|
||||
'delete',
|
||||
|
@ -687,6 +697,7 @@ class WorkflowStatusPage(Directory):
|
|||
'fullscreen',
|
||||
('schema.svg', 'svg'),
|
||||
'svg',
|
||||
('update-documentation', 'update_documentation'),
|
||||
]
|
||||
do_not_call_in_templates = True
|
||||
|
||||
|
@ -698,6 +709,8 @@ class WorkflowStatusPage(Directory):
|
|||
raise errors.TraversalError()
|
||||
|
||||
self.items_dir = WorkflowItemsDir(workflow, self.status)
|
||||
self.documented_object = self.workflow
|
||||
self.documented_element = self.status
|
||||
get_response().breadcrumb.append(('status/%s/' % status_id, self.status.name))
|
||||
|
||||
def _q_index(self):
|
||||
|
@ -1087,9 +1100,16 @@ class WorkflowStatusDirectory(Directory):
|
|||
return r.getvalue()
|
||||
|
||||
|
||||
class WorkflowVariablesFieldDefPage(FieldDefPage):
|
||||
class WorkflowVariablesFieldDefPage(FieldDefPage, DocumentableFieldMixin):
|
||||
section = 'workflows'
|
||||
blacklisted_attributes = ['condition', 'prefill', 'display_locations', 'anonymise']
|
||||
has_documentation = True
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.workflow = self.objectdef.workflow
|
||||
self.documented_object = self.workflow
|
||||
self.documented_element = self.field
|
||||
|
||||
def form(self):
|
||||
form = super().form()
|
||||
|
@ -1115,7 +1135,7 @@ class WorkflowVariablesFieldDefPage(FieldDefPage):
|
|||
super().submit(form)
|
||||
|
||||
|
||||
class WorkflowBackofficeFieldDefPage(FieldDefPage):
|
||||
class WorkflowBackofficeFieldDefPage(FieldDefPage, DocumentableFieldMixin):
|
||||
section = 'workflows'
|
||||
blacklisted_attributes = ['condition']
|
||||
|
||||
|
@ -1128,21 +1148,23 @@ class WorkflowBackofficeFieldDefPage(FieldDefPage):
|
|||
continue
|
||||
if any(x.get('field_id') == self.field.id for x in action.fields or []):
|
||||
usage_actions.append(action)
|
||||
if not usage_actions:
|
||||
return
|
||||
|
||||
r = TemplateIO(html=True)
|
||||
r += htmltext('<div class="actions-using-this-field">')
|
||||
r += htmltext('<h3>%s</h3>') % _('Actions using this field')
|
||||
r += htmltext('<ul>')
|
||||
for action in usage_actions:
|
||||
label = _('"%s" action') % action.label if action.label else _('Action')
|
||||
if isinstance(action.parent, WorkflowGlobalAction):
|
||||
location = _('in global action "%s"') % action.parent.name
|
||||
else:
|
||||
location = _('in status "%s"') % action.parent.name
|
||||
r += htmltext(f'<li><a href="{action.get_admin_url()}">%s %s</a></li>') % (label, location)
|
||||
r += htmltext('</ul>')
|
||||
r += htmltext('<div>')
|
||||
r += self.documentation_part()
|
||||
if usage_actions:
|
||||
get_response().filter['sidebar_attrs'] = ''
|
||||
r += htmltext('<div class="actions-using-this-field">')
|
||||
r += htmltext('<h3>%s</h3>') % _('Actions using this field')
|
||||
r += htmltext('<ul>')
|
||||
for action in usage_actions:
|
||||
label = _('"%s" action') % action.label if action.label else _('Action')
|
||||
if isinstance(action.parent, WorkflowGlobalAction):
|
||||
location = _('in global action "%s"') % action.parent.name
|
||||
else:
|
||||
location = _('in status "%s"') % action.parent.name
|
||||
r += htmltext(f'<li><a href="{action.get_admin_url()}">%s %s</a></li>') % (label, location)
|
||||
r += htmltext('</ul>')
|
||||
r += htmltext('<div>')
|
||||
return r.getvalue()
|
||||
|
||||
def form(self):
|
||||
|
@ -1164,7 +1186,7 @@ class WorkflowBackofficeFieldDefPage(FieldDefPage):
|
|||
|
||||
|
||||
class WorkflowVariablesFieldsDirectory(FieldsDirectory):
|
||||
_q_exports = ['', 'update_order', 'new']
|
||||
_q_exports = ['', 'update_order', 'new', ('update-documentation', 'update_documentation')]
|
||||
|
||||
section = 'workflows'
|
||||
field_def_page_class = WorkflowVariablesFieldDefPage
|
||||
|
@ -1181,8 +1203,12 @@ class WorkflowVariablesFieldsDirectory(FieldsDirectory):
|
|||
|
||||
def index_top(self):
|
||||
r = TemplateIO(html=True)
|
||||
r += htmltext('<div id="appbar">')
|
||||
r += htmltext('<h2>%s - %s - %s</h2>') % (_('Workflow'), self.objectdef.name, _('Variables'))
|
||||
r += htmltext('<span class="actions">%s</span>') % self.get_documentable_button()
|
||||
r += htmltext('</div>')
|
||||
r += get_session().display_message()
|
||||
r += self.get_documentable_zone()
|
||||
if not self.objectdef.fields:
|
||||
r += htmltext('<p>%s</p>') % _('There are not yet any variables.')
|
||||
return r.getvalue()
|
||||
|
@ -1192,7 +1218,7 @@ class WorkflowVariablesFieldsDirectory(FieldsDirectory):
|
|||
|
||||
|
||||
class WorkflowBackofficeFieldsDirectory(FieldsDirectory):
|
||||
_q_exports = ['', 'update_order', 'new']
|
||||
_q_exports = ['', 'update_order', 'new', ('update-documentation', 'update_documentation')]
|
||||
|
||||
section = 'workflows'
|
||||
field_def_page_class = WorkflowBackofficeFieldDefPage
|
||||
|
@ -1207,10 +1233,19 @@ class WorkflowBackofficeFieldsDirectory(FieldsDirectory):
|
|||
fields_count_total_soft_limit = 40
|
||||
fields_count_total_hard_limit = 80
|
||||
|
||||
def __init__(self, objectdef):
|
||||
super().__init__(objectdef)
|
||||
self.documented_object = objectdef
|
||||
self.documented_element = objectdef
|
||||
|
||||
def index_top(self):
|
||||
r = TemplateIO(html=True)
|
||||
r += htmltext('<div id="appbar">')
|
||||
r += htmltext('<h2>%s - %s - %s</h2>') % (_('Workflow'), self.objectdef.name, _('Backoffice Fields'))
|
||||
r += htmltext('<span class="actions">%s</span>') % self.get_documentable_button()
|
||||
r += htmltext('</div>')
|
||||
r += get_session().display_message()
|
||||
r += self.get_documentable_zone()
|
||||
if not self.objectdef.fields:
|
||||
r += htmltext('<p>%s</p>') % _('There are not yet any backoffice fields.')
|
||||
return r.getvalue()
|
||||
|
@ -1431,6 +1466,7 @@ class GlobalActionPage(WorkflowStatusPage):
|
|||
('triggers', 'triggers_dir'),
|
||||
'update_triggers_order',
|
||||
'options',
|
||||
('update-documentation', 'update_documentation'),
|
||||
]
|
||||
|
||||
def __init__(self, workflow, action_id):
|
||||
|
@ -1442,6 +1478,8 @@ class GlobalActionPage(WorkflowStatusPage):
|
|||
self.status = self.action
|
||||
self.items_dir = GlobalActionItemsDir(workflow, self.action)
|
||||
self.triggers_dir = GlobalActionTriggersDir(workflow, self.action)
|
||||
self.documented_object = self.workflow
|
||||
self.documented_element = self.action
|
||||
|
||||
def _q_traverse(self, path):
|
||||
get_response().breadcrumb.append(
|
||||
|
@ -1619,7 +1657,7 @@ class GlobalActionsDirectory(Directory):
|
|||
return r.getvalue()
|
||||
|
||||
|
||||
class WorkflowPage(Directory):
|
||||
class WorkflowPage(Directory, DocumentableMixin):
|
||||
_q_exports = [
|
||||
'',
|
||||
'edit',
|
||||
|
@ -1643,6 +1681,7 @@ class WorkflowPage(Directory):
|
|||
('logged-errors', 'logged_errors_dir'),
|
||||
('history', 'snapshots_dir'),
|
||||
('fullscreen'),
|
||||
('update-documentation', 'update_documentation'),
|
||||
]
|
||||
do_not_call_in_templates = True
|
||||
|
||||
|
@ -1665,6 +1704,8 @@ class WorkflowPage(Directory):
|
|||
self.criticality_levels_dir = CriticalityLevelsDirectory(self.workflow)
|
||||
self.logged_errors_dir = LoggedErrorsDirectory(parent_dir=self, workflow_id=self.workflow.id)
|
||||
self.snapshots_dir = SnapshotsDirectory(self.workflow)
|
||||
self.documented_object = self.workflow
|
||||
self.documented_element = self.workflow
|
||||
if component:
|
||||
get_response().breadcrumb.append((component + '/', self.workflow.name))
|
||||
|
||||
|
|
|
@ -21,10 +21,11 @@ from quixote.directory import Directory
|
|||
from quixote.html import TemplateIO, htmltext
|
||||
|
||||
from wcs.admin import utils
|
||||
from wcs.admin.documentable import DocumentableMixin
|
||||
from wcs.backoffice.applications import ApplicationsDirectory
|
||||
from wcs.backoffice.snapshots import SnapshotsDirectory
|
||||
from wcs.qommon import _, errors, misc, template
|
||||
from wcs.qommon.form import CheckboxWidget, FileWidget, Form, HtmlWidget, SlugWidget, StringWidget, TextWidget
|
||||
from wcs.qommon.form import CheckboxWidget, FileWidget, Form, HtmlWidget, SlugWidget, StringWidget
|
||||
from wcs.utils import grep_strings
|
||||
from wcs.wscalls import NamedWsCall, NamedWsCallImportError, WsCallRequestWidget
|
||||
|
||||
|
@ -38,9 +39,6 @@ class NamedWsCallUI:
|
|||
def get_form(self):
|
||||
form = Form(enctype='multipart/form-data', use_tabs=True)
|
||||
form.add(StringWidget, 'name', title=_('Name'), required=True, size=30, value=self.wscall.name)
|
||||
form.add(
|
||||
TextWidget, 'description', title=_('Description'), cols=40, rows=5, value=self.wscall.description
|
||||
)
|
||||
if self.wscall.slug:
|
||||
form.add(
|
||||
SlugWidget,
|
||||
|
@ -100,7 +98,6 @@ class NamedWsCallUI:
|
|||
raise ValueError()
|
||||
|
||||
self.wscall.name = name
|
||||
self.wscall.description = form.get_widget('description').parse()
|
||||
self.wscall.notify_on_errors = form.get_widget('notify_on_errors').parse()
|
||||
self.wscall.record_on_errors = form.get_widget('record_on_errors').parse()
|
||||
self.wscall.request = form.get_widget('request').parse()
|
||||
|
@ -109,7 +106,7 @@ class NamedWsCallUI:
|
|||
self.wscall.store()
|
||||
|
||||
|
||||
class NamedWsCallPage(Directory):
|
||||
class NamedWsCallPage(Directory, DocumentableMixin):
|
||||
do_not_call_in_templates = True
|
||||
_q_exports = [
|
||||
'',
|
||||
|
@ -118,6 +115,7 @@ class NamedWsCallPage(Directory):
|
|||
'export',
|
||||
('history', 'snapshots_dir'),
|
||||
'usage',
|
||||
('update-documentation', 'update_documentation'),
|
||||
]
|
||||
|
||||
def __init__(self, component, instance=None):
|
||||
|
@ -128,6 +126,8 @@ class NamedWsCallPage(Directory):
|
|||
self.wscall_ui = NamedWsCallUI(self.wscall)
|
||||
get_response().breadcrumb.append((component + '/', self.wscall.name))
|
||||
self.snapshots_dir = SnapshotsDirectory(self.wscall)
|
||||
self.documented_object = self.wscall
|
||||
self.documented_element = self.wscall
|
||||
|
||||
def get_sidebar(self):
|
||||
r = TemplateIO(html=True)
|
||||
|
|
|
@ -60,11 +60,12 @@ class BlockDef(StorableObject):
|
|||
fields = None
|
||||
digest_template = None
|
||||
category_id = None
|
||||
documentation = None
|
||||
|
||||
SLUG_DASH = '_'
|
||||
|
||||
# declarations for serialization
|
||||
TEXT_ATTRIBUTES = ['name', 'slug', 'digest_template']
|
||||
TEXT_ATTRIBUTES = ['name', 'slug', 'digest_template', 'documentation']
|
||||
|
||||
def __init__(self, name=None, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
|
|
|
@ -34,7 +34,7 @@ class CommentTemplate(XmlStorableObject):
|
|||
|
||||
name = None
|
||||
slug = None
|
||||
description = None
|
||||
documentation = None
|
||||
comment = None
|
||||
attachments = []
|
||||
category_id = None
|
||||
|
@ -43,7 +43,8 @@ class CommentTemplate(XmlStorableObject):
|
|||
XML_NODES = [
|
||||
('name', 'str'),
|
||||
('slug', 'str'),
|
||||
('description', 'str'),
|
||||
('description', 'str'), # legacy
|
||||
('documentation', 'str'),
|
||||
('comment', 'str'),
|
||||
('attachments', 'str_list'),
|
||||
]
|
||||
|
@ -52,6 +53,16 @@ class CommentTemplate(XmlStorableObject):
|
|||
XmlStorableObject.__init__(self)
|
||||
self.name = name
|
||||
|
||||
def migrate(self):
|
||||
changed = False
|
||||
if getattr(self, 'description', None): # 2024-04-07
|
||||
self.documentation = getattr(self, 'description')
|
||||
self.description = None
|
||||
changed = True
|
||||
if changed:
|
||||
self.store(comment=_('Automatic update'), snapshot_store_user=False)
|
||||
return changed
|
||||
|
||||
@property
|
||||
def category(self):
|
||||
return CommentTemplateCategory.get(self.category_id, ignore_errors=True)
|
||||
|
@ -67,14 +78,16 @@ class CommentTemplate(XmlStorableObject):
|
|||
base_url = get_publisher().get_backoffice_url()
|
||||
return '%s/workflows/comment-templates/%s/' % (base_url, self.id)
|
||||
|
||||
def store(self, comment=None, application=None, *args, **kwargs):
|
||||
def store(self, comment=None, snapshot_store_user=True, application=None, *args, **kwargs):
|
||||
assert not self.is_readonly()
|
||||
if self.slug is None:
|
||||
# set slug if it's not yet there
|
||||
self.slug = self.get_new_slug()
|
||||
super().store(*args, **kwargs)
|
||||
if get_publisher().snapshot_class:
|
||||
get_publisher().snapshot_class.snap(instance=self, comment=comment, application=application)
|
||||
get_publisher().snapshot_class.snap(
|
||||
instance=self, store_user=snapshot_store_user, comment=comment, application=application
|
||||
)
|
||||
|
||||
def get_places_of_use(self):
|
||||
from wcs.workflows import Workflow
|
||||
|
|
|
@ -676,7 +676,7 @@ class NamedDataSource(XmlStorableObject):
|
|||
|
||||
name = None
|
||||
slug = None
|
||||
description = None
|
||||
documentation = None
|
||||
data_source = None
|
||||
cache_duration = None
|
||||
query_parameter = None
|
||||
|
@ -702,7 +702,8 @@ class NamedDataSource(XmlStorableObject):
|
|||
XML_NODES = [
|
||||
('name', 'str'),
|
||||
('slug', 'str'),
|
||||
('description', 'str'),
|
||||
('description', 'str'), # legacy
|
||||
('documentation', 'str'),
|
||||
('cache_duration', 'str'),
|
||||
('query_parameter', 'str'),
|
||||
('id_parameter', 'str'),
|
||||
|
@ -737,6 +738,11 @@ class NamedDataSource(XmlStorableObject):
|
|||
self.data_source['value'] = translate_url(publisher, url)
|
||||
changed = True
|
||||
|
||||
if getattr(self, 'description', None): # 2024-04-07
|
||||
self.documentation = getattr(self, 'description')
|
||||
self.description = None
|
||||
changed = True
|
||||
|
||||
if changed:
|
||||
self.store(comment=_('Automatic update'), snapshot_store_user=False)
|
||||
|
||||
|
|
|
@ -223,6 +223,7 @@ class Field:
|
|||
is_no_data_field = False
|
||||
can_include_in_listing = False
|
||||
available_for_filter = False
|
||||
documentation = None
|
||||
|
||||
# flag a field for removal by AnonymiseWorkflowStatusItem
|
||||
# possible values are final, intermediate, no.
|
||||
|
@ -232,7 +233,7 @@ class Field:
|
|||
|
||||
# declarations for serialization, they are mostly for legacy files,
|
||||
# new exports directly include typing attributes.
|
||||
TEXT_ATTRIBUTES = ['label', 'type', 'hint', 'varname', 'extra_css_class']
|
||||
TEXT_ATTRIBUTES = ['label', 'type', 'hint', 'varname', 'extra_css_class', 'documentation']
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
for k, v in kwargs.items():
|
||||
|
@ -320,7 +321,7 @@ class Field:
|
|||
|
||||
def export_to_xml(self, include_id=False):
|
||||
field = ET.Element('field')
|
||||
extra_fields = ['default_value'] # specific to workflow variables
|
||||
extra_fields = ['default_value', 'documentation'] # default_value is specific to workflow variables
|
||||
if include_id:
|
||||
extra_fields.append('id')
|
||||
ET.SubElement(field, 'type').text = self.key
|
||||
|
@ -359,7 +360,7 @@ class Field:
|
|||
return field
|
||||
|
||||
def init_with_xml(self, elem, include_id=False, snapshot=False):
|
||||
extra_fields = ['default_value'] # specific to workflow variables
|
||||
extra_fields = ['documentation', 'default_value'] # default_value is specific to workflow variables
|
||||
for attribute in self.get_admin_attributes() + extra_fields:
|
||||
el = elem.find(attribute)
|
||||
if hasattr(self, '%s_init_with_xml' % attribute):
|
||||
|
|
|
@ -186,6 +186,7 @@ class FormDef(StorableObject):
|
|||
drafts_lifespan = None
|
||||
drafts_max_per_user = None
|
||||
user_support = None
|
||||
documentation = None
|
||||
|
||||
geolocations = None
|
||||
history_pane_default_mode = 'expanded'
|
||||
|
@ -219,6 +220,7 @@ class FormDef(StorableObject):
|
|||
'drafts_lifespan',
|
||||
'drafts_max_per_user',
|
||||
'user_support',
|
||||
'documentation',
|
||||
]
|
||||
BOOLEAN_ATTRIBUTES = [
|
||||
'discussion',
|
||||
|
|
|
@ -34,7 +34,7 @@ class MailTemplate(XmlStorableObject):
|
|||
|
||||
name = None
|
||||
slug = None
|
||||
description = None
|
||||
documentation = None
|
||||
subject = None
|
||||
body = None
|
||||
attachments = []
|
||||
|
@ -44,7 +44,8 @@ class MailTemplate(XmlStorableObject):
|
|||
XML_NODES = [
|
||||
('name', 'str'),
|
||||
('slug', 'str'),
|
||||
('description', 'str'),
|
||||
('description', 'str'), # legacy
|
||||
('documentation', 'str'),
|
||||
('subject', 'str'),
|
||||
('body', 'str'),
|
||||
('attachments', 'str_list'),
|
||||
|
@ -54,6 +55,16 @@ class MailTemplate(XmlStorableObject):
|
|||
XmlStorableObject.__init__(self)
|
||||
self.name = name
|
||||
|
||||
def migrate(self):
|
||||
changed = False
|
||||
if getattr(self, 'description', None): # 2024-04-07
|
||||
self.documentation = getattr(self, 'description')
|
||||
self.description = None
|
||||
changed = True
|
||||
if changed:
|
||||
self.store(comment=_('Automatic update'), snapshot_store_user=False)
|
||||
return changed
|
||||
|
||||
@property
|
||||
def category(self):
|
||||
return MailTemplateCategory.get(self.category_id, ignore_errors=True)
|
||||
|
@ -69,14 +80,16 @@ class MailTemplate(XmlStorableObject):
|
|||
base_url = get_publisher().get_backoffice_url()
|
||||
return '%s/workflows/mail-templates/%s/' % (base_url, self.id)
|
||||
|
||||
def store(self, comment=None, application=None, *args, **kwargs):
|
||||
def store(self, comment=None, snapshot_store_user=True, application=None, *args, **kwargs):
|
||||
assert not self.is_readonly()
|
||||
if self.slug is None:
|
||||
# set slug if it's not yet there
|
||||
self.slug = self.get_new_slug()
|
||||
super().store(*args, **kwargs)
|
||||
if get_publisher().snapshot_class:
|
||||
get_publisher().snapshot_class.snap(instance=self, comment=comment, application=application)
|
||||
get_publisher().snapshot_class.snap(
|
||||
instance=self, store_user=snapshot_store_user, comment=comment, application=application
|
||||
)
|
||||
|
||||
def get_places_of_use(self):
|
||||
from wcs.workflows import Workflow
|
||||
|
|
|
@ -2681,49 +2681,49 @@ class WysiwygTextWidget(TextWidget):
|
|||
def get_plain_text_value(self):
|
||||
return misc.html2text(self.value)
|
||||
|
||||
def clean_html(self, value):
|
||||
try:
|
||||
from bleach.css_sanitizer import CSSSanitizer
|
||||
|
||||
css_sanitizer = CSSSanitizer(allowed_css_properties=self.ALL_STYLES)
|
||||
kwargs = {
|
||||
'css_sanitizer': css_sanitizer,
|
||||
}
|
||||
except ModuleNotFoundError:
|
||||
# bleach < 5
|
||||
kwargs = {'styles': self.ALL_STYLES}
|
||||
|
||||
cleaner = Cleaner(
|
||||
tags=getattr(self, 'allowed_tags', None) or self.ALL_TAGS,
|
||||
attributes=self.ALL_ATTRS,
|
||||
strip=True,
|
||||
strip_comments=False,
|
||||
filters=[
|
||||
partial(
|
||||
linkifier.LinkifyFilter,
|
||||
skip_tags=['pre'],
|
||||
parse_email=True,
|
||||
url_re=self.URL_RE,
|
||||
email_re=self.EMAIL_RE,
|
||||
)
|
||||
],
|
||||
**kwargs,
|
||||
)
|
||||
value = cleaner.clean(value).removeprefix('<br />').removesuffix('<br />')
|
||||
if not strip_tags(value).strip() and not ('<img' in value or '<hr' in value):
|
||||
value = ''
|
||||
return value
|
||||
|
||||
def _parse(self, request):
|
||||
TextWidget._parse(self, request, use_validation_function=False)
|
||||
if self.value:
|
||||
all_tags = self.ALL_TAGS[:]
|
||||
self.allowed_tags = self.ALL_TAGS[:]
|
||||
if get_publisher().get_site_option('ckeditor-allow-style-tag'):
|
||||
all_tags.append('style')
|
||||
self.allowed_tags.append('style')
|
||||
if get_publisher().get_site_option('ckeditor-allow-script-tag'):
|
||||
all_tags.append('script')
|
||||
self.allowed_tags.append('script')
|
||||
|
||||
try:
|
||||
from bleach.css_sanitizer import CSSSanitizer
|
||||
|
||||
css_sanitizer = CSSSanitizer(allowed_css_properties=self.ALL_STYLES)
|
||||
kwargs = {
|
||||
'css_sanitizer': css_sanitizer,
|
||||
}
|
||||
except ModuleNotFoundError:
|
||||
# bleach < 5
|
||||
kwargs = {'styles': self.ALL_STYLES}
|
||||
|
||||
cleaner = Cleaner(
|
||||
tags=all_tags,
|
||||
attributes=self.ALL_ATTRS,
|
||||
strip=True,
|
||||
strip_comments=False,
|
||||
filters=[
|
||||
partial(
|
||||
linkifier.LinkifyFilter,
|
||||
skip_tags=['pre'],
|
||||
parse_email=True,
|
||||
url_re=self.URL_RE,
|
||||
email_re=self.EMAIL_RE,
|
||||
)
|
||||
],
|
||||
**kwargs,
|
||||
)
|
||||
self.value = cleaner.clean(self.value)
|
||||
if self.value.startswith('<br />'):
|
||||
self.value = self.value[6:]
|
||||
if self.value.endswith('<br />'):
|
||||
self.value = self.value[:-6]
|
||||
if not strip_tags(self.value).strip() and not ('<img' in self.value or '<hr' in self.value):
|
||||
self.value = ''
|
||||
self.value = self.clean_html(self.value)
|
||||
|
||||
# unescape Django template tags
|
||||
def unquote_django(matchobj):
|
||||
|
|
|
@ -3243,3 +3243,83 @@ div.dataview.compact-dataview {
|
|||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
.ro-documentation {
|
||||
p:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
p:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.bo-block.documentation {
|
||||
position: relative;
|
||||
margin-bottom: 0;
|
||||
button.save {
|
||||
display: none;
|
||||
position: absolute;
|
||||
height: 2.5em;
|
||||
bottom: -1.25em;
|
||||
right: 0.5em;
|
||||
}
|
||||
&.active {
|
||||
button.save {
|
||||
display: block;
|
||||
}
|
||||
border-color: var(--primary-color);
|
||||
}
|
||||
}
|
||||
|
||||
aside .bo-block.documentation {
|
||||
// keep some space for godo toolbar
|
||||
margin-top: 5em;
|
||||
}
|
||||
|
||||
.focus-editor-link {
|
||||
&::before {
|
||||
font-family: FontAwesome;
|
||||
content: "\f040"; // pencil
|
||||
}
|
||||
}
|
||||
|
||||
.godo.html-edition,
|
||||
.godo.html-edition--show {
|
||||
--padding: 0.5em;
|
||||
outline: none;
|
||||
background: transparent;
|
||||
padding-bottom: 0;
|
||||
p:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.godo--editor {
|
||||
min-height: auto;
|
||||
border: none;
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.godo.html-edition--show {
|
||||
.godo--editor > :first-child {
|
||||
padding-top: var(--padding);
|
||||
}
|
||||
}
|
||||
|
||||
.documentation-save-marks {
|
||||
position: absolute;
|
||||
right: 0.5em;
|
||||
margin-top: -1.5em;
|
||||
span {
|
||||
visibility: hidden;
|
||||
margin-left: -0.5em;
|
||||
}
|
||||
}
|
||||
|
||||
#appbar.field-edit {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
#appbar > h2 {
|
||||
// always keep a bit of space, for documentation button
|
||||
max-width: calc(100% - 80px);
|
||||
}
|
||||
|
|
|
@ -519,4 +519,81 @@ $(function() {
|
|||
el.parentNode.classList.toggle('display-codepoints')
|
||||
})
|
||||
)
|
||||
|
||||
const documentation_block = document.querySelector('.bo-block.documentation')
|
||||
const editor = document.getElementById('documentation-editor')
|
||||
const editor_link = document.querySelector('.focus-editor-link')
|
||||
const title_byline = document.querySelector('.object--status-infos')
|
||||
const documentation_save_button = document.querySelector('.bo-block.documentation button.save')
|
||||
var clear_documentation_save_marks_timeout_id = null
|
||||
if (editor_link) {
|
||||
document.querySelector('#documentation-editor .godo--editor').setAttribute('contenteditable', 'false')
|
||||
|
||||
documentation_save_button.addEventListener('click', (e) => {
|
||||
editor.sourceContent = editor.getHTML()
|
||||
var documentation_message = Object()
|
||||
documentation_message['content'] = editor.sourceContent.innerHTML
|
||||
document.querySelector('.documentation-save-marks .mark-error').style.visibility = 'hidden'
|
||||
document.querySelector('.documentation-save-marks .mark-success').style.visibility = 'hidden'
|
||||
document.querySelector('.documentation-save-marks .mark-sent').style.visibility = 'visible'
|
||||
fetch(`${window.location.pathname}update-documentation`, {
|
||||
method: 'POST',
|
||||
headers: {'Accept': 'application/json', 'Content-Type': 'application/json'},
|
||||
body: JSON.stringify(documentation_message)
|
||||
}).then((response) => {
|
||||
if (! response.ok) {
|
||||
return
|
||||
}
|
||||
return response.json()
|
||||
}).then((json) => {
|
||||
if (json && json.err == 0) {
|
||||
if (json.changed) {
|
||||
document.querySelector('.documentation-save-marks .mark-success').style.visibility = 'visible'
|
||||
} else {
|
||||
document.querySelector('.documentation-save-marks .mark-sent').style.visibility = 'hidden'
|
||||
document.querySelector('.documentation-save-marks .mark-success').style.visibility = 'hidden'
|
||||
}
|
||||
if (json.empty) {
|
||||
document.querySelector('.bo-block.documentation').setAttribute('hidden', 'hidden')
|
||||
}
|
||||
} else {
|
||||
document.querySelector('.documentation-save-marks .mark-error').style.visibility = 'visible'
|
||||
}
|
||||
if (clear_documentation_save_marks_timeout_id) clearTimeout(clear_documentation_save_marks_timeout_id)
|
||||
clear_documentation_save_marks_timeout_id = setTimeout(
|
||||
function() {
|
||||
document.querySelector('.documentation-save-marks .mark-error').style.visibility = 'hidden'
|
||||
document.querySelector('.documentation-save-marks .mark-success').style.visibility = 'hidden'
|
||||
document.querySelector('.documentation-save-marks .mark-sent').style.visibility = 'hidden'
|
||||
}, 5000)
|
||||
})
|
||||
})
|
||||
|
||||
editor_link.addEventListener('click', (e) => {
|
||||
e.preventDefault()
|
||||
if (editor_link.getAttribute('aria-pressed') == 'true') {
|
||||
editor.validEdition()
|
||||
documentation_save_button.dispatchEvent(new Event('click'))
|
||||
documentation_block.classList.remove('active')
|
||||
document.querySelector('#documentation-editor .godo--editor').setAttribute('contenteditable', 'false')
|
||||
editor_link.setAttribute('aria-pressed', false)
|
||||
if (title_byline) title_byline.style.visibility = 'visible'
|
||||
} else {
|
||||
documentation_block.classList.add('active')
|
||||
document.querySelector('.bo-block.documentation').removeAttribute('hidden')
|
||||
if (document.querySelector('aside .bo-block.documentation')) {
|
||||
document.getElementById('sidebar').style.display = 'block'
|
||||
document.getElementById('sidebar').removeAttribute('hidden')
|
||||
if (document.getElementById('sticky-sidebar').style.display == 'none') {
|
||||
document.getElementById('sidebar-toggle').dispatchEvent(new Event('click'))
|
||||
}
|
||||
}
|
||||
if (title_byline) title_byline.style.visibility = 'hidden'
|
||||
editor_link.setAttribute('aria-pressed', true)
|
||||
document.querySelector('#documentation-editor .godo--editor').setAttribute('contenteditable', 'true')
|
||||
editor.showEdition()
|
||||
editor.view.focus()
|
||||
}
|
||||
})
|
||||
}
|
||||
});
|
||||
|
|
|
@ -3,6 +3,10 @@
|
|||
CKEDITOR.env.origIsCompatible = CKEDITOR.env.isCompatible;
|
||||
CKEDITOR.env.isCompatible = true;
|
||||
|
||||
/* do not turn all contenteditable into ckeditor, as some pages may have both
|
||||
* godo and ckeditor */
|
||||
CKEDITOR.disableAutoInline = true;
|
||||
|
||||
$(document).ready( function() {
|
||||
if (CKEDITOR.env.origIsCompatible == false) {
|
||||
/* bail out if ckeditor advertised itself as not supported */
|
||||
|
|
|
@ -52,7 +52,7 @@
|
|||
|
||||
{% block sidebar %}
|
||||
{% if sidebar or has_sidebar %}
|
||||
<aside id="sidebar">
|
||||
<aside id="sidebar" {% block sidebar-attrs %}{{ sidebar_attrs|default:"" }}{% endblock %}>
|
||||
<button id="sidebar-toggle" aria-label="{% trans "Toggle sidebar" %}">⁞</button>
|
||||
<div id="sticky-sidebar">
|
||||
{% block sidebar-content %}
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
|
||||
{{ publisher.get_request.session.display_message|safe }}
|
||||
{% include "wcs/backoffice/includes/sql-fields-integrity.html" %}
|
||||
{% include "wcs/backoffice/includes/documentation.html" with element=formdef object=formdef %}
|
||||
|
||||
<div class="bo-block">
|
||||
<h3>{% trans "Information" %}</h3>
|
||||
|
|
|
@ -5,14 +5,13 @@
|
|||
|
||||
{% block appbar-actions %}
|
||||
{% if not comment_template.is_readonly %}
|
||||
{% include "wcs/backoffice/includes/documentation-editor-link.html" %}
|
||||
<a href="edit">{% trans "Edit" %}</a>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% if comment_template.description %}
|
||||
<div class="bo-block">{{ comment_template.description }}</div>
|
||||
{% endif %}
|
||||
{% include "wcs/backoffice/includes/documentation.html" with element=comment_template object=comment_template %}
|
||||
|
||||
{% if comment_template.comment %}
|
||||
<div class="section">
|
||||
|
|
|
@ -5,14 +5,13 @@
|
|||
<h2>{% trans "Data Source" %} - {{ datasource.name }}</h2>
|
||||
<span class="actions">
|
||||
{% if not datasource.is_readonly %}
|
||||
{% include "wcs/backoffice/includes/documentation-editor-link.html" %}
|
||||
<a href="edit">{% trans "Edit" %}</a>
|
||||
{% endif %}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{% if datasource.description %}
|
||||
<div class="bo-block">{{ datasource.description }}</div>
|
||||
{% endif %}
|
||||
{% include "wcs/backoffice/includes/documentation.html" with element=datasource object=datasource %}
|
||||
|
||||
{% if datasource.data_source %}
|
||||
<div class="section">
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
<li><a href="export">{% trans "Export" %}</a></li>
|
||||
<li><a href="delete" rel="popup">{% trans "Delete" %}</a></li>
|
||||
</ul>
|
||||
{% include "wcs/backoffice/includes/documentation-editor-link.html" %}
|
||||
<a rel="popup" href="title">{% trans "change title" %}</a>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
@ -35,7 +36,9 @@
|
|||
</div>
|
||||
|
||||
{{ publisher.get_request.session.display_message|safe }}
|
||||
|
||||
{% include "wcs/backoffice/includes/sql-fields-integrity.html" %}
|
||||
{% include "wcs/backoffice/includes/documentation.html" with element=formdef object=formdef %}
|
||||
|
||||
<div class="bo-block">
|
||||
<h3>{% trans "Information" %}</h3>
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
{% load i18n %}
|
||||
<a class="focus-editor-link" title="{% trans "Edit documentation" %}"><span class="sr-only">{% trans "Edit documentation" %}</span></a>
|
|
@ -0,0 +1,21 @@
|
|||
{% load i18n %}
|
||||
<div class="bo-block documentation" {% if not element.documentation %}hidden{% endif %}>
|
||||
{% if object.is_readonly %}
|
||||
<div class="ro-documentation">{{ element.documentation|safe }}</div>
|
||||
{% else %}
|
||||
<script type="module" src="/static/xstatic/js/godo.js?{{version_hash}}"></script>
|
||||
<div class="documentation-save-marks">
|
||||
<span class="mark-error">✘</span>
|
||||
<span class="mark-success">✔</span>
|
||||
<span class="mark-sent">✔</span>
|
||||
</div>
|
||||
<div id="div-godo-source" >{{ element.documentation|default:"<p></p>"|safe }}</div>
|
||||
<godo-editor
|
||||
tabindex="0"
|
||||
linked-source="div-godo-source"
|
||||
heading-levels="3,4"
|
||||
id="documentation-editor"
|
||||
></godo-editor>
|
||||
<button class="save">{% trans "Save" %}</button>
|
||||
{% endif %}
|
||||
</div>
|
|
@ -5,14 +5,13 @@
|
|||
|
||||
{% block appbar-actions %}
|
||||
{% if not mail_template.is_readonly %}
|
||||
{% include "wcs/backoffice/includes/documentation-editor-link.html" %}
|
||||
<a href="edit">{% trans "Edit" %}</a>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% if mail_template.description %}
|
||||
<div class="bo-block">{{ mail_template.description }}</div>
|
||||
{% endif %}
|
||||
{% include "wcs/backoffice/includes/documentation.html" with element=mail_template object=mail_template %}
|
||||
|
||||
{% if mail_template.subject and mail_template.body %}
|
||||
<div class="section">
|
||||
|
|
|
@ -3,6 +3,12 @@
|
|||
|
||||
{% block appbar-title %}{{ action.description }}{% endblock %}
|
||||
|
||||
{% block appbar-actions %}
|
||||
{% if not workflow.is_readonly %}
|
||||
{% include "wcs/backoffice/includes/documentation-editor-link.html" %}
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{{ block.super }}
|
||||
|
||||
|
@ -13,3 +19,11 @@
|
|||
{% endif %}
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block sidebar-attrs %}
|
||||
{% if not action.documentation %}style="display: none"{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
{% block sidebar-content %}
|
||||
{% include "wcs/backoffice/includes/documentation.html" with element=action object=workflow %}
|
||||
{% endblock %}
|
||||
|
|
|
@ -11,12 +11,15 @@
|
|||
{% endif %}
|
||||
</ul>
|
||||
{% if not workflow.is_readonly %}
|
||||
{% include "wcs/backoffice/includes/documentation-editor-link.html" %}
|
||||
<a rel="popup" href="options">{% trans "Options" %}</a>
|
||||
<a rel="popup" href="edit">{% trans "Change Name" %}</a>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
{% include "wcs/backoffice/includes/documentation.html" with element=action object=workflow %}
|
||||
|
||||
<div class="bo-block">
|
||||
<h2>{% trans "Actions" %}</h2>
|
||||
{% if not action.items %}
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
{% endif %}
|
||||
</ul>
|
||||
{% if not workflow.is_readonly %}
|
||||
{% include "wcs/backoffice/includes/documentation-editor-link.html" %}
|
||||
<a href="options">{% trans "Options" %}</a>
|
||||
<a rel="popup" href="edit">{% trans "Change Name" %}</a>
|
||||
{% endif %}
|
||||
|
@ -19,6 +20,8 @@
|
|||
{% block content %}
|
||||
{{ block.super }}
|
||||
|
||||
{% include "wcs/backoffice/includes/documentation.html" with element=status object=workflow %}
|
||||
|
||||
{% with visibility_mode=status.get_visibility_mode %}
|
||||
{% if visibility_mode != 'all' %}
|
||||
<div class="bo-block">
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
{% endif %}
|
||||
</ul>
|
||||
{% if not workflow.is_readonly %}
|
||||
{% include "wcs/backoffice/includes/documentation-editor-link.html" %}
|
||||
<a rel="popup" href="category">{% trans "change category" %}</a>
|
||||
<a rel="popup" href="edit">{% trans "change title" %}</a>
|
||||
{% endif %}
|
||||
|
@ -22,6 +23,8 @@
|
|||
{{ view.last_modification_block|safe }}
|
||||
</div>
|
||||
|
||||
{% include "wcs/backoffice/includes/documentation.html" with element=workflow object=workflow %}
|
||||
|
||||
<div class="splitcontent-left">
|
||||
<div class="bo-block">
|
||||
<h3>{% trans "Possible Status" %}
|
||||
|
|
|
@ -5,14 +5,13 @@
|
|||
<h2>{% trans "Webservice Call" %} - {{ wscall.name }}</h2>
|
||||
{% if not wscall.is_readonly %}
|
||||
<span class="actions">
|
||||
{% include "wcs/backoffice/includes/documentation-editor-link.html" %}
|
||||
<a href="edit">{% trans "Edit" %}</a>
|
||||
</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{% if wscall.description %}
|
||||
<div class="bo-block">{{ wscall.description }}</div>
|
||||
{% endif %}
|
||||
{% include "wcs/backoffice/includes/documentation.html" with element=wscall object=wscall %}
|
||||
|
||||
<div class="bo-block">
|
||||
<h3>{% trans "Parameters" %}</h3>
|
||||
|
|
|
@ -118,6 +118,7 @@ class WorkflowFormFieldsFormDef(FormDef):
|
|||
class WorkflowFormFieldDefPage(FieldDefPage):
|
||||
section = 'workflows'
|
||||
blacklisted_attributes = ['display_locations', 'anonymise']
|
||||
is_documentable = False
|
||||
|
||||
def get_deletion_extra_warning(self):
|
||||
return None
|
||||
|
|
105
wcs/workflows.py
105
wcs/workflows.py
|
@ -714,8 +714,9 @@ class WorkflowVariablesFieldsFormDef(FormDef):
|
|||
self.workflow = workflow
|
||||
if self.workflow.is_readonly():
|
||||
self.readonly = True
|
||||
if workflow.variables_formdef and workflow.variables_formdef.fields:
|
||||
self.fields = self.workflow.variables_formdef.fields
|
||||
if workflow.variables_formdef:
|
||||
self.documentation = workflow.variables_formdef.documentation
|
||||
self.fields = self.workflow.variables_formdef.fields or []
|
||||
else:
|
||||
self.fields = []
|
||||
|
||||
|
@ -768,8 +769,9 @@ class WorkflowBackofficeFieldsFormDef(FormDef):
|
|||
def __init__(self, workflow):
|
||||
self.id = None
|
||||
self.workflow = workflow
|
||||
if workflow.backoffice_fields_formdef and workflow.backoffice_fields_formdef.fields:
|
||||
self.fields = self.workflow.backoffice_fields_formdef.fields
|
||||
if workflow.backoffice_fields_formdef:
|
||||
self.documentation = workflow.backoffice_fields_formdef.documentation
|
||||
self.fields = self.workflow.backoffice_fields_formdef.fields or []
|
||||
else:
|
||||
self.fields = []
|
||||
|
||||
|
@ -804,6 +806,7 @@ class Workflow(StorableObject):
|
|||
|
||||
name = None
|
||||
slug = None
|
||||
documentation = None
|
||||
possible_status = None
|
||||
roles = None
|
||||
variables_formdef = None
|
||||
|
@ -1207,9 +1210,10 @@ class Workflow(StorableObject):
|
|||
root = ET.Element('workflow')
|
||||
if include_id and self.id and not str(self.id).startswith('_'):
|
||||
root.attrib['id'] = str(self.id)
|
||||
ET.SubElement(root, 'name').text = self.name
|
||||
if self.slug:
|
||||
ET.SubElement(root, 'slug').text = self.slug
|
||||
for attr in ('name', 'slug', 'documentation'):
|
||||
value = getattr(self, attr, None)
|
||||
if value:
|
||||
ET.SubElement(root, attr).text = value
|
||||
|
||||
WorkflowCategory.object_category_xml_export(self, root, include_id=include_id)
|
||||
|
||||
|
@ -1238,6 +1242,8 @@ class Workflow(StorableObject):
|
|||
variables = ET.SubElement(root, 'variables')
|
||||
formdef = ET.SubElement(variables, 'formdef')
|
||||
ET.SubElement(formdef, 'name').text = '-' # required by formdef xml import
|
||||
if self.variables_formdef.documentation:
|
||||
ET.SubElement(formdef, 'documentation').text = self.variables_formdef.documentation
|
||||
fields = ET.SubElement(formdef, 'fields')
|
||||
for field in self.variables_formdef.fields:
|
||||
fields.append(field.export_to_xml(include_id=include_id))
|
||||
|
@ -1246,6 +1252,8 @@ class Workflow(StorableObject):
|
|||
variables = ET.SubElement(root, 'backoffice-fields')
|
||||
formdef = ET.SubElement(variables, 'formdef')
|
||||
ET.SubElement(formdef, 'name').text = '-' # required by formdef xml import
|
||||
if self.backoffice_fields_formdef.documentation:
|
||||
ET.SubElement(formdef, 'documentation').text = self.backoffice_fields_formdef.documentation
|
||||
fields = ET.SubElement(formdef, 'fields')
|
||||
for field in self.backoffice_fields_formdef.fields:
|
||||
fields.append(field.export_to_xml(include_id=include_id))
|
||||
|
@ -1297,8 +1305,9 @@ class Workflow(StorableObject):
|
|||
|
||||
workflow.name = xml_node_text(tree.find('name'))
|
||||
|
||||
if tree.find('slug') is not None:
|
||||
workflow.slug = xml_node_text(tree.find('slug'))
|
||||
for attribute in ('slug', 'documentation'):
|
||||
if tree.find(attribute) is not None:
|
||||
setattr(workflow, attribute, xml_node_text(tree.find(attribute)))
|
||||
|
||||
WorkflowCategory.object_category_xml_import(workflow, tree, include_id=include_id)
|
||||
|
||||
|
@ -1365,6 +1374,7 @@ class Workflow(StorableObject):
|
|||
raise WorkflowImportError(e.msg, details=e.details)
|
||||
else:
|
||||
workflow.variables_formdef = WorkflowVariablesFieldsFormDef(workflow=workflow)
|
||||
workflow.variables_formdef.documentation = imported_formdef.documentation
|
||||
workflow.variables_formdef.fields = imported_formdef.fields
|
||||
|
||||
variables = tree.find('backoffice-fields')
|
||||
|
@ -1381,6 +1391,7 @@ class Workflow(StorableObject):
|
|||
raise WorkflowImportError(e.msg, details=e.details)
|
||||
else:
|
||||
workflow.backoffice_fields_formdef = WorkflowBackofficeFieldsFormDef(workflow=workflow)
|
||||
workflow.backoffice_fields_formdef.documentation = imported_formdef.documentation
|
||||
workflow.backoffice_fields_formdef.fields = imported_formdef.fields
|
||||
|
||||
if unknown_referenced_objects_details:
|
||||
|
@ -1585,7 +1596,7 @@ class XmlSerialisable:
|
|||
node.attrib['type'] = self.key
|
||||
if include_id and getattr(self, 'id', None):
|
||||
node.attrib['id'] = self.id
|
||||
for attribute in self.get_parameters():
|
||||
for attribute in self.get_parameters() + ('documentation',):
|
||||
if getattr(self, '%s_export_to_xml' % attribute, None):
|
||||
getattr(self, '%s_export_to_xml' % attribute)(node, include_id=include_id)
|
||||
continue
|
||||
|
@ -1611,7 +1622,7 @@ class XmlSerialisable:
|
|||
def init_with_xml(self, elem, include_id=False, snapshot=False, check_datasources=True):
|
||||
if include_id and elem.attrib.get('id'):
|
||||
self.id = elem.attrib.get('id')
|
||||
for attribute in self.get_parameters():
|
||||
for attribute in self.get_parameters() + ('documentation',):
|
||||
el = elem.find(attribute)
|
||||
if getattr(self, '%s_init_with_xml' % attribute, None):
|
||||
getattr(self, '%s_init_with_xml' % attribute)(el, include_id=include_id, snapshot=snapshot)
|
||||
|
@ -2513,6 +2524,7 @@ class WorkflowGlobalAction(SerieOfActionsMixin):
|
|||
name = None
|
||||
triggers = None
|
||||
backoffice_info_text = None
|
||||
documentation = None
|
||||
|
||||
def __init__(self, name=None):
|
||||
self.name = name
|
||||
|
@ -2575,11 +2587,11 @@ class WorkflowGlobalAction(SerieOfActionsMixin):
|
|||
|
||||
def export_to_xml(self, include_id=False):
|
||||
status = ET.Element('action')
|
||||
ET.SubElement(status, 'id').text = self.id
|
||||
ET.SubElement(status, 'name').text = self.name
|
||||
|
||||
if self.backoffice_info_text:
|
||||
ET.SubElement(status, 'backoffice_info_text').text = self.backoffice_info_text
|
||||
for attr in ('id', 'name', 'backoffice_info_text', 'documentation'):
|
||||
value = getattr(self, attr, None)
|
||||
if value:
|
||||
ET.SubElement(status, attr).text = str(value)
|
||||
|
||||
items = ET.SubElement(status, 'items')
|
||||
for item in self.items:
|
||||
|
@ -2592,10 +2604,10 @@ class WorkflowGlobalAction(SerieOfActionsMixin):
|
|||
return status
|
||||
|
||||
def init_with_xml(self, elem, include_id=False, snapshot=False):
|
||||
self.id = xml_node_text(elem.find('id'))
|
||||
self.name = xml_node_text(elem.find('name'))
|
||||
if elem.find('backoffice_info_text') is not None:
|
||||
self.backoffice_info_text = xml_node_text(elem.find('backoffice_info_text'))
|
||||
for attr in ('id', 'name', 'backoffice_info_text', 'documentation'):
|
||||
node = elem.find(attr)
|
||||
if node is not None:
|
||||
setattr(self, attr, xml_node_text(node))
|
||||
|
||||
self.items = []
|
||||
for item in elem.find('items'):
|
||||
|
@ -2689,6 +2701,7 @@ class WorkflowStatus(SerieOfActionsMixin):
|
|||
forced_endpoint = False
|
||||
colour = '#FFFFFF'
|
||||
backoffice_info_text = None
|
||||
documentation = None
|
||||
extra_css_class = ''
|
||||
loop_items_template = None
|
||||
after_loop_status = None
|
||||
|
@ -2948,18 +2961,24 @@ class WorkflowStatus(SerieOfActionsMixin):
|
|||
|
||||
def export_to_xml(self, include_id=False):
|
||||
status = ET.Element('status')
|
||||
ET.SubElement(status, 'id').text = str(self.id)
|
||||
ET.SubElement(status, 'name').text = self.name
|
||||
ET.SubElement(status, 'colour').text = self.colour
|
||||
if self.extra_css_class:
|
||||
ET.SubElement(status, 'extra_css_class').text = self.extra_css_class
|
||||
|
||||
for attr in (
|
||||
'id',
|
||||
'name',
|
||||
'colour',
|
||||
'extra_css_class',
|
||||
'backoffice_info_text',
|
||||
'loop_items_template',
|
||||
'after_loop_status',
|
||||
'documentation',
|
||||
):
|
||||
value = getattr(self, attr, None)
|
||||
if value:
|
||||
ET.SubElement(status, attr).text = str(value)
|
||||
|
||||
if self.forced_endpoint:
|
||||
ET.SubElement(status, 'forced_endpoint').text = 'true'
|
||||
|
||||
if self.backoffice_info_text:
|
||||
ET.SubElement(status, 'backoffice_info_text').text = self.backoffice_info_text
|
||||
|
||||
visibility_node = ET.SubElement(status, 'visibility')
|
||||
for role in self.visibility or []:
|
||||
ET.SubElement(visibility_node, 'role').text = str(role)
|
||||
|
@ -2968,28 +2987,25 @@ class WorkflowStatus(SerieOfActionsMixin):
|
|||
for item in self.items:
|
||||
items.append(item.export_to_xml(include_id=include_id))
|
||||
|
||||
if self.loop_items_template:
|
||||
ET.SubElement(status, 'loop_items_template').text = self.loop_items_template
|
||||
if self.after_loop_status:
|
||||
ET.SubElement(status, 'after_loop_status').text = self.after_loop_status
|
||||
|
||||
return status
|
||||
|
||||
def init_with_xml(self, elem, include_id=False, snapshot=False, check_datasources=True):
|
||||
self.id = xml_node_text(elem.find('id'))
|
||||
self.name = xml_node_text(elem.find('name'))
|
||||
if elem.find('colour') is not None:
|
||||
self.colour = xml_node_text(elem.find('colour'))
|
||||
if elem.find('extra_css_class') is not None:
|
||||
self.extra_css_class = xml_node_text(elem.find('extra_css_class'))
|
||||
for attr in (
|
||||
'id',
|
||||
'name',
|
||||
'colour',
|
||||
'extra_css_class',
|
||||
'backoffice_info_text',
|
||||
'loop_items_template',
|
||||
'after_loop_status',
|
||||
'documentation',
|
||||
):
|
||||
node = elem.find(attr)
|
||||
if node is not None:
|
||||
setattr(self, attr, xml_node_text(node))
|
||||
|
||||
if elem.find('forced_endpoint') is not None:
|
||||
self.forced_endpoint = elem.find('forced_endpoint').text == 'true'
|
||||
if elem.find('backoffice_info_text') is not None:
|
||||
self.backoffice_info_text = xml_node_text(elem.find('backoffice_info_text'))
|
||||
if elem.find('loop_items_template') is not None:
|
||||
self.loop_items_template = xml_node_text(elem.find('loop_items_template'))
|
||||
if elem.find('after_loop_status') is not None:
|
||||
self.after_loop_status = xml_node_text(elem.find('after_loop_status'))
|
||||
|
||||
self.visibility = []
|
||||
for visibility_role in elem.findall('visibility/role'):
|
||||
|
@ -3047,6 +3063,7 @@ class WorkflowStatusItem(XmlSerialisable):
|
|||
category = None # (key, label)
|
||||
id = None
|
||||
condition = None
|
||||
documentation = None
|
||||
|
||||
endpoint = True # means it's not possible to interact, and/or cause a status change
|
||||
waitpoint = False # means it's possible to wait (user interaction, or other event)
|
||||
|
|
|
@ -250,7 +250,7 @@ class NamedWsCall(XmlStorableObject):
|
|||
|
||||
name = None
|
||||
slug = None
|
||||
description = None
|
||||
documentation = None
|
||||
request = None
|
||||
notify_on_errors = False
|
||||
record_on_errors = False
|
||||
|
@ -261,7 +261,8 @@ class NamedWsCall(XmlStorableObject):
|
|||
XML_NODES = [
|
||||
('name', 'str'),
|
||||
('slug', 'str'),
|
||||
('description', 'str'),
|
||||
('description', 'str'), # legacy
|
||||
('documentation', 'str'),
|
||||
('request', 'request'),
|
||||
('notify_on_errors', 'bool'),
|
||||
('record_on_errors', 'bool'),
|
||||
|
@ -271,6 +272,16 @@ class NamedWsCall(XmlStorableObject):
|
|||
XmlStorableObject.__init__(self)
|
||||
self.name = name
|
||||
|
||||
def migrate(self):
|
||||
changed = False
|
||||
if getattr(self, 'description', None): # 2024-04-07
|
||||
self.documentation = getattr(self, 'description')
|
||||
self.description = None
|
||||
changed = True
|
||||
if changed:
|
||||
self.store(comment=_('Automatic update'), snapshot_store_user=False)
|
||||
return changed
|
||||
|
||||
def get_admin_url(self):
|
||||
base_url = get_publisher().get_backoffice_url()
|
||||
return '%s/settings/wscalls/%s/' % (base_url, self.slug)
|
||||
|
@ -331,7 +342,7 @@ class NamedWsCall(XmlStorableObject):
|
|||
request['post_formdata'] = bool(element.find('post_formdata') is not None)
|
||||
return request
|
||||
|
||||
def store(self, comment=None, application=None, *args, **kwargs):
|
||||
def store(self, comment=None, snapshot_store_user=True, application=None, *args, **kwargs):
|
||||
assert not self.is_readonly()
|
||||
if self.slug is None:
|
||||
# set slug if it's not yet there
|
||||
|
@ -341,7 +352,9 @@ class NamedWsCall(XmlStorableObject):
|
|||
self.id = self.slug
|
||||
super().store(*args, **kwargs)
|
||||
if get_publisher().snapshot_class:
|
||||
get_publisher().snapshot_class.snap(instance=self, comment=comment, application=application)
|
||||
get_publisher().snapshot_class.snap(
|
||||
instance=self, comment=comment, store_user=snapshot_store_user, application=application
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def get_substitution_variables(cls):
|
||||
|
|
Loading…
Reference in New Issue