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.

259 lines
11 KiB

Excel Import/Export/Report (#1522) * [ADD] v12 excel_import_export * Change from eval() to safe_evel() * Change variable to format to style, as fomat is a common python function :100644 100644 00ee3d9f... e9e48d87... M excel_import_export/models/common.py :100644 100644 a215d29b... 5b4d1fb1... M excel_import_export/models/styles.py :100644 100644 ace11a32... 01e5b9f5... M excel_import_export/models/xlsx_export.py :100644 100644 881b814f... cadfb0f2... M excel_import_export/models/xlsx_import.py :100644 100644 58689ee5... 80490ce8... M excel_import_export/models/xlsx_template.py :100644 100644 5c9c09a6... a363ad19... M excel_import_export/views/xlsx_template_view.xml :100644 100644 475b5187... 392fe6e5... M excel_import_export_demo/import_export_sale_order/templates.xml :100644 100644 4af9c519... 45ee33c6... M excel_import_export_demo/report_sale_order/templates.xml :100644 100644 96157ea3... 17d3964d... M excel_import_export/__manifest__.py :100644 100644 00ee3d9f... 51c2572a... M excel_import_export/models/common.py :100644 100644 a215d29b... 5b4d1fb1... M excel_import_export/models/styles.py :100644 100644 ace11a32... 185a3330... M excel_import_export/models/xlsx_export.py :100644 100644 881b814f... cadfb0f2... M excel_import_export/models/xlsx_import.py :100644 100644 58689ee5... 80490ce8... M excel_import_export/models/xlsx_template.py :100644 100644 5c9c09a6... a363ad19... M excel_import_export/views/xlsx_template_view.xml :100644 100644 475b5187... 392fe6e5... M excel_import_export_demo/import_export_sale_order/templates.xml :100644 100644 4af9c519... 45ee33c6... M excel_import_export_demo/report_sale_order/templates.xml :100644 100644 96157ea3... 933ce0dc... M excel_import_export/__manifest__.py :100644 100644 00ee3d9f... 51c2572a... M excel_import_export/models/common.py :100644 100644 a215d29b... 5b4d1fb1... M excel_import_export/models/styles.py :100644 100644 ace11a32... 185a3330... M excel_import_export/models/xlsx_export.py :100644 100644 881b814f... cadfb0f2... M excel_import_export/models/xlsx_import.py :100644 100644 58689ee5... 80490ce8... M excel_import_export/models/xlsx_template.py :100644 100644 5c9c09a6... a363ad19... M excel_import_export/views/xlsx_template_view.xml :100644 100644 475b5187... 392fe6e5... M excel_import_export_demo/import_export_sale_order/templates.xml :100644 100644 4af9c519... 45ee33c6... M excel_import_export_demo/report_sale_order/templates.xml :100644 100644 96157ea3 3b1217e8 M excel_import_export/__manifest__.py :100644 100644 00ee3d9f 51c2572a M excel_import_export/models/common.py :100644 100644 a215d29b 5b4d1fb1 M excel_import_export/models/styles.py :100644 100644 ace11a32 185a3330 M excel_import_export/models/xlsx_export.py :100644 100644 881b814f cadfb0f2 M excel_import_export/models/xlsx_import.py :100644 100644 58689ee5 80490ce8 M excel_import_export/models/xlsx_template.py :100644 100644 5c9c09a6 a363ad19 M excel_import_export/views/xlsx_template_view.xml :100644 100644 475b5187 392fe6e5 M excel_import_export_demo/import_export_sale_order/templates.xml :100644 100644 4af9c519 45ee33c6 M excel_import_export_demo/report_sale_order/templates.xml :100644 100644 96157ea3 fee958bc M excel_import_export/__manifest__.py :100644 100644 00ee3d9f 51c2572a M excel_import_export/models/common.py :100644 100644 a215d29b 5b4d1fb1 M excel_import_export/models/styles.py :100644 100644 ace11a32 185a3330 M excel_import_export/models/xlsx_export.py :100644 100644 881b814f cadfb0f2 M excel_import_export/models/xlsx_import.py :100644 100644 58689ee5 80490ce8 M excel_import_export/models/xlsx_template.py :100644 100644 5c9c09a6 a363ad19 M excel_import_export/views/xlsx_template_view.xml :100644 100644 475b5187 392fe6e5 M excel_import_export_demo/import_export_sale_order/templates.xml :100644 100644 4af9c519 45ee33c6 M excel_import_export_demo/report_sale_order/templates.xml :100644 100644 96157ea3 fee958bc M excel_import_export/__manifest__.py :100644 100644 00ee3d9f 51c2572a M excel_import_export/models/common.py :100644 100644 a215d29b 9738a3c8 M excel_import_export/models/styles.py :100644 100644 ace11a32 a7d6adc5 M excel_import_export/models/xlsx_export.py :100644 100644 881b814f 12f9ca99 M excel_import_export/models/xlsx_import.py :100644 100644 70c37799 f123d2a6 M excel_import_export/models/xlsx_report.py :100644 100644 58689ee5 578a1fd8 M excel_import_export/models/xlsx_template.py :100644 100644 5c9c09a6 a363ad19 M excel_import_export/views/xlsx_template_view.xml :100644 100644 800ea573 1807ea7e M excel_import_export/wizard/export_xlsx_wizard.py :100644 100644 febed8d0 750dc17e M excel_import_export/wizard/import_xlsx_wizard.py :100644 100644 475b5187 392fe6e5 M excel_import_export_demo/import_export_sale_order/templates.xml :100644 100644 8e40a2d0 21574896 M excel_import_export_demo/report_sale_order/report_sale_order.py :100644 100644 4af9c519 45ee33c6 M excel_import_export_demo/report_sale_order/templates.xml :100644 100644 96157ea3 fee958bc M excel_import_export/__manifest__.py :100644 100644 00ee3d9f 51c2572a M excel_import_export/models/common.py :100644 100644 a215d29b 9738a3c8 M excel_import_export/models/styles.py :100644 100644 ace11a32 c7db3f92 M excel_import_export/models/xlsx_export.py :100644 100644 881b814f 12f9ca99 M excel_import_export/models/xlsx_import.py :100644 100644 70c37799 f123d2a6 M excel_import_export/models/xlsx_report.py :100644 100644 58689ee5 578a1fd8 M excel_import_export/models/xlsx_template.py :100644 100644 5c9c09a6 a363ad19 M excel_import_export/views/xlsx_template_view.xml :100644 100644 800ea573 1807ea7e M excel_import_export/wizard/export_xlsx_wizard.py :100644 100644 febed8d0 750dc17e M excel_import_export/wizard/import_xlsx_wizard.py :100644 100644 475b5187 392fe6e5 M excel_import_export_demo/import_export_sale_order/templates.xml :100644 100644 8e40a2d0 21574896 M excel_import_export_demo/report_sale_order/report_sale_order.py :100644 100644 4af9c519 45ee33c6 M excel_import_export_demo/report_sale_order/templates.xml :100644 100644 96157ea3 fee958bc M excel_import_export/__manifest__.py :100644 100644 00ee3d9f 51c2572a M excel_import_export/models/common.py :100644 100644 a215d29b 9738a3c8 M excel_import_export/models/styles.py :100644 100644 ace11a32 c7db3f92 M excel_import_export/models/xlsx_export.py :100644 100644 881b814f 12f9ca99 M excel_import_export/models/xlsx_import.py :100644 100644 70c37799 f123d2a6 M excel_import_export/models/xlsx_report.py :100644 100644 58689ee5 e3826e08 M excel_import_export/models/xlsx_template.py :000000 100644 00000000 34aa53bf A excel_import_export/tests/__init__.py :000000 100644 00000000 18618688 A excel_import_export/tests/sale_order.xlsx :000000 100644 00000000 c8481487 A excel_import_export/tests/test_xlsx_template.py :100644 100644 5c9c09a6 a363ad19 M excel_import_export/views/xlsx_template_view.xml :100644 100644 800ea573 1807ea7e M excel_import_export/wizard/export_xlsx_wizard.py :100644 100644 febed8d0 750dc17e M excel_import_export/wizard/import_xlsx_wizard.py :100644 100644 475b5187 392fe6e5 M excel_import_export_demo/import_export_sale_order/templates.xml :100644 100644 8e40a2d0 21574896 M excel_import_export_demo/report_sale_order/report_sale_order.py :100644 100644 4af9c519 45ee33c6 M excel_import_export_demo/report_sale_order/templates.xml :100644 100644 96157ea3 fee958bc M excel_import_export/__manifest__.py :100644 100644 00ee3d9f 51c2572a M excel_import_export/models/common.py :100644 100644 a215d29b 9738a3c8 M excel_import_export/models/styles.py :100644 100644 ace11a32 c7db3f92 M excel_import_export/models/xlsx_export.py :100644 100644 881b814f 12f9ca99 M excel_import_export/models/xlsx_import.py :100644 100644 70c37799 f123d2a6 M excel_import_export/models/xlsx_report.py :100644 100644 58689ee5 ed8c9fc7 M excel_import_export/models/xlsx_template.py :000000 100644 00000000 34aa53bf A excel_import_export/tests/__init__.py :000000 100644 00000000 18618688 A excel_import_export/tests/sale_order.xlsx :000000 100644 00000000 69aa6ea0 A excel_import_export/tests/test_xlsx_template.py :100644 100644 5c9c09a6 a363ad19 M excel_import_export/views/xlsx_template_view.xml :100644 100644 800ea573 1807ea7e M excel_import_export/wizard/export_xlsx_wizard.py :100644 100644 febed8d0 750dc17e M excel_import_export/wizard/import_xlsx_wizard.py :100644 100644 475b5187 392fe6e5 M excel_import_export_demo/import_export_sale_order/templates.xml :100644 100644 8e40a2d0 21574896 M excel_import_export_demo/report_sale_order/report_sale_order.py :100644 100644 4af9c519 45ee33c6 M excel_import_export_demo/report_sale_order/templates.xml :100644 100644 96157ea3 fee958bc M excel_import_export/__manifest__.py :100644 100644 00ee3d9f 51c2572a M excel_import_export/models/common.py :100644 100644 a215d29b 9738a3c8 M excel_import_export/models/styles.py :100644 100644 ace11a32 c7db3f92 M excel_import_export/models/xlsx_export.py :100644 100644 881b814f 933d8614 M excel_import_export/models/xlsx_import.py :100644 100644 70c37799 f123d2a6 M excel_import_export/models/xlsx_report.py :100644 100644 58689ee5 1460473a M excel_import_export/models/xlsx_template.py :100644 100644 5c9c09a6 a363ad19 M excel_import_export/views/xlsx_template_view.xml :100644 100644 800ea573 1807ea7e M excel_import_export/wizard/export_xlsx_wizard.py :100644 100644 febed8d0 750dc17e M excel_import_export/wizard/import_xlsx_wizard.py :100644 100644 a2d035ef 9463f279 M excel_import_export_demo/__manifest__.py :100644 100644 475b5187 e7f1255b M excel_import_export_demo/import_export_sale_order/templates.xml :100644 100644 8e40a2d0 21574896 M excel_import_export_demo/report_sale_order/report_sale_order.py :100644 100644 4af9c519 45ee33c6 M excel_import_export_demo/report_sale_order/templates.xml :000000 100644 00000000 79db62f7 A excel_import_export_demo/tests/__init__.py :000000 100644 00000000 18618688 A excel_import_export_demo/tests/sale_order.xlsx :000000 100644 00000000 c9733b95 A excel_import_export_demo/tests/test_common.py :000000 100644 00000000 9c943768 A excel_import_export_demo/tests/test_xlsx_import_export.py :000000 100644 00000000 730605c1 A excel_import_export_demo/tests/test_xlsx_template.py :100644 100644 96157ea3 fee958bc M excel_import_export/__manifest__.py :100644 100644 00ee3d9f 51c2572a M excel_import_export/models/common.py :100644 100644 a215d29b 9738a3c8 M excel_import_export/models/styles.py :100644 100644 ace11a32 c7db3f92 M excel_import_export/models/xlsx_export.py :100644 100644 881b814f 933d8614 M excel_import_export/models/xlsx_import.py :100644 100644 70c37799 f123d2a6 M excel_import_export/models/xlsx_report.py :100644 100644 58689ee5 1460473a M excel_import_export/models/xlsx_template.py :100644 100644 5c9c09a6 a363ad19 M excel_import_export/views/xlsx_template_view.xml :100644 100644 800ea573 1807ea7e M excel_import_export/wizard/export_xlsx_wizard.py :100644 100644 febed8d0 750dc17e M excel_import_export/wizard/import_xlsx_wizard.py :100644 100644 a2d035ef 9463f279 M excel_import_export_demo/__manifest__.py :100644 100644 475b5187 e7f1255b M excel_import_export_demo/import_export_sale_order/templates.xml :100644 100644 8e40a2d0 21574896 M excel_import_export_demo/report_sale_order/report_sale_order.py :100644 100644 4af9c519 45ee33c6 M excel_import_export_demo/report_sale_order/templates.xml :000000 100644 00000000 79db62f7 A excel_import_export_demo/tests/__init__.py :000000 100644 00000000 18618688 A excel_import_export_demo/tests/sale_order.xlsx :000000 100644 00000000 bb3ea32e A excel_import_export_demo/tests/test_common.py :000000 100644 00000000 9c943768 A excel_import_export_demo/tests/test_xlsx_import_export.py :000000 100644 00000000 730605c1 A excel_import_export_demo/tests/test_xlsx_template.py
5 years ago
  1. # Copyright 2019 Ecosoft Co., Ltd (http://ecosoft.co.th/)
  2. # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html)
  3. import base64
  4. import uuid
  5. import xlrd
  6. import xlwt
  7. import time
  8. from io import BytesIO
  9. from . import common as co
  10. from ast import literal_eval
  11. from datetime import date, datetime as dt
  12. from odoo.tools.float_utils import float_compare
  13. from odoo import models, api, _
  14. from odoo.exceptions import ValidationError
  15. from odoo.tools.safe_eval import safe_eval
  16. class XLSXImport(models.AbstractModel):
  17. _name = 'xlsx.import'
  18. _description = 'Excel Import AbstractModel'
  19. @api.model
  20. def get_eval_context(self, model=False, value=False):
  21. eval_context = {'float_compare': float_compare,
  22. 'time': time,
  23. 'datetime': dt,
  24. 'date': date,
  25. 'env': self.env,
  26. 'context': self._context,
  27. 'value': False,
  28. 'model': False,
  29. }
  30. if model:
  31. eval_context.update({'model': self.env[model]})
  32. if value:
  33. if isinstance(value, str): # Remove non Ord 128 character
  34. value = ''.join([i if ord(i) < 128 else ' ' for i in value])
  35. eval_context.update({'value': value})
  36. return eval_context
  37. @api.model
  38. def get_external_id(self, record):
  39. """ Get external ID of the record, if not already exists create one """
  40. ModelData = self.env['ir.model.data']
  41. xml_id = record.get_external_id()
  42. if not xml_id or (record.id in xml_id and xml_id[record.id] == ''):
  43. ModelData.create({'name': '%s_%s' % (record._table, record.id),
  44. 'module': 'excel_import_export',
  45. 'model': record._name,
  46. 'res_id': record.id, })
  47. xml_id = record.get_external_id()
  48. return xml_id[record.id]
  49. @api.model
  50. def _get_field_type(self, model, field):
  51. try:
  52. record = self.env[model].new()
  53. for f in field.split('/'):
  54. field_type = record._fields[f].type
  55. if field_type in ('one2many', 'many2many'):
  56. record = record[f]
  57. else:
  58. return field_type
  59. except Exception:
  60. raise ValidationError(
  61. _('Invalid declaration, %s has no valid field type') % field)
  62. @api.model
  63. def _delete_record_data(self, record, data_dict):
  64. """ If no _NODEL_, delete existing lines before importing """
  65. if not record or not data_dict:
  66. return
  67. try:
  68. for sheet_name in data_dict:
  69. worksheet = data_dict[sheet_name]
  70. line_fields = filter(lambda x: x != '_HEAD_', worksheet)
  71. for line_field in line_fields:
  72. if '_NODEL_' not in line_field:
  73. if line_field in record and record[line_field]:
  74. record[line_field].unlink()
  75. # Remove _NODEL_ from dict
  76. for s, sv in data_dict.items():
  77. for f, fv in data_dict[s].items():
  78. if '_NODEL_' in f:
  79. new_fv = data_dict[s].pop(f)
  80. data_dict[s][f.replace('_NODEL_', '')] = new_fv
  81. except Exception as e:
  82. raise ValidationError(_('Error deleting data\n%s') % e)
  83. @api.model
  84. def _get_line_vals(self, st, worksheet, model, line_field):
  85. """ Get values of this field from excel sheet """
  86. vals = {}
  87. for rc, columns in worksheet.get(line_field, {}).items():
  88. if not isinstance(columns, list):
  89. columns = [columns]
  90. for field in columns:
  91. rc, key_eval_cond = co.get_field_condition(rc)
  92. x_field, val_eval_cond = co.get_field_condition(field)
  93. row, col = co.pos2idx(rc)
  94. out_field = '%s/%s' % (line_field, x_field)
  95. field_type = self._get_field_type(model, out_field)
  96. vals.update({out_field: []})
  97. for idx in range(row, st.nrows):
  98. value = co._get_cell_value(st.cell(idx, col),
  99. field_type=field_type)
  100. eval_context = self.get_eval_context(model=model,
  101. value=value)
  102. if key_eval_cond:
  103. value = safe_eval(key_eval_cond, eval_context)
  104. if val_eval_cond:
  105. value = safe_eval(val_eval_cond, eval_context)
  106. vals[out_field].append(value)
  107. if not filter(lambda x: x != '', vals[out_field]):
  108. vals.pop(out_field)
  109. return vals
  110. @api.model
  111. def _import_record_data(self, import_file, record, data_dict):
  112. """ From complex excel, create temp simple excel and do import """
  113. if not data_dict:
  114. return
  115. try:
  116. header_fields = []
  117. decoded_data = base64.decodestring(import_file)
  118. wb = xlrd.open_workbook(file_contents=decoded_data)
  119. col_idx = 0
  120. out_wb = xlwt.Workbook()
  121. out_st = out_wb.add_sheet("Sheet 1")
  122. xml_id = record and self.get_external_id(record) or \
  123. '%s.%s' % ('xls', uuid.uuid4())
  124. out_st.write(0, 0, 'id') # id and xml_id on first column
  125. out_st.write(1, 0, xml_id)
  126. header_fields.append('id')
  127. col_idx += 1
  128. model = record._name
  129. for sheet_name in data_dict: # For each Sheet
  130. worksheet = data_dict[sheet_name]
  131. st = False
  132. if isinstance(sheet_name, str):
  133. st = co.xlrd_get_sheet_by_name(wb, sheet_name)
  134. elif isinstance(sheet_name, int):
  135. st = wb.sheet_by_index(sheet_name - 1)
  136. if not st:
  137. raise ValidationError(
  138. _('Sheet %s not found') % sheet_name)
  139. # HEAD updates
  140. for rc, field in worksheet.get('_HEAD_', {}).items():
  141. rc, key_eval_cond = co.get_field_condition(rc)
  142. field, val_eval_cond = co.get_field_condition(field)
  143. field_type = self._get_field_type(model, field)
  144. value = False
  145. try:
  146. row, col = co.pos2idx(rc)
  147. value = co._get_cell_value(st.cell(row, col),
  148. field_type=field_type)
  149. except Exception:
  150. pass
  151. eval_context = self.get_eval_context(model=model,
  152. value=value)
  153. if key_eval_cond:
  154. value = str(safe_eval(key_eval_cond, eval_context))
  155. if val_eval_cond:
  156. value = str(safe_eval(val_eval_cond, eval_context))
  157. out_st.write(0, col_idx, field) # Next Column
  158. out_st.write(1, col_idx, value) # Next Value
  159. header_fields.append(field)
  160. col_idx += 1
  161. # Line Items
  162. line_fields = filter(lambda x: x != '_HEAD_', worksheet)
  163. for line_field in line_fields:
  164. vals = self._get_line_vals(st, worksheet,
  165. model, line_field)
  166. for field in vals:
  167. # Columns, i.e., line_ids/field_id
  168. out_st.write(0, col_idx, field)
  169. header_fields.append(field)
  170. # Data
  171. i = 1
  172. for value in vals[field]:
  173. out_st.write(i, col_idx, value)
  174. i += 1
  175. col_idx += 1
  176. content = BytesIO()
  177. out_wb.save(content)
  178. content.seek(0) # Set index to 0, and start reading
  179. xls_file = content.read()
  180. # Do the import
  181. Import = self.env['base_import.import']
  182. imp = Import.create({
  183. 'res_model': model,
  184. 'file': xls_file,
  185. 'file_type': 'application/vnd.ms-excel',
  186. 'file_name': 'temp.xls',
  187. })
  188. errors = imp.do(
  189. header_fields,
  190. header_fields,
  191. {'headers': True,
  192. 'advanced': True,
  193. 'keep_matches': False,
  194. 'encoding': '',
  195. 'separator': '',
  196. 'quoting': '"',
  197. 'date_style': '',
  198. 'datetime_style': '%Y-%m-%d %H:%M:%S',
  199. 'float_thousand_separator': ',',
  200. 'float_decimal_separator': '.',
  201. 'fields': []})
  202. if errors.get('messages'):
  203. message = errors['messages']['message'].encode('utf-8')
  204. raise ValidationError(message)
  205. return self.env.ref(xml_id)
  206. except xlrd.XLRDError:
  207. raise ValidationError(
  208. _('Invalid file style, only .xls or .xlsx file allowed'))
  209. except Exception as e:
  210. raise ValidationError(_('Error importing data\n%s') % e)
  211. @api.model
  212. def _post_import_operation(self, record, operation):
  213. """ Run python code after import """
  214. if not record or not operation:
  215. return
  216. try:
  217. if '${' in operation:
  218. code = (operation.split('${'))[1].split('}')[0]
  219. eval_context = {'object': record}
  220. safe_eval(code, eval_context)
  221. except Exception as e:
  222. raise ValidationError(_('Post import operation error\n%s') % e)
  223. @api.model
  224. def import_xlsx(self, import_file, template,
  225. res_model=False, res_id=False):
  226. """
  227. - If res_id = False, we want to create new document first
  228. - Delete fields' data according to data_dict['__IMPORT__']
  229. - Import data from excel according to data_dict['__IMPORT__']
  230. """
  231. self = self.sudo()
  232. if res_model and template.res_model != res_model:
  233. raise ValidationError(_("Template's model mismatch"))
  234. record = self.env[template.res_model].browse(res_id)
  235. data_dict = literal_eval(template.instruction.strip())
  236. if not data_dict.get('__IMPORT__'):
  237. raise ValidationError(
  238. _("No data_dict['__IMPORT__'] in template %s") % template.name)
  239. if record:
  240. # Delete existing data first
  241. self._delete_record_data(record, data_dict['__IMPORT__'])
  242. # Fill up record with data from excel sheets
  243. record = self._import_record_data(import_file, record,
  244. data_dict['__IMPORT__'])
  245. # Post Import Operation, i.e., cleanup some data
  246. if data_dict.get('__POST_IMPORT__', False):
  247. self._post_import_operation(record, data_dict['__POST_IMPORT__'])
  248. return record