From 7452d80f3429fef4b0934426dbdacd86329aeab2 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20P=C3=A9ters?= <fpeters@entrouvert.com>
Date: Wed, 25 Mar 2020 10:00:46 +0100
Subject: [PATCH] api: expose /ods as endpoint (#40995)

---
 tests/test_api.py            | 63 +++++++++++++++++++++++++++++++++++-
 wcs/api.py                   |  3 +-
 wcs/backoffice/management.py |  8 +++--
 3 files changed, 70 insertions(+), 4 deletions(-)

diff --git a/tests/test_api.py b/tests/test_api.py
index 9f8618827..18b6235dc 100644
--- a/tests/test_api.py
+++ b/tests/test_api.py
@@ -13,15 +13,18 @@ import datetime
 import time
 import json
 import sys
+import xml.etree.ElementTree as ET
+import zipfile
 
 from django.utils.encoding import force_bytes, force_text
-from django.utils.six import StringIO
+from django.utils.six import StringIO, BytesIO
 from django.utils.six.moves.urllib import parse as urllib
 from django.utils.six.moves.urllib import parse as urlparse
 
 from quixote import cleanup, get_publisher
 from wcs.qommon.http_request import HTTPRequest
 from wcs.qommon.form import PicklableUpload
+from wcs.qommon import ods
 from wcs.users import User
 from wcs.roles import Role
 from wcs.carddef import CardDef
@@ -1970,6 +1973,64 @@ def test_api_geojson_formdata(pub, local_user):
     resp = get_app(pub).get(sign_uri('/api/forms/test/geojson', user=local_user), status=404)
 
 
+def test_api_ods_formdata(pub, local_user):
+    Role.wipe()
+    role = Role(name='test')
+    role.store()
+
+    FormDef.wipe()
+    formdef = FormDef()
+    formdef.name = 'test'
+    formdef.workflow_roles = {'_receiver': role.id}
+    formdef.fields = [
+        fields.StringField(id='0', label='foobar', varname='foobar', type='string'),
+    ]
+    formdef.store()
+
+    data_class = formdef.data_class()
+    data_class.wipe()
+
+    # check access is denied if the user has not the appropriate role
+    resp = get_app(pub).get(sign_uri('/api/forms/test/ods', user=local_user), status=403)
+    # even if there's an anonymise parameter
+    resp = get_app(pub).get(sign_uri('/api/forms/test/ods?anonymise', user=local_user), status=403)
+
+    data = {'0': 'foobar'}
+    for i in range(30):
+        formdata = data_class()
+        formdata.data = data
+        formdata.user_id = local_user.id
+        formdata.just_created()
+        if i % 3 == 0:
+            formdata.jump_status('new')
+        else:
+            formdata.jump_status('finished')
+        formdata.store()
+
+    # add proper role to user
+    local_user.roles = [role.id]
+    local_user.store()
+
+    # check it gets the data
+    resp = get_app(pub).get(sign_uri('/api/forms/test/ods', user=local_user))
+    assert resp.content_type == 'application/vnd.oasis.opendocument.spreadsheet'
+
+    # check it still gives a ods file when there is more date
+    for i in range(300):
+        formdata = data_class()
+        formdata.data = data
+        formdata.user_id = local_user.id
+        formdata.just_created()
+        formdata.jump_status('new')
+        formdata.store()
+
+    resp = get_app(pub).get(sign_uri('/api/forms/test/ods', user=local_user))
+    assert resp.content_type == 'application/vnd.oasis.opendocument.spreadsheet'
+    zipf = zipfile.ZipFile(BytesIO(resp.body))
+    ods_sheet = ET.parse(zipf.open('content.xml'))
+    assert len(ods_sheet.findall('.//{%s}table-row' % ods.NS['table'])) == 311
+
+
 def test_api_global_geojson(pub, local_user):
     Role.wipe()
     role = Role(name='test')
diff --git a/wcs/api.py b/wcs/api.py
index fb2bfeaba..0ca7c2641 100644
--- a/wcs/api.py
+++ b/wcs/api.py
@@ -164,8 +164,9 @@ class ApiFormdataPage(FormStatusPage):
 
 
 class ApiFormPage(BackofficeFormPage):
-    _q_exports = [('list', 'json'), 'geojson'] # restrict to API endpoints
+    _q_exports = [('list', 'json'), 'geojson', 'ods']  # restrict to API endpoints
     formdef_class = FormDef
+    is_api = True
 
     def __init__(self, component):
         try:
diff --git a/wcs/backoffice/management.py b/wcs/backoffice/management.py
index 213d132a3..4417d0acd 100644
--- a/wcs/backoffice/management.py
+++ b/wcs/backoffice/management.py
@@ -1771,9 +1771,12 @@ class FormPage(Directory):
 
     def ods(self):
         self.check_access()
+        if 'anonymise' in get_request().form:
+            # api/ will let this pass but we don't want that.
+            raise errors.AccessForbiddenError()
         fields = self.get_fields_from_query()
         selected_filter = self.get_filter_from_query()
-        user = get_request().user
+        user = get_user_from_api_query_string() or get_request().user
         query = get_request().form.get('q')
         criterias = self.get_criterias_from_query()
 
@@ -1814,7 +1817,8 @@ class FormPage(Directory):
 
         count = self.formdef.data_class().count()
         exporter = Exporter(self, self.formdef, fields, selected_filter)
-        if count > 100: # Arbitrary threshold
+        if count > 100 and not getattr(self, 'is_api', False):
+            # The "100" threshold is arbitrary
             job = get_response().add_after_job(
                 str(N_('Exporting forms in Open Document format')),
                 exporter.export)
-- 
2.26.0

