Projet

Général

Profil

0001-api-add-support-for-POST-on-existing-formdata-for-ed.patch

Frédéric Péters, 02 mai 2016 12:42

Télécharger (10,1 ko)

Voir les différences:

Subject: [PATCH] api: add support for POST on existing formdata, for edition
 (#10749)

 help/fr/api-fill.page | 56 +++++++++++++++++++++++++-----------
 tests/test_api.py     | 55 +++++++++++++++++++++++++++++++++++-
 wcs/api.py            | 78 ++++++++++++++++++++++++++++++++++++---------------
 wcs/forms/common.py   |  1 +
 4 files changed, 150 insertions(+), 40 deletions(-)
help/fr/api-fill.page
12 12

  
13 13
</info>
14 14

  
15
<title>Complétion et soumission d'un formulaire</title>
15
<title>Complétion et modification d'un formulaire</title>
16 16

  
17 17
<p>
18 18
w.c.s expose une API autorisant les logiciels tiers à transmettre des données
19
structurées permettant la complétion d'un formulaire. Deux modes d'opération
20
sont possibles, dans le premier l'ensemble des données est fourni en une étape
21
unique (mode données), dans le second les données sont fournies en fonction des
22
différentes pages définies dans le formulaire (mode flux).
19
structurées permettant la complétion d'un formulaire ou la modification d'un
20
formulaire existant.
23 21
</p>
24 22

  
25
<section id="data">
26
  <title>Mode données</title>
27

  
28
  <p>
29
   Ce mode est le plus simple et le plus adapté au développement d'un système
30
   tout automatique, sans interaction avec un utilisateur.
31
  </p>
23
<section id="create">
24
  <title>Complétion d'un formulaire</title>
32 25

  
33 26
  <p>
34 27
   La complétion d'un formulaire se fait par une requête <code>POST</code> à
......
133 126

  
134 127
</section>
135 128

  
136
<section id="flow">
137
  <title>Mode flux</title>
129
<section id="edit">
130
  <title>Modification d'un formulaire</title>
131

  
132
  <p>
133
   Un formulaire qui peut être modifié (par la présence d'une action de workflow
134
   de type « Permettre l'édition ») peut également être modifié via un appel à
135
   l'API, en faisant un <code>POST</code> sur l'adresse du formulaire.
136
  </p>
137

  
138
  <p>
139
   Les données attendues sont similaires à la création d'un nouveau formulaire,
140
   seuls les champs présents seront pris en compte.
141
  </p>
142

  
143
  <p>
144
   Cet appel :
145
  </p>
146

  
147
<screen>
148
<output style="prompt">$ </output><input>curl -H "Content-type: application/json" \
149
       -H "Accept: application/json" \
150
       -d@donnees.json \
151
       https://www.example.net/api/forms/newsletter/1/</input>
152
<output>{"err": 0}</output>
153
  </screen>
138 154

  
139 155
  <p>
140
   Ce mode est à utiliser si l'on veut exposer à un usager les différentes
141
   étapes de la complétion d'un formulaire.
156
  Avec les données suivantes en entrée, modifiera donc uniquement le champ
157
  « email ».
142 158
  </p>
143 159

  
160
  <code mime="application/json">
161
{
162
  "data": {
163
     "email": "marc@example.org"
164
  }
165
}
166
  </code>
167

  
144 168
</section>
145 169

  
146 170
</page>
tests/test_api.py
19 19
from wcs.formdef import FormDef
20 20
from wcs.categories import Category
21 21
from wcs.data_sources import NamedDataSource
22
from wcs.workflows import Workflow
22
from wcs.workflows import Workflow, EditableWorkflowStatusItem
23 23
from wcs.wf.jump import JumpWorkflowStatusItem
24 24
from wcs import fields, qommon
25 25
from wcs.api_utils import sign_url
......
737 737
    formdef.store()
738 738
    item_field = formdef.fields[4]
739 739

  
740
    formdef.data_class().wipe()
740 741
    formdata = formdef.data_class()()
741 742
    date = time.strptime('2014-01-20', '%Y-%m-%d')
742 743
    upload = PicklableUpload('test.txt', 'text/plain', 'ascii')
......
794 795
    resp2 = get_app(pub).get(sign_uri('/test/%s/' % formdata.id,
795 796
        user=local_user), status=403)
796 797

  
798
def test_formdata_edit(pub, local_user):
799
    test_formdata(pub, local_user)
800
    formdef = FormDef.select()[0]
801
    formdata = formdef.data_class().select()[0]
802
    workflow = formdef.workflow
803

  
804
    # not user
805
    resp = get_app(pub).post_json(
806
            sign_uri('/api/forms/test/%s/' % formdata.id),
807
            {'data': {'0': 'bar@localhost'}},
808
            status=403)
809

  
810
    # no editable action
811
    resp = get_app(pub).post_json(
812
            sign_uri('/api/forms/test/%s/' % formdata.id, user=local_user),
813
            {'data': {'0': 'bar@localhost'}},
814
            status=403)
815

  
816
    wfedit = EditableWorkflowStatusItem()
817
    wfedit.id = '_wfedit'
818
    wfedit.by = [local_user.roles[0]]
819
    workflow.possible_status[1].items.append(wfedit)
820
    wfedit.parent = workflow.possible_status[1]
821
    workflow.store()
822

  
823
    resp = get_app(pub).post_json(
824
            sign_uri('/api/forms/test/%s/' % formdata.id, user=local_user),
825
            {'data': {'0': 'bar@localhost'}},
826
            status=200)
827
    assert formdef.data_class().select()[0].data['0'] == 'bar@localhost'
828

  
829
    # not editable by user role
830
    wfedit.by = ['XX']
831
    workflow.store()
832
    resp = get_app(pub).post_json(
833
            sign_uri('/api/forms/test/%s/' % formdata.id, user=local_user),
834
            {'data': {'0': 'bar@localhost'}},
835
            status=403)
836

  
837
    # edit + jump
838
    wfedit.status = 'rejected'
839
    wfedit.by = [local_user.roles[0]]
840
    workflow.store()
841

  
842
    resp = get_app(pub).post_json(
843
            sign_uri('/api/forms/test/%s/' % formdata.id, user=local_user),
844
            {'data': {'0': 'bar2@localhost'}},
845
            status=200)
846
    assert formdef.data_class().select()[0].data['0'] == 'bar2@localhost'
847
    assert formdef.data_class().select()[0].status == 'wf-rejected'
848

  
849

  
797 850
def test_user_by_nameid(pub, local_user):
798 851
    resp = get_app(pub).get(sign_uri('/api/users/xyz/', user=local_user),
799 852
            status=404)
wcs/api.py
34 34

  
35 35
from backoffice.management import FormPage as BackofficeFormPage
36 36

  
37
def posted_json_data_to_formdata_data(formdef, data):
38
    # remap fields from varname to field id
39
    for field in formdef.fields:
40
        if not field.varname:
41
            continue
42
        if not field.varname in data:
43
            continue
44
        raw = '%s_raw' % field.varname
45
        structured = '%s_structured' % field.varname
46
        if field.store_display_value and raw in data:
47
            data[field.id] = data.pop(raw)
48
            data['%s_display' % field.id] = data.pop(field.varname)
49
        else:
50
            data[field.id] = data.pop(field.varname)
51
        if field.store_structured_value and structured in data:
52
            data['%s_structured' % field.id] = data.pop(structured)
53

  
54
    # parse special fields
55
    for field in formdef.fields:
56
        if not hasattr(field, 'from_json_value'):
57
            continue
58
        if not field.id in data:
59
            continue
60
        data[field.id] = field.from_json_value(data[field.id])
61

  
62
    return data
63

  
37 64

  
38 65
class ApiFormdataPage(FormStatusPage):
39 66
    _q_exports = ['', 'download']
40 67

  
41 68
    def _q_index(self):
69
        if get_request().get_method() == 'POST':
70
            return self.post()
42 71
        return self.json()
43 72

  
73
    def post(self):
74
        get_response().set_content_type('application/json')
75
        api_user = get_user_from_api_query_string()
76

  
77
        # check the formdata is currently editable
78
        wf_status = self.formdata.get_status()
79
        for item in wf_status.items:
80
            if not item.key == 'editable':
81
                continue
82
            if not item.check_auth(self.formdata, api_user):
83
                continue
84

  
85
            json_input = get_request().json
86
            data = posted_json_data_to_formdata_data(self.formdef, json_input['data'])
87
            self.formdata.data.update(data)
88
            self.formdata.store()
89

  
90
            if item.status:
91
                self.formdata.jump_status(item.status)
92
                self.formdata.perform_workflow()
93

  
94
            return json.dumps({'err': 0, 'data': {'id': self.formdata.id}})
95

  
96
        raise AccessForbiddenError('formdata is not editable by given user')
97

  
44 98
    def check_receiver(self):
45 99
        api_user = get_user_from_api_query_string()
46 100
        if not api_user:
......
131 185
        else:
132 186
            data = {}
133 187

  
134
        # remap fields from varname to field id
135
        for field in self.formdef.fields:
136
            if not field.varname:
137
                continue
138
            if not field.varname in data:
139
                continue
140
            raw = '%s_raw' % field.varname
141
            structured = '%s_structured' % field.varname
142
            if field.store_display_value and raw in data:
143
                data[field.id] = data.pop(raw)
144
                data['%s_display' % field.id] = data.pop(field.varname)
145
            else:
146
                data[field.id] = data.pop(field.varname)
147
            if field.store_structured_value and structured in data:
148
                data['%s_structured' % field.id] = data.pop(structured)
188
        formdata.data = posted_json_data_to_formdata_data(self.formdef, data)
149 189

  
150
        # parse special fields
151
        for field in self.formdef.fields:
152
            if not hasattr(field, 'from_json_value'):
153
                continue
154
            if not field.id in data:
155
                continue
156
            data[field.id] = field.from_json_value(data[field.id])
157
        formdata.data = data
158 190
        meta = json_input.get('meta') or {}
159 191
        if meta.get('backoffice-submission'):
160 192
            if not user:
wcs/forms/common.py
121 121
    def __init__(self, formdef, filled, register_workflow_subdirs=True):
122 122
        get_publisher().substitutions.feed(filled)
123 123
        self.formdef = formdef
124
        self.formdata = filled
124 125
        self.filled = filled
125 126
        for q in self._q_extra_exports:
126 127
            if not q in self._q_exports:
127
-