api: include file info in list API (#42371)

This commit is contained in:
Frédéric Péters 2020-05-01 17:39:53 +02:00
parent 739f2557b3
commit 07617b5f1b
5 changed files with 57 additions and 47 deletions

View File

@ -238,7 +238,8 @@ Les champs de type « Fichier » sont exportés selon le schéma suivant :
"photo": {
"filename": "exemple.txt",
"content_type": "text/plain",
"content": "Q2VjaSBuJ2VzdCBwYXMgdW4gZXhlbXBsZS4="
"content": "Q2VjaSBuJ2VzdCBwYXMgdW4gZXhlbXBsZS4=",
"url": "https://.../"
}
},
(...)
@ -333,8 +334,8 @@ l'adresse.
</screen>
<p>
À noter que pour ne pas alourdir l'export en mode <code>full=on</code>, les
champs de type « Fichier » ne sont pas exportés.
À noter que pour ne pas alourdir l'export en mode <code>full=on</code>, le
contenu des champs de type « Fichier » n'est pas exporté.
</p>
</section>

View File

@ -1633,7 +1633,8 @@ def test_user_drafts(pub, local_user):
assert resp.json['err'] == 0
assert 'fields' in resp.json['data'][0]
assert resp.json['data'][0]['fields']['foobar'] == 'foo@localhost'
assert 'file' not in resp.json['data'][0]['fields'] # no file export in full lists
assert 'url' in resp.json['data'][0]['fields']['file']
assert 'content' not in resp.json['data'][0]['fields']['file'] # no file content in full lists
assert resp.json['data'][0]['keywords'] == ['hello', 'world']
formdef.enable_tracking_codes = False
@ -1724,7 +1725,8 @@ def test_api_list_formdata(pub, local_user):
assert len(resp.json) == 30
assert 'receipt_time' in resp.json[0]
assert 'fields' in resp.json[0]
assert 'file' not in resp.json[0]['fields'] # no file export in full lists
assert 'url' in resp.json[0]['fields']['file']
assert 'content' not in resp.json[0]['fields']['file'] # no file content in full lists
assert 'user' in resp.json[0]
assert 'evolution' in resp.json[0]
assert len(resp.json[0]['evolution']) == 2

View File

@ -1127,6 +1127,9 @@ class FileField(WidgetField):
t += htmltext('</div>')
return t.getvalue()
def get_download_url(self, formdata):
return '%sdownload?f=%s' % (formdata.get_url(), self.id)
def get_opendocument_node_value(self, value, formdata=None, **kwargs):
show_link = True
if value.has_redirect_url():
@ -1134,7 +1137,7 @@ class FileField(WidgetField):
show_link = bool(value.get_redirect_url(backoffice=is_in_backoffice))
if show_link and formdata:
node = ET.Element('{%s}a' % OD_NS['text'])
node.attrib['{%s}href' % OD_NS['xlink']] = '%sdownload?f=%s' % (formdata.get_url(), self.id)
node.attrib['{%s}href' % OD_NS['xlink']] = self.get_download_url(formdata)
else:
node = ET.Element('{%s}span' % OD_NS['text'])
node.text = od_clean_text(force_text(value))
@ -1143,8 +1146,10 @@ class FileField(WidgetField):
def get_csv_value(self, value, **kwargs):
return [str(value) if value else '']
def get_json_value(self, value):
out = value.get_json_value()
def get_json_value(self, value, formdata=None, include_file_content=True, **kwargs):
out = value.get_json_value(include_file_content=include_file_content)
if formdata:
out['url'] = self.get_download_url(formdata)
out['field_id'] = self.id
return out
@ -1323,7 +1328,7 @@ class DateField(WidgetField):
span.text = od_clean_text(self.get_view_value(value))
return span
def get_json_value(self, value):
def get_json_value(self, value, **kwargs):
try:
return strftime('%Y-%m-%d', value)
except TypeError:
@ -2406,7 +2411,7 @@ class MapField(WidgetField):
return None
return value
def get_json_value(self, value):
def get_json_value(self, value, **kwargs):
if not value or ';' not in value:
return None
lat, lon = value.split(';')

View File

