PageRenderTime 61ms CodeModel.GetById 18ms RepoModel.GetById 0ms app.codeStats 1ms

/odk_viewer/tests/test_exports.py

https://github.com/zoulema/formhub
Python | 2024 lines | 1810 code | 95 blank | 119 comment | 38 complexity | 466d32a40eeeccb0facb39ef0aa0f3ab MD5 | raw file
Possible License(s): BSD-3-Clause
  1. from sys import stdout
  2. import os
  3. import datetime
  4. import json
  5. import StringIO
  6. import csv
  7. import tempfile
  8. import zipfile
  9. import shutil
  10. from openpyxl import load_workbook
  11. from time import sleep
  12. from pyxform.builder import create_survey_from_xls
  13. from django.conf import settings
  14. from main.tests.test_base import MainTestCase
  15. from django.utils.dateparse import parse_datetime
  16. from django.core.urlresolvers import reverse
  17. from django.core.files.temp import NamedTemporaryFile
  18. from odk_viewer.xls_writer import XlsWriter
  19. from odk_viewer.views import delete_export, export_list, create_export,\
  20. export_progress, export_download
  21. from pyxform import SurveyElementBuilder
  22. from odk_viewer.models import Export, ParsedInstance
  23. from utils.export_tools import generate_export, increment_index_in_filename,\
  24. dict_to_joined_export, ExportBuilder
  25. from odk_logger.models import Instance, XForm
  26. from main.views import delete_data
  27. from utils.logger_tools import inject_instanceid
  28. from django.core.files.storage import get_storage_class
  29. from odk_viewer.pandas_mongo_bridge import NoRecordsFoundError
  30. from odk_viewer.tasks import create_xls_export
  31. from xlrd import open_workbook
  32. from odk_viewer.models.parsed_instance import _encode_for_mongo
  33. from odk_logger.xform_instance_parser import XFormInstanceParser
  34. class TestExportList(MainTestCase):
  35. def setUp(self):
  36. super(TestExportList, self).setUp()
  37. self._publish_transportation_form()
  38. survey = self.surveys[0]
  39. self._make_submission(
  40. os.path.join(
  41. self.this_directory, 'fixtures', 'transportation',
  42. 'instances', survey, survey + '.xml'))
  43. def test_csv_export_list(self):
  44. kwargs = {'username': self.user.username,
  45. 'id_string': self.xform.id_string,
  46. 'export_type': Export.CSV_EXPORT}
  47. # test csv
  48. url = reverse(export_list, kwargs=kwargs)
  49. response = self.client.get(url)
  50. self.assertEqual(response.status_code, 200)
  51. def test_xls_export_list(self):
  52. kwargs = {'username': self.user.username,
  53. 'id_string': self.xform.id_string,
  54. 'export_type': Export.XLS_EXPORT}
  55. url = reverse(export_list, kwargs=kwargs)
  56. response = self.client.get(url)
  57. self.assertEqual(response.status_code, 200)
  58. def test_kml_export_list(self):
  59. kwargs = {'username': self.user.username,
  60. 'id_string': self.xform.id_string,
  61. 'export_type': Export.KML_EXPORT}
  62. url = reverse(export_list, kwargs=kwargs)
  63. response = self.client.get(url)
  64. self.assertEqual(response.status_code, 200)
  65. def test_zip_export_list(self):
  66. kwargs = {'username': self.user.username,
  67. 'id_string': self.xform.id_string,
  68. 'export_type': Export.ZIP_EXPORT}
  69. url = reverse(export_list, kwargs=kwargs)
  70. response = self.client.get(url)
  71. self.assertEqual(response.status_code, 200)
  72. def test_gdoc_export_list(self):
  73. kwargs = {'username': self.user.username,
  74. 'id_string': self.xform.id_string,
  75. 'export_type': Export.GDOC_EXPORT}
  76. url = reverse(export_list, kwargs=kwargs)
  77. response = self.client.get(url)
  78. self.assertEqual(response.status_code, 302)
  79. def test_xsv_zip_export_list(self):
  80. kwargs = {'username': self.user.username,
  81. 'id_string': self.xform.id_string,
  82. 'export_type': Export.CSV_ZIP_EXPORT}
  83. url = reverse(export_list, kwargs=kwargs)
  84. response = self.client.get(url)
  85. self.assertEqual(response.status_code, 200)
  86. class TestDataExportURL(MainTestCase):
  87. def setUp(self):
  88. super(TestDataExportURL, self).setUp()
  89. self._publish_transportation_form()
  90. def _filename_from_disposition(self, content_disposition):
  91. filename_pos = content_disposition.index('filename=')
  92. self.assertTrue(filename_pos != -1)
  93. return content_disposition[filename_pos + len('filename='):]
  94. def test_csv_export_url(self):
  95. self._submit_transport_instance()
  96. url = reverse('csv_export', kwargs={
  97. 'username': self.user.username,
  98. 'id_string': self.xform.id_string,
  99. })
  100. response = self.client.get(url)
  101. headers = dict(response.items())
  102. self.assertEqual(headers['Content-Type'], 'application/csv')
  103. content_disposition = headers['Content-Disposition']
  104. filename = self._filename_from_disposition(content_disposition)
  105. basename, ext = os.path.splitext(filename)
  106. self.assertEqual(ext, '.csv')
  107. def test_csv_export_url_without_records(self):
  108. # csv using the pandas path can throw a NoRecordsFound Exception -
  109. # handle it gracefully
  110. url = reverse('csv_export', kwargs={
  111. 'username': self.user.username,
  112. 'id_string': self.xform.id_string,
  113. })
  114. response = self.client.get(url)
  115. self.assertEqual(response.status_code, 404)
  116. def test_xls_export_url(self):
  117. self._submit_transport_instance()
  118. url = reverse('xls_export', kwargs={
  119. 'username': self.user.username,
  120. 'id_string': self.xform.id_string,
  121. })
  122. response = self.client.get(url)
  123. headers = dict(response.items())
  124. self.assertEqual(headers['Content-Type'],
  125. 'application/vnd.openxmlformats')
  126. content_disposition = headers['Content-Disposition']
  127. filename = self._filename_from_disposition(content_disposition)
  128. basename, ext = os.path.splitext(filename)
  129. self.assertEqual(ext, '.xlsx')
  130. def test_csv_zip_export_url(self):
  131. self._submit_transport_instance()
  132. url = reverse('csv_zip_export', kwargs={
  133. 'username': self.user.username,
  134. 'id_string': self.xform.id_string,
  135. })
  136. response = self.client.get(url)
  137. headers = dict(response.items())
  138. self.assertEqual(headers['Content-Type'], 'application/zip')
  139. content_disposition = headers['Content-Disposition']
  140. filename = self._filename_from_disposition(content_disposition)
  141. basename, ext = os.path.splitext(filename)
  142. self.assertEqual(ext, '.zip')
  143. class TestExports(MainTestCase):
  144. def setUp(self):
  145. super(TestExports, self).setUp()
  146. self._submission_time = parse_datetime('2013-02-18 15:54:01Z')
  147. def test_unique_xls_sheet_name(self):
  148. xls_writer = XlsWriter()
  149. xls_writer.add_sheet('section9_pit_latrine_with_slab_group')
  150. xls_writer.add_sheet('section9_pit_latrine_without_slab_group')
  151. # create a set of sheet names keys
  152. sheet_names_set = set(xls_writer._sheets.keys())
  153. self.assertEqual(len(sheet_names_set), 2)
  154. def test_csv_http_response(self):
  155. self._publish_transportation_form()
  156. survey = self.surveys[0]
  157. self._make_submission(
  158. os.path.join(
  159. self.this_directory, 'fixtures', 'transportation',
  160. 'instances', survey, survey + '.xml'),
  161. forced_submission_time=self._submission_time)
  162. response = self.client.get(reverse('csv_export',
  163. kwargs={
  164. 'username': self.user.username,
  165. 'id_string': self.xform.id_string
  166. }))
  167. self.assertEqual(response.status_code, 200)
  168. test_file_path = os.path.join(os.path.dirname(__file__),
  169. 'fixtures', 'transportation.csv')
  170. content = self._get_response_content(response)
  171. with open(test_file_path, 'r') as test_file:
  172. self.assertEqual(content, test_file.read())
  173. def test_responses_for_empty_exports(self):
  174. self._publish_transportation_form()
  175. # test csv though xls uses the same view
  176. url = reverse('csv_export',
  177. kwargs={
  178. 'username': self.user.username,
  179. 'id_string': self.xform.id_string
  180. }
  181. )
  182. self.response = self.client.get(url)
  183. self.assertEqual(self.response.status_code, 404)
  184. self.assertIn('text/html', self.response['content-type'])
  185. def test_create_export(self):
  186. self._publish_transportation_form_and_submit_instance()
  187. storage = get_storage_class()()
  188. # test xls
  189. export = generate_export(Export.XLS_EXPORT, 'xls', self.user.username,
  190. self.xform.id_string)
  191. self.assertTrue(storage.exists(export.filepath))
  192. path, ext = os.path.splitext(export.filename)
  193. self.assertEqual(ext, '.xls')
  194. # test csv
  195. export = generate_export(Export.CSV_EXPORT, 'csv', self.user.username,
  196. self.xform.id_string)
  197. self.assertTrue(storage.exists(export.filepath))
  198. path, ext = os.path.splitext(export.filename)
  199. self.assertEqual(ext, '.csv')
  200. # test xls with existing export_id
  201. existing_export = Export.objects.create(xform=self.xform,
  202. export_type=Export.XLS_EXPORT)
  203. export = generate_export(Export.XLS_EXPORT, 'xls', self.user.username,
  204. self.xform.id_string, existing_export.id)
  205. self.assertEqual(existing_export.id, export.id)
  206. def test_delete_file_on_export_delete(self):
  207. self._publish_transportation_form()
  208. self._submit_transport_instance()
  209. export = generate_export(Export.XLS_EXPORT, 'xls', self.user.username,
  210. self.xform.id_string)
  211. storage = get_storage_class()()
  212. self.assertTrue(storage.exists(export.filepath))
  213. # delete export object
  214. export.delete()
  215. self.assertFalse(storage.exists(export.filepath))
  216. def test_graceful_exit_on_export_delete_if_file_doesnt_exist(self):
  217. self._publish_transportation_form()
  218. self._submit_transport_instance()
  219. export = generate_export(Export.XLS_EXPORT, 'xls', self.user.username,
  220. self.xform.id_string)
  221. storage = get_storage_class()()
  222. # delete file
  223. storage.delete(export.filepath)
  224. self.assertFalse(storage.exists(export.filepath))
  225. # clear filename, like it would be in an incomplete export
  226. export.filename = None
  227. export.filedir = None
  228. export.save()
  229. # delete export record, which should try to delete file as well
  230. delete_url = reverse(delete_export, kwargs={
  231. 'username': self.user.username,
  232. 'id_string': self.xform.id_string,
  233. 'export_type': 'xls'
  234. })
  235. post_data = {'export_id': export.id}
  236. response = self.client.post(delete_url, post_data)
  237. self.assertEqual(response.status_code, 302)
  238. def test_delete_oldest_export_on_limit(self):
  239. self._publish_transportation_form()
  240. self._submit_transport_instance()
  241. # create first export
  242. first_export = generate_export(Export.XLS_EXPORT, 'xls', self.user.username,
  243. self.xform.id_string)
  244. self.assertIsNotNone(first_export.pk)
  245. # create exports that exceed set limit
  246. for i in range(Export.MAX_EXPORTS):
  247. generate_export(Export.XLS_EXPORT, 'xls', self.user.username,
  248. self.xform.id_string)
  249. # first export should be deleted
  250. exports = Export.objects.filter(id=first_export.id)
  251. self.assertEqual(len(exports), 0)
  252. def test_create_export_url(self):
  253. self._publish_transportation_form()
  254. self._submit_transport_instance()
  255. num_exports = Export.objects.count()
  256. # create export
  257. create_export_url = reverse(create_export, kwargs={
  258. 'username': self.user.username,
  259. 'id_string': self.xform.id_string,
  260. 'export_type': Export.XLS_EXPORT
  261. })
  262. response = self.client.post(create_export_url)
  263. self.assertEqual(response.status_code, 302)
  264. self.assertEqual(Export.objects.count(), num_exports + 1)
  265. def test_delete_export_url(self):
  266. self._publish_transportation_form()
  267. self._submit_transport_instance()
  268. # create export
  269. export = generate_export(Export.XLS_EXPORT, 'xls', self.user.username,
  270. self.xform.id_string)
  271. exports = Export.objects.filter(id=export.id)
  272. self.assertEqual(len(exports), 1)
  273. delete_url = reverse(delete_export, kwargs={
  274. 'username': self.user.username,
  275. 'id_string': self.xform.id_string,
  276. 'export_type': 'xls'
  277. })
  278. post_data = {'export_id': export.id}
  279. response = self.client.post(delete_url, post_data)
  280. self.assertEqual(response.status_code, 302)
  281. exports = Export.objects.filter(id=export.id)
  282. self.assertEqual(len(exports), 0)
  283. def test_export_progress_output(self):
  284. self._publish_transportation_form()
  285. self._submit_transport_instance()
  286. # create exports
  287. for i in range(2):
  288. generate_export(Export.XLS_EXPORT, 'xls', self.user.username,
  289. self.xform.id_string)
  290. self.assertEqual(Export.objects.count(), 2)
  291. # progress for multiple exports
  292. progress_url = reverse(export_progress, kwargs={
  293. 'username': self.user.username,
  294. 'id_string': self.xform.id_string,
  295. 'export_type': 'xls'
  296. })
  297. get_data = {'export_ids': [e.id for e in Export.objects.all()]}
  298. response = self.client.get(progress_url, get_data)
  299. content = json.loads(response.content)
  300. self.assertEqual(len(content), 2)
  301. self.assertEqual(sorted(['url', 'export_id', 'complete', 'filename']),
  302. sorted(content[0].keys()))
  303. def test_auto_export_if_none_exists(self):
  304. self._publish_transportation_form()
  305. self._submit_transport_instance()
  306. # get export list url
  307. num_exports = Export.objects.count()
  308. export_list_url = reverse(export_list, kwargs={
  309. 'username': self.user.username,
  310. 'id_string': self.xform.id_string,
  311. 'export_type': Export.XLS_EXPORT
  312. })
  313. response = self.client.get(export_list_url)
  314. self.assertEqual(Export.objects.count(), num_exports + 1)
  315. def test_dont_auto_export_if_exports_exist(self):
  316. self._publish_transportation_form()
  317. self._submit_transport_instance()
  318. # create export
  319. create_export_url = reverse(create_export, kwargs={
  320. 'username': self.user.username,
  321. 'id_string': self.xform.id_string,
  322. 'export_type': Export.XLS_EXPORT
  323. })
  324. response = self.client.post(create_export_url)
  325. num_exports = Export.objects.count()
  326. export_list_url = reverse(export_list, kwargs={
  327. 'username': self.user.username,
  328. 'id_string': self.xform.id_string,
  329. 'export_type': Export.XLS_EXPORT
  330. })
  331. response = self.client.get(export_list_url)
  332. self.assertEqual(Export.objects.count(), num_exports)
  333. def test_last_submission_time_on_export(self):
  334. self._publish_transportation_form()
  335. self._submit_transport_instance()
  336. # create export
  337. xls_export = generate_export(Export.XLS_EXPORT, 'xls', self.user.username,
  338. self.xform.id_string)
  339. num_exports = Export.objects.filter(xform=self.xform,
  340. export_type=Export.XLS_EXPORT).count()
  341. # check that our function knows there are no more submissions
  342. self.assertFalse(Export.exports_outdated(xform=self.xform,
  343. export_type=Export.XLS_EXPORT))
  344. sleep(1)
  345. # force new last submission date on xform
  346. last_submission = self.xform.surveys.order_by('-date_created')[0]
  347. last_submission.date_created += datetime.timedelta(hours=1)
  348. last_submission.save()
  349. # check that our function knows data has changed
  350. self.assertTrue(Export.exports_outdated(xform=self.xform,
  351. export_type=Export.XLS_EXPORT))
  352. # check that requesting list url will generate a new export
  353. export_list_url = reverse(export_list, kwargs={
  354. 'username': self.user.username,
  355. 'id_string': self.xform.id_string,
  356. 'export_type': Export.XLS_EXPORT
  357. })
  358. response = self.client.get(export_list_url)
  359. self.assertEqual(Export.objects.filter(xform=self.xform,
  360. export_type=Export.XLS_EXPORT).count(), num_exports + 1)
  361. # make sure another export type causes auto-generation
  362. num_exports = Export.objects.filter(xform=self.xform,
  363. export_type=Export.CSV_EXPORT).count()
  364. export_list_url = reverse(export_list, kwargs={
  365. 'username': self.user.username,
  366. 'id_string': self.xform.id_string,
  367. 'export_type': Export.CSV_EXPORT
  368. })
  369. response = self.client.get(export_list_url)
  370. self.assertEqual(Export.objects.filter(xform=self.xform,
  371. export_type=Export.CSV_EXPORT).count(), num_exports + 1)
  372. def test_last_submission_time_empty(self):
  373. self._publish_transportation_form()
  374. self._submit_transport_instance()
  375. # create export
  376. export = generate_export(Export.XLS_EXPORT, 'xls', self.user.username,
  377. self.xform.id_string)
  378. # set time of last submission to None
  379. export.time_of_last_submission = None
  380. export.save()
  381. self.assertTrue(Export.exports_outdated(xform=self.xform,
  382. export_type=Export.XLS_EXPORT))
  383. def test_invalid_export_type(self):
  384. self._publish_transportation_form()
  385. self._submit_transport_instance()
  386. export_list_url = reverse(export_list, kwargs={
  387. 'username': self.user.username,
  388. 'id_string': self.xform.id_string,
  389. 'export_type': 'invalid'
  390. })
  391. response = self.client.get(export_list_url)
  392. self.assertEqual(response.status_code, 400)
  393. # test create url
  394. create_export_url = reverse(create_export, kwargs={
  395. 'username': self.user.username,
  396. 'id_string': self.xform.id_string,
  397. 'export_type': 'invalid'
  398. })
  399. response = self.client.post(create_export_url)
  400. self.assertEqual(response.status_code, 400)
  401. def test_add_index_to_filename(self):
  402. filename = "file_name-123f.txt"
  403. new_filename = increment_index_in_filename(filename)
  404. expected_filename = "file_name-123f-1.txt"
  405. self.assertEqual(new_filename, expected_filename)
  406. # test file that already has an index
  407. filename = "file_name-123.txt"
  408. new_filename = increment_index_in_filename(filename)
  409. expected_filename = "file_name-124.txt"
  410. self.assertEqual(new_filename, expected_filename)
  411. def test_duplicate_export_filename_is_renamed(self):
  412. self._publish_transportation_form()
  413. self._submit_transport_instance()
  414. # create an export object in the db
  415. # TODO: only works if the time we time we generate the basename is exact to the second with the time the 2nd export is created
  416. basename = "%s_%s" % (self.xform.id_string,
  417. datetime.datetime.now().strftime("%Y_%m_%d_%H_%M_%S"))
  418. filename = basename + ".csv"
  419. export = Export.objects.create(xform=self.xform,
  420. export_type=Export.CSV_EXPORT, filename=filename)
  421. # 2nd export
  422. export_2 = generate_export(Export.CSV_EXPORT, 'csv', self.user.username,
  423. self.xform.id_string)
  424. if export.created_on.timetuple() == export_2.created_on.timetuple():
  425. new_filename = increment_index_in_filename(filename)
  426. self.assertEqual(new_filename, export_2.filename)
  427. else:
  428. stdout.write("duplicate export filename test skipped because export times differ.")
  429. def test_export_download_url(self):
  430. self._publish_transportation_form()
  431. self._submit_transport_instance()
  432. export = generate_export(Export.CSV_EXPORT, 'csv', self.user.username,
  433. self.xform.id_string)
  434. csv_export_url = reverse(export_download, kwargs={
  435. "username": self.user.username,
  436. "id_string": self.xform.id_string,
  437. "export_type": Export.CSV_EXPORT,
  438. "filename": export.filename
  439. })
  440. response = self.client.get(csv_export_url)
  441. self.assertEqual(response.status_code, 200)
  442. # test xls
  443. export = generate_export(Export.XLS_EXPORT, 'xls', self.user.username,
  444. self.xform.id_string)
  445. xls_export_url = reverse(export_download, kwargs={
  446. "username": self.user.username,
  447. "id_string": self.xform.id_string,
  448. "export_type": Export.XLS_EXPORT,
  449. "filename": export.filename
  450. })
  451. response = self.client.get(xls_export_url)
  452. self.assertEqual(response.status_code, 200)
  453. def test_404_on_export_io_error(self):
  454. """
  455. Test that we return a 404 when the response_with_mimetype_and_name encounters an IOError
  456. """
  457. self._publish_transportation_form()
  458. self._submit_transport_instance()
  459. export = generate_export(Export.CSV_EXPORT, 'csv', self.user.username,
  460. self.xform.id_string)
  461. export_url = reverse(export_download, kwargs={
  462. "username": self.user.username,
  463. "id_string": self.xform.id_string,
  464. "export_type": Export.CSV_EXPORT,
  465. "filename": export.filename
  466. })
  467. # delete the export
  468. export.delete()
  469. # access the export
  470. response = self.client.get(export_url)
  471. self.assertEqual(response.status_code, 404)
  472. def test_deleted_submission_not_in_export(self):
  473. self._publish_transportation_form()
  474. initial_count = ParsedInstance.query_mongo(
  475. self.user.username, self.xform.id_string, '{}', '[]', '{}',
  476. count=True)[0]['count']
  477. self._submit_transport_instance(0)
  478. self._submit_transport_instance(1)
  479. count = ParsedInstance.query_mongo(
  480. self.user.username, self.xform.id_string, '{}', '[]', '{}',
  481. count=True)[0]['count']
  482. self.assertEqual(count, initial_count+2)
  483. # get id of second submission
  484. instance_id = Instance.objects.filter(
  485. xform=self.xform).order_by('id').reverse()[0].id
  486. delete_url = reverse(
  487. delete_data, kwargs={"username": self.user.username,
  488. "id_string": self.xform.id_string})
  489. params = {'id': instance_id}
  490. self.client.post(delete_url, params)
  491. count = ParsedInstance.query_mongo(
  492. self.user.username, self.xform.id_string, '{}', '[]', '{}',
  493. count=True)[0]['count']
  494. self.assertEqual(count, initial_count + 1)
  495. # create the export
  496. csv_export_url = reverse(
  497. 'csv_export', kwargs={"username": self.user.username,
  498. "id_string":self.xform.id_string})
  499. response = self.client.get(csv_export_url)
  500. self.assertEqual(response.status_code, 200)
  501. f = StringIO.StringIO(self._get_response_content(response))
  502. csv_reader = csv.reader(f)
  503. num_rows = len([row for row in csv_reader])
  504. f.close()
  505. # number of rows == 2 i.e. initial_count + header plus one row
  506. self.assertEqual(num_rows, initial_count + 2)
  507. def test_edited_submissions_in_exports(self):
  508. self._publish_transportation_form()
  509. initial_count = ParsedInstance.query_mongo(
  510. self.user.username, self.xform.id_string, '{}', '[]', '{}',
  511. count=True)[0]['count']
  512. instance_name = 'transport_2011-07-25_19-05-36'
  513. path = os.path.join(
  514. 'main', 'tests', 'fixtures', 'transportation', 'instances_w_uuid',
  515. instance_name, instance_name + '.xml')
  516. self._make_submission(path)
  517. count = ParsedInstance.query_mongo(
  518. self.user.username, self.xform.id_string, '{}', '[]', '{}',
  519. count=True)[0]['count']
  520. self.assertEqual(count, initial_count+1)
  521. instance = Instance.objects.filter(
  522. xform=self.xform).order_by('id').reverse()[0]
  523. # make edited submission - simulating what enketo would return
  524. instance_name = 'transport_2011-07-25_19-05-36-edited'
  525. path = os.path.join(
  526. 'main', 'tests', 'fixtures', 'transportation', 'instances_w_uuid',
  527. instance_name, instance_name + '.xml')
  528. self._make_submission(path)
  529. count = ParsedInstance.query_mongo(
  530. self.user.username, self.xform.id_string, '{}', '[]', '{}',
  531. count=True)[0]['count']
  532. self.assertEqual(count, initial_count+1)
  533. # create the export
  534. csv_export_url = reverse(
  535. 'csv_export', kwargs={"username": self.user.username,
  536. "id_string":self.xform.id_string})
  537. response = self.client.get(csv_export_url)
  538. self.assertEqual(response.status_code, 200)
  539. f = StringIO.StringIO(self._get_response_content(response))
  540. csv_reader = csv.DictReader(f)
  541. data = [row for row in csv_reader]
  542. f.close()
  543. num_rows = len(data)
  544. # number of rows == initial_count + 1
  545. self.assertEqual(num_rows, initial_count + 1)
  546. key ='transport/loop_over_transport_types_frequency/ambulance/frequency_to_referral_facility'
  547. self.assertEqual(data[initial_count][key], "monthly")
  548. def test_export_ids_dont_have_comma_separation(self):
  549. """
  550. It seems using {{ }} to output numbers greater than 1000 formats the
  551. number with a thousand separator
  552. """
  553. self._publish_transportation_form()
  554. self._submit_transport_instance()
  555. # create an in-complete export
  556. export = Export.objects.create(id=1234, xform=self.xform,
  557. export_type=Export.XLS_EXPORT)
  558. self.assertEqual(export.pk, 1234)
  559. export_list_url = reverse(
  560. export_list, kwargs={
  561. "username": self.user.username,
  562. "id_string": self.xform.id_string,
  563. "export_type": Export.XLS_EXPORT
  564. })
  565. response = self.client.get(export_list_url)
  566. self.assertContains(response, '#delete-1234"')
  567. self.assertNotContains(response, '#delete-1,234"')
  568. def test_export_progress_updates(self):
  569. """
  570. Test that after generate_export is called, we change out state to
  571. pending and after its complete, we change it to complete, if we fail
  572. between the two, updates, we have failed
  573. """
  574. self._publish_transportation_form()
  575. # generate an export that fails because of the NoRecordsFound exception
  576. export = Export.objects.create(xform=self.xform,
  577. export_type=Export.XLS_EXPORT)
  578. # check that progress url says pending
  579. progress_url = reverse(export_progress, kwargs={
  580. 'username': self.user.username,
  581. 'id_string': self.xform.id_string,
  582. 'export_type': 'xls'
  583. })
  584. params = {'export_ids': [export.id]}
  585. response = self.client.get(progress_url, params)
  586. status = json.loads(response.content)[0]
  587. self.assertEqual(status["complete"], False)
  588. self.assertEqual(status["filename"], None)
  589. export.internal_status = Export.FAILED
  590. export.save()
  591. # check that progress url says failed
  592. progress_url = reverse(export_progress, kwargs={
  593. 'username': self.user.username,
  594. 'id_string': self.xform.id_string,
  595. 'export_type': 'xls'
  596. })
  597. params = {'export_ids': [export.id]}
  598. response = self.client.get(progress_url, params)
  599. status = json.loads(response.content)[0]
  600. self.assertEqual(status["complete"], True)
  601. self.assertEqual(status["filename"], None)
  602. # make a submission and create a valid export
  603. self._submit_transport_instance()
  604. create_xls_export(
  605. self.user.username,
  606. self.xform.id_string, export.id)
  607. params = {'export_ids': [export.id]}
  608. response = self.client.get(progress_url, params)
  609. status = json.loads(response.content)[0]
  610. self.assertEqual(status["complete"], True)
  611. self.assertIsNotNone(status["filename"])
  612. def test_direct_export_returns_newset_export_if_not_updated_since(self):
  613. self._publish_transportation_form()
  614. self._submit_transport_instance()
  615. self.assertEqual(self.response.status_code, 201)
  616. sleep(1)
  617. self._submit_transport_instance_w_uuid("transport_2011-07-25_19-05-36")
  618. self.assertEqual(self.response.status_code, 201)
  619. initial_num_csv_exports = Export.objects.filter(
  620. xform=self.xform, export_type=Export.CSV_EXPORT).count()
  621. initial_num_xls_exports = Export.objects.filter(
  622. xform=self.xform, export_type=Export.XLS_EXPORT).count()
  623. # request a direct csv export
  624. csv_export_url = reverse('csv_export', kwargs={
  625. 'username': self.user.username,
  626. 'id_string': self.xform.id_string
  627. })
  628. xls_export_url = reverse('xls_export', kwargs={
  629. 'username': self.user.username,
  630. 'id_string': self.xform.id_string
  631. })
  632. response = self.client.get(csv_export_url)
  633. self.assertEqual(response.status_code, 200)
  634. # we should have initial_num_exports + 1 exports
  635. num_csv_exports = Export.objects.filter(
  636. xform=self.xform, export_type=Export.CSV_EXPORT).count()
  637. self.assertEqual(num_csv_exports, initial_num_csv_exports + 1)
  638. # request another export without changing the data
  639. response = self.client.get(csv_export_url)
  640. self.assertEqual(response.status_code, 200)
  641. # we should still only have a single export object
  642. num_csv_exports = Export.objects.filter(
  643. xform=self.xform, export_type=Export.CSV_EXPORT).count()
  644. self.assertEqual(num_csv_exports, initial_num_csv_exports + 1)
  645. # this should not affect a direct XLS export and XLS should still re-generate
  646. response = self.client.get(xls_export_url)
  647. self.assertEqual(response.status_code, 200)
  648. num_xls_exports = Export.objects.filter(
  649. xform=self.xform, export_type=Export.XLS_EXPORT).count()
  650. self.assertEqual(num_xls_exports, initial_num_xls_exports + 1)
  651. # make sure xls doesnt re-generate if data hasn't changed
  652. response = self.client.get(xls_export_url)
  653. self.assertEqual(response.status_code, 200)
  654. num_xls_exports = Export.objects.filter(
  655. xform=self.xform, export_type=Export.XLS_EXPORT).count()
  656. self.assertEqual(num_xls_exports, initial_num_xls_exports + 1)
  657. sleep(1)
  658. # check that data edits cause a re-generation
  659. self._submit_transport_instance_w_uuid(
  660. "transport_2011-07-25_19-05-36-edited")
  661. self.assertEqual(self.response.status_code, 201)
  662. self.client.get(csv_export_url)
  663. self.assertEqual(response.status_code, 200)
  664. # we should have an extra export now that the data has been updated
  665. num_csv_exports = Export.objects.filter(
  666. xform=self.xform, export_type=Export.CSV_EXPORT).count()
  667. self.assertEqual(num_csv_exports, initial_num_csv_exports + 2)
  668. sleep(1)
  669. # and when we delete
  670. delete_url = reverse(delete_data, kwargs={
  671. 'username': self.user.username,
  672. 'id_string': self.xform.id_string
  673. })
  674. instance = Instance.objects.filter().order_by('-pk')[0]
  675. response = self.client.post(delete_url, {'id': instance.id})
  676. self.assertEqual(response.status_code, 200)
  677. response = self.client.get(csv_export_url)
  678. self.assertEqual(response.status_code, 200)
  679. # we should have an extra export now that the data has been updated by the delete
  680. num_csv_exports = Export.objects.filter(
  681. xform=self.xform, export_type=Export.CSV_EXPORT).count()
  682. self.assertEqual(num_csv_exports, initial_num_csv_exports + 3)
  683. def test_exports_outdated_doesnt_consider_failed_exports(self):
  684. self._publish_transportation_form()
  685. self._submit_transport_instance()
  686. # create a bad export
  687. export = Export.objects.create(
  688. xform=self.xform, export_type=Export.XLS_EXPORT,
  689. internal_status=Export.FAILED)
  690. self.assertTrue(
  691. Export.exports_outdated(self.xform, export.export_type))
  692. def test_exports_outdated_considers_pending_exports(self):
  693. self._publish_transportation_form()
  694. self._submit_transport_instance()
  695. # create a pending export
  696. export = Export.objects.create(
  697. xform=self.xform, export_type=Export.XLS_EXPORT,
  698. internal_status=Export.PENDING)
  699. self.assertFalse(
  700. Export.exports_outdated(self.xform, export.export_type))
  701. def _get_csv_data(self, filepath):
  702. storage = get_storage_class()()
  703. csv_file = storage.open(filepath)
  704. reader = csv.DictReader(csv_file)
  705. data = reader.next()
  706. csv_file.close()
  707. return data
  708. def _get_xls_data(self, filepath):
  709. storage = get_storage_class()()
  710. with storage.open(filepath) as f:
  711. workbook = open_workbook(file_contents=f.read())
  712. transportation_sheet = workbook.sheet_by_name("transportation")
  713. self.assertTrue(transportation_sheet.nrows > 1)
  714. headers = transportation_sheet.row_values(0)
  715. column1 = transportation_sheet.row_values(1)
  716. return dict(zip(headers, column1))
  717. def test_column_header_delimiter_export_option(self):
  718. self._publish_transportation_form()
  719. # survey 1 has ambulance and bicycle as values for
  720. # transport/available_transportation_types_to_referral_facility
  721. self._submit_transport_instance(survey_at=1)
  722. create_csv_export_url = reverse(create_export, kwargs={
  723. 'username': self.user.username,
  724. 'id_string': self.xform.id_string,
  725. 'export_type': 'csv'
  726. })
  727. default_params = {}
  728. custom_params = {
  729. 'options[group_delimiter]': '.',
  730. }
  731. # test csv with default group delimiter
  732. response = self.client.post(create_csv_export_url, default_params)
  733. self.assertEqual(response.status_code, 302)
  734. export = Export.objects.filter(
  735. xform=self.xform, export_type='csv').latest('created_on')
  736. self.assertTrue(bool(export.filepath))
  737. data = self._get_csv_data(export.filepath)
  738. self.assertTrue(
  739. data.has_key(
  740. 'transport/available_transportation_types_to_referral_facility/ambulance'))
  741. self.assertEqual(
  742. data['transport/available_transportation_types_to_referral_facility/ambulance'], 'True')
  743. sleep(1)
  744. # test csv with dot delimiter
  745. response = self.client.post(create_csv_export_url, custom_params)
  746. self.assertEqual(response.status_code, 302)
  747. export = Export.objects.filter(
  748. xform=self.xform, export_type='csv').latest('created_on')
  749. self.assertTrue(bool(export.filepath))
  750. data = self._get_csv_data(export.filepath)
  751. self.assertTrue(
  752. data.has_key(
  753. 'transport.available_transportation_types_to_referral_facility.ambulance'))
  754. self.assertEqual(
  755. data['transport.available_transportation_types_to_referral_facility.ambulance'], 'True')
  756. # test xls with default group delimiter
  757. create_csv_export_url = reverse(create_export, kwargs={
  758. 'username': self.user.username,
  759. 'id_string': self.xform.id_string,
  760. 'export_type': 'xls'
  761. })
  762. response = self.client.post(create_csv_export_url, default_params)
  763. self.assertEqual(response.status_code, 302)
  764. export = Export.objects.filter(
  765. xform=self.xform, export_type='xls').latest('created_on')
  766. self.assertTrue(bool(export.filepath))
  767. data = self._get_xls_data(export.filepath)
  768. self.assertTrue(
  769. data.has_key("transport/available_transportation_types_to_referral_facility/ambulance"))
  770. # xlrd reader seems to convert bools into integers i.e. 0 or 1
  771. self.assertEqual(
  772. data["transport/available_transportation_types_to_referral_facility/ambulance"], 1)
  773. sleep(1)
  774. # test xls with dot delimiter
  775. response = self.client.post(create_csv_export_url, custom_params)
  776. self.assertEqual(response.status_code, 302)
  777. export = Export.objects.filter(
  778. xform=self.xform, export_type='xls').latest('created_on')
  779. self.assertTrue(bool(export.filepath))
  780. data = self._get_xls_data(export.filepath)
  781. self.assertTrue(
  782. data.has_key("transport.available_transportation_types_to_referral_facility.ambulance"))
  783. # xlrd reader seems to convert bools into integers i.e. 0 or 1
  784. self.assertEqual(
  785. data["transport.available_transportation_types_to_referral_facility.ambulance"], 1)
  786. def test_split_select_multiple_export_option(self):
  787. self._publish_transportation_form()
  788. self._submit_transport_instance(survey_at=1)
  789. create_csv_export_url = reverse(create_export, kwargs={
  790. 'username': self.user.username,
  791. 'id_string': self.xform.id_string,
  792. 'export_type': 'csv'
  793. })
  794. default_params = {}
  795. custom_params = {
  796. 'options[dont_split_select_multiples]': 'yes'
  797. }
  798. # test csv with default split select multiples
  799. response = self.client.post(create_csv_export_url, default_params)
  800. self.assertEqual(response.status_code, 302)
  801. export = Export.objects.filter(
  802. xform=self.xform, export_type='csv').latest('created_on')
  803. self.assertTrue(bool(export.filepath))
  804. data = self._get_csv_data(export.filepath)
  805. # we should have transport/available_transportation_types_to_referral_facility/ambulance as a separate column
  806. self.assertTrue(
  807. data.has_key(
  808. 'transport/available_transportation_types_to_referral_facility/ambulance'))
  809. sleep(1)
  810. # test csv without default split select multiples
  811. response = self.client.post(create_csv_export_url, custom_params)
  812. self.assertEqual(response.status_code, 302)
  813. export = Export.objects.filter(
  814. xform=self.xform, export_type='csv').latest('created_on')
  815. self.assertTrue(bool(export.filepath))
  816. data = self._get_csv_data(export.filepath)
  817. # transport/available_transportation_types_to_referral_facility/ambulance should not be in its own column
  818. self.assertFalse(
  819. data.has_key(
  820. 'transport/available_transportation_types_to_referral_facility/ambulance'))
  821. # transport/available_transportation_types_to_referral_facility should be a column
  822. self.assertTrue(
  823. data.has_key(
  824. 'transport/available_transportation_types_to_referral_facility'))
  825. # check that ambulance is one the values within the transport/available_transportation_types_to_referral_facility column
  826. self.assertTrue("ambulance" in data['transport/available_transportation_types_to_referral_facility'].split(" "))
  827. create_xls_export_url = reverse(create_export, kwargs={
  828. 'username': self.user.username,
  829. 'id_string': self.xform.id_string,
  830. 'export_type': 'xls'
  831. })
  832. # test xls with default split select multiples
  833. response = self.client.post(create_xls_export_url, default_params)
  834. self.assertEqual(response.status_code, 302)
  835. export = Export.objects.filter(
  836. xform=self.xform, export_type='xls').latest('created_on')
  837. self.assertTrue(bool(export.filepath))
  838. data = self._get_xls_data(export.filepath)
  839. # we should have transport/available_transportation_types_to_referral_facility/ambulance as a separate column
  840. self.assertTrue(
  841. data.has_key(
  842. 'transport/available_transportation_types_to_referral_facility/ambulance'))
  843. sleep(1)
  844. # test xls without default split select multiples
  845. response = self.client.post(create_xls_export_url, custom_params)
  846. self.assertEqual(response.status_code, 302)
  847. export = Export.objects.filter(
  848. xform=self.xform, export_type='xls').latest('created_on')
  849. self.assertTrue(bool(export.filepath))
  850. data = self._get_xls_data(export.filepath)
  851. # transport/available_transportation_types_to_referral_facility/ambulance should NOT be in its own column
  852. self.assertFalse(
  853. data.has_key(
  854. 'transport/available_transportation_types_to_referral_facility/ambulance'))
  855. # transport/available_transportation_types_to_referral_facility should be a column
  856. self.assertTrue(
  857. data.has_key(
  858. 'transport/available_transportation_types_to_referral_facility'))
  859. # check that ambulance is one the values within the transport/available_transportation_types_to_referral_facility column
  860. self.assertTrue("ambulance" in data['transport/available_transportation_types_to_referral_facility'].split(" "))
  861. def test_dict_to_joined_export_works(self):
  862. data =\
  863. {
  864. 'name': 'Abe',
  865. 'age': '35',
  866. '_geolocation': [None, None],
  867. 'attachments': ['abcd.jpg', 'efgh.jpg'],
  868. 'children':
  869. [
  870. {
  871. 'children/name': 'Mike',
  872. 'children/age': '5',
  873. 'children/cartoons':
  874. [
  875. {
  876. 'children/cartoons/name': 'Tom & Jerry',
  877. 'children/cartoons/why': 'Tom is silly',
  878. },
  879. {
  880. 'children/cartoons/name': 'Flinstones',
  881. 'children/cartoons/why': u"I like bamb bam\u0107",
  882. }
  883. ]
  884. },
  885. {
  886. 'children/name': 'John',
  887. 'children/age': '2',
  888. 'children/cartoons':[]
  889. },
  890. {
  891. 'children/name': 'Imora',
  892. 'children/age': '3',
  893. 'children/cartoons':
  894. [
  895. {
  896. 'children/cartoons/name': 'Shrek',
  897. 'children/cartoons/why': 'He\'s so funny'
  898. },
  899. {
  900. 'children/cartoons/name': 'Dexter\'s Lab',
  901. 'children/cartoons/why': 'He thinks hes smart',
  902. 'children/cartoons/characters':
  903. [
  904. {
  905. 'children/cartoons/characters/name': 'Dee Dee',
  906. 'children/cartoons/characters/good_or_evil': 'good'
  907. },
  908. {
  909. 'children/cartoons/characters/name': 'Dexter',
  910. 'children/cartoons/characters/good_or_evil': 'evil'
  911. },
  912. ]
  913. }
  914. ]
  915. }
  916. ]
  917. }
  918. expected_output =\
  919. {
  920. 'survey': {
  921. 'name': 'Abe',
  922. 'age': '35'
  923. },
  924. 'children':
  925. [
  926. {
  927. 'children/name': 'Mike',
  928. 'children/age': '5',
  929. '_index': 1,
  930. '_parent_table_name': 'survey',
  931. '_parent_index': 1
  932. },
  933. {
  934. 'children/name': 'John',
  935. 'children/age': '2',
  936. '_index': 2,
  937. '_parent_table_name': 'survey',
  938. '_parent_index': 1
  939. },
  940. {
  941. 'children/name': 'Imora',
  942. 'children/age': '3',
  943. '_index': 3,
  944. '_parent_table_name': 'survey',
  945. '_parent_index': 1
  946. },
  947. ],
  948. 'children/cartoons':
  949. [
  950. {
  951. 'children/cartoons/name': 'Tom & Jerry',
  952. 'children/cartoons/why': 'Tom is silly',
  953. '_index': 1,
  954. '_parent_table_name': 'children',
  955. '_parent_index': 1
  956. },
  957. {
  958. 'children/cartoons/name': 'Flinstones',
  959. 'children/cartoons/why': u"I like bamb bam\u0107",
  960. '_index': 2,
  961. '_parent_table_name': 'children',
  962. '_parent_index': 1
  963. },
  964. {
  965. 'children/cartoons/name': 'Shrek',
  966. 'children/cartoons/why': 'He\'s so funny',
  967. '_index': 3,
  968. '_parent_table_name': 'children',
  969. '_parent_index': 3
  970. },
  971. {
  972. 'children/cartoons/name': 'Dexter\'s Lab',
  973. 'children/cartoons/why': 'He thinks hes smart',
  974. '_index': 4,
  975. '_parent_table_name': 'children',
  976. '_parent_index': 3
  977. }
  978. ],
  979. 'children/cartoons/characters':
  980. [
  981. {
  982. 'children/cartoons/characters/name': 'Dee Dee',
  983. 'children/cartoons/characters/good_or_evil': 'good',
  984. '_index': 1,
  985. '_parent_table_name': 'children/cartoons',
  986. '_parent_index': 4
  987. },
  988. {
  989. 'children/cartoons/characters/name': 'Dexter',
  990. 'children/cartoons/characters/good_or_evil': 'evil',
  991. '_index': 2,
  992. '_parent_table_name': 'children/cartoons',
  993. '_parent_index': 4
  994. }
  995. ]
  996. }
  997. survey_name = 'survey'
  998. indices = {survey_name: 0}
  999. output = dict_to_joined_export(data, 1, indices, survey_name)
  1000. self.assertEqual(output[survey_name], expected_output[survey_name])
  1001. # 1st level
  1002. self.assertEqual(len(output['children']), 3)
  1003. for child in enumerate(['Mike', 'John', 'Imora']):
  1004. index = child[0]
  1005. name = child[1]
  1006. self.assertEqual(
  1007. filter(
  1008. lambda x: x['children/name'] == name,
  1009. output['children'])[0],
  1010. expected_output['children'][index])
  1011. # 2nd level
  1012. self.assertEqual(len(output['children/cartoons']), 4)
  1013. for cartoon in enumerate(
  1014. ['Tom & Jerry', 'Flinstones', 'Shrek', 'Dexter\'s Lab']):
  1015. index = cartoon[0]
  1016. name = cartoon[1]
  1017. self.assertEqual(
  1018. filter(
  1019. lambda x: x['children/cartoons/name'] == name,
  1020. output['children/cartoons'])[0],
  1021. expected_output['children/cartoons'][index])
  1022. # 3rd level
  1023. self.assertEqual(len(output['children/cartoons/characters']), 2)
  1024. for characters in enumerate(['Dee Dee', 'Dexter']):
  1025. index = characters[0]
  1026. name = characters[1]
  1027. self.assertEqual(
  1028. filter(
  1029. lambda x: x['children/cartoons/characters/name'] == name,
  1030. output['children/cartoons/characters'])[0],
  1031. expected_output['children/cartoons/characters'][index])
  1032. def test_generate_csv_zip_export(self):
  1033. # publish xls form
  1034. self._publish_transportation_form_and_submit_instance()
  1035. # create export db object
  1036. export = generate_export(
  1037. Export.CSV_ZIP_EXPORT, "zip", self.user.username,
  1038. self.xform.id_string, group_delimiter='/',
  1039. split_select_multiples=True)
  1040. storage = get_storage_class()()
  1041. self.assertTrue(storage.exists(export.filepath))
  1042. path, ext = os.path.splitext(export.filename)
  1043. self.assertEqual(ext, '.zip')
  1044. class TestExportBuilder(MainTestCase):
  1045. data = [
  1046. {
  1047. 'name': 'Abe',
  1048. 'age': 35,
  1049. 'tel/telLg==office': '020123456',
  1050. 'children':
  1051. [
  1052. {
  1053. 'children/name': 'Mike',
  1054. 'children/age': 5,
  1055. 'children/fav_colors': 'red blue',
  1056. 'children/iceLg==creams': 'vanilla chocolate',
  1057. 'children/cartoons':
  1058. [
  1059. {
  1060. 'children/cartoons/name': 'Tom & Jerry',
  1061. 'children/cartoons/why': 'Tom is silly',
  1062. },
  1063. {
  1064. 'children/cartoons/name': 'Flinstones',
  1065. 'children/cartoons/why': u"I like bam bam\u0107"
  1066. # throw in a unicode character
  1067. }
  1068. ]
  1069. },
  1070. {
  1071. 'children/name': 'John',
  1072. 'children/age': 2,
  1073. 'children/cartoons': []
  1074. },
  1075. {
  1076. 'children/name': 'Imora',
  1077. 'children/age': 3,
  1078. 'children/cartoons':
  1079. [
  1080. {
  1081. 'children/cartoons/name': 'Shrek',
  1082. 'children/cartoons/why': 'He\'s so funny'
  1083. },
  1084. {
  1085. 'children/cartoons/name': 'Dexter\'s Lab',
  1086. 'children/cartoons/why': 'He thinks hes smart',
  1087. 'children/cartoons/characters':
  1088. [
  1089. {
  1090. 'children/cartoons/characters/name': 'Dee Dee',
  1091. 'children/cartoons/characters/good_or_evil': 'good'
  1092. },
  1093. {
  1094. 'children/cartoons/characters/name': 'Dexter',
  1095. 'children/cartoons/characters/good_or_evil': 'evil'
  1096. },
  1097. ]
  1098. }
  1099. ]
  1100. }
  1101. ]
  1102. },
  1103. {
  1104. # blank data just to be sure
  1105. 'children': []
  1106. }
  1107. ]
  1108. long_survey_data = [
  1109. {
  1110. 'name': 'Abe',
  1111. 'age': 35,
  1112. 'childrens_survey_with_a_very_lo':
  1113. [
  1114. {
  1115. 'childrens_survey_with_a_very_lo/name': 'Mike',
  1116. 'childrens_survey_with_a_very_lo/age': 5,
  1117. 'childrens_survey_with_a_very_lo/fav_colors': 'red blue',
  1118. 'childrens_survey_with_a_very_lo/cartoons':
  1119. [
  1120. {
  1121. 'childrens_survey_with_a_very_lo/cartoons/name': 'Tom & Jerry',
  1122. 'childrens_survey_with_a_very_lo/cartoons/why': 'Tom is silly',
  1123. },
  1124. {
  1125. 'childrens_survey_with_a_very_lo/cartoons/name': 'Flinstones',
  1126. 'childrens_survey_with_a_very_lo/cartoons/why': u"I like bam bam\u0107"
  1127. # throw in a unicode character
  1128. }
  1129. ]
  1130. },
  1131. {
  1132. 'childrens_survey_with_a_very_lo/name': 'John',
  1133. 'childrens_survey_with_a_very_lo/age': 2,
  1134. 'childrens_survey_with_a_very_lo/cartoons': []
  1135. },
  1136. {
  1137. 'childrens_survey_with_a_very_lo/name': 'Imora',
  1138. 'childrens_survey_with_a_very_lo/age': 3,
  1139. 'childrens_survey_with_a_very_lo/cartoons':
  1140. [
  1141. {
  1142. 'childrens_survey_with_a_very_lo/cartoons/name': 'Shrek',
  1143. 'childrens_survey_with_a_very_lo/cartoons/why': 'He\'s so funny'
  1144. },
  1145. {
  1146. 'childrens_survey_with_a_very_lo/cartoons/name': 'Dexter\'s Lab',
  1147. 'childrens_survey_with_a_very_lo/cartoons/why': 'He thinks hes smart',
  1148. 'childrens_survey_with_a_very_lo/cartoons/characters':
  1149. [
  1150. {
  1151. 'childrens_survey_with_a_very_lo/cartoons/characters/name': 'Dee Dee',
  1152. 'children/cartoons/characters/good_or_evil': 'good'
  1153. },
  1154. {
  1155. 'childrens_survey_with_a_very_lo/cartoons/characters/name': 'Dexter',
  1156. 'children/cartoons/characters/good_or_evil': 'evil'
  1157. },
  1158. ]
  1159. }
  1160. ]
  1161. }
  1162. ]
  1163. }
  1164. ]
  1165. data_utf8 = [
  1166. {
  1167. 'name': 'Abe',
  1168. 'age': 35,
  1169. 'tel/telLg==office': '020123456',
  1170. 'childrenLg==info':
  1171. [
  1172. {
  1173. 'childrenLg==info/nameLg==first': 'Mike',
  1174. 'childrenLg==info/age': 5,
  1175. 'childrenLg==info/fav_colors': u'red\u2019s blue\u2019s',
  1176. 'childrenLg==info/ice_creams': 'vanilla chocolate',
  1177. 'childrenLg==info/cartoons':
  1178. [
  1179. {
  1180. 'childrenLg==info/cartoons/name': 'Tom & Jerry',
  1181. 'childrenLg==info/cartoons/why': 'Tom is silly',
  1182. },
  1183. {
  1184. 'childrenLg==info/cartoons/name': 'Flinstones',
  1185. 'childrenLg==info/cartoons/why': u"I like bam bam\u0107"
  1186. # throw in a unicode character
  1187. }
  1188. ]
  1189. }
  1190. ]
  1191. }
  1192. ]
  1193. def _create_childrens_survey(self):
  1194. survey = create_survey_from_xls(
  1195. os.path.join(
  1196. os.path.abspath('./'), 'odk_logger', 'tests', 'fixtures',
  1197. 'childrens_survey.xls'))
  1198. return survey
  1199. def test_build_sections_from_survey(self):
  1200. survey = self._create_childrens_survey()
  1201. export_builder = ExportBuilder()
  1202. export_builder.set_survey(survey)
  1203. # test that we generate the proper sections
  1204. expected_sections = [
  1205. survey.name, 'children', 'children/cartoons',
  1206. 'children/cartoons/characters']
  1207. self.assertEqual(
  1208. expected_sections, [s['name'] for s in export_builder.sections])
  1209. # main section should have split geolocations
  1210. expected_element_names = [
  1211. 'name', 'age', 'geo/geolocation', 'geo/_geolocation_longitude',
  1212. 'geo/_geolocation_latitude', 'geo/_geolocation_altitude',
  1213. 'geo/_geolocation_precision', 'tel/tel.office', 'tel/tel.mobile',
  1214. 'meta/instanceID']
  1215. section = export_builder.section_by_name(survey.name)
  1216. element_names = [element['xpath'] for element in section['elements']]
  1217. # fav_colors should have its choices split
  1218. self.assertEqual(
  1219. sorted(expected_element_names), sorted(element_names))
  1220. expected_element_names = [
  1221. 'children/name', 'children/age', 'children/fav_colors',
  1222. 'children/fav_colors/red', 'children/fav_colors/blue',
  1223. 'children/fav_colors/pink', 'children/ice.creams',
  1224. 'children/ice.creams/vanilla', 'children/ice.creams/strawberry',
  1225. 'children/ice.creams/chocolate']
  1226. section = export_builder.section_by_name('children')
  1227. element_names = [element['xpath'] for element in section['elements']]
  1228. self.assertEqual(
  1229. sorted(expected_element_names), sorted(element_names))
  1230. expected_element_names = [
  1231. 'children/cartoons/name', 'children/cartoons/why']
  1232. section = export_builder.section_by_name('children/cartoons')
  1233. element_names = [element['xpath'] for element in section['elements']]
  1234. self.assertEqual(
  1235. sorted(expected_element_names), sorted(element_names))
  1236. expected_element_names = [
  1237. 'children/cartoons/characters/name',
  1238. 'children/cartoons/characters/good_or_evil']
  1239. section = export_builder.section_by_name('children/cartoons/characters')
  1240. element_names = [element['xpath'] for element in section['elements']]
  1241. self.assertEqual(
  1242. sorted(expected_element_names), sorted(element_names))
  1243. def test_zipped_csv_export_works(self):
  1244. survey = self._create_childrens_survey()
  1245. export_builder = ExportBuilder()
  1246. export_builder.set_survey(survey)
  1247. temp_zip_file = NamedTemporaryFile(suffix='.zip')
  1248. export_builder.to_zipped_csv(temp_zip_file.name, self.data)
  1249. temp_zip_file.seek(0)
  1250. temp_dir = tempfile.mkdtemp()
  1251. zip_file = zipfile.ZipFile(temp_zip_file.name, "r")
  1252. zip_file.extractall(temp_dir)
  1253. zip_file.close()
  1254. temp_zip_file.close()
  1255. # generate data to compare with
  1256. index = 1
  1257. indices = {}
  1258. survey_name = survey.name
  1259. outputs = []
  1260. for d in self.data:
  1261. outputs.append(
  1262. dict_to_joined_export(d, index, indices, survey_name))
  1263. index += 1
  1264. # check that each file exists
  1265. self.assertTrue(
  1266. os.path.exists(
  1267. os.path.join(temp_dir, "{0}.csv".format(survey.name))))
  1268. with open(
  1269. os.path.join(
  1270. temp_dir, "{0}.csv".format(survey.name))) as csv_file:
  1271. reader = csv.reader(csv_file)
  1272. rows = [r for r in reader]
  1273. # open comparison file
  1274. with open(
  1275. os.path.join(
  1276. os.path.abspath('./'), 'odk_logger', 'tests', 'fixtures',
  1277. 'csvs', 'childrens_survey.csv')) as fixture_csv:
  1278. fixture_reader = csv.reader(fixture_csv)
  1279. expected_rows = [r for r in fixture_reader]
  1280. self.assertEqual(rows, expected_rows)
  1281. self.assertTrue(
  1282. os.path.exists(
  1283. os.path.join(temp_dir, "children.csv")))
  1284. with open(os.path.join(temp_dir, "children.csv")) as csv_file:
  1285. reader = csv.reader(csv_file)
  1286. rows = [r for r in reader]
  1287. # open comparison file
  1288. with open(
  1289. os.path.join(
  1290. os.path.abspath('./'), 'odk_logger', 'tests', 'fixtures',
  1291. 'csvs', 'children.csv')) as fixture_csv:
  1292. fixture_reader = csv.reader(fixture_csv)
  1293. expected_rows = [r for r in fixture_reader]
  1294. self.assertEqual(rows, expected_rows)
  1295. self.assertTrue(
  1296. os.path.exists(
  1297. os.path.join(temp_dir, "children_cartoons.csv")))
  1298. with open(os.path.join(temp_dir, "children_cartoons.csv")) as csv_file:
  1299. reader = csv.reader(csv_file)
  1300. rows = [r for r in reader]
  1301. # open comparison file
  1302. with open(
  1303. os.path.join(
  1304. os.path.abspath('./'), 'odk_logger', 'tests', 'fixtures',
  1305. 'csvs', 'children_cartoons.csv')) as fixture_csv:
  1306. fixture_reader = csv.reader(fixture_csv)
  1307. expected_rows = [r for r in fixture_reader]
  1308. self.assertEqual(rows, expected_rows)
  1309. self.assertTrue(
  1310. os.path.exists(
  1311. os.path.join(temp_dir, "children_cartoons_characters.csv")))
  1312. with open(os.path.join(
  1313. temp_dir, "children_cartoons_characters.csv")) as csv_file:
  1314. reader = csv.reader(csv_file)
  1315. rows = [r for r in reader]
  1316. # open comparison file
  1317. with open(
  1318. os.path.join(
  1319. os.path.abspath('./'), 'odk_logger', 'tests', 'fixtures',
  1320. 'csvs', 'children_cartoons_characters.csv')) as fixture_csv:
  1321. fixture_reader = csv.reader(fixture_csv)
  1322. expected_rows = [r for r in fixture_reader]
  1323. self.assertEqual(rows, expected_rows)
  1324. shutil.rmtree(temp_dir)
  1325. def test_decode_mongo_encoded_section_names(self):
  1326. data = {
  1327. 'main_section': [1, 2, 3, 4],
  1328. 'sectionLg==1/info': [1, 2, 3, 4],
  1329. 'sectionLg==2/info': [1, 2, 3, 4],
  1330. }
  1331. result = ExportBuilder.decode_mongo_encoded_section_names(data)
  1332. expected_result = {
  1333. 'main_section': [1, 2, 3, 4],
  1334. 'section.1/info': [1, 2, 3, 4],
  1335. 'section.2/info': [1, 2, 3, 4],
  1336. }
  1337. self.assertEqual(result, expected_result)
  1338. def test_zipped_csv_export_works_with_unicode(self):
  1339. """
  1340. cvs writer doesnt handle unicode we we have to encode to ascii
  1341. """
  1342. survey = create_survey_from_xls(
  1343. os.path.join(
  1344. os.path.abspath('./'), 'odk_logger', 'tests', 'fixtures',
  1345. 'childrens_survey_unicode.xls'))
  1346. export_builder = ExportBuilder()
  1347. export_builder.set_survey(survey)
  1348. temp_zip_file = NamedTemporaryFile(suffix='.zip')
  1349. export_builder.to_zipped_csv(temp_zip_file.name, self.data_utf8)
  1350. temp_zip_file.seek(0)
  1351. temp_dir = tempfile.mkdtemp()
  1352. zip_file = zipfile.ZipFile(temp_zip_file.name, "r")
  1353. zip_file.extractall(temp_dir)
  1354. zip_file.close()
  1355. temp_zip_file.close()
  1356. # check that the children's file (which has the unicode header) exists
  1357. self.assertTrue(
  1358. os.path.exists(
  1359. os.path.join(temp_dir, "children.info.csv")))
  1360. # check file's contents
  1361. with open(os.path.join(temp_dir, "children.info.csv")) as csv_file:
  1362. reader = csv.reader(csv_file)
  1363. expected_headers = ['children.info/name.first',
  1364. 'children.info/age',
  1365. 'children.info/fav_colors',
  1366. u'children.info/fav_colors/red\u2019s',
  1367. u'children.info/fav_colors/blue\u2019s',
  1368. u'children.info/fav_colors/pink\u2019s',
  1369. 'children.info/ice_creams',
  1370. 'children.info/ice_creams/vanilla',
  1371. 'children.info/ice_creams/strawberry',
  1372. 'children.info/ice_creams/chocolate', '_id',
  1373. '_uuid', '_submission_time', '_index',
  1374. '_parent_table_name', '_parent_index']
  1375. rows = [row for row in reader]
  1376. actual_headers = [h.decode('utf-8') for h in rows[0]]
  1377. self.assertEqual(sorted(actual_headers), sorted(expected_headers))
  1378. data = dict(zip(rows[0], rows[1]))
  1379. self.assertEqual(
  1380. data[u'children.info/fav_colors/red\u2019s'.encode('utf-8')],
  1381. 'True')
  1382. self.assertEqual(
  1383. data[u'children.info/fav_colors/blue\u2019s'.encode('utf-8')],
  1384. 'True')
  1385. self.assertEqual(
  1386. data[u'children.info/fav_colors/pink\u2019s'.encode('utf-8')],
  1387. 'False')
  1388. # check that red and blue are set to true
  1389. shutil.rmtree(temp_dir)
  1390. def test_xls_export_works_with_unicode(self):
  1391. survey = create_survey_from_xls(
  1392. os.path.join(
  1393. os.path.abspath('./'), 'odk_logger', 'tests', 'fixtures',
  1394. 'childrens_survey_unicode.xls'))
  1395. export_builder = ExportBuilder()
  1396. export_builder.set_survey(survey)
  1397. temp_xls_file = NamedTemporaryFile(suffix='.xlsx')
  1398. export_builder.to_xls_export(temp_xls_file.name, self.data_utf8)
  1399. temp_xls_file.seek(0)
  1400. # check that values for red\u2019s and blue\u2019s are set to true
  1401. wb = load_workbook(temp_xls_file.name)
  1402. children_sheet = wb.get_sheet_by_name("children.info")
  1403. data = dict([(r[0].value, r[1].value) for r in children_sheet.columns])
  1404. self.assertTrue(data[u'children.info/fav_colors/red\u2019s'])
  1405. self.assertTrue(data[u'children.info/fav_colors/blue\u2019s'])
  1406. self.assertFalse(data[u'children.info/fav_colors/pink\u2019s'])
  1407. temp_xls_file.close()
  1408. def test_generation_of_multi_selects_works(self):
  1409. survey = self._create_childrens_survey()
  1410. export_builder = ExportBuilder()
  1411. export_builder.set_survey(survey)
  1412. expected_select_multiples =\
  1413. {
  1414. 'children':
  1415. {
  1416. 'children/fav_colors':
  1417. [
  1418. 'children/fav_colors/red', 'children/fav_colors/blue',
  1419. 'children/fav_colors/pink'
  1420. ],
  1421. 'children/ice.creams':
  1422. [
  1423. 'children/ice.creams/vanilla',
  1424. 'children/ice.creams/strawberry',
  1425. 'children/ice.creams/chocolate'
  1426. ]
  1427. }
  1428. }
  1429. select_multiples = export_builder.select_multiples
  1430. self.assertTrue('children' in select_multiples)
  1431. self.assertTrue('children/fav_colors' in select_multiples['children'])
  1432. self.assertTrue('children/ice.creams' in select_multiples['children'])
  1433. self.assertEqual(
  1434. sorted(select_multiples['children']['children/fav_colors']),
  1435. sorted(
  1436. expected_select_multiples['children']['children/fav_colors']))
  1437. self.assertEqual(
  1438. sorted(select_multiples['children']['children/ice.creams']),
  1439. sorted(
  1440. expected_select_multiples['children']['children/ice.creams']))
  1441. def test_split_select_multiples_works(self):
  1442. select_multiples =\
  1443. {
  1444. 'children/fav_colors': [
  1445. 'children/fav_colors/red', 'children/fav_colors/blue',
  1446. 'children/fav_colors/pink']
  1447. }
  1448. row = \
  1449. {
  1450. 'children/name': 'Mike',
  1451. 'children/age': 5,
  1452. 'children/fav_colors': 'red blue'
  1453. }
  1454. new_row = ExportBuilder.split_select_multiples(
  1455. row, select_multiples)
  1456. expected_row = \
  1457. {
  1458. 'children/name': 'Mike',
  1459. 'children/age': 5,
  1460. 'children/fav_colors': 'red blue',
  1461. 'children/fav_colors/red': True,
  1462. 'children/fav_colors/blue': True,
  1463. 'children/fav_colors/pink': False
  1464. }
  1465. self.assertEqual(new_row, expected_row)
  1466. def test_split_select_multiples_works_when_data_is_blank(self):
  1467. select_multiples =\
  1468. {
  1469. 'children/fav_colors': [
  1470. 'children/fav_colors/red', 'children/fav_colors/blue',
  1471. 'children/fav_colors/pink']
  1472. }
  1473. row = \
  1474. {
  1475. 'children/name': 'Mike',
  1476. 'children/age': 5,
  1477. 'children/fav_colors': ''
  1478. }
  1479. new_row = ExportBuilder.split_select_multiples(
  1480. row, select_multiples)
  1481. expected_row = \
  1482. {
  1483. 'children/name': 'Mike',
  1484. 'children/age': 5,
  1485. 'children/fav_colors': '',
  1486. 'children/fav_colors/red': False,
  1487. 'children/fav_colors/blue': False,
  1488. 'children/fav_colors/pink': False
  1489. }
  1490. self.assertEqual(new_row, expected_row)
  1491. def test_generation_of_gps_fields_works(self):
  1492. survey = self._create_childrens_survey()
  1493. export_builder = ExportBuilder()
  1494. export_builder.set_survey(survey)
  1495. expected_gps_fields =\
  1496. {
  1497. 'childrens_survey':
  1498. {
  1499. 'geo/geolocation':
  1500. [
  1501. 'geo/_geolocation_latitude', 'geo/_geolocation_longitude',
  1502. 'geo/_geolocation_altitude', 'geo/_geolocation_precision'
  1503. ]
  1504. }
  1505. }
  1506. gps_fields = export_builder.gps_fields
  1507. self.assertTrue(gps_fields.has_key('childrens_survey'))
  1508. self.assertEqual(
  1509. sorted(gps_fields['childrens_survey']),
  1510. sorted(expected_gps_fields['childrens_survey']))
  1511. def test_split_gps_components_works(self):
  1512. gps_fields =\
  1513. {
  1514. 'geo/geolocation':
  1515. [
  1516. 'geo/_geolocation_latitude', 'geo/_geolocation_longitude',
  1517. 'geo/_geolocation_altitude', 'geo/_geolocation_precision'
  1518. ]
  1519. }
  1520. row = \
  1521. {
  1522. 'geo/geolocation': '1.0 36.1 2000 20',
  1523. }
  1524. new_row = ExportBuilder.split_gps_components(
  1525. row, gps_fields)
  1526. expected_row = \
  1527. {
  1528. 'geo/geolocation': '1.0 36.1 2000 20',
  1529. 'geo/_geolocation_latitude': '1.0',
  1530. 'geo/_geolocation_longitude': '36.1',
  1531. 'geo/_geolocation_altitude': '2000',
  1532. 'geo/_geolocation_precision': '20'
  1533. }
  1534. self.assertEqual(new_row, expected_row)
  1535. def test_split_gps_components_works_when_gps_data_is_blank(self):
  1536. gps_fields =\
  1537. {
  1538. 'geo/geolocation':
  1539. [
  1540. 'geo/_geolocation_latitude', 'geo/_geolocation_longitude',
  1541. 'geo/_geolocation_altitude', 'geo/_geolocation_precision'
  1542. ]
  1543. }
  1544. row = \
  1545. {
  1546. 'geo/geolocation': '',
  1547. }
  1548. new_row = ExportBuilder.split_gps_components(
  1549. row, gps_fields)
  1550. expected_row = \
  1551. {
  1552. 'geo/geolocation': '',
  1553. }
  1554. self.assertEqual(new_row, expected_row)
  1555. def test_generation_of_mongo_encoded_fields_works(self):
  1556. survey = self._create_childrens_survey()
  1557. export_builder = ExportBuilder()
  1558. export_builder.set_survey(survey)
  1559. expected_encoded_fields =\
  1560. {
  1561. 'childrens_survey':
  1562. {
  1563. 'tel/tel.office': 'tel/{0}'.format(
  1564. _encode_for_mongo('tel.office')),
  1565. 'tel/tel.mobile': 'tel/{0}'.format(
  1566. _encode_for_mongo('tel.mobile')),
  1567. }
  1568. }
  1569. encoded_fields = export_builder.encoded_fields
  1570. self.assertTrue('childrens_survey' in encoded_fields)
  1571. self.assertEqual(
  1572. encoded_fields['childrens_survey'],
  1573. expected_encoded_fields['childrens_survey'])
  1574. def test_decode_fields_names_encoded_for_mongo(self):
  1575. encoded_fields = \
  1576. {
  1577. 'tel/tel.office': 'tel/{0}'.format(
  1578. _encode_for_mongo('tel.office'))
  1579. }
  1580. row = \
  1581. {
  1582. 'name': 'Abe',
  1583. 'age': 35,
  1584. 'tel/{0}'.format(_encode_for_mongo('tel.office')): '123-456-789'
  1585. }
  1586. new_row = ExportBuilder.decode_mongo_encoded_fields(row, encoded_fields)
  1587. expected_row = \
  1588. {
  1589. 'name': 'Abe',
  1590. 'age': 35,
  1591. 'tel/tel.office': '123-456-789'
  1592. }
  1593. self.assertEqual(new_row, expected_row)
  1594. def test_generate_field_title(self):
  1595. field_name = ExportBuilder.format_field_title("child/age", ".")
  1596. expected_field_name = "child.age"
  1597. self.assertEqual(field_name, expected_field_name)
  1598. def test_delimiter_replacement_works_existing_fields(self):
  1599. survey = self._create_childrens_survey()
  1600. export_builder = ExportBuilder()
  1601. export_builder.GROUP_DELIMITER = "."
  1602. export_builder.set_survey(survey)
  1603. expected_sections =\
  1604. [
  1605. {
  1606. 'name': 'children',
  1607. 'elements': [
  1608. {
  1609. 'title': 'children.name',
  1610. 'xpath': 'children/name'
  1611. }
  1612. ]
  1613. }
  1614. ]
  1615. children_section = export_builder.section_by_name('children')
  1616. self.assertEqual(
  1617. children_section['elements'][0]['title'],
  1618. expected_sections[0]['elements'][0]['title'])
  1619. def test_delimiter_replacement_works_generated_multi_select_fields(self):
  1620. survey = self._create_childrens_survey()
  1621. export_builder = ExportBuilder()
  1622. export_builder.GROUP_DELIMITER = "."
  1623. export_builder.set_survey(survey)
  1624. expected_section =\
  1625. {
  1626. 'name': 'children',
  1627. 'elements': [
  1628. {
  1629. 'title': 'children.fav_colors.red',
  1630. 'xpath': 'children/fav_colors/red'
  1631. }
  1632. ]
  1633. }
  1634. childrens_section = export_builder.section_by_name('children')
  1635. match = filter(lambda x: expected_section['elements'][0]['xpath']
  1636. == x['xpath'], childrens_section['elements'])[0]
  1637. self.assertEqual(
  1638. expected_section['elements'][0]['title'], match['title'])
  1639. def test_delimiter_replacement_works_for_generated_gps_fields(self):
  1640. survey = self._create_childrens_survey()
  1641. export_builder = ExportBuilder()
  1642. export_builder.GROUP_DELIMITER = "."
  1643. export_builder.set_survey(survey)
  1644. expected_section = \
  1645. {
  1646. 'name': 'childrens_survey',
  1647. 'elements': [
  1648. {
  1649. 'title': 'geo._geolocation_latitude',
  1650. 'xpath': 'geo/_geolocation_latitude'
  1651. }
  1652. ]
  1653. }
  1654. main_section = export_builder.section_by_name('childrens_survey')
  1655. match = filter(
  1656. lambda x: (expected_section['elements'][0]['xpath']
  1657. == x['xpath']), main_section['elements'])[0]
  1658. self.assertEqual(
  1659. expected_section['elements'][0]['title'], match['title'])
  1660. def test_to_xls_export_works(self):
  1661. survey = self._create_childrens_survey()
  1662. export_builder = ExportBuilder()
  1663. export_builder.set_survey(survey)
  1664. xls_file = NamedTemporaryFile(suffix='.xls')
  1665. filename = xls_file.name
  1666. export_builder.to_xls_export(filename, self.data)
  1667. xls_file.seek(0)
  1668. wb = load_workbook(filename)
  1669. # check that we have childrens_survey, children, children_cartoons
  1670. # and children_cartoons_characters sheets
  1671. expected_sheet_names = ['childrens_survey', 'children',
  1672. 'children_cartoons',
  1673. 'children_cartoons_characters']
  1674. self.assertEqual(wb.get_sheet_names(), expected_sheet_names)
  1675. # check header columns
  1676. main_sheet = wb.get_sheet_by_name('childrens_survey')
  1677. expected_column_headers = [
  1678. u'name', u'age', u'geo/geolocation', u'geo/_geolocation_latitude',
  1679. u'geo/_geolocation_longitude', u'geo/_geolocation_altitude',
  1680. u'geo/_geolocation_precision', u'tel/tel.office',
  1681. u'tel/tel.mobile', u'_id', u'meta/instanceID', u'_uuid',
  1682. u'_submission_time', u'_index', u'_parent_index',
  1683. u'_parent_table_name']
  1684. column_headers = [c[0].value for c in main_sheet.columns]
  1685. self.assertEqual(sorted(column_headers),
  1686. sorted(expected_column_headers))
  1687. childrens_sheet = wb.get_sheet_by_name('children')
  1688. expected_column_headers = [
  1689. u'children/name', u'children/age', u'children/fav_colors',
  1690. u'children/fav_colors/red', u'children/fav_colors/blue',
  1691. u'children/fav_colors/pink', u'children/ice.creams',
  1692. u'children/ice.creams/vanilla', u'children/ice.creams/strawberry',
  1693. u'children/ice.creams/chocolate', u'_id', u'_uuid',
  1694. u'_submission_time', u'_index', u'_parent_index',
  1695. u'_parent_table_name']
  1696. column_headers = [c[0].value for c in childrens_sheet.columns]
  1697. self.assertEqual(sorted(column_headers),
  1698. sorted(expected_column_headers))
  1699. cartoons_sheet = wb.get_sheet_by_name('children_cartoons')
  1700. expected_column_headers = [
  1701. u'children/cartoons/name', u'children/cartoons/why', u'_id',
  1702. u'_uuid', u'_submission_time', u'_index', u'_parent_index',
  1703. u'_parent_table_name']
  1704. column_headers = [c[0].value for c in cartoons_sheet.columns]
  1705. self.assertEqual(sorted(column_headers),
  1706. sorted(expected_column_headers))
  1707. characters_sheet = wb.get_sheet_by_name('children_cartoons_characters')
  1708. expected_column_headers = [
  1709. u'children/cartoons/characters/name',
  1710. u'children/cartoons/characters/good_or_evil', u'_id', u'_uuid',
  1711. u'_submission_time', u'_index', u'_parent_index',
  1712. u'_parent_table_name']
  1713. column_headers = [c[0].value for c in characters_sheet.columns]
  1714. self.assertEqual(sorted(column_headers),
  1715. sorted(expected_column_headers))
  1716. xls_file.close()
  1717. def test_to_xls_export_respects_custom_field_delimiter(self):
  1718. survey = self._create_childrens_survey()
  1719. export_builder = ExportBuilder()
  1720. export_builder.GROUP_DELIMITER = ExportBuilder.GROUP_DELIMITER_DOT
  1721. export_builder.set_survey(survey)
  1722. xls_file = NamedTemporaryFile(suffix='.xls')
  1723. filename = xls_file.name
  1724. export_builder.to_xls_export(filename, self.data)
  1725. xls_file.seek(0)
  1726. wb = load_workbook(filename)
  1727. # check header columns
  1728. main_sheet = wb.get_sheet_by_name('childrens_survey')
  1729. expected_column_headers = [
  1730. u'name', u'age', u'geo.geolocation', u'geo._geolocation_latitude',
  1731. u'geo._geolocation_longitude', u'geo._geolocation_altitude',
  1732. u'geo._geolocation_precision', u'tel.tel.office',
  1733. u'tel.tel.mobile', u'_id', u'meta.instanceID', u'_uuid',
  1734. u'_submission_time', u'_index', u'_parent_index',
  1735. u'_parent_table_name']
  1736. column_headers = [c[0].value for c in main_sheet.columns]
  1737. self.assertEqual(sorted(column_headers),
  1738. sorted(expected_column_headers))
  1739. xls_file.close()
  1740. def test_get_valid_sheet_name_catches_duplicates(self):
  1741. work_sheets = {'childrens_survey': "Worksheet"}
  1742. desired_sheet_name = "childrens_survey"
  1743. expected_sheet_name = "childrens_survey1"
  1744. generated_sheet_name = ExportBuilder.get_valid_sheet_name(
  1745. desired_sheet_name, work_sheets)
  1746. self.assertEqual(generated_sheet_name, expected_sheet_name)
  1747. def test_get_valid_sheet_name_catches_long_names(self):
  1748. desired_sheet_name = "childrens_survey_with_a_very_long_name"
  1749. expected_sheet_name = "childrens_survey_with_a_very_lo"
  1750. generated_sheet_name = ExportBuilder.get_valid_sheet_name(
  1751. desired_sheet_name, [])
  1752. self.assertEqual(generated_sheet_name, expected_sheet_name)
  1753. def test_get_valid_sheet_name_catches_long_duplicate_names(self):
  1754. work_sheet_titles = ['childrens_survey_with_a_very_lo']
  1755. desired_sheet_name = "childrens_survey_with_a_very_long_name"
  1756. expected_sheet_name = "childrens_survey_with_a_very_l1"
  1757. generated_sheet_name = ExportBuilder.get_valid_sheet_name(
  1758. desired_sheet_name, work_sheet_titles)
  1759. self.assertEqual(generated_sheet_name, expected_sheet_name)
  1760. def test_to_xls_export_generates_valid_sheet_names(self):
  1761. survey = create_survey_from_xls(
  1762. os.path.join(
  1763. os.path.abspath('./'), 'odk_logger', 'tests', 'fixtures',
  1764. 'childrens_survey_with_a_very_long_name.xls'))
  1765. export_builder = ExportBuilder()
  1766. export_builder.set_survey(survey)
  1767. xls_file = NamedTemporaryFile(suffix='.xls')
  1768. filename = xls_file.name
  1769. export_builder.to_xls_export(filename, self.data)
  1770. xls_file.seek(0)
  1771. wb = load_workbook(filename)
  1772. # check that we have childrens_survey, children, children_cartoons
  1773. # and children_cartoons_characters sheets
  1774. expected_sheet_names = ['childrens_survey_with_a_very_lo',
  1775. 'childrens_survey_with_a_very_l1',
  1776. 'childrens_survey_with_a_very_l2',
  1777. 'childrens_survey_with_a_very_l3']
  1778. self.assertEqual(wb.get_sheet_names(), expected_sheet_names)
  1779. xls_file.close()
  1780. def test_child_record_parent_table_is_updated_when_sheet_is_renamed(self):
  1781. survey = create_survey_from_xls(
  1782. os.path.join(
  1783. os.path.abspath('./'), 'odk_logger', 'tests', 'fixtures',
  1784. 'childrens_survey_with_a_very_long_name.xls'))
  1785. export_builder = ExportBuilder()
  1786. export_builder.set_survey(survey)
  1787. xls_file = NamedTemporaryFile(suffix='.xlsx')
  1788. filename = xls_file.name
  1789. export_builder.to_xls_export(filename, self.long_survey_data)
  1790. xls_file.seek(0)
  1791. wb = load_workbook(filename)
  1792. # get the children's sheet
  1793. ws1 = wb.get_sheet_by_name('childrens_survey_with_a_very_l1')
  1794. # parent_table is in cell K2
  1795. parent_table_name = ws1.cell('K2').value
  1796. expected_parent_table_name = 'childrens_survey_with_a_very_lo'
  1797. self.assertEqual(parent_table_name, expected_parent_table_name)
  1798. # get cartoons sheet
  1799. ws2 = wb.get_sheet_by_name('childrens_survey_with_a_very_l2')
  1800. parent_table_name = ws2.cell('G2').value
  1801. expected_parent_table_name = 'childrens_survey_with_a_very_l1'
  1802. self.assertEqual(parent_table_name, expected_parent_table_name)
  1803. xls_file.close()
  1804. def test_type_conversion(self):
  1805. submission_1 = {
  1806. "_id": 579827,
  1807. "geolocation": "-1.2625482 36.7924794 0.0 21.0",
  1808. "_bamboo_dataset_id": "",
  1809. "meta/instanceID": "uuid:2a8129f5-3091-44e1-a579-bed2b07a12cf",
  1810. "name": "Smith",
  1811. "formhub/uuid": "633ec390e024411ba5ce634db7807e62",
  1812. "_submission_time": "2013-07-03T08:25:30",
  1813. "age": "107",
  1814. "_uuid": "2a8129f5-3091-44e1-a579-bed2b07a12cf",
  1815. "when": "2013-07-03",
  1816. # "_deleted_at": None,
  1817. "amount": "250.0",
  1818. "_geolocation": [
  1819. "-1.2625482",
  1820. "36.7924794"
  1821. ],
  1822. "_xform_id_string": "test_data_types",
  1823. "_userform_id": "larryweya_test_data_types",
  1824. "_status": "submitted_via_web",
  1825. "precisely": "2013-07-03T15:24:00.000+03",
  1826. "really": "15:24:00.000+03"
  1827. }
  1828. submission_2 = {
  1829. "_id": 579828,
  1830. "_submission_time": "2013-07-03T08:26:10",
  1831. "_uuid": "5b4752eb-e13c-483e-87cb-e67ca6bb61e5",
  1832. "_bamboo_dataset_id": "",
  1833. # "_deleted_at": None,
  1834. "_xform_id_string": "test_data_types",
  1835. "_userform_id": "larryweya_test_data_types",
  1836. "_status": "submitted_via_web",
  1837. "meta/instanceID": "uuid:5b4752eb-e13c-483e-87cb-e67ca6bb61e5",
  1838. "formhub/uuid": "633ec390e024411ba5ce634db7807e62",
  1839. "amount": "",
  1840. }
  1841. survey = create_survey_from_xls(
  1842. os.path.join(
  1843. os.path.abspath('./'), 'odk_viewer', 'tests', 'fixtures',
  1844. 'test_data_types/test_data_types.xls'))
  1845. export_builder = ExportBuilder()
  1846. export_builder.set_survey(survey)
  1847. # format submission 1 for export
  1848. survey_name = survey.name
  1849. indices = {survey_name: 0}
  1850. data = dict_to_joined_export(submission_1, 1, indices, survey_name)
  1851. new_row = export_builder.pre_process_row(data[survey_name],
  1852. export_builder.sections[0])
  1853. self.assertIsInstance(new_row['age'], int)
  1854. self.assertIsInstance(new_row['when'], datetime.date)
  1855. #self.assertIsInstance(new_row['precisely'], datetime.datetime)
  1856. self.assertIsInstance(new_row['amount'], float)
  1857. #self.assertIsInstance(new_row['_submission_time'], datetime.datetime)
  1858. #self.assertIsInstance(new_row['really'], datetime.time)
  1859. # check missing values dont break and empty values return blank strings
  1860. indices = {survey_name: 0}
  1861. data = dict_to_joined_export(submission_2, 1, indices, survey_name)
  1862. new_row = export_builder.pre_process_row(data[survey_name],
  1863. export_builder.sections[0])
  1864. self.assertIsInstance(new_row['amount'], basestring)
  1865. self.assertEqual(new_row['amount'], '')
  1866. def test_xls_convert_dates_before_1900(self):
  1867. survey = create_survey_from_xls(
  1868. os.path.join(
  1869. os.path.abspath('./'), 'odk_viewer', 'tests', 'fixtures',
  1870. 'test_data_types/test_data_types.xls'))
  1871. export_builder = ExportBuilder()
  1872. export_builder.set_survey(survey)
  1873. data = [
  1874. {
  1875. 'name': 'Abe',
  1876. 'when': '1899-07-03',
  1877. }
  1878. ]
  1879. # create export file
  1880. temp_xls_file = NamedTemporaryFile(suffix='.xlsx')
  1881. export_builder.to_xls_export(temp_xls_file.name, data)
  1882. temp_xls_file.close()
  1883. # this should error if there is a problem, not sure what to assert
  1884. def test_convert_types(self):
  1885. val = '1'
  1886. expected_val = 1
  1887. converted_val = ExportBuilder.convert_type(val, 'int')
  1888. self.assertIsInstance(converted_val, int)
  1889. self.assertEqual(converted_val, expected_val)
  1890. val = '1.2'
  1891. expected_val = 1.2
  1892. converted_val = ExportBuilder.convert_type(val, 'decimal')
  1893. self.assertIsInstance(converted_val, float)
  1894. self.assertEqual(converted_val, expected_val)
  1895. val = '2012-06-23'
  1896. expected_val = datetime.date(2012, 6, 23)
  1897. converted_val = ExportBuilder.convert_type(val, 'date')
  1898. self.assertIsInstance(converted_val, datetime.date)
  1899. self.assertEqual(converted_val, expected_val)