workflows: add comment template management (#39178)

This commit is contained in:
Lauréline Guérin 2022-12-06 16:24:10 +01:00
parent c68eb965c7
commit 2152ddbe67
No known key found for this signature in database
GPG Key ID: 1FAB9B9B4F93D473
16 changed files with 1329 additions and 29 deletions

View File

@ -15,17 +15,21 @@ from webtest import Upload
from wcs import fields
from wcs.api_access import ApiAccess
from wcs.blocks import BlockDef
from wcs.carddef import CardDef
from wcs.categories import (
BlockCategory,
CardDefCategory,
Category,
CommentTemplateCategory,
DataSourceCategory,
MailTemplateCategory,
WorkflowCategory,
)
from wcs.comment_templates import CommentTemplate
from wcs.data_sources import NamedDataSource
from wcs.formdef import FormDef
from wcs.mail_templates import MailTemplate
from wcs.qommon.form import UploadedFile
from wcs.qommon.http_request import HTTPRequest
from wcs.wf.export_to_model import ExportToModel
@ -97,7 +101,11 @@ def test_settings_export_import(pub):
ApiAccess.wipe()
BlockCategory.wipe()
MailTemplateCategory.wipe()
CommentTemplateCategory.wipe()
DataSourceCategory.wipe()
MailTemplate.wipe()
CommentTemplate.wipe()
BlockDef.wipe()
wipe()
create_superuser(pub)
@ -132,9 +140,13 @@ def test_settings_export_import(pub):
WorkflowCategory(name='foobaz').store()
BlockCategory(name='category for blocks').store()
MailTemplateCategory(name='category for mail templates').store()
CommentTemplateCategory(name='category for mail templates').store()
DataSourceCategory(name='category for data sources').store()
MailTemplate(name='Mail templates').store()
CommentTemplate(name='Comment templates').store()
pub.role_class(name='qux').store()
NamedDataSource(name='quux').store()
BlockDef(name='blockdef').store()
ds = NamedDataSource(name='agenda')
ds.external = 'agenda'
ds.store()
@ -179,10 +191,13 @@ def test_settings_export_import(pub):
assert 'carddef_categories/1' in filelist
assert 'workflow_categories/1' in filelist
assert 'block_categories/1' in filelist
assert 'data_source_categories/1' in filelist
assert 'mail_template_categories/1' in filelist
assert 'comment_template_categories/1' in filelist
assert 'data_source_categories/1' in filelist
assert 'datasources/1' in filelist
assert 'datasources/2' not in filelist # agenda datasource, not exported
assert 'mail-templates/1' in filelist
assert 'comment-templates/1' in filelist
assert 'wscalls/corge' in filelist
assert 'apiaccess/1' in filelist
for filename in filelist:
@ -204,20 +219,42 @@ def test_settings_export_import(pub):
resp.form['file'] = Upload('export.wcs', zip_content.getvalue())
resp = resp.form.submit('submit')
assert 'Imported successfully' in resp.text
assert '1 form' in resp.text
assert '1 card' in resp.text
assert '2 categories' in resp.text
assert '1 card category' in resp.text
assert '1 workflow category' in resp.text
assert '1 form</li>' in resp.text
assert '1 card</li>' in resp.text
assert '1 fields block</li>' in resp.text
assert '1 workflow</li>' in resp.text
assert '1 role</li>' in resp.text
assert '2 categories</li>' in resp.text
assert '1 card category</li>' in resp.text
assert '1 workflow category</li>' in resp.text
assert '1 block category</li>' in resp.text
assert '1 mail template category</li>' in resp.text
assert '1 comment template category</li>' in resp.text
assert '1 data source category</li>' in resp.text
assert '1 data source</li>' in resp.text
assert '1 mail template</li>' in resp.text
assert '1 comment template</li>' in resp.text
assert '1 webservice call</li>' in resp.text
assert '1 API access</li>' in resp.text
assert FormDef.count() == 1
assert FormDef.select()[0].url_name == 'foo'
assert CardDef.count() == 1
assert CardDef.select()[0].url_name == 'bar'
assert BlockDef.count() == 1
assert Workflow.count() == 1
assert pub.role_class.count() == 1
assert Category.count() == 2
assert CardDefCategory.count() == 1
assert WorkflowCategory.count() == 1
assert BlockCategory.count() == 1
assert MailTemplateCategory.count() == 1
assert CommentTemplateCategory.count() == 1
assert DataSourceCategory.count() == 1
assert NamedDataSource.count() == 1
assert MailTemplate.count() == 1
assert CommentTemplate.count() == 1
assert NamedWsCall.count() == 1
assert ApiAccess.count() == 1
assert pub.role_class.count() == 1
# check roles are found by name
wipe()

View File

@ -5,6 +5,7 @@ import pytest
from wcs.blocks import BlockDef
from wcs.carddef import CardDef
from wcs.comment_templates import CommentTemplate
from wcs.data_sources import NamedDataSource
from wcs.formdef import FormDef
from wcs.mail_templates import MailTemplate
@ -48,6 +49,7 @@ def test_studio_home(pub):
assert '../settings/data-sources/' not in resp.text
assert '../forms/blocks/' in resp.text
assert '../workflows/mail-templates/' in resp.text
assert '../workflows/comment-templates/' in resp.text
assert '../settings/wscalls/' in resp.text
assert 'Recent errors' in resp.text
@ -136,17 +138,36 @@ def test_studio_home_recent_changes(pub):
NamedDataSource.wipe()
FormDef.wipe()
MailTemplate.wipe()
CommentTemplate.wipe()
Workflow.wipe()
NamedWsCall.wipe()
objects = defaultdict(list)
for i in range(6):
for klass in [BlockDef, CardDef, NamedDataSource, FormDef, MailTemplate, Workflow, NamedWsCall]:
for klass in [
BlockDef,
CardDef,
NamedDataSource,
FormDef,
MailTemplate,
CommentTemplate,
Workflow,
NamedWsCall,
]:
obj = klass()
obj.name = 'foo %s' % i
obj.store()
objects[klass.xml_root_node].append(obj)
for klass in [BlockDef, CardDef, NamedDataSource, FormDef, MailTemplate, Workflow, NamedWsCall]:
for klass in [
BlockDef,
CardDef,
NamedDataSource,
FormDef,
MailTemplate,
CommentTemplate,
Workflow,
NamedWsCall,
]:
assert pub.snapshot_class.count(clause=[Equal('object_type', klass.xml_root_node)]) == 6
# 2 snapshots for this one, but will be displayed only once
objects[klass.xml_root_node][-1].name += ' bar'
@ -184,14 +205,18 @@ def test_studio_home_recent_changes(pub):
assert (
'backoffice/workflows/mail-templates/%s/' % objects[MailTemplate.xml_root_node][i].id not in resp
)
assert (
'backoffice/workflows/comment-templates/%s/' % objects[CommentTemplate.xml_root_node][i].id
not in resp
)
assert 'backoffice/workflows/%s/' % objects[Workflow.xml_root_node][i].id not in resp
assert 'backoffice/settings/wscalls/%s/' % objects[NamedWsCall.xml_root_node][i].id not in resp
# too old
assert 'backoffice/forms/blocks/%s/' % objects[BlockDef.xml_root_node][5].id not in resp
assert 'backoffice/cards/%s/' % objects[CardDef.xml_root_node][5].id not in resp
assert 'backoffice/settings/data-sources/%s/' % objects[NamedDataSource.xml_root_node][5].id not in resp
# only 5 elements
assert 'backoffice/settings/data-sources/%s/' % objects[NamedDataSource.xml_root_node][5].id in resp
assert (
'backoffice/forms/data-sources/%s/' % objects[NamedDataSource.xml_root_node][5].id not in resp
) # not this url
@ -201,6 +226,7 @@ def test_studio_home_recent_changes(pub):
)
assert 'backoffice/forms/%s/' % objects[FormDef.xml_root_node][5].id in resp
assert 'backoffice/workflows/mail-templates/%s/' % objects[MailTemplate.xml_root_node][5].id in resp
assert 'backoffice/workflows/comment-templates/%s/' % objects[CommentTemplate.xml_root_node][5].id in resp
assert 'backoffice/workflows/%s/' % objects[Workflow.xml_root_node][5].id in resp
assert 'backoffice/settings/wscalls/%s/' % objects[NamedWsCall.xml_root_node][5].id in resp
@ -227,11 +253,15 @@ def test_studio_home_recent_changes(pub):
assert (
'backoffice/workflows/mail-templates/%s/' % objects[MailTemplate.xml_root_node][i].id not in resp
)
assert (
'backoffice/workflows/comment-templates/%s/' % objects[CommentTemplate.xml_root_node][i].id
not in resp
)
assert 'backoffice/workflows/%s/' % objects[Workflow.xml_root_node][i].id not in resp
# too old
assert 'backoffice/forms/blocks/%s/' % objects[BlockDef.xml_root_node][5].id not in resp
assert 'backoffice/cards/%s/' % objects[CardDef.xml_root_node][5].id not in resp
# only 5 elements
assert 'backoffice/cards/%s/' % objects[CardDef.xml_root_node][5].id in resp
assert 'backoffice/forms/data-sources/%s/' % objects[NamedDataSource.xml_root_node][5].id in resp
assert (
'backoffice/workflows/data-sources/%s/' % objects[NamedDataSource.xml_root_node][5].id
@ -239,6 +269,7 @@ def test_studio_home_recent_changes(pub):
)
assert 'backoffice/forms/%s/' % objects[FormDef.xml_root_node][5].id in resp
assert 'backoffice/workflows/mail-templates/%s/' % objects[MailTemplate.xml_root_node][5].id in resp
assert 'backoffice/workflows/comment-templates/%s/' % objects[CommentTemplate.xml_root_node][5].id in resp
assert 'backoffice/workflows/%s/' % objects[Workflow.xml_root_node][5].id in resp
pub.cfg['admin-permissions'] = {}
@ -256,7 +287,7 @@ def test_studio_home_recent_changes(pub):
assert 'backoffice/forms/%s/' % objects[FormDef.xml_root_node][i].id not in resp
assert 'backoffice/settings/wscalls/%s/' % objects[NamedWsCall.xml_root_node][i].id not in resp
# too old
for i in range(4):
for i in range(5):
assert 'backoffice/cards/%s/' % objects[CardDef.xml_root_node][i].id not in resp
assert (
'backoffice/workflows/data-sources/%s/' % objects[NamedDataSource.xml_root_node][i].id not in resp
@ -264,16 +295,16 @@ def test_studio_home_recent_changes(pub):
assert (
'backoffice/workflows/mail-templates/%s/' % objects[MailTemplate.xml_root_node][i].id not in resp
)
assert (
'backoffice/workflows/comment-templates/%s/' % objects[CommentTemplate.xml_root_node][i].id
not in resp
)
assert 'backoffice/workflows/%s/' % objects[Workflow.xml_root_node][i].id not in resp
# too old
assert 'backoffice/cards/%s/' % objects[CardDef.xml_root_node][4].id not in resp
assert 'backoffice/workflows/data-sources/%s/' % objects[NamedDataSource.xml_root_node][4].id not in resp
assert 'backoffice/workflows/mail-templates/%s/' % objects[MailTemplate.xml_root_node][4].id not in resp
# only 5 elements
assert 'backoffice/workflows/%s/' % objects[Workflow.xml_root_node][4].id in resp
assert 'backoffice/cards/%s/' % objects[CardDef.xml_root_node][5].id in resp
assert 'backoffice/workflows/data-sources/%s/' % objects[NamedDataSource.xml_root_node][5].id in resp
assert 'backoffice/workflows/mail-templates/%s/' % objects[MailTemplate.xml_root_node][5].id in resp
assert 'backoffice/workflows/comment-templates/%s/' % objects[CommentTemplate.xml_root_node][5].id in resp
assert 'backoffice/workflows/%s/' % objects[Workflow.xml_root_node][5].id in resp
pub.cfg['admin-permissions'] = {}
@ -296,6 +327,10 @@ def test_studio_home_recent_changes(pub):
assert (
'backoffice/workflows/mail-templates/%s/' % objects[MailTemplate.xml_root_node][i].id not in resp
)
assert (
'backoffice/workflows/comment-templates/%s/' % objects[CommentTemplate.xml_root_node][i].id
not in resp
)
assert 'backoffice/workflows/%s/' % objects[Workflow.xml_root_node][i].id not in resp
# too old
assert 'backoffice/cards/%s/' % objects[CardDef.xml_root_node][0].id not in resp
@ -324,11 +359,11 @@ def test_studio_home_recent_changes(pub):
pub.cfg['admin-permissions'] = {}
pub.write_cfg()
resp = app.get('/backoffice/studio/all-changes/')
assert '(1-20/42)' in resp
assert '(1-20/48)' in resp
resp = resp.click('<!--Next Page-->')
assert '21-40/42' in resp.text
assert '21-40/48' in resp.text
resp = resp.click('<!--Next Page-->')
assert '41-42/42' in resp.text
assert '41-48/48' in resp.text
user.is_admin = False
user.store()

View File

@ -0,0 +1,510 @@
import io
import os
import re
import xml.etree.ElementTree as ET
import pytest
from quixote import cleanup
from webtest import Upload
from wcs.categories import CommentTemplateCategory
from wcs.comment_templates import CommentTemplate
from wcs.fields import FileField
from wcs.formdef import FormDef
from wcs.qommon.http_request import HTTPRequest
from wcs.qommon.ident.password_accounts import PasswordAccount
from wcs.qommon.misc import indent_xml as indent
from wcs.qommon.upload_storage import PicklableUpload
from wcs.workflows import Workflow
from .utilities import clean_temporary_pub, create_temporary_pub, get_app, login
def setup_module(module):
cleanup()
def teardown_module(module):
clean_temporary_pub()
@pytest.fixture
def pub(request):
pub = create_temporary_pub()
req = HTTPRequest(None, {'SCRIPT_NAME': '/', 'SERVER_NAME': 'example.net'})
pub.set_app_dir(req)
pub._set_request(req)
pub.cfg['identification'] = {'methods': ['password']}
pub.write_cfg()
return pub
@pytest.fixture
def superuser(pub):
if pub.user_class.select(lambda x: x.name == 'admin'):
user1 = pub.user_class.select(lambda x: x.name == 'admin')[0]
user1.is_admin = True
user1.store()
return user1
user1 = pub.user_class(name='admin')
user1.is_admin = True
user1.store()
account1 = PasswordAccount(id='admin')
account1.set_password('admin')
account1.user_id = user1.id
account1.store()
return user1
@pytest.fixture
def comment_template():
CommentTemplate.wipe()
comment_template = CommentTemplate(name='test CT')
comment_template.comment = 'test comment'
comment_template.store()
return comment_template
def test_comment_templates_basics(pub, superuser):
CommentTemplateCategory.wipe()
CommentTemplate.wipe()
app = login(get_app(pub))
resp = app.get('/backoffice/workflows/')
assert 'Comment Templates' in resp
resp = resp.click('Comment Templates')
assert 'There are no comment templates defined.' in resp
resp = resp.click('New')
resp.form['name'] = 'first comment template'
resp = resp.form.submit('cancel').follow()
assert 'There are no comment templates defined.' in resp
resp = resp.click('New')
resp.form['name'] = 'first comment template'
resp = resp.form.submit('submit').follow()
resp.form['comment'] = 'comment body'
resp = resp.form.submit('submit').follow()
resp = resp.click('Edit')
resp.form['comment'] = 'edited comment body'
resp.form['attachments$element0'] = 'plop'
resp = resp.form.submit('submit').follow()
resp = resp.click('Edit')
assert resp.form['comment'].value == 'edited comment body'
assert resp.form['attachments$element0'].value == 'plop'
resp = resp.form.submit('submit').follow()
resp = resp.click('Delete')
resp = resp.form.submit('cancel').follow()
assert 'first comment template' in resp
resp = resp.click('Delete')
resp = resp.form.submit('submit').follow()
assert 'first comment template' not in resp
assert 'There are no comment templates defined.' in resp
resp = resp.click('New')
resp.form['name'] = 'first comment template'
resp = resp.form.submit('submit').follow()
resp.form['comment'] = 'comment body'
resp = resp.form.submit('submit').follow()
resp = app.get('/backoffice/workflows/')
resp = resp.click('Comment Templates')
assert 'first comment template' in resp
def test_comment_template_in_use(pub, superuser):
Workflow.wipe()
CommentTemplate.wipe()
workflow = Workflow(name='test workflow')
st1 = workflow.add_status('Status1')
item = st1.add_action('register-comment')
item.comment = 'Hello'
workflow.store()
comment_template = CommentTemplate(name='test comment template')
comment_template.comment = 'test comment'
comment_template.store()
assert comment_template.is_in_use() is False
item.comment_template = comment_template.slug
workflow.store()
assert comment_template.is_in_use() is True
# check workflow usage is displayed
app = login(get_app(pub))
resp = app.get('/backoffice/workflows/comment-templates/%s/' % comment_template.id)
assert 'Usage in workflows' in resp.text
assert 'test workflow' in resp.text
resp.click('test workflow') # make sure the link is ok
resp = resp.click('Delete')
assert 'still used' in resp.text
def test_admin_workflow_edit(pub, superuser):
CommentTemplateCategory.wipe()
Workflow.wipe()
CommentTemplate.wipe()
comment_template = CommentTemplate(name='test comment template')
comment_template.comment = 'test comment'
comment_template.store()
workflow = Workflow(name='test comment template')
st1 = workflow.add_status('Status1')
item = st1.add_action('register-comment')
item.comment = 'Hello'
workflow.store()
app = login(get_app(pub))
resp = app.get('/backoffice/workflows/%s/status/%s/items/1/' % (workflow.id, st1.id))
assert [o[0] for o in resp.form['comment_template'].options] == ['', 'test-comment-template']
cat_b = CommentTemplateCategory(name='Cat B')
cat_b.store()
comment_template = CommentTemplate(name='foo bar')
comment_template.category_id = cat_b.id
comment_template.store()
comment_template = CommentTemplate(name='bar foo')
comment_template.category_id = cat_b.id
comment_template.store()
cat_a = CommentTemplateCategory(name='Cat A')
cat_a.store()
comment_template = CommentTemplate(name='foo baz')
comment_template.category_id = cat_a.id
comment_template.store()
resp = app.get('/backoffice/workflows/%s/status/%s/items/1/' % (workflow.id, st1.id))
assert [o[0] for o in resp.form['comment_template'].options] == [
'',
'foo-baz',
'bar-foo',
'foo-bar',
'test-comment-template',
]
resp.form['comment_template'] = 'test-comment-template'
resp = resp.form.submit('submit')
workflow = Workflow.get(workflow.id)
assert workflow.possible_status[0].items[0].comment_template == 'test-comment-template'
def test_comment_templates_category(pub, superuser):
CommentTemplateCategory.wipe()
CommentTemplate.wipe()
app = login(get_app(pub))
resp = app.get('/backoffice/workflows/comment-templates/new')
assert 'category_id' not in resp.form.fields
comment_template = CommentTemplate(name='foo')
comment_template.store()
resp = app.get('/backoffice/workflows/comment-templates/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 CommentTemplateCategory.count() == 1
category = CommentTemplateCategory.select()[0]
assert category.name == 'a new category'
resp = app.get('/backoffice/workflows/comment-templates/new')
resp.form['name'] = 'template 2'
resp = resp.form.submit('submit').follow()
assert CommentTemplate.count() == 2
assert CommentTemplate.get(2).category_id is None
resp = app.get('/backoffice/workflows/comment-templates/new')
resp.form['name'] = 'template 3'
resp.form['category_id'] = str(category.id)
resp = resp.form.submit('submit').follow()
assert CommentTemplate.count() == 3
assert CommentTemplate.get(3).category_id == str(category.id)
resp = app.get('/backoffice/workflows/comment-templates/%s/' % comment_template.id)
resp = resp.click(href=re.compile('^edit$'))
resp.form['category_id'] = str(category.id)
resp = resp.form.submit('cancel').follow()
comment_template.refresh_from_storage()
assert comment_template.category_id is None
resp = app.get('/backoffice/workflows/comment-templates/%s/' % comment_template.id)
resp = resp.click(href=re.compile('^edit$'))
resp.form['category_id'] = str(category.id)
resp.form['comment'] = 'comment body'
resp = resp.form.submit('submit').follow()
comment_template.refresh_from_storage()
assert str(comment_template.category_id) == str(category.id)
resp = app.get('/backoffice/workflows/comment-templates/%s/' % comment_template.id)
resp = resp.click(href=re.compile('^edit$'))
assert resp.form['category_id'].value == str(category.id)
resp = app.get('/backoffice/workflows/comment-templates/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 CommentTemplateCategory.count() == 2
category2 = [x for x in CommentTemplateCategory.select() if x.id != category.id][0]
assert category2.name == 'a second category'
app.get(
'/backoffice/workflows/comment-templates/categories/update_order?order=%s;%s;'
% (category2.id, category.id)
)
categories = CommentTemplateCategory.select()
CommentTemplateCategory.sort_by_position(categories)
assert [x.id for x in categories] == [str(category2.id), str(category.id)]
app.get(
'/backoffice/workflows/comment-templates/categories/update_order?order=%s;%s;0'
% (category.id, category2.id)
)
categories = CommentTemplateCategory.select()
CommentTemplateCategory.sort_by_position(categories)
assert [x.id for x in categories] == [str(category.id), str(category2.id)]
resp = app.get('/backoffice/workflows/comment-templates/categories/')
resp = resp.click('a new category')
resp = resp.click('Delete')
resp = resp.form.submit()
comment_template.refresh_from_storage()
assert not comment_template.category_id
def test_workflow_register_comment_template(pub):
Workflow.wipe()
CommentTemplate.wipe()
comment_template = CommentTemplate(name='test comment template')
comment_template.comment = 'test comment'
comment_template.store()
workflow = Workflow(name='test comment template')
st1 = workflow.add_status('Status1')
item = st1.add_action('register-comment')
item.comment = 'Hello'
item.comment_template = comment_template.slug
workflow.store()
formdef = FormDef()
formdef.name = 'baz'
formdef.fields = []
formdef.store()
formdata = formdef.data_class()()
formdata.just_created()
formdata.store()
item.perform(formdata)
assert len(formdata.evolution) == 1
assert len(formdata.evolution[0].parts) == 2
assert formdata.evolution[-1].parts[1].content == 'test comment'
# check nothing is registered and an error is logged if the comment template is missing
CommentTemplate.wipe()
item.perform(formdata)
assert len(formdata.evolution) == 1
assert len(formdata.evolution[0].parts) == 2
assert pub.loggederror_class.count() == 1
logged_error = pub.loggederror_class.select()[0]
assert (
logged_error.summary
== 'reference to invalid comment template test-comment-template in status Status1'
)
def test_workflow_register_comment_template_attachments(pub):
Workflow.wipe()
CommentTemplate.wipe()
comment_template = CommentTemplate(name='test comment template')
comment_template.comment = 'test comment'
comment_template.attachments = ['form_var_file1_raw']
comment_template.store()
workflow = Workflow(name='test comment template')
st1 = workflow.add_status('Status1')
item = st1.add_action('register-comment')
item.comment = 'Hello'
item.comment_template = comment_template.slug
workflow.store()
formdef = FormDef()
formdef.name = 'baz'
formdef.fields = [
FileField(id='1', label='File', type='file', varname='file1'),
]
formdef.store()
upload = PicklableUpload('test.jpeg', 'image/jpeg')
with open(os.path.join(os.path.dirname(__file__), 'image-with-gps-data.jpeg'), 'rb') as fd:
upload.receive([fd.read()])
formdata = formdef.data_class()()
formdata.data = {'1': upload}
formdata.just_created()
formdata.store()
pub.substitutions.feed(formdata)
item.perform(formdata)
assert len(formdata.evolution) == 1
assert len(formdata.evolution[0].parts) == 3
assert formdata.evolution[-1].parts[2].content == 'test comment'
assert formdata.evolution[-1].parts[1].base_filename == 'test.jpeg'
# check two files are sent if attachments are also defined on the action itself.
item.attachments = ['form_var_file1_raw']
item.perform(formdata)
assert len(formdata.evolution) == 1
assert len(formdata.evolution[0].parts) == 6
assert formdata.evolution[-1].parts[5].content == 'test comment'
assert formdata.evolution[-1].parts[4].base_filename == 'test.jpeg'
assert formdata.evolution[-1].parts[3].base_filename == 'test.jpeg'
def test_workflow_register_comment_template_empty(pub):
Workflow.wipe()
CommentTemplate.wipe()
comment_template = CommentTemplate(name='test comment template')
comment_template.comment = None
comment_template.store()
workflow = Workflow(name='test comment template')
st1 = workflow.add_status('Status1')
item = st1.add_action('register-comment')
item.comment = 'Hello'
item.comment_template = comment_template.slug
workflow.store()
formdef = FormDef()
formdef.name = 'baz'
formdef.store()
formdata = formdef.data_class()()
formdata.data = {}
formdata.just_created()
formdata.store()
pub.substitutions.feed(formdata)
item.perform(formdata)
assert len(formdata.evolution) == 1
assert len(formdata.evolution[0].parts) == 1
def test_comment_templates_export(pub, superuser, comment_template):
app = login(get_app(pub))
resp = app.get('/backoffice/workflows/comment-templates/1/')
resp = resp.click(href='export')
xml_export = resp.text
ds = io.StringIO(xml_export)
comment_template2 = CommentTemplate.import_from_xml(ds)
assert comment_template2.name == 'test CT'
def test_comment_templates_import(pub, superuser, comment_template):
comment_template.slug = 'foobar'
comment_template.store()
comment_template_xml = ET.tostring(comment_template.export_to_xml(include_id=True))
CommentTemplate.wipe()
assert CommentTemplate.count() == 0
app = login(get_app(pub))
resp = app.get('/backoffice/workflows/comment-templates/')
resp = resp.click(href='import')
resp.forms[0]['file'] = Upload('comment_template.wcs', comment_template_xml)
resp = resp.forms[0].submit()
assert CommentTemplate.count() == 1
assert {wc.slug for wc in CommentTemplate.select()} == {'foobar'}
# check slug
resp = app.get('/backoffice/workflows/comment-templates/')
resp = resp.click(href='import')
resp.forms[0]['file'] = Upload('comment_template.wcs', comment_template_xml)
resp = resp.forms[0].submit()
assert CommentTemplate.count() == 2
assert {wc.slug for wc in CommentTemplate.select()} == {'foobar', 'test-ct'}
resp = app.get('/backoffice/workflows/comment-templates/')
resp = resp.click(href='import')
resp.forms[0]['file'] = Upload('comment_template.wcs', comment_template_xml)
resp = resp.forms[0].submit()
assert CommentTemplate.count() == 3
assert {wc.slug for wc in CommentTemplate.select()} == {'foobar', 'test-ct', 'test-ct-1'}
# import an invalid file
resp = app.get('/backoffice/workflows/comment-templates/')
resp = resp.click(href='import')
resp.form['file'] = Upload('comment_template.wcs', b'garbage')
resp = resp.form.submit()
assert 'Invalid File' in resp.text
def test_comment_templates_duplicate(pub, superuser, comment_template):
app = login(get_app(pub))
resp = app.get('/backoffice/workflows/comment-templates/1/')
resp = resp.click(href='duplicate')
assert resp.form['name'].value == 'test CT (copy)'
resp = resp.form.submit('cancel').follow()
assert CommentTemplate.count() == 1
resp = resp.click(href='duplicate')
assert resp.form['name'].value == 'test CT (copy)'
resp = resp.form.submit('submit').follow()
assert CommentTemplate.count() == 2
resp = app.get('/backoffice/workflows/comment-templates/1/')
resp = resp.click(href='duplicate')
assert resp.form['name'].value == 'test CT (copy 2)'
resp.form['name'].value = 'other copy'
resp = resp.form.submit('submit').follow()
assert CommentTemplate.count() == 3
assert {x.name for x in CommentTemplate.select()} == {'test CT', 'test CT (copy)', 'other copy'}
assert {x.slug for x in CommentTemplate.select()} == {'test-ct', 'test-ct-copy', 'other-copy'}
def export_to_indented_xml(comment_template, include_id=False):
comment_template_xml = comment_template.export_to_xml(include_id=include_id)
indent(comment_template_xml)
return comment_template_xml
def assert_import_export_works(comment_template, include_id=False):
comment_template2 = CommentTemplate.import_from_xml_tree(
ET.fromstring(ET.tostring(comment_template.export_to_xml(include_id))), include_id
)
assert ET.tostring(export_to_indented_xml(comment_template)) == ET.tostring(
export_to_indented_xml(comment_template2)
)
return comment_template2
def test_comment_template(pub):
comment_template = CommentTemplate(name='test')
assert_import_export_works(comment_template, include_id=True)
def test_comment_template_with_category(pub):
category = CommentTemplateCategory(name='test category')
category.store()
comment_template = CommentTemplate(name='test category')
comment_template.category_id = category.id
comment_template.store()
comment_template2 = assert_import_export_works(comment_template, include_id=True)
assert comment_template2.category_id == comment_template.category_id
# import with non existing category
CommentTemplateCategory.wipe()
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

View File

@ -9,6 +9,7 @@ from quixote.http_request import Upload
from wcs.blocks import BlockDef
from wcs.carddef import CardDef
from wcs.categories import Category
from wcs.comment_templates import CommentTemplate
from wcs.data_sources import NamedDataSource
from wcs.fields import CommentField, ItemField, PageField, StringField
from wcs.formdef import FormDef
@ -1151,6 +1152,29 @@ def test_mail_template_snapshot_browse(pub):
resp = resp.click('Edit')
def test_comment_template_snapshot_browse(pub):
create_superuser(pub)
create_role(pub)
CommentTemplate.wipe()
comment_template = CommentTemplate(name='test')
comment_template.store()
assert pub.snapshot_class.count() == 1
# check calling .store() without changes doesn't create snapshots
comment_template.store()
assert pub.snapshot_class.count() == 1
app = login(get_app(pub))
resp = app.get('/backoffice/workflows/comment-templates/%s/history/' % comment_template.id)
snapshot = pub.snapshot_class.select_object_history(comment_template)[0]
resp = resp.click(href='%s/view/' % snapshot.id)
assert 'This comment template is readonly' in resp.text
assert '<p>%s</p>' % localstrftime(snapshot.timestamp) in resp.text
with pytest.raises(IndexError):
resp = resp.click('Edit')
def test_category_snapshot_browse(pub):
create_superuser(pub)
create_role(pub)

View File

@ -26,10 +26,12 @@ from wcs.categories import (
BlockCategory,
CardDefCategory,
Category,
CommentTemplateCategory,
DataSourceCategory,
MailTemplateCategory,
WorkflowCategory,
)
from wcs.comment_templates import CommentTemplate
from wcs.data_sources import NamedDataSource
from wcs.formdef import FormDef
from wcs.mail_templates import MailTemplate
@ -177,6 +179,11 @@ class MailTemplateCategoryUI(CategoryUI):
management_roles_hint_text = None
class CommentTemplateCategoryUI(CategoryUI):
category_class = CommentTemplateCategory
management_roles_hint_text = None
class DataSourceCategoryUI(CategoryUI):
category_class = DataSourceCategory
management_roles_hint_text = None
@ -334,6 +341,14 @@ class MailTemplateCategoryPage(CategoryPage):
empty_message = _('No mail template associated to this category.')
class CommentTemplateCategoryPage(CategoryPage):
category_class = CommentTemplateCategory
category_ui_class = CommentTemplateCategoryUI
object_class = CommentTemplate
usage_title = _('Comment templates in this category')
empty_message = _('No comment template associated to this category.')
class DataSourceCategoryPage(CategoryPage):
category_class = DataSourceCategory
category_ui_class = DataSourceCategoryUI
@ -452,6 +467,14 @@ class MailTemplateCategoriesDirectory(CategoriesDirectory):
category_explanation = _('Categories are used to sort the different mail templates.')
class CommentTemplateCategoriesDirectory(CategoriesDirectory):
base_section = 'workflows'
category_class = CommentTemplateCategory
category_ui_class = CommentTemplateCategoryUI
category_page_class = CommentTemplateCategoryPage
category_explanation = _('Categories are used to sort the different comment templates.')
class DataSourceCategoriesDirectory(CategoriesDirectory):
base_section = 'workflows'
category_class = DataSourceCategory

View File

@ -0,0 +1,360 @@
# w.c.s. - web application for online forms
# Copyright (C) 2005-2022 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/>.
from quixote import get_publisher, get_response, redirect
from quixote.directory import Directory
from quixote.html import TemplateIO, htmltext
from wcs.admin import utils
from wcs.admin.categories import CommentTemplateCategoriesDirectory, get_categories
from wcs.backoffice.snapshots import SnapshotsDirectory
from wcs.categories import CommentTemplateCategory
from wcs.comment_templates import CommentTemplate
from wcs.qommon import _, errors, misc, template
from wcs.qommon.backoffice.menu import html_top
from wcs.qommon.form import (
ComputedExpressionWidget,
FileWidget,
Form,
HtmlWidget,
SingleSelectWidget,
SlugWidget,
StringWidget,
TextWidget,
WidgetList,
get_session,
)
class CommentTemplatesDirectory(Directory):
_q_exports = ['', 'new', 'categories', ('import', 'p_import')]
do_not_call_in_templates = True
categories = CommentTemplateCategoriesDirectory()
def _q_traverse(self, path):
if not get_publisher().get_backoffice_root().is_global_accessible('workflows'):
raise errors.AccessForbiddenError()
get_response().breadcrumb.append(('comment-templates/', _('Comment Templates')))
return super()._q_traverse(path)
def _q_lookup(self, component):
return CommentTemplatePage(component)
def _q_index(self):
html_top('comment_templates', title=_('Comment Templates'))
categories = CommentTemplateCategory.select()
CommentTemplateCategory.sort_by_position(categories)
comment_templates = CommentTemplate.select(order_by='name')
if categories:
categories.append(CommentTemplateCategory(_('Misc')))
for category in categories:
category.comment_templates = [x for x in comment_templates if x.category_id == category.id]
return template.QommonTemplateResponse(
templates=['wcs/backoffice/comment-templates.html'],
context={'view': self, 'comment_templates': comment_templates, 'categories': categories},
)
def new(self):
form = Form(enctype='multipart/form-data')
form.add(StringWidget, 'name', title=_('Name'), required=True, size=50)
category_options = get_categories(CommentTemplateCategory)
if category_options:
category_options = [(None, '---', '')] + list(category_options)
form.add(
SingleSelectWidget,
'category_id',
title=_('Category'),
options=category_options,
)
form.add_submit('submit', _('Add'))
form.add_submit('cancel', _('Cancel'))
if form.get_widget('cancel').parse():
return redirect('.')
if form.is_submitted() and not form.has_errors():
comment_template = CommentTemplate(name=form.get_widget('name').parse())
if form.get_widget('category_id'):
comment_template.category_id = form.get_widget('category_id').parse()
comment_template.store()
return redirect('%s/edit' % comment_template.id)
get_response().breadcrumb.append(('new', _('New Comment Template')))
html_top('comment_templates', title=_('New Comment Template'))
r = TemplateIO(html=True)
r += htmltext('<h2>%s</h2>') % _('New Comment Template')
r += form.render()
return r.getvalue()
def p_import(self):
form = Form(enctype='multipart/form-data')
import_title = _('Import Comment Template')
form.add(FileWidget, 'file', title=_('File'), required=True)
form.add_submit('submit', import_title)
form.add_submit('cancel', _('Cancel'))
if form.get_submit() == 'cancel':
return redirect('.')
if form.is_submitted() and not form.has_errors():
try:
return self.import_submit(form)
except ValueError:
pass
get_response().breadcrumb.append(('import', _('Import')))
html_top('comment_templates', title=import_title)
r = TemplateIO(html=True)
r += htmltext('<h2>%s</h2>') % import_title
r += htmltext('<p>%s</p>') % _('You can install a new comment template by uploading a file.')
r += form.render()
return r.getvalue()
def import_submit(self, form):
fp = form.get_widget('file').parse().fp
error = False
try:
comment_template = CommentTemplate.import_from_xml(fp)
get_session().message = ('info', _('This comment template has been successfully imported.'))
except ValueError:
error = True
if error:
form.set_error('file', _('Invalid File'))
raise ValueError()
# check slug unicity
known_slugs = {
x.slug: x.id for x in CommentTemplate.select(ignore_migration=True, ignore_errors=True)
}
if comment_template.slug in known_slugs:
comment_template.slug = None # a new one will be set in .store()
comment_template.store()
return redirect('%s/' % comment_template.id)
class CommentTemplatePage(Directory):
_q_exports = [
'',
'edit',
'delete',
'duplicate',
'export',
('history', 'snapshots_dir'),
]
do_not_call_in_templates = True
def __init__(self, component, instance=None):
try:
self.comment_template = instance or CommentTemplate.get(component)
except KeyError:
raise errors.TraversalError()
get_response().breadcrumb.append((component + '/', self.comment_template.name))
self.snapshots_dir = SnapshotsDirectory(self.comment_template)
def get_sidebar(self):
r = TemplateIO(html=True)
if self.comment_template.is_readonly():
r += htmltext('<div class="infonotice"><p>%s</p></div>') % _('This comment template is readonly.')
r += utils.snapshot_info_block(snapshot=self.comment_template.snapshot_object)
r += htmltext('<ul id="sidebar-actions">')
if not self.comment_template.is_readonly():
r += htmltext('<li><a href="export">%s</a></li>') % _('Export')
r += htmltext('<li><a href="duplicate" rel="popup">%s</a></li>') % _('Duplicate')
r += htmltext('<li><a href="delete" rel="popup">%s</a></li>') % _('Delete')
if get_publisher().snapshot_class:
r += htmltext('<li><a rel="popup" href="history/save">%s</a></li>') % _('Save snapshot')
r += htmltext('<li><a href="history/">%s</a></li>') % _('History')
r += htmltext('</ul>')
return r.getvalue()
def _q_index(self):
html_top('comment_templates', title=self.comment_template.name)
get_response().filter['sidebar'] = self.get_sidebar()
return template.QommonTemplateResponse(
templates=['wcs/backoffice/comment-template.html'],
context={'view': self, 'comment_template': self.comment_template},
)
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.comment_template.name
)
category_options = get_categories(CommentTemplateCategory)
if category_options:
category_options = [(None, '---', '')] + list(category_options)
form.add(
SingleSelectWidget,
'category_id',
title=_('Category'),
options=category_options,
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',
title=_('Comment'),
value=self.comment_template.comment,
cols=80,
rows=15,
require=True,
validation_function=ComputedExpressionWidget.validate_template,
)
if self.comment_template.slug and not self.comment_template.is_in_use():
form.add(
SlugWidget,
'slug',
value=self.comment_template.slug,
advanced=True,
)
form.add(
WidgetList,
'attachments',
title=_('Attachments (templates or Python expressions)'),
element_type=StringWidget,
value=self.comment_template.attachments,
add_element_label=_('Add attachment'),
element_kwargs={'render_br': False, 'size': 50},
advanced=True,
)
if not self.comment_template.is_readonly():
form.add_submit('submit', _('Submit'))
form.add_submit('cancel', _('Cancel'))
return form
def submit_form(self, form):
name = form.get_widget('name').parse()
slug_widget = form.get_widget('slug')
if slug_widget:
slug = form.get_widget('slug').parse()
for comment_template in CommentTemplate.select():
if comment_template.id == self.comment_template.id:
continue
if slug_widget and slug == comment_template.slug:
slug_widget.set_error(_('This value is already used.'))
if form.has_errors():
raise ValueError()
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:
self.comment_template.slug = slug
self.comment_template.store()
def edit(self):
form = self.get_form()
if form.get_submit() == 'cancel':
return redirect('.')
if form.get_submit() == 'submit' and not form.has_errors():
try:
self.submit_form(form)
except ValueError:
pass
else:
return redirect('.')
get_response().breadcrumb.append(('edit', _('Edit')))
html_top('comment_templates', title=_('Edit Comment Template'))
r = TemplateIO(html=True)
r += htmltext('<h2>%s</h2>') % _('Edit Comment Template')
r += form.render()
r += get_publisher().substitutions.get_substitution_html_table()
return r.getvalue()
def delete(self):
form = Form(enctype='multipart/form-data')
if not self.comment_template.is_in_use():
form.widgets.append(
HtmlWidget('<p>%s</p>' % _('You are about to irrevocably delete this comment template.'))
)
form.add_submit('delete', _('Submit'))
else:
form.widgets.append(
HtmlWidget('<p>%s</p>' % _('This comment template is still used, it cannot be deleted.'))
)
form.add_submit('cancel', _('Cancel'))
if form.get_widget('cancel').parse():
return redirect('.')
if not form.is_submitted() or form.has_errors():
get_response().breadcrumb.append(('delete', _('Delete')))
html_top('comment_templates', title=_('Delete Comment Template'))
r = TemplateIO(html=True)
r += htmltext('<h2>%s %s</h2>') % (_('Deleting Comment Template:'), self.comment_template.name)
r += form.render()
return r.getvalue()
else:
self.comment_template.remove_self()
return redirect('..')
def export(self):
return misc.xml_response(
self.comment_template,
filename='comment-template-%s.wcs' % self.comment_template.slug,
content_type='application/x-wcs-comment-template',
)
def duplicate(self):
form = Form(enctype='multipart/form-data')
name_widget = form.add(StringWidget, 'name', title=_('Name'), required=True, size=30)
form.add_submit('submit', _('Submit'))
form.add_submit('cancel', _('Cancel'))
if form.get_widget('cancel').parse():
return redirect('.')
if not form.is_submitted():
original_name = self.comment_template.name
new_name = '%s %s' % (original_name, _('(copy)'))
names = [x.name for x in CommentTemplate.select()]
no = 2
while new_name in names:
new_name = _('%(name)s (copy %(no)d)') % {'name': original_name, 'no': no}
no += 1
name_widget.set_value(new_name)
if not form.is_submitted() or form.has_errors():
html_top('comment_templates', title=_('Duplicate Comment Template'))
r = TemplateIO(html=True)
get_response().breadcrumb.append(('duplicate', _('Duplicate')))
r += htmltext('<h2>%s</h2>') % _('Duplicate Comment Template')
r += form.render()
return r.getvalue()
self.comment_template.id = None
self.comment_template.slug = None
self.comment_template.name = form.get_widget('name').parse()
self.comment_template.store()
return redirect('../%s/' % self.comment_template.id)

View File

@ -738,10 +738,14 @@ class SettingsDirectory(AccessControlled, Directory):
form.add(CheckboxWidget, 'workflow_categories', title=_('Workflow Categories'), value=True)
form.add(CheckboxWidget, 'block_categories', title=_('Fields Blocks Categories'), value=True)
form.add(CheckboxWidget, 'mail_template_categories', title=_('Mail Templates Categories'), value=True)
form.add(
CheckboxWidget, 'comment_template_categories', title=_('Comment Templates Categories'), value=True
)
form.add(CheckboxWidget, 'data_source_categories', title=_('Data Sources Categories'), value=True)
form.add(CheckboxWidget, 'settings', title=_('Settings'), value=False)
form.add(CheckboxWidget, 'datasources', title=_('Data sources'), value=True)
form.add(CheckboxWidget, 'mail-templates', title=_('Mail templates'), value=True)
form.add(CheckboxWidget, 'comment-templates', title=_('Comment templates'), value=True)
form.add(CheckboxWidget, 'wscalls', title=_('Webservice calls'), value=True)
form.add(CheckboxWidget, 'apiaccess', title=_('API access'), value=True)
form.add_submit('submit', _('Submit'))
@ -773,9 +777,11 @@ class SettingsDirectory(AccessControlled, Directory):
'workflow_categories',
'block_categories',
'mail_template_categories',
'comment_template_categories',
'data_source_categories',
'wscalls',
'mail-templates',
'comment-templates',
'apiaccess',
):
continue
@ -871,10 +877,12 @@ class SettingsDirectory(AccessControlled, Directory):
'workflow_categories',
'block_categories',
'mail_template_categories',
'comment_template_categories',
'data_source_categories',
'datasources',
'wscalls',
'mail-templates',
'comment-templates',
'blockdefs',
'apiaccess',
):
@ -934,6 +942,7 @@ class SettingsDirectory(AccessControlled, Directory):
try:
results = self.import_submit(form)
results['mail_templates'] = results['mail-templates']
results['comment_templates'] = results['comment-templates']
except zipfile.BadZipfile:
results = None
reason = _('Not a valid export file')