@ -108,32 +108,6 @@ def flatten_dict(d):
del d[k]
def get_json_dict(fields, data, include_files=True, anonymise=False):
new_data = {}
for field in fields:
if anonymise and field.anonymise:
continue
if not field.varname: # exports only named fields
continue
if not include_files and isinstance(field, FileField):
continue
if data is not None:
value = data.get(field.id)
if value and hasattr(field, 'get_json_value'):
value = field.get_json_value(value)
else:
value = display_value = None
if field.store_display_value:
new_data[field.varname + '_raw'] = value
new_data[field.varname] = data.get('%s_display' % field.id)
else:
new_data[field.varname] = value
if field.store_structured_value:
if data.get('%s_structured' % field.id):
new_data[field.varname + '_structured'] = data.get('%s_structured' % field.id)
return new_data
class Evolution(object):
who = None
status = None
@ -1018,6 +992,29 @@ class FormData(StorableObject):
return field.get_json_value(self.data[field.id])
return None
def get_json_dict(self, fields, include_files=True, anonymise=False):
new_data = {}
for field in fields:
if anonymise and field.anonymise:
continue
if not field.varname: # exports only named fields
continue
if self.data is not None:
value = self.data.get(field.id)
if value and hasattr(field, 'get_json_value'):
value = field.get_json_value(value, formdata=self, include_file_content=include_files)
else:
value = None
if field.store_display_value:
new_data[field.varname + '_raw'] = value
new_data[field.varname] = self.data.get('%s_display' % field.id)
else:
new_data[field.varname] = value
if field.store_structured_value:
if self.data.get('%s_structured' % field.id):
new_data[field.varname + '_structured'] = self.data.get('%s_structured' % field.id)
return new_data
def get_json_export_dict(self, include_files=True, anonymise=False, user=None):
data = {}
data['id'] = str(self.id)
@ -1038,7 +1035,7 @@ class FormData(StorableObject):
if user:
data['user'] = user.get_json_export_dict()
data['fields'] = get_json_dict(self.formdef.fields, self.data,
data['fields'] = self.get_json_dict(self.formdef.fields,
include_files=include_files, anonymise=anonymise)
data['workflow'] = {}
@ -1049,9 +1046,9 @@ class FormData(StorableObject):
if self.workflow_data and not anonymise:
data['workflow']['data'] = self.workflow_data
if self.formdef.workflow.get_backoffice_fields():
data['workflow']['fields'] = get_json_dict(
data['workflow']['fields'] = self.get_json_dict(
self.formdef.workflow.get_backoffice_fields(),
self.data, include_files=include_files, anonymise=anonymise)
include_files=include_files, anonymise=anonymise)
# add a roles dictionary, with workflow functions and two special
# entries for concerned/actions roles.

View File

@ -80,8 +80,9 @@ class PicklableUpload(Upload):
return base64.encodestring(content)
return b''
def get_json_value(self):
return get_storage_object(getattr(self, 'storage', None)).get_json_value(self)
def get_json_value(self, include_file_content=True):
return get_storage_object(getattr(self, 'storage', None)).get_json_value(
self, include_file_content=include_file_content)
def can_thumbnail(self):
return get_storage_object(getattr(self, 'storage', None)).can_thumbnail(self)
@ -136,12 +137,14 @@ class UploadStorage(object):
upload.fp.seek(0)
atomic_write(filepath, upload.fp, async_op=False)
def get_json_value(self, upload):
return {
def get_json_value(self, upload, include_file_content=True):
value = {
'filename': upload.base_filename,
'content_type': upload.content_type or 'application/octet-stream',
'content': force_text(base64.b64encode(upload.get_content())),
}
if include_file_content:
value['content'] = force_text(base64.b64encode(upload.get_content()))
return value
def can_thumbnail(self, upload):
return can_thumbnail(upload.content_type)
@ -217,14 +220,16 @@ class RemoteOpaqueUploadStorage(object):
def save(self, upload):
pass
def get_json_value(self, upload):
return {
def get_json_value(self, upload, include_file_content=True):
value = {
'filename': upload.base_filename,
'content_type': upload.content_type or 'application/octet-stream',
'content': '',
'storage': upload.storage,
'storage_attrs': upload.storage_attrs,
}
if include_file_content: # actually not possible
value['content'] = ''
return value
def can_thumbnail(self, upload):
return False