Projet

Général

Profil

0001-workflows-add-target-roles-in-commenter-workflow-ite.patch

Nicolas Roche, 11 décembre 2020 16:44

Télécharger (18,7 ko)

Voir les différences:

Subject: [PATCH] workflows: add target roles in commenter workflow item
 (#38254)

 tests/test_formdata.py     |  4 +-
 tests/test_workflows.py    | 94 ++++++++++++++++++++++++++++++++++++--
 wcs/formdata.py            |  2 +-
 wcs/sql.py                 |  2 +-
 wcs/wf/create_formdata.py  |  2 +-
 wcs/wf/register_comment.py | 42 ++++++++++++++---
 wcs/wf/wscall.py           |  2 +-
 wcs/workflows.py           |  2 +-
 8 files changed, 132 insertions(+), 18 deletions(-)
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 &quot;do that&quot;' 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
-