Projet

Général

Profil

0001-export-evolutions-in-form-API-10820.patch

Benjamin Dauvergne, 09 mai 2016 11:44

Télécharger (15,2 ko)

Voir les différences:

Subject: [PATCH] export evolutions in form API (#10820)

 help/fr/api-get.page       | 40 +++++++++++++++++++++--
 tests/test_api.py          | 23 +++++++++++++-
 tests/test_formdata.py     | 79 ++++++++++++++++++++++++++++++++++++++++++++++
 wcs/formdata.py            | 44 ++++++++++++++++++--------
 wcs/users.py               | 11 +++++++
 wcs/wf/register_comment.py |  8 +++++
 wcs/wf/wscall.py           | 12 +++++++
 7 files changed, 200 insertions(+), 17 deletions(-)
help/fr/api-get.page
54 54
    },
55 55
    "workflow": {
56 56
        "status": {
57
            "id": "new",
57
            "id": "1",
58 58
            "name": "New"
59 59
        },
60 60
        "data": {
......
110 110
    "submission": {
111 111
      "backoffice": false,
112 112
      "channel": "Web"
113
    }
113
    },
114
    "evolution": [
115
      {
116
        "status": "1",
117
        "time": "2013-01-04T13:39:49",
118
        "user": {
119
            "id": 1,
120
            "name": "Fred"
121
            "email": "fred@example.com",
122
            "NameID": ["123456"]
123
        },
124
        "parts": [
125
          {
126
            "type": "wscall-error",
127
            "summary": "description de l'erreur",
128
            "label": "appel du web-service XYZ",
129
            "data": "données reçues jusqu'à 10000 octets..."
130
          },
131
          {
132
            "type": "workflow-comment",
133
            "content": "commentaire"
134
          }
135
        ]
136
      },
137
    ]
114 138
}
115 139
</code>
116 140

  
......
133 157
dans l'attribut <code>submission</code>.
134 158
</p>
135 159

  
160
<p>
161
L'historique du formulaire, ses transitions dans différents statuts, est disponible dans l'attribut
162
<code>evolution</code>. Cette liste de dictionnaires contient l'instant de la transition
163
dans l'attribut <code>time</code>, le code du statut concerné dans <code>status</code> et
164
une description de l'utilisateur responsable de la transition dans <code>user</code>. L'attribut
165
optionnel <code>parts</code> peut contenir une liste de dictionnaires liés aux actions de workflow,
166
comme un commentaire ou une erreur lors de l'appel d'un <em>web service</em>.
167
</p>
168

  
169

  
136 170
<note>
137 171
 <p>
138 172
  Il est bien sûr nécessaire de disposer des autorisations nécessaires pour
......
302 336
paramètre supplémentaire <code>anonymise</code>. Quand celui-ci est présent des données anonymisées
303 337
des formulaires sont renvoyées et les contrôles d'accès sont simplifiés à une signature simple, il
304 338
n'est pas nécessaire de préciser l'identifiant d'un utilisateur.
305
<p>
339
</p>
306 340

  
307 341
<screen>
308 342
<output style="prompt">$ </output><input>curl -H "Accept: application/json" \
tests/test_api.py
17 17
from wcs.users import User
18 18
from wcs.roles import Role
19 19
from wcs.formdef import FormDef
20
from wcs.formdata import Evolution
20 21
from wcs.categories import Category
21 22
from wcs.data_sources import NamedDataSource
22 23
from wcs.workflows import Workflow, EditableWorkflowStatusItem
......
1007 1008
        if i%7 == 0:
1008 1009
            formdata.backoffice_submission = True
1009 1010
            formdata.submission_channel = 'mail'
1010

  
1011 1011
        formdata.store()
1012 1012

  
1013 1013
    # check access is denied if the user has not the appropriate role
......
1030 1030
    assert 'fields' in resp.json[0]
1031 1031
    assert 'file' not in resp.json[0]['fields'] # no file export in full lists
