api: add support for POST on existing formdata, for edition (#10749)

This commit is contained in:
Frédéric Péters 2016-05-02 12:41:33 +02:00
parent 51426266c2
commit 98f638cedd
4 changed files with 150 additions and 40 deletions

View File

@ -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>

View File

@ -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)

View File

@ -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:

View File

@ -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: