0001-workflows-add-target-roles-in-commenter-workflow-ite.patch
tests/test_formdata.py | ||
---|---|---|
423 | 423 |
d.user_id = local_user.id |
424 | 424 |
d.receipt_time = time.localtime() |
425 | 425 |
evo = Evolution() |
426 | 426 |
evo.time = time.localtime() |
427 | 427 |
evo.status = 'wf-%s' % st_new.id |
428 | 428 |
evo.who = '_submitter' |
429 | 429 |
d.evolution = [evo] |
430 | 430 |
d.store() |
431 |
evo.add_part(JournalEvolutionPart(d, "ok")) |
|
431 |
evo.add_part(JournalEvolutionPart(d, "ok", None))
|
|
432 | 432 |
evo.add_part(JournalWsCallErrorPart("summary", "label", "data")) |
433 | 433 |
evo = Evolution() |
434 | 434 |
evo.time = time.localtime() |
435 | 435 |
evo.status = 'wf-%s' % st_finished.id |
436 | 436 |
evo.who = '_submitter' |
437 | 437 |
d.evolution.append(evo) |
438 | 438 |
d.store() |
439 | 439 | |
... | ... | |
463 | 463 |
export = d.get_json_export_dict(anonymise=True) |
464 | 464 |
assert 'evolution' in export |
465 | 465 |
assert len(export['evolution']) == 2 |
466 | 466 |
assert export['evolution'][0]['status'] == st_new.id |
467 | 467 |
assert 'time' in export['evolution'][0] |
468 | 468 |
assert 'who' not in export['evolution'][0] |
469 | 469 |
assert 'parts' in export['evolution'][0] |
470 | 470 |
assert len(export['evolution'][0]['parts']) == 2 |
471 |
assert len(export['evolution'][0]['parts'][0]) == 1
|
|
471 |
assert len(export['evolution'][0]['parts'][0]) == 2
|
|
472 | 472 |
assert export['evolution'][0]['parts'][0]['type'] == 'workflow-comment' |
473 | 473 |
assert len(export['evolution'][0]['parts'][1]) == 1 |
474 | 474 |
assert export['evolution'][0]['parts'][1]['type'] == 'wscall-error' |
475 | 475 |
assert export['evolution'][1]['status'] == st_finished.id |
476 | 476 |
assert 'time' in export['evolution'][1] |
477 | 477 |
assert 'who' not in export['evolution'][0] |
478 | 478 |
assert 'parts' not in export['evolution'][1] |
479 | 479 |
tests/test_workflows.py | ||
---|---|---|
1048 | 1048 |
assert len(os.listdir(os.path.join(get_publisher().app_dir, 'attachments', subdir))) == 1 |
1049 | 1049 | |
1050 | 1050 |
assert len(formdata.evolution[-1].parts) == 2 |
1051 | 1051 |
assert isinstance(formdata.evolution[-1].parts[0], AttachmentEvolutionPart) |
1052 | 1052 |
assert formdata.evolution[-1].parts[0].orig_filename == upload.orig_filename |
1053 | 1053 | |
1054 | 1054 |
assert isinstance(formdata.evolution[-1].parts[1], JournalEvolutionPart) |
1055 | 1055 |
assert len(formdata.evolution[-1].parts[1].content) > 0 |
1056 |
comment_view = str(formdata.evolution[-1].parts[1].view()) |
|
1056 |
comment_view = str(formdata.evolution[-1].parts[1].view(formdata))
|
|
1057 | 1057 |
assert comment_view == '<p>%s</p>' % comment_text |
1058 | 1058 | |
1059 | 1059 |
if os.path.exists(os.path.join(get_publisher().app_dir, 'attachments')): |
1060 | 1060 |
shutil.rmtree(os.path.join(get_publisher().app_dir, 'attachments')) |
1061 | 1061 | |
1062 | 1062 |
formdata.evolution[-1].parts = [] |
1063 | 1063 |
formdata.store() |
1064 | 1064 | |
... | ... | |
1084 | 1084 |
assert len(os.listdir(os.path.join(get_publisher().app_dir, 'attachments', subdir))) == 1 |
1085 | 1085 | |
1086 | 1086 |
assert len(formdata.evolution[-1].parts) == 2 |
1087 | 1087 |
assert isinstance(formdata.evolution[-1].parts[0], AttachmentEvolutionPart) |
1088 | 1088 |
assert formdata.evolution[-1].parts[0].orig_filename == 'hello.txt' |
1089 | 1089 | |
1090 | 1090 |
assert isinstance(formdata.evolution[-1].parts[1], JournalEvolutionPart) |
1091 | 1091 |
assert len(formdata.evolution[-1].parts[1].content) > 0 |
1092 |
comment_view = str(formdata.evolution[-1].parts[1].view()) |
|
1092 |
comment_view = str(formdata.evolution[-1].parts[1].view(formdata))
|
|
1093 | 1093 |
assert comment_view == '<p>%s</p>' % comment_text |
1094 | 1094 | |
1095 | 1095 | |
1096 |
def test_register_comment_to(pub): |
|
1097 |
workflow = Workflow(name='register comment to') |
|
1098 |
st1 = workflow.add_status('Status1', 'st1') |
|
1099 | ||
1100 |
role = Role(name='foorole') |
|
1101 |
role.store() |
|
1102 |
role2 = Role(name='no-one-role') |
|
1103 |
role2.store() |
|
1104 |
user = pub.user_class(name='baruser') |
|
1105 |
user.roles = [] |
|
1106 |
user.store() |
|
1107 | ||
1108 |
FormDef.wipe() |
|
1109 |
formdef = FormDef() |
|
1110 |
formdef.url_name = 'foobar' |
|
1111 |
formdef._workflow = workflow |
|
1112 | ||
1113 |
formdata = formdef.data_class()() |
|
1114 |
formdata.just_created() |
|
1115 |
assert formdata.status == 'wf-st1' |
|
1116 |
formdata.store() |
|
1117 | ||
1118 |
register_commenter = RegisterCommenterWorkflowStatusItem() |
|
1119 |
register_commenter.parent = st1 |
|
1120 |
st1.items.append(register_commenter) |
|
1121 | ||
1122 |
def display_last_part(): |
|
1123 |
rendered = formdata.evolution[-1].parts[-1].view(formdata) |
|
1124 |
return str(rendered) |
|
1125 | ||
1126 |
register_commenter.comment = 'all' |
|
1127 |
register_commenter.to = None |
|
1128 |
register_commenter.perform(formdata) |
|
1129 |
assert len(formdata.evolution[-1].parts) == 1 |
|
1130 |
assert display_last_part() == '<p>all</p>' |
|
1131 | ||
1132 |
register_commenter.comment = 'to-role' |
|
1133 |
register_commenter.to = [role.id] |
|
1134 |
register_commenter.perform(formdata) |
|
1135 |
assert len(formdata.evolution[-1].parts) == 2 |
|
1136 |
assert not display_last_part() |
|
1137 |
pub._request._user = user |
|
1138 |
assert not display_last_part() |
|
1139 |
user.roles = [role.id] |
|
1140 |
register_commenter.perform(formdata) |
|
1141 |
assert display_last_part() == '<p>to-role</p>' |
|
1142 | ||
1143 |
user.roles = [] |
|
1144 |
register_commenter.comment = 'to-submitter' |
|
1145 |
register_commenter.to = ['_submitter'] |
|
1146 |
register_commenter.perform(formdata) |
|
1147 |
assert not display_last_part() |
|
1148 |
formdata.user_id = user.id |
|
1149 |
assert display_last_part() == '<p>to-submitter</p>' |
|
1150 | ||
1151 |
register_commenter.comment = 'to-role-or-submitter' |
|
1152 |
register_commenter.to = [role.id, '_submitter'] |
|
1153 |
register_commenter.perform(formdata) |
|
1154 |
assert display_last_part() == '<p>to-role-or-submitter</p>' |
|
1155 |
formdata.user_id = None |
|
1156 |
assert not display_last_part() |
|
1157 |
user.roles = [role.id] |
|
1158 |
assert display_last_part() == '<p>to-role-or-submitter</p>' |
|
1159 |
user.roles = [] |
|
1160 |
formdata.user_id = user.id |
|
1161 |
assert display_last_part() == '<p>to-role-or-submitter</p>' |
|
1162 |
register_commenter.to = [role2.id] |
|
1163 |
register_commenter.perform(formdata) |
|
1164 |
assert not display_last_part() |
|
1165 | ||
1166 |
register_commenter.comment = 'd1' |
|
1167 |
register_commenter2 = RegisterCommenterWorkflowStatusItem() |
|
1168 |
register_commenter2.parent = st1 |
|
1169 |
st1.items.append(register_commenter2) |
|
1170 |
register_commenter2.comment = 'd2' |
|
1171 |
register_commenter2.to = [role.id, '_submitter'] |
|
1172 |
user.roles = [role.id, role2.id] |
|
1173 |
register_commenter.perform(formdata) |
|
1174 |
register_commenter2.perform(formdata) |
|
1175 |
display_parts = formdata.evolution[-1].display_parts() |
|
1176 |
assert '<p>d1</p>' in [str(x) for x in display_parts] |
|
1177 |
assert '<p>d2</p>' in [str(x) for x in display_parts] |
|
1178 | ||
1179 | ||
1096 | 1180 |
def test_email(pub, emails): |
1097 | 1181 |
pub.substitutions.feed(MockSubstitutionVariables()) |
1098 | 1182 | |
1099 | 1183 |
formdef = FormDef() |
1100 | 1184 |
formdef.name = 'baz' |
1101 | 1185 |
formdef.fields = [] |
1102 | 1186 |
formdef.store() |
1103 | 1187 | |
... | ... | |
1719 | 1803 |
formdata.workflow_data = None |
1720 | 1804 | |
1721 | 1805 |
item = WebserviceCallStatusItem() |
1722 | 1806 |
item.url = 'http://remote.example.net/400-json' |
1723 | 1807 |
item.record_errors = True |
1724 | 1808 |
item.action_on_4xx = ':stop' |
1725 | 1809 |
with pytest.raises(AbortActionException): |
1726 | 1810 |
item.perform(formdata) |
1727 |
rendered = formdata.evolution[-1].parts[-1].view() |
|
1811 |
rendered = formdata.evolution[-1].parts[-1].view(formdata)
|
|
1728 | 1812 |
assert not rendered # empty if not in backoffice |
1729 | 1813 |
req = HTTPRequest(None, {'SERVER_NAME': 'example.net', 'SCRIPT_NAME': '/backoffice/'}) |
1730 | 1814 |
pub._set_request(req) |
1731 |
rendered = formdata.evolution[-1].parts[-1].view() |
|
1815 |
rendered = formdata.evolution[-1].parts[-1].view(formdata)
|
|
1732 | 1816 |
assert 'Error during webservice call' in str(rendered) |
1733 | 1817 |
assert 'Error Code: 1' in str(rendered) |
1734 | 1818 |
assert 'Error Description: :(' in str(rendered) |
1735 | 1819 | |
1736 | 1820 |
item.label = 'do that' |
1737 | 1821 |
with pytest.raises(AbortActionException): |
1738 | 1822 |
item.perform(formdata) |
1739 |
rendered = formdata.evolution[-1].parts[-1].view() |
|
1823 |
rendered = formdata.evolution[-1].parts[-1].view(formdata)
|
|
1740 | 1824 |
assert 'Error during webservice call "do that"' in str(rendered) |
1741 | 1825 | |
1742 | 1826 |
item = WebserviceCallStatusItem() |
1743 | 1827 |
item.method = 'GET' |
1744 | 1828 |
item.url = 'http://remote.example.net?in_url=1' |
1745 | 1829 |
item.qs_data = { |
1746 | 1830 |
'str': 'abcd', |
1747 | 1831 |
'one': '=1', |
wcs/formdata.py | ||
---|---|---|
149 | 149 | |
150 | 150 |
if not self.parts: |
151 | 151 |
return [] |
152 | 152 | |
153 | 153 |
l = [] |
154 | 154 |
for p in self.parts: |
155 | 155 |
if not hasattr(p, 'view'): |
156 | 156 |
continue |
157 |
text = p.view() |
|
157 |
text = p.view(self.formdata)
|
|
158 | 158 |
if text: |
159 | 159 |
l.append(text) |
160 | 160 |
self._display_parts = l |
161 | 161 |
return self._display_parts |
162 | 162 | |
163 | 163 |
def get_json_export_dict(self, user, anonymise=False): |
164 | 164 |
data = { |
165 | 165 |
'time': datetime.datetime(*self.time[:6]) if self.time else None, |
wcs/sql.py | ||
---|---|---|
1733 | 1733 |
elif type(value) in (tuple, list): |
1734 | 1734 |
fts_strings.extend(value) |
1735 | 1735 |
if self._evolution: |
1736 | 1736 |
for evo in self._evolution: |
1737 | 1737 |
if evo.comment: |
1738 | 1738 |
fts_strings.append(evo.comment) |
1739 | 1739 |
for part in evo.parts or []: |
1740 | 1740 |
if hasattr(part, 'view'): |
1741 |
html_part = part.view() |
|
1741 |
html_part = part.view(self)
|
|
1742 | 1742 |
if html_part: |
1743 | 1743 |
fts_strings.append(qommon.misc.html2text(html_part)) |
1744 | 1744 |
user = self.get_user() |
1745 | 1745 |
if user: |
1746 | 1746 |
fts_strings.append(user.get_display_name()) |
1747 | 1747 | |
1748 | 1748 |
sql_statement = '''UPDATE %s SET fts = to_tsvector( %%(fts)s) |
1749 | 1749 |
WHERE id = %%(id)s''' % self._table_name |
wcs/wf/create_formdata.py | ||
---|---|---|
158 | 158 |
d = {} |
159 | 159 |
for part in formdata.iter_evolution_parts(): |
160 | 160 |
if not isinstance(part, cls): |
161 | 161 |
continue |
162 | 162 |
if part.formdata: |
163 | 163 |
d['form_links_%s' % (part.varname or '*')] = part |
164 | 164 |
return d |
165 | 165 | |
166 |
def view(self): |
|
166 |
def view(self, filled):
|
|
167 | 167 |
if self.attach_to_history: |
168 | 168 |
return htmltext('<p class="wf-links">%s <a href="%s">%s %s</a>') % ( |
169 | 169 |
_('Created new form'), |
170 | 170 |
self.formdata and self.formdata.get_url( |
171 | 171 |
backoffice=bool(get_request() and get_request().is_in_backoffice())), |
172 | 172 |
self.formdef.name if self.formdef else _('Deleted'), |
173 | 173 |
self.formdata_id) |
174 | 174 |
else: |
wcs/wf/register_comment.py | ||
---|---|---|
9 | 9 |
# This program is distributed in the hope that it will be useful, |
10 | 10 |
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
11 | 11 |
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
12 | 12 |
# GNU General Public License for more details. |
13 | 13 |
# |
14 | 14 |
# You should have received a copy of the GNU General Public License |
15 | 15 |
# along with this program; if not, see <http://www.gnu.org/licenses/>. |
16 | 16 | |
17 |
from quixote import get_request |
|
18 | ||
17 | 19 |
from ..qommon import _, N_, ezt |
18 | 20 |
from ..qommon.form import * |
19 | 21 |
from ..qommon.template import TemplateError |
20 | 22 |
from ..qommon import get_logger |
21 | 23 | |
22 | 24 |
from wcs.workflows import (WorkflowStatusItem, register_item_class, template_on_formdata, |
23 |
AttachmentEvolutionPart) |
|
25 |
AttachmentEvolutionPart, get_role_translation)
|
|
24 | 26 | |
25 | 27 |
import sys |
26 | 28 | |
27 | 29 | |
28 | 30 |
class JournalEvolutionPart: #pylint: disable=C1001 |
29 | 31 |
content = None |
32 |
to = None |
|
30 | 33 | |
31 |
def __init__(self, formdata, message): |
|
34 |
def __init__(self, formdata, message, to):
|
|
32 | 35 |
if not message: |
33 | 36 |
return |
37 |
self.to = to |
|
34 | 38 |
if '{{' in message or '{%' in message: |
35 | 39 |
# django template |
36 | 40 |
content = template_on_formdata(formdata, message) |
37 | 41 |
if content and not content.startswith('<'): |
38 | 42 |
# add <div> to mark the string as processed as HTML |
39 | 43 |
content = '<div>%s</div>' % content |
40 | 44 |
self.content = content |
41 | 45 |
return |
... | ... | |
44 | 48 |
# treat it as html, escape strings from ezt variables |
45 | 49 |
self.content = template_on_formdata(formdata, message, |
46 | 50 |
ezt_format=ezt.FORMAT_HTML) |
47 | 51 |
return |
48 | 52 | |
49 | 53 |
# treat is as text/plain |
50 | 54 |
self.content = template_on_formdata(formdata, message) |
51 | 55 | |
52 |
def view(self): |
|
53 |
if not self.content: |
|
56 |
def is_for_current_user(self, filled): |
|
57 |
if not self.to: |
|
58 |
return True |
|
59 |
if not get_request(): |
|
60 |
return False |
|
61 |
user = get_request().user |
|
62 |
for role in self.to or []: |
|
63 |
if role == '_submitter': |
|
64 |
if filled.is_submitter(user): |
|
65 |
return True |
|
66 |
elif user: |
|
67 |
role = get_role_translation(filled, role) |
|
68 |
if role in user.get_roles(): |
|
69 |
return True |
|
70 |
return False |
|
71 | ||
72 |
def view(self, filled): |
|
73 |
if not (self.content and self.is_for_current_user(filled)): |
|
54 | 74 |
return '' |
55 | 75 |
if self.content.startswith('<'): |
56 | 76 |
return htmltext(self.content) |
57 | 77 |
else: |
58 | 78 |
# use empty lines to mark paragraphs |
59 | 79 |
return htmltext('<p>') + \ |
60 | 80 |
htmltext('\n').join( |
61 | 81 |
[(x or htmltext('</p><p>')) for x in self.content.splitlines()]) + \ |
62 | 82 |
htmltext('</p>') |
63 | 83 | |
64 | 84 |
def get_json_export_dict(self, anonymise=False): |
65 | 85 |
d = { |
66 | 86 |
'type': 'workflow-comment', |
87 |
'to': self.to, |
|
67 | 88 |
} |
68 | 89 |
if not anonymise: |
69 | 90 |
d['content'] = self.content |
70 | 91 |
return d |
71 | 92 | |
72 | 93 | |
73 | 94 |
class RegisterCommenterWorkflowStatusItem(WorkflowStatusItem): |
74 | 95 |
description = N_('History Message') |
75 | 96 |
key = 'register-comment' |
76 | 97 |
category = 'interaction' |
77 | 98 | |
78 | 99 |
comment = None |
100 |
to = None |
|
79 | 101 |
attachments = None |
80 | 102 | |
81 | 103 |
def add_parameters_widgets(self, form, parameters, prefix='', formdef=None): |
82 | 104 |
super(RegisterCommenterWorkflowStatusItem, self).add_parameters_widgets( |
83 | 105 |
form, parameters, prefix=prefix, formdef=formdef) |
84 | 106 |
if 'comment' in parameters: |
85 | 107 |
form.add(TextWidget, '%scomment' % prefix, title=_('Message'), |
86 | 108 |
value=self.comment, cols=80, rows=10) |
109 |
if 'to' in parameters: |
|
110 |
form.add(WidgetList, '%sto' % prefix, title=_('To'), |
|
111 |
element_type=SingleSelectWidget, |
|
112 |
value=self.to or [], |
|
113 |
add_element_label=self.get_add_role_label(), |
|
114 |
element_kwargs={'render_br': False, |
|
115 |
'options': [(None, '---', None)] + |
|
116 |
self.get_list_of_roles(include_logged_in_users=False)}) |
|
87 | 117 | |
88 | 118 |
def get_parameters(self): |
89 |
return ('comment', 'attachments', 'condition') |
|
119 |
return ('comment', 'to', 'attachments', 'condition')
|
|
90 | 120 | |
91 | 121 |
def attach_uploads_to_formdata(self, formdata, uploads): |
92 | 122 |
if not formdata.evolution[-1].parts: |
93 | 123 |
formdata.evolution[-1].parts = [] |
94 | 124 |
for upload in uploads: |
95 | 125 |
try: |
96 | 126 |
# useless but required to restore upload.fp from serialized state, |
97 | 127 |
# needed by AttachmentEvolutionPart.from_upload() |
... | ... | |
110 | 140 |
# (with substitution vars) |
111 | 141 |
if self.attachments: |
112 | 142 |
uploads = self.convert_attachments_to_uploads() |
113 | 143 |
self.attach_uploads_to_formdata(formdata, uploads) |
114 | 144 |
formdata.store() # store and invalidate cache, so references can be used in the comment message. |
115 | 145 | |
116 | 146 |
# the comment can use attachments done above |
117 | 147 |
try: |
118 |
formdata.evolution[-1].add_part(JournalEvolutionPart(formdata, self.comment)) |
|
148 |
formdata.evolution[-1].add_part(JournalEvolutionPart(formdata, self.comment, self.to))
|
|
119 | 149 |
formdata.store() |
120 | 150 |
except TemplateError as e: |
121 | 151 |
url = formdata.get_url() |
122 | 152 |
get_logger().error('error in template for comment [%s], ' |
123 | 153 |
'comment could not be generated: %s' % (url, str(e))) |
124 | 154 | |
125 | 155 | |
126 | 156 |
register_item_class(RegisterCommenterWorkflowStatusItem) |
wcs/wf/wscall.py | ||
---|---|---|
42 | 42 |
label = None |
43 | 43 | |
44 | 44 |
def __init__(self, summary, label=None, data=None): |
45 | 45 |
self.summary = summary |
46 | 46 |
self.label = label |
47 | 47 |
if data: |
48 | 48 |
self.data = data[:10000] # beware of huge responses |
49 | 49 | |
50 |
def view(self): |
|
50 |
def view(self, filled):
|
|
51 | 51 |
if not (get_request() and get_request().get_path().startswith('/backoffice/')): |
52 | 52 |
return '' |
53 | 53 |
r = TemplateIO(html=True) |
54 | 54 |
r += htmltext('<div class="ws-error">') |
55 | 55 |
r += htmltext('<h4 class="foldable folded">') |
56 | 56 |
if self.label: |
57 | 57 |
r += _('Error during webservice call "%s"') % self.label |
58 | 58 |
else: |
wcs/workflows.py | ||
---|---|---|
226 | 226 |
if not os.path.exists(dirname): |
227 | 227 |
os.mkdir(dirname) |
228 | 228 |
odict['filename'] = os.path.join(dirname, filename) |
229 | 229 |
self.filename = odict['filename'] |
230 | 230 |
self.fp.seek(0) |
231 | 231 |
atomic_write(self.filename, self.fp) |
232 | 232 |
return odict |
233 | 233 | |
234 |
def view(self): |
|
234 |
def view(self, filled):
|
|
235 | 235 |
show_link = True |
236 | 236 |
if self.has_redirect_url(): |
237 | 237 |
is_in_backoffice = bool(get_request() and get_request().is_in_backoffice()) |
238 | 238 |
show_link = bool(self.get_redirect_url(backoffice=is_in_backoffice)) |
239 | 239 |
if show_link: |
240 | 240 |
return htmltext('<p class="wf-attachment"><a href="attachment?f=%s">%s</a></p>' % ( |
241 | 241 |
os.path.basename(self.filename), self.orig_filename)) |
242 | 242 |
else: |
243 |
- |