1032 1032
    assert 'user' in resp.json[0]
1033
    assert 'evolution' in resp.json[0]
1034
    assert len(resp.json[0]['evolution']) == 2
1035
    assert 'status' in resp.json[0]['evolution'][0]
1036
    assert 'who' in resp.json[0]['evolution'][0]
1037
    assert 'time' in resp.json[0]['evolution'][0]
1038
    assert resp.json[0]['evolution'][0]['who']['id'] == local_user.id
1033 1039

  
1034 1040
    assert [x for x in resp.json if x['fields']['foobar'] == 'FOO BAR 0'][0]['submission']['backoffice'] is True
1035 1041
    assert [x for x in resp.json if x['fields']['foobar'] == 'FOO BAR 0'][0]['submission']['channel'] == 'Mail'
......
1105 1111
    assert 'file' not in resp.json[0]['fields'] # no file export in full lists
1106 1112
    assert 'foobar3' in resp.json[0]['fields']
1107 1113
    assert 'foobar' not in resp.json[0]['fields']
1114
    assert 'evolution' in resp.json[0]
1115
    assert len(resp.json[0]['evolution']) == 2
1116
    assert 'status' in resp.json[0]['evolution'][0]
1117
    assert not 'who' in resp.json[0]['evolution'][0]
1118
    assert 'time' in resp.json[0]['evolution'][0]
1108 1119

  
1109 1120
    # check access is granted event if there is no user
1110 1121
    resp = get_app(pub).get(sign_uri('/api/forms/test/list?anonymise&full=on'))
......
1115 1126
    assert 'file' not in resp.json[0]['fields'] # no file export in full lists
1116 1127
    assert 'foobar3' in resp.json[0]['fields']
1117 1128
    assert 'foobar' not in resp.json[0]['fields']
1129
    assert 'evolution' in resp.json[0]
1130
    assert len(resp.json[0]['evolution']) == 2
1131
    assert 'status' in resp.json[0]['evolution'][0]
1132
    assert not 'who' in resp.json[0]['evolution'][0]
1133
    assert 'time' in resp.json[0]['evolution'][0]
1118 1134
    # check anonymise is enforced on detail view
1119 1135
    resp = get_app(pub).get(sign_uri('/api/forms/%s/?anonymise&full=on' % resp.json[0]['id']))
1120 1136
    assert 'receipt_time' in resp.json
......
1123 1139
    assert 'file' not in resp.json['fields'] # no file export in detail
1124 1140
    assert 'foobar3' in resp.json['fields']
1125 1141
    assert 'foobar' not in resp.json['fields']
1142
    assert 'evolution' in resp.json
1143
    assert len(resp.json['evolution']) == 2
1144
    assert 'status' in resp.json['evolution'][0]
1145
    assert not 'who' in resp.json['evolution'][0]
1146
    assert 'time' in resp.json['evolution'][0]
1126 1147

  
1127 1148
def test_roles(pub, local_user):
1128 1149
    Role.wipe()
tests/test_formdata.py
12 12
from wcs.formdata import Evolution
13 13
from wcs.workflows import Workflow, WorkflowCriticalityLevel
14 14
from wcs.wf.anonymise import AnonymiseWorkflowStatusItem
15
from wcs.wf.wscall import JournalWsCallErrorPart
16
from wcs.wf.register_comment import JournalEvolutionPart
15 17
from wcs.qommon.form import NoUpload
16 18
import mock
17 19

  
18 20
from utilities import create_temporary_pub, clean_temporary_pub
19 21

  
22
from test_api import local_user
23

  
20 24
def pytest_generate_tests(metafunc):
21 25
    if 'pub' in metafunc.fixturenames:
22 26
        metafunc.parametrize('pub', ['pickle', 'sql'], indirect=True)
......
434 438
    variables = formdata.get_substitution_variables()
435 439
    assert variables.get('form_var_xxx') == 'un'
436 440
    assert variables.get('form_var_xxx_raw') == '1'
