api: add support for POST on existing formdata, for edition (#10749)
This commit is contained in:
parent
51426266c2
commit
98f638cedd
|
@ -12,23 +12,16 @@
|
|||
|
||||
</info>
|
||||
|
||||
<title>Complétion et soumission d'un formulaire</title>
|
||||
<title>Complétion et modification d'un formulaire</title>
|
||||
|
||||
<p>
|
||||
w.c.s expose une API autorisant les logiciels tiers à transmettre des données
|
||||
structurées permettant la complétion d'un formulaire. Deux modes d'opération
|
||||
sont possibles, dans le premier l'ensemble des données est fourni en une étape
|
||||
unique (mode données), dans le second les données sont fournies en fonction des
|
||||
différentes pages définies dans le formulaire (mode flux).
|
||||
structurées permettant la complétion d'un formulaire ou la modification d'un
|
||||
formulaire existant.
|
||||
</p>
|
||||
|
||||
<section id="data">
|
||||
<title>Mode données</title>
|
||||
|
||||
<p>
|
||||
Ce mode est le plus simple et le plus adapté au développement d'un système
|
||||
tout automatique, sans interaction avec un utilisateur.
|
||||
</p>
|
||||
<section id="create">
|
||||
<title>Complétion d'un formulaire</title>
|
||||
|
||||
<p>
|
||||
La complétion d'un formulaire se fait par une requête <code>POST</code> à
|
||||
|
@ -133,14 +126,45 @@ différentes pages définies dans le formulaire (mode flux).
|
|||
|
||||
</section>
|
||||
|
||||
<section id="flow">
|
||||
<title>Mode flux</title>
|
||||
<section id="edit">
|
||||
<title>Modification d'un formulaire</title>
|
||||
|
||||
<p>
|
||||
Ce mode est à utiliser si l'on veut exposer à un usager les différentes
|
||||
étapes de la complétion d'un formulaire.
|
||||
Un formulaire qui peut être modifié (par la présence d'une action de workflow
|
||||
de type « Permettre l'édition ») peut également être modifié via un appel à
|
||||
l'API, en faisant un <code>POST</code> sur l'adresse du formulaire.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Les données attendues sont similaires à la création d'un nouveau formulaire,
|
||||
seuls les champs présents seront pris en compte.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Cet appel :
|
||||
</p>
|
||||
|
||||
<screen>
|
||||
<output style="prompt">$ </output><input>curl -H "Content-type: application/json" \
|
||||
-H "Accept: application/json" \
|
||||
-d@donnees.json \
|
||||
https://www.example.net/api/forms/newsletter/1/</input>
|
||||
<output>{"err": 0}</output>
|
||||
</screen>
|
||||
|
||||
<p>
|
||||
Avec les données suivantes en entrée, modifiera donc uniquement le champ
|
||||
« email ».
|
||||
</p>
|
||||
|
||||
<code mime="application/json">
|
||||
{
|
||||
"data": {
|
||||
"email": "marc@example.org"
|
||||
}
|
||||
}
|
||||
</code>
|
||||
|
||||
</section>
|
||||
|
||||
</page>
|
||||
|
|
|
@ -19,7 +19,7 @@ from wcs.roles import Role
|
|||
from wcs.formdef import FormDef
|
||||
from wcs.categories import Category
|
||||
from wcs.data_sources import NamedDataSource
|
||||
from wcs.workflows import Workflow
|
||||
from wcs.workflows import Workflow, EditableWorkflowStatusItem
|
||||
from wcs.wf.jump import JumpWorkflowStatusItem
|
||||
from wcs import fields, qommon
|
||||
from wcs.api_utils import sign_url
|
||||
|
@ -737,6 +737,7 @@ def test_formdata(pub, local_user):
|
|||
formdef.store()
|
||||
item_field = formdef.fields[4]
|
||||
|
||||
formdef.data_class().wipe()
|
||||
formdata = formdef.data_class()()
|
||||
date = time.strptime('2014-01-20', '%Y-%m-%d')
|
||||
upload = PicklableUpload('test.txt', 'text/plain', 'ascii')
|
||||
|
@ -794,6 +795,58 @@ def test_formdata(pub, local_user):
|
|||
resp2 = get_app(pub).get(sign_uri('/test/%s/' % formdata.id,
|
||||
user=local_user), status=403)
|
||||
|
||||
def test_formdata_edit(pub, local_user):
|
||||
test_formdata(pub, local_user)
|
||||
formdef = FormDef.select()[0]
|
||||
formdata = formdef.data_class().select()[0]
|
||||
workflow = formdef.workflow
|
||||
|
||||
# not user
|
||||
resp = get_app(pub).post_json(
|
||||
sign_uri('/api/forms/test/%s/' % formdata.id),
|
||||
{'data': {'0': 'bar@localhost'}},
|
||||
status=403)
|
||||
|
||||
# no editable action
|
||||
resp = get_app(pub).post_json(
|
||||
sign_uri('/api/forms/test/%s/' % formdata.id, user=local_user),
|
||||
{'data': {'0': 'bar@localhost'}},
|
||||
status=403)
|
||||
|
||||
wfedit = EditableWorkflowStatusItem()
|
||||
wfedit.id = '_wfedit'
|
||||
wfedit.by = [local_user.roles[0]]
|
||||
workflow.possible_status[1].items.append(wfedit)
|
||||
wfedit.parent = workflow.possible_status[1]
|
||||
workflow.store()
|
||||
|
||||
resp = get_app(pub).post_json(
|
||||
sign_uri('/api/forms/test/%s/' % formdata.id, user=local_user),
|
||||
{'data': {'0': 'bar@localhost'}},
|
||||
status=200)
|
||||
assert formdef.data_class().select()[0].data['0'] == 'bar@localhost'
|
||||
|
||||
# not editable by user role
|
||||
wfedit.by = ['XX']
|
||||
workflow.store()
|
||||
resp = get_app(pub).post_json(
|
||||
sign_uri('/api/forms/test/%s/' % formdata.id, user=local_user),
|
||||
{'data': {'0': 'bar@localhost'}},
|
||||
status=403)
|
||||
|
||||
# edit + jump
|
||||
wfedit.status = 'rejected'
|
||||
wfedit.by = [local_user.roles[0]]
|
||||
workflow.store()
|
||||
|
||||
resp = get_app(pub).post_json(
|
||||
sign_uri('/api/forms/test/%s/' % formdata.id, user=local_user),
|
||||
{'data': {'0': 'bar2@localhost'}},
|
||||
status=200)
|
||||
assert formdef.data_class().select()[0].data['0'] == 'bar2@localhost'
|
||||
assert formdef.data_class().select()[0].status == 'wf-rejected'
|
||||
|
||||
|
||||
def test_user_by_nameid(pub, local_user):
|
||||
resp = get_app(pub).get(sign_uri('/api/users/xyz/', user=local_user),
|
||||
status=404)
|
||||
|
|
78
wcs/api.py
78
wcs/api.py
|
@ -34,13 +34,67 @@ from wcs.api_utils import is_url_signed, get_user_from_api_query_string
|
|||
|
||||
from backoffice.management import FormPage as BackofficeFormPage
|
||||
|
||||
def posted_json_data_to_formdata_data(formdef, data):
|
||||
# remap fields from varname to field id
|
||||
for field in formdef.fields:
|
||||
if not field.varname:
|
||||
continue
|
||||
if not field.varname in data:
|
||||
continue
|
||||
raw = '%s_raw' % field.varname
|
||||
structured = '%s_structured' % field.varname
|
||||
if field.store_display_value and raw in data:
|
||||
data[field.id] = data.pop(raw)
|
||||
data['%s_display' % field.id] = data.pop(field.varname)
|
||||
else:
|
||||
data[field.id] = data.pop(field.varname)
|
||||
if field.store_structured_value and structured in data:
|
||||
data['%s_structured' % field.id] = data.pop(structured)
|
||||
|
||||
# parse special fields
|
||||
for field in formdef.fields:
|
||||
if not hasattr(field, 'from_json_value'):
|
||||
continue
|
||||
if not field.id in data:
|
||||
continue
|
||||
data[field.id] = field.from_json_value(data[field.id])
|
||||
|
||||
return data
|
||||
|
||||
|
||||
class ApiFormdataPage(FormStatusPage):
|
||||
_q_exports = ['', 'download']
|
||||
|
||||
def _q_index(self):
|
||||
if get_request().get_method() == 'POST':
|
||||
return self.post()
|
||||
return self.json()
|
||||
|
||||
def post(self):
|
||||
get_response().set_content_type('application/json')
|
||||
api_user = get_user_from_api_query_string()
|
||||
|
||||
# check the formdata is currently editable
|
||||
wf_status = self.formdata.get_status()
|
||||
for item in wf_status.items:
|
||||
if not item.key == 'editable':
|
||||
continue
|
||||
if not item.check_auth(self.formdata, api_user):
|
||||
continue
|
||||
|
||||
json_input = get_request().json
|
||||
data = posted_json_data_to_formdata_data(self.formdef, json_input['data'])
|
||||
self.formdata.data.update(data)
|
||||
self.formdata.store()
|
||||
|
||||
if item.status:
|
||||
self.formdata.jump_status(item.status)
|
||||
self.formdata.perform_workflow()
|
||||
|
||||
return json.dumps({'err': 0, 'data': {'id': self.formdata.id}})
|
||||
|
||||
raise AccessForbiddenError('formdata is not editable by given user')
|
||||
|
||||
def check_receiver(self):
|
||||
api_user = get_user_from_api_query_string()
|
||||
if not api_user:
|
||||
|
@ -131,30 +185,8 @@ class ApiFormdefDirectory(Directory):
|
|||
else:
|
||||
data = {}
|
||||
|
||||
# remap fields from varname to field id
|
||||
for field in self.formdef.fields:
|
||||
if not field.varname:
|
||||
continue
|
||||
if not field.varname in data:
|
||||
continue
|
||||
raw = '%s_raw' % field.varname
|
||||
structured = '%s_structured' % field.varname
|
||||
if field.store_display_value and raw in data:
|
||||
data[field.id] = data.pop(raw)
|
||||
data['%s_display' % field.id] = data.pop(field.varname)
|
||||
else:
|
||||
data[field.id] = data.pop(field.varname)
|
||||
if field.store_structured_value and structured in data:
|
||||
data['%s_structured' % field.id] = data.pop(structured)
|
||||
formdata.data = posted_json_data_to_formdata_data(self.formdef, data)
|
||||
|
||||
# parse special fields
|
||||
for field in self.formdef.fields:
|
||||
if not hasattr(field, 'from_json_value'):
|
||||
continue
|
||||
if not field.id in data:
|
||||
continue
|
||||
data[field.id] = field.from_json_value(data[field.id])
|
||||
formdata.data = data
|
||||
meta = json_input.get('meta') or {}
|
||||
if meta.get('backoffice-submission'):
|
||||
if not user:
|
||||
|
|
|
@ -121,6 +121,7 @@ class FormStatusPage(Directory):
|
|||
def __init__(self, formdef, filled, register_workflow_subdirs=True):
|
||||
get_publisher().substitutions.feed(filled)
|
||||
self.formdef = formdef
|
||||
self.formdata = filled
|
||||
self.filled = filled
|
||||
for q in self._q_extra_exports:
|
||||
if not q in self._q_exports:
|
||||
|
|
Loading…
Reference in New Issue