View File

@ -63,6 +63,7 @@ from wcs.workflows import (
)
from . import utils
from .comment_templates import CommentTemplatesDirectory
from .data_sources import NamedDataSourcesDirectory
from .fields import FieldDefPage, FieldsDirectory
from .logged_errors import LoggedErrorsDirectory
@ -1944,10 +1945,12 @@ class WorkflowsDirectory(Directory):
('import', 'p_import'),
('data-sources', 'data_sources'),
('mail-templates', 'mail_templates'),
('comment-templates', 'comment_templates'),
]
data_sources = NamedDataSourcesDirectoryInWorkflows()
mail_templates = MailTemplatesDirectory()
comment_templates = CommentTemplatesDirectory()
category_class = WorkflowCategory
categories = WorkflowCategoriesDirectory()
@ -1979,6 +1982,7 @@ class WorkflowsDirectory(Directory):
r += htmltext('<h2>%s</h2>') % _('Workflows')
r += htmltext('<span class="actions">')
if is_global_accessible():
r += htmltext('<a href="comment-templates/">%s</a>') % _('Comment Templates')
r += htmltext('<a href="mail-templates/">%s</a>') % _('Mail Templates')
r += htmltext('<a href="data-sources/">%s</a>') % _('Data sources')
r += htmltext('<a href="categories/">%s</a>') % _('Categories')