441

  
442

  
443
def test_get_json_export_dict_evolution(pub, local_user):
444
    Workflow.wipe()
445
    workflow = Workflow(name='test')
446
    st_new = workflow.add_status('New')
447
    st_finished = workflow.add_status('Finished')
448
    workflow.store()
449

  
450
    formdef = FormDef()
451
    formdef.workflow_id = workflow.id
452
    formdef.name = 'foo'
453
    formdef.fields = []
454
    formdef.store()
455
    formdef.data_class().wipe()
456

  
457
    d = formdef.data_class()()
458
    d.status = 'wf-%s' % st_new.id
459
    d.user_id = local_user.id
460
    d.receipt_time = time.localtime()
461
    evo = Evolution()
462
    evo.time = time.localtime()
463
    evo.status = 'wf-%s' % st_new.id
464
    evo.who = '_submitter'
465
    d.evolution = [evo]
466
    d.store()
467
    evo.add_part(JournalEvolutionPart(d, "ok"))
468
    evo.add_part(JournalWsCallErrorPart("summary", "label", "data"))
469
    evo = Evolution()
470
    evo.time = time.localtime()
471
    evo.status = 'wf-%s' % st_finished.id
472
    evo.who = '_submitter'
473
    d.evolution.append(evo)
474
    d.store()
475

  
476
    export = d.get_json_export_dict()
477
    assert 'evolution' in export
478
    assert len(export['evolution']) == 2
479
    assert export['evolution'][0]['status'] == st_new.id
480
    assert 'time' in export['evolution'][0]
481
    assert export['evolution'][0]['who']['id'] == local_user.id
482
    assert export['evolution'][0]['who']['email'] == local_user.email
483
    assert export['evolution'][0]['who']['NameID'] == local_user.name_identifiers
484
    assert 'parts' in export['evolution'][0]
485
    assert len(export['evolution'][0]['parts']) == 2
486
    assert export['evolution'][0]['parts'][0]['type'] == 'workflow-comment'
487
    assert export['evolution'][0]['parts'][0]['content'] == 'ok'
488
    assert export['evolution'][0]['parts'][1]['type'] == 'wscall-error'
489
    assert export['evolution'][0]['parts'][1]['summary'] == 'summary'
490
    assert export['evolution'][0]['parts'][1]['label'] == 'label'
491
    assert export['evolution'][0]['parts'][1]['data'] == 'data'
492
    assert export['evolution'][1]['status'] == st_finished.id
493
    assert 'time' in export['evolution'][1]
494
    assert export['evolution'][1]['who']['id'] == local_user.id
495
    assert export['evolution'][1]['who']['email'] == local_user.email
496
    assert export['evolution'][1]['who']['NameID'] == local_user.name_identifiers
497
    assert 'parts' not in export['evolution'][1]
498

  
499
    export = d.get_json_export_dict(anonymise=True)
500
    assert 'evolution' in export
501
    assert len(export['evolution']) == 2
502
    assert export['evolution'][0]['status'] == st_new.id
503
    assert 'time' in export['evolution'][0]
504
    assert 'who' not in export['evolution'][0]
505
    assert 'parts' in export['evolution'][0]
506
    assert len(export['evolution'][0]['parts']) == 2
507
    assert len(export['evolution'][0]['parts'][0]) == 1
508
    assert export['evolution'][0]['parts'][0]['type'] == 'workflow-comment'
509
    assert len(export['evolution'][0]['parts'][1]) == 1
510
    assert export['evolution'][0]['parts'][1]['type'] == 'wscall-error'
511
    assert export['evolution'][1]['status'] == st_finished.id
512
    assert 'time' in export['evolution'][1]
513
    assert 'who' not in export['evolution'][0]
514
    assert 'parts' not in export['evolution'][1]
515

  
wcs/formdata.py
152 152
            l.append(p.view())
153 153
        return l
154 154

  
155
    def get_json_export_dict(self, user, anonymise=False):
