diff --git a/tests/admin_pages/test_settings.py b/tests/admin_pages/test_settings.py index e6a62e144..9998321e0 100644 --- a/tests/admin_pages/test_settings.py +++ b/tests/admin_pages/test_settings.py @@ -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' in resp.text + assert '1 card' in resp.text + assert '1 fields block' in resp.text + assert '1 workflow' in resp.text + assert '1 role' 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 block category' in resp.text + assert '1 mail template category' in resp.text + assert '1 comment template category' in resp.text + assert '1 data source category' in resp.text + assert '1 data source' in resp.text + assert '1 mail template' in resp.text + assert '1 comment template' in resp.text + assert '1 webservice call' in resp.text + assert '1 API access' 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() diff --git a/tests/admin_pages/test_studio.py b/tests/admin_pages/test_studio.py index f0a158b2f..9d6da4948 100644 --- a/tests/admin_pages/test_studio.py +++ b/tests/admin_pages/test_studio.py @@ -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('') - assert '21-40/42' in resp.text + assert '21-40/48' in resp.text resp = resp.click('') - assert '41-42/42' in resp.text + assert '41-48/48' in resp.text user.is_admin = False user.store() diff --git a/tests/test_comment_template.py b/tests/test_comment_template.py new file mode 100644 index 000000000..95be60dce --- /dev/null +++ b/tests/test_comment_template.py @@ -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 diff --git a/tests/test_snapshots.py b/tests/test_snapshots.py index b579a5c4d..81ec97586 100644 --- a/tests/test_snapshots.py +++ b/tests/test_snapshots.py @@ -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 '
%s
' % 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) diff --git a/wcs/admin/categories.py b/wcs/admin/categories.py index 876378f23..e64591c03 100644 --- a/wcs/admin/categories.py +++ b/wcs/admin/categories.py @@ -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 diff --git a/wcs/admin/comment_templates.py b/wcs/admin/comment_templates.py new file mode 100644 index 000000000..e517a1fef --- /dev/null +++ b/wcs/admin/comment_templates.py @@ -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%s
') % _('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('%s
%s
' % _('You are about to irrevocably delete this comment template.')) + ) + form.add_submit('delete', _('Submit')) + else: + form.widgets.append( + HtmlWidget('%s
' % _('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('