View File

@ -21,6 +21,7 @@ from wcs.admin.logged_errors import LoggedErrorsDirectory
from wcs.backoffice.deprecations import DeprecationsDirectory
from wcs.blocks import BlockDef
from wcs.carddef import CardDef
from wcs.comment_templates import CommentTemplate
from wcs.data_sources import NamedDataSource
from wcs.formdef import FormDef
from wcs.mail_templates import MailTemplate
@ -46,7 +47,7 @@ class ChangesDirectory(Directory):
backoffice_root = get_publisher().get_backoffice_root()
object_types = []
if backoffice_root.is_accessible('workflows'):
object_types += [Workflow, MailTemplate]
object_types += [Workflow, MailTemplate, CommentTemplate]
if backoffice_root.is_accessible('forms'):
object_types += [NamedDataSource, BlockDef, FormDef]
if backoffice_root.is_accessible('workflows'):
@ -103,7 +104,8 @@ class StudioDirectory(Directory):
extra_links.append(('../forms/blocks/', pgettext('studio', 'Field blocks')))
if backoffice_root.is_accessible('workflows'):
extra_links.append(('../workflows/mail-templates/', pgettext('studio', 'Mail templates')))
object_types += [Workflow, MailTemplate]
extra_links.append(('../workflows/comment-templates/', pgettext('studio', 'Comment templates')))
object_types += [Workflow, MailTemplate, CommentTemplate]
if backoffice_root.is_accessible('forms'):
extra_links.append(('../forms/data-sources/', pgettext('studio', 'Data sources')))
object_types += [NamedDataSource, BlockDef, FormDef]