156
        data = {
157
            'status': self.status[3:],
158
            'time': self.time,
159
        }
160
        if not anonymise:
161
            try:
162
                if self.who != '_submitter':
163
                    user = get_publisher().user_class.get(self.who)
164
            except KeyError:
165
                pass
166
            else:
167
                if user:
168
                    data['who'] = user.get_json_export_dict()
169
            if self.comment:
170
                data['comment'] = self.comment
171
        parts = []
172
        for part in self.parts or []:
173
            if hasattr(part, 'get_json_export_dict'):
174
                parts.append(part.get_json_export_dict(anonymise=anonymise))
175
        if parts:
176
            data['parts'] = parts
177
        return data
178

  
155 179

  
156 180
class FormData(StorableObject):
157 181
    _names = 'XX'
......
703 727
                user = get_publisher().user_class.get(self.user_id)
704 728
            except KeyError:
705 729
                user = None
706
            # this is custom code so it is possible to mark forms as anonyms, this
707
            # is done through the VoteAnonymity field, this is very specific but
708
            # isn't generalised yet into an useful extension mechanism, as it's not
709
            # clear at the moment what could be useful.
710
            for f in self.formdef.fields:
711
                if f.key == 'vote-anonymity':
712
                    user = None
713
                    break
714 730
            if user:
715
                data['user'] = {'id': user.id, 'name': user.display_name}
716
                if user.email:
717
                    data['user']['email'] = user.email
718
                if user.name_identifiers:
719
                    data['user']['NameID'] = user.name_identifiers
731
                data['user'] = user.get_json_export_dict()
720 732

  
721 733
        data['fields'] = get_json_dict(self.formdef.fields, self.data,
722 734
                include_files=include_files, anonymise=anonymise)
......
756 768
            'channel': self.get_submission_channel_label(),
757 769
        }
758 770

  
771
        if self.evolution:
772
            evolution = data['evolution'] = []
773
            for evo in self.evolution:
774
                evolution.append(evo.get_json_export_dict(None if anonymise else user,
775
                                                          anonymise=anonymise))
776

  
759 777
        return data
760 778

  
761 779
    def export_to_json(self, include_files=True, anonymise=False):
wcs/users.py
188 188
            return self.__dict__['form_data'].get(attr[1:])
189 189
        raise AttributeError()
190 190

  
191
    def get_json_export_dict(self):
192
        data = {
193
            'id': self.id,
194
            'name': self.display_name,
195
        }
196
        if self.email:
197
            data['email'] = self.email
198
        if self.name_identifiers:
199
            data['NameID'] = self.name_identifiers
200
        return data
201

  
191 202

  
192 203
Substitutions.register('session_user', category=N_('User'), comment=N_('Session User'))
193 204
Substitutions.register('session_user_display_name', category=N_('User'), comment=N_('Session User Display Name'))
wcs/wf/register_comment.py
51 51
                            [(x or htmltext('</p><p>')) for x in self.content.splitlines()]) + \
52 52
                   htmltext('</p>')
53 53

  
54
    def get_json_export_dict(self, anonymise=False):
55
        d = {
56
            'type': 'workflow-comment',
57
        }
58
        if not anonymise:
59
            d['content'] = self.content
60
        return d
61

  
54 62

  
55 63
class RegisterCommenterWorkflowStatusItem(WorkflowStatusItem):
56 64
    description = N_('Record in Log')
wcs/wf/wscall.py
79 79
        r += htmltext('</div>')
80 80
        return r.getvalue()
81 81

  
82
    def get_json_export_dict(self, anonymise=False):
83
        d = {
84
            'type': 'wscall-error',
85
        }
86
        if not anonymise:
87
            d.update({
88
                'summary': self.summary,
89
                'label': self.label,
90
                'data': self.data,
91
            })
92
        return d
93

  
82 94

  
83 95
class WebserviceCallStatusItem(WorkflowStatusItem):
84 96
    description = N_('Webservice Call')
85
-