OCA reporting engine fork for dev and update.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

190 lines
8.1 KiB

  1. # -*- coding: utf-8 -*-
  2. # Copyright 2016 ACSONE SA/NV
  3. # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).).
  4. import base64
  5. from base64 import b64decode
  6. import mock
  7. import os
  8. import pkg_resources
  9. import shutil
  10. import tempfile
  11. from contextlib import contextmanager
  12. from odoo import tools
  13. from odoo.tests.common import TransactionCase
  14. from odoo.exceptions import ValidationError
  15. from ..models.py3o_report import TemplateNotFound
  16. from base64 import b64encode
  17. @contextmanager
  18. def temporary_copy(path):
  19. filname, ext = os.path.splitext(path)
  20. tmp_filename = tempfile.mktemp(suffix='.' + ext)
  21. try:
  22. shutil.copy2(path, tmp_filename)
  23. yield tmp_filename
  24. finally:
  25. os.unlink(tmp_filename)
  26. class TestReportPy3o(TransactionCase):
  27. def setUp(self):
  28. super(TestReportPy3o, self).setUp()
  29. self.report = self.env.ref("report_py3o.res_users_report_py3o")
  30. self.py3o_report = self.env['py3o.report'].create({
  31. 'ir_actions_report_xml_id': self.report.id})
  32. def test_required_py3_filetype(self):
  33. self.assertEqual(self.report.report_type, "py3o")
  34. with self.assertRaises(ValidationError) as e:
  35. self.report.py3o_filetype = False
  36. self.assertEqual(
  37. e.exception.name,
  38. "Field 'Output Format' is required for Py3O report")
  39. def _render_patched(self, result_text='test result', call_count=1):
  40. py3o_report = self.env['py3o.report']
  41. with mock.patch.object(
  42. py3o_report.__class__, '_create_single_report') as patched_pdf:
  43. result = tempfile.mktemp('.txt')
  44. with open(result, 'w') as fp:
  45. fp.write(result_text)
  46. patched_pdf.return_value = result
  47. patched_pdf.side_effect = lambda record, data, save_attachments:\
  48. py3o_report._postprocess_report(
  49. result, record.id, save_attachments,
  50. ) or result
  51. # test the call the the create method inside our custom parser
  52. self.report.render_report(self.env.user.ids,
  53. self.report.report_name,
  54. {})
  55. self.assertEqual(call_count, patched_pdf.call_count)
  56. # generated files no more exists
  57. self.assertFalse(os.path.exists(result))
  58. def test_reports(self):
  59. res = self.report.render_report(
  60. self.env.user.ids, self.report.report_name, {})
  61. self.assertTrue(res)
  62. self.report.py3o_filetype = 'pdf'
  63. res = self.report.render_report(
  64. self.env.user.ids, self.report.report_name, {})
  65. self.assertTrue(res)
  66. def test_report_load_from_attachment(self):
  67. self.report.write({"attachment_use": True,
  68. "attachment": "'my_saved_report'"})
  69. attachments = self.env['ir.attachment'].search([])
  70. self._render_patched()
  71. new_attachments = self.env['ir.attachment'].search([])
  72. created_attachement = new_attachments - attachments
  73. self.assertEqual(1, len(created_attachement))
  74. content = b64decode(created_attachement.datas)
  75. self.assertEqual("test result", content)
  76. # put a new content into tha attachement and check that the next
  77. # time we ask the report we received the saved attachment not a newly
  78. # generated document
  79. created_attachement.datas = base64.encodestring("new content")
  80. res = self.report.render_report(
  81. self.env.user.ids, self.report.report_name, {})
  82. self.assertEqual(('new content', self.report.py3o_filetype), res)
  83. def test_report_post_process(self):
  84. """
  85. By default the post_process method is in charge to save the
  86. generated report into an ir.attachment if requested.
  87. """
  88. self.report.attachment = "object.name + '.txt'"
  89. ir_attachment = self.env['ir.attachment']
  90. attachements = ir_attachment.search([(1, '=', 1)])
  91. self._render_patched()
  92. attachements = ir_attachment.search([(1, '=', 1)]) - attachements
  93. self.assertEqual(1, len(attachements.ids))
  94. self.assertEqual(self.env.user.name + '.txt', attachements.name)
  95. self.assertEqual(self.env.user._name, attachements.res_model)
  96. self.assertEqual(self.env.user.id, attachements.res_id)
  97. self.assertEqual('test result', b64decode(attachements.datas))
  98. @tools.misc.mute_logger('odoo.addons.report_py3o.models.py3o_report')
  99. def test_report_template_configs(self):
  100. # the demo template is specified with a relative path in in the module
  101. # path
  102. tmpl_name = self.report.py3o_template_fallback
  103. flbk_filename = pkg_resources.resource_filename(
  104. "odoo.addons.%s" % self.report.module,
  105. tmpl_name)
  106. self.assertTrue(os.path.exists(flbk_filename))
  107. res = self.report.render_report(
  108. self.env.user.ids, self.report.report_name, {})
  109. self.assertTrue(res)
  110. # The generation fails if the template is not found
  111. self.report.module = False
  112. with self.assertRaises(TemplateNotFound), self.env.cr.savepoint():
  113. self.report.render_report(
  114. self.env.user.ids, self.report.report_name, {})
  115. # the template can also be provided as an abspath if it's root path
  116. # is trusted
  117. self.report.py3o_template_fallback = flbk_filename
  118. with self.assertRaises(TemplateNotFound):
  119. self.report.render_report(
  120. self.env.user.ids, self.report.report_name, {})
  121. with temporary_copy(flbk_filename) as tmp_filename:
  122. self.report.py3o_template_fallback = tmp_filename
  123. tools.config.misc['report_py3o'] = {
  124. 'root_tmpl_path': os.path.dirname(tmp_filename)}
  125. res = self.report.render_report(
  126. self.env.user.ids, self.report.report_name, {})
  127. self.assertTrue(res)
  128. # the tempalte can also be provided as a binary field
  129. self.report.py3o_template_fallback = False
  130. with open(flbk_filename) as tmpl_file:
  131. tmpl_data = b64encode(tmpl_file.read())
  132. py3o_template = self.env['py3o.template'].create({
  133. 'name': 'test_template',
  134. 'py3o_template_data': tmpl_data,
  135. 'filetype': 'odt'})
  136. self.report.py3o_template_id = py3o_template
  137. self.report.py3o_template_fallback = flbk_filename
  138. res = self.report.render_report(
  139. self.env.user.ids, self.report.report_name, {})
  140. self.assertTrue(res)
  141. @tools.misc.mute_logger('odoo.addons.report_py3o.models.py3o_report')
  142. def test_report_template_fallback_validity(self):
  143. tmpl_name = self.report.py3o_template_fallback
  144. flbk_filename = pkg_resources.resource_filename(
  145. "odoo.addons.%s" % self.report.module,
  146. tmpl_name)
  147. # an exising file in a native format is a valid template if it's
  148. self.assertTrue(self.py3o_report._get_template_from_path(
  149. tmpl_name))
  150. self.report.module = None
  151. # a directory is not a valid template..
  152. self.assertFalse(self.py3o_report._get_template_from_path('/etc/'))
  153. self.assertFalse(self.py3o_report._get_template_from_path('.'))
  154. # an vaild template outside the root_tmpl_path is not a valid template
  155. # path
  156. # located in trusted directory
  157. self.report.py3o_template_fallback = flbk_filename
  158. self.assertFalse(self.py3o_report._get_template_from_path(
  159. flbk_filename))
  160. with temporary_copy(flbk_filename) as tmp_filename:
  161. self.assertTrue(self.py3o_report._get_template_from_path(
  162. tmp_filename))
  163. # check security
  164. self.assertFalse(self.py3o_report._get_template_from_path(
  165. 'rm -rf . & %s' % flbk_filename))
  166. # a file in a non native LibreOffice format is not a valid template
  167. with tempfile.NamedTemporaryFile(suffix='.toto')as f:
  168. self.assertFalse(self.py3o_report._get_template_from_path(
  169. f.name))
  170. # non exising files are not valid template
  171. self.assertFalse(self.py3o_report._get_template_from_path(
  172. '/etc/test.odt'))