View File

@ -301,6 +301,27 @@ class MailTemplateCategory(Category):
return MailTemplate
class CommentTemplateCategory(Category):
_names = 'comment_template_categories'
xml_root_node = 'comment_template_category'
backoffice_class = 'wcs.admin.categories.CommentTemplateCategoryPage'
backoffice_base_url = 'workflows/comment-templates/categories/'
# declarations for serialization
XML_NODES = [
('name', 'str'),
('url_name', 'str'),
('description', 'str'),
('position', 'int'),
]
@classmethod
def get_object_class(cls):
from .comment_templates import CommentTemplate
return CommentTemplate
class DataSourceCategory(Category):
_names = 'data_source_categories'
xml_root_node = 'data_source_category'

138
wcs/comment_templates.py Normal file
View File

@ -0,0 +1,138 @@
# w.c.s. - web application for online forms
# Copyright (C) 2005-2022 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/>.
from collections import defaultdict
from quixote import get_publisher
from wcs.categories import CommentTemplateCategory
from wcs.qommon import _, get_logger
from wcs.qommon.form import OptGroup
from wcs.qommon.xml_storage import XmlStorableObject
class CommentTemplate(XmlStorableObject):
_names = 'comment-templates'
xml_root_node = 'comment-template'
backoffice_class = 'wcs.admin.comment_templates.CommentTemplatePage'
verbose_name = _('Comment template')
verbose_name_plural = _('Comment templates')
name = None
slug = None
description = None
comment = None
attachments = []
category_id = None
# declarations for serialization
XML_NODES = [
('name', 'str'),
('slug', 'str'),
('description', 'str'),
('comment', 'str'),
('attachments', 'str_list'),
]
def __init__(self, name=None):
XmlStorableObject.__init__(self)
self.name = name
@property
def category(self):
return CommentTemplateCategory.get(self.category_id, ignore_errors=True)
@category.setter
def category(self, category):
if category:
self.category_id = category.id
elif self.category_id:
self.category_id = None
def get_admin_url(self):
base_url = get_publisher().get_backoffice_url()
return '%s/workflows/comment-templates/%s/' % (base_url, self.id)
def store(self, comment=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)
def get_places_of_use(self):
from wcs.workflows import Workflow
for workflow in Workflow.select(ignore_errors=True, ignore_migration=True):
for item in workflow.get_all_items():
if item.key != 'register-comment':
continue
if item.comment_template == self.slug:
yield workflow
break
def is_in_use(self):
return any(self.get_places_of_use())
@classmethod
def get_as_options_list(cls):
def get_option(mt):
option = [mt.slug, mt.name, mt.slug]
if get_publisher().get_backoffice_root().is_accessible('workflows'):
option.append({'data-goto-url': mt.get_admin_url()})
return option
comment_templates_by_category_names = defaultdict(list)
for comment_template in cls.select(order_by='name'):
name = ''
if comment_template.category:
name = comment_template.category.name
comment_templates_by_category_names[name].append(comment_template)
category_names = list(comment_templates_by_category_names.keys())
if len(category_names) == 1 and category_names[0] == '':
# no category found
return [get_option(mt) for mt in comment_templates_by_category_names['']]
options = []
# sort categories
category_names = sorted(category_names)
# comment template without categories at the end
if category_names[0] == '':
category_names = category_names[1:] + ['']
# group by category name
for name in category_names:
options.append(OptGroup(name or _('Without category')))
options.extend([get_option(mt) for mt in comment_templates_by_category_names[name]])
return options
@classmethod
def get_by_slug(cls, slug, ignore_errors=True):
comment_template = super().get_by_slug(slug, ignore_errors=ignore_errors)
if comment_template is None:
get_logger().warning("comment template '%s' does not exist" % slug)
return comment_template
def export_to_xml(self, include_id=False):
root = super().export_to_xml(include_id=include_id)
CommentTemplateCategory.object_category_xml_export(self, root, include_id=include_id)
return root
@classmethod
def import_from_xml_tree(cls, tree, include_id=False, **kwargs):
comment_template = super().import_from_xml_tree(tree, include_id=include_id, **kwargs)
CommentTemplateCategory.object_category_xml_import(comment_template, tree, include_id=include_id)
return comment_template

View File

@ -225,12 +225,14 @@ class WcsPublisher(QommonPublisher):
'workflow_categories': 0,
'block_categories': 0,
'mail_template_categories': 0,
'comment_template_categories': 0,
'data_source_categories': 0,
'roles': 0,
'settings': 0,
'datasources': 0,
'wscalls': 0,
'mail-templates': 0,
'comment-templates': 0,
'blockdefs': 0,
'apiaccess': 0,
}
@ -496,6 +498,7 @@ class WcsPublisher(QommonPublisher):
from wcs.blocks import BlockDef
from wcs.carddef import CardDef
from wcs.categories import BlockCategory, CardDefCategory, Category, WorkflowCategory
from wcs.comment_templates import CommentTemplate
from wcs.data_sources import NamedDataSource
from wcs.formdef import FormDef
from wcs.mail_templates import MailTemplate
@ -510,6 +513,7 @@ class WcsPublisher(QommonPublisher):
Workflow,
NamedWsCall,
MailTemplate,
CommentTemplate,
Category,
CardDefCategory,
WorkflowCategory,

View File

@ -0,0 +1,39 @@
{% extends "wcs/backoffice/base.html" %}
{% load i18n %}
{% block appbar-title %}{% trans "Comment Template" %} - {{ comment_template.name }}{% endblock %}
{% block appbar-actions %}
{% if not comment_template.is_readonly %}
<a href="edit">{% trans "Edit" %}</a>
{% endif %}
{% endblock %}
{% block content %}
{% if comment_template.description %}
<div class="bo-block">{{ comment_template.description }}</div>
{% endif %}
{% if comment_template.comment %}
<div class="section">
<div class="comment-comment">{{ comment_template.comment }}</div>
</div>
{% for workflow in comment_template.get_places_of_use %}
{% if forloop.first %}
<div class="section">
<h3>{% trans "Usage in workflows" %}</h3>
<ul class="objects-list single-links">
{% endif %}
<li><a href="{{ workflow.get_admin_url }}">{{ workflow.name }}</a></li>
{% if forloop.last %}
</ul>
</div>
{% endif %}
{% endfor %}
{% else %}
<div class="infonotice">{% trans "This comment template still needs to be configured." %}</div>
{% endif %}
{% endblock %}

