From b4bcc8f0820b128294a5a37e85462df9d87d718e Mon Sep 17 00:00:00 2001
From: Benjamin Dauvergne
Date: Tue, 23 Feb 2016 15:17:23 +0100
Subject: [PATCH] api: handle submit of forms with date, file and map fields
(#10059)
---
help/fr/api-fill.page | 49 ++++++++++++++++++++++++++++++++++++++++++++++
tests/test_api.py | 54 ++++++++++++++++++++++++++++++++++++++-------------
wcs/api.py | 7 +++++++
wcs/fields.py | 25 ++++++++++++++++++++++++
4 files changed, 122 insertions(+), 13 deletions(-)
diff --git a/help/fr/api-fill.page b/help/fr/api-fill.page
index 98bf6de..522f991 100644
--- a/help/fr/api-fill.page
+++ b/help/fr/api-fill.page
@@ -46,6 +46,55 @@ différentes pages définies dans le formulaire (mode flux).
+ Les champs de type simple tels que « Texte », « Texte long » ou
+ « Courriel » sont des chaînes de caractères.
+
+
+
+ Les champs de type « Date » sont des chaînes de caractères au format
+ ISO-8601, i.e. YYYY-MM-DD
.
+
+
+
+ Les champs de type « Fichier » » sont des dictionnaires contenant les clés
+ suivantes.
+
+
+
+ filename
le nom du fichier
+ content
le contenu du fichier encodé en base64
+
+
+
+ Les champs de type « Fichier » » sont des dictionnaires contenant les clés
+ suivantes.
+
+
+
+ -
+
filename
le nom du fichier en chaîne de caractère
+
+ -
+
content
le contenu du fichier en chaîne de caractère encodé en
+ base64
+
+
+
+
+ Les champs de type « Carte » » sont des dictionnaires contenant les clés
+ suivantes.
+
+
+
+ -
+
lat
la latitute en nombre décimal
+
+ -
+
lon
la longitude en nombre décimal
+
+
+
+
L'attribut meta
est optionnel et contient une série de
paramètres supplémentaires concernant le formulaire.
diff --git a/tests/test_api.py b/tests/test_api.py
index e4e9f0e..99a9476 100644
--- a/tests/test_api.py
+++ b/tests/test_api.py
@@ -440,6 +440,9 @@ def test_formdef_submit_with_varname(pub, local_user):
data_source={'type': 'foobar'}),
fields.ItemField(id='2', label='foobar2', varname='foobar2',
data_source={'type': 'foobar_jsonp'}),
+ fields.DateField(id='3', label='foobar3', varname='date'),
+ fields.FileField(id='4', label='foobar4', varname='file'),
+ fields.MapField(id='5', label='foobar5', varname='map'),
]
formdef.store()
data_class = formdef.data_class()
@@ -451,19 +454,30 @@ def test_formdef_submit_with_varname(pub, local_user):
signed_url = sign_url('http://example.net/api/formdefs/test/submit' +
'?format=json&orig=coucou&email=%s' % urllib.quote(local_user.email), '1234')
url = signed_url[len('http://example.net'):]
- resp = get_app(pub).post_json(url,
- {'data':
- {'foobar0': 'xxx',
- 'foobar1': '1',
- 'foobar1_structured': {
- 'id': '1',
- 'text': 'foo',
- 'more': 'XXX',
- },
- 'foobar2': 'bar',
- 'foobar2_raw': '10',
- }
- })
+ payload = {
+ 'data':
+ {
+ 'foobar0': 'xxx',
+ 'foobar1': '1',
+ 'foobar1_structured': {
+ 'id': '1',
+ 'text': 'foo',
+ 'more': 'XXX',
+ },
+ 'foobar2': 'bar',
+ 'foobar2_raw': '10',
+ 'date': '1970-01-01',
+ 'file': {
+ 'filename': 'test.txt',
+ 'content': base64.b64encode('test'),
+ },
+ 'map': {
+ 'lat': 1.5,
+ 'lon': 2.25,
+ },
+ }
+ }
+ resp = get_app(pub).post_json(url, payload)
assert resp.json['err'] == 0
assert data_class.get(resp.json['data']['id']).status == 'wf-new'
assert data_class.get(resp.json['data']['id']).user_id == str(local_user.id)
@@ -473,6 +487,20 @@ def test_formdef_submit_with_varname(pub, local_user):
assert data_class.get(resp.json['data']['id']).data['1_structured'] == source[0]
assert data_class.get(resp.json['data']['id']).data['2'] == '10'
assert data_class.get(resp.json['data']['id']).data['2_display'] == 'bar'
+ assert data_class.get(resp.json['data']['id']).data['3'] == time.struct_time((1970, 1, 1, 0, 0,
+ 0, 3, 1, -1))
+
+ assert data_class.get(resp.json['data']['id']).data['4'].orig_filename == 'test.txt'
+ assert data_class.get(resp.json['data']['id']).data['4'].get_content() == 'test'
+ assert data_class.get(resp.json['data']['id']).data['5'] == '1.5;2.25'
+ # test bijectivity
+ assert (formdef.fields[3].get_json_value(data_class.get(resp.json['data']['id']).data['3']) ==
+ payload['data']['date'])
+ for k in payload['data']['file']:
+ data = data_class.get(resp.json['data']['id']).data['4']
+ assert formdef.fields[4].get_json_value(data)[k] == payload['data']['file'][k]
+ assert (formdef.fields[5].get_json_value(data_class.get(resp.json['data']['id']).data['5']) ==
+ payload['data']['map'])
data_class.wipe()
diff --git a/wcs/api.py b/wcs/api.py
index 3617452..6fb723c 100644
--- a/wcs/api.py
+++ b/wcs/api.py
@@ -243,6 +243,13 @@ class ApiFormdefDirectory(Directory):
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 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 = json_input['data']
meta = json_input.get('meta') or {}
if meta.get('backoffice-submission'):
diff --git a/wcs/fields.py b/wcs/fields.py
index e0e4395..570c169 100644
--- a/wcs/fields.py
+++ b/wcs/fields.py
@@ -763,6 +763,19 @@ class FileField(WidgetField):
'content': base64.b64encode(value.get_content())
}
+ def from_json_value(self, value):
+ if 'filename' in value and 'content' in value:
+ content = base64.b64decode(value['content'])
+ content_type = value.get('content_type', 'application/octet-stream')
+ if content_type.startswith('text/'):
+ charset = 'utf-8'
+ else:
+ charset = None
+ upload = PicklableUpload(value['filename'], content_type, charset)
+ upload.receive([content])
+ return upload
+ return None
+
def perform_more_widget_changes(self, form, kwargs, edit = True):
if not edit:
value = get_request().get_field(self.field_key)
@@ -915,6 +928,12 @@ class DateField(WidgetField):
except TypeError:
return ''
+ def from_json_value(self, value):
+ try:
+ return time.strptime(value, '%Y-%m-%d')
+ except (TypeError, ValueError):
+ return None
+
register_field_class(DateField)
@@ -1756,6 +1775,12 @@ class MapField(WidgetField):
return None
return {'lat': lat, 'lon': lon}
+ def from_json_value(self, value):
+ if 'lat' in value and 'lon' in value:
+ return '%s;%s' % (float(value['lat']), float(value['lon']))
+ else:
+ return None
+
register_field_class(MapField)
--
2.1.4