0001-api-add-support-for-POST-on-existing-formdata-for-ed.patch
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 |
- |