View File

@ -0,0 +1,37 @@
{% extends "wcs/backoffice/base.html" %}
{% load i18n %}
{% block appbar-title %}{% trans "Comment Templates" %}{% endblock %}
{% block appbar-actions %}
<a href="categories/">{% trans "Categories" %}</a>
<a rel="popup" href="import">{% trans "Import" %}</a>
<a rel="popup" href="new">{% trans "New comment template" %}</a>
{% endblock %}
{% block content %}
{% if categories %}
{% for category in categories %}
{% if category.comment_templates %}
<div class="section">
<h2>{{ category.name }}</h2>
<ul class="objects-list single-links">
{% for comment_template in category.comment_templates %}
<li><a href="{{ comment_template.id }}/">{{ comment_template.name }}</a></li>
{% endfor %}
</ul>
</div>
{% endif %}
{% endfor %}
{% elif comment_templates %}
<ul class="objects-list single-links">
{% for comment_template in comment_templates %}
<li><a href="{{ comment_template.id }}/">{{ comment_template.name }}</a></li>
{% endfor %}
</ul>
{% else %}
<div class="infonotice">
{% trans "There are no comment templates defined." %}
</div>
{% endif %}
{% endblock %}

View File

@ -33,12 +33,27 @@
{% if results.workflow_categories %}
<li>{% blocktrans count counter=results.workflow_categories %}1 workflow category{% plural %}{{ counter }} workflow categories{% endblocktrans %}</li>
{% endif %}
{% if results.block_categories %}
<li>{% blocktrans count counter=results.block_categories %}1 block category{% plural %}{{ counter }} block categories{% endblocktrans %}</li>
{% endif %}
{% if results.mail_template_categories %}
<li>{% blocktrans count counter=results.mail_template_categories %}1 mail template category{% plural %}{{ counter }} mail template categories{% endblocktrans %}</li>
{% endif %}
{% if results.comment_template_categories %}
<li>{% blocktrans count counter=results.comment_template_categories %}1 comment template category{% plural %}{{ counter }} comment template categories{% endblocktrans %}</li>
{% endif %}
{% if results.data_source_categories %}
<li>{% blocktrans count counter=results.data_source_categories %}1 data source category{% plural %}{{ counter }} data source categories{% endblocktrans %}</li>
{% endif %}
{% if results.datasources %}
<li>{% blocktrans count counter=results.datasources %}1 data source{% plural %}{{ counter }} data sources{% endblocktrans %}</li>
{% endif %}
{% if results.mail_templates %}
<li>{% blocktrans count counter=results.mail_templates %}1 mail template{% plural %}{{ counter }} mail templates{% endblocktrans %}</li>
{% endif %}
{% if results.comment_templates %}
<li>{% blocktrans count counter=results.comment_templates %}1 comment template{% plural %}{{ counter }} comment templates{% endblocktrans %}</li>
{% endif %}
{% if results.wscalls %}
<li>{% blocktrans count counter=results.wscalls %}1 webservice call{% plural %}{{ counter }} webservice calls{% endblocktrans %}</li>
{% endif %}

View File

@ -17,6 +17,7 @@
from quixote import get_publisher
from quixote.html import htmltext
from wcs.comment_templates import CommentTemplate
from wcs.workflows import (
AttachmentEvolutionPart,
EvolutionPart,
@ -26,7 +27,7 @@ from wcs.workflows import (
)
from ..qommon import _, ezt
from ..qommon.form import TextWidget, WidgetListOfRoles
from ..qommon.form import SingleSelectWidget, TextWidget, WidgetListOfRoles
from ..qommon.template import TemplateError
@ -84,14 +85,37 @@ class RegisterCommenterWorkflowStatusItem(WorkflowStatusItem):
category = 'interaction'
comment = None
comment_template = None
to = None
attachments = None
def add_parameters_widgets(self, form, parameters, prefix='', formdef=None, **kwargs):
super().add_parameters_widgets(form, parameters, prefix=prefix, formdef=formdef, **kwargs)
subject_body_attrs = {}
if 'comment' in parameters:
if CommentTemplate.count():
subject_body_attrs = {
'data-dynamic-display-value': '',
'data-dynamic-display-child-of': '%scomment_template' % prefix,
}
if 'comment' in parameters:
form.add(
TextWidget, '%scomment' % prefix, title=_('Message'), value=self.comment, cols=80, rows=10
TextWidget,
'%scomment' % prefix,
title=_('Message'),
value=self.comment,
cols=80,
rows=10,
attrs=subject_body_attrs,
)
if 'comment_template' in parameters and CommentTemplate.count():
form.add(
SingleSelectWidget,
'%scomment_template' % prefix,
title=_('Comment Template'),
value=self.comment_template,
options=[(None, '', '')] + CommentTemplate.get_as_options_list(),
attrs={'data-dynamic-display-parent': 'true'},
)
if 'to' in parameters:
form.add(
@ -105,7 +129,7 @@ class RegisterCommenterWorkflowStatusItem(WorkflowStatusItem):
)
def get_parameters(self):
return ('comment', 'to', 'attachments', 'condition')
return ('comment_template', 'comment', 'to', 'attachments', 'condition')
def attach_uploads_to_formdata(self, formdata, uploads, to):
if not formdata.evolution[-1].parts:
@ -124,18 +148,36 @@ class RegisterCommenterWorkflowStatusItem(WorkflowStatusItem):
if not formdata.evolution:
return
if self.comment_template:
comment_template = CommentTemplate.get_by_slug(self.comment_template)
if comment_template:
comment = comment_template.comment
extra_attachments = comment_template.attachments
else:
message = _(
'reference to invalid comment template %(comment_template)s in status %(status)s'
) % {
'status': self.parent.name,
'comment_template': self.comment_template,
}
get_publisher().record_error(message, formdata=formdata, status_item=self)
return
else:
comment = self.comment
extra_attachments = None
# process attachments first, they might be used in the comment
# (with substitution vars)
if self.attachments:
uploads = self.convert_attachments_to_uploads()
if self.attachments or extra_attachments:
uploads = self.convert_attachments_to_uploads(extra_attachments)
self.attach_uploads_to_formdata(formdata, uploads, self.to)
formdata.store() # store and invalidate cache, so references can be used in the comment message.
# the comment can use attachments done above
if self.comment:
if comment:
try:
formdata.evolution[-1].add_part(
JournalEvolutionPart(formdata, get_publisher().translate(self.comment), self.to)
JournalEvolutionPart(formdata, get_publisher().translate(comment), self.to)
)
formdata.store()
except TemplateError as e: