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.

335 lines
10 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
6 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 re
  4. import uuid
  5. import csv
  6. import base64
  7. import string
  8. import itertools
  9. import logging
  10. from datetime import datetime as dt
  11. from ast import literal_eval
  12. from dateutil.parser import parse
  13. from io import StringIO
  14. from odoo.exceptions import ValidationError
  15. from odoo import _
  16. _logger = logging.getLogger(__name__)
  17. try:
  18. import xlrd
  19. except ImportError:
  20. _logger.debug('Cannot import "xlrd". Please make sure it is installed.')
  21. def adjust_cell_formula(value, k):
  22. """ Cell formula, i.e., if i=5, val=?(A11)+?(B12) -> val=A16+B17 """
  23. if isinstance(value, str):
  24. for i in range(value.count('?(')):
  25. if value and '?(' in value and ')' in value:
  26. i = value.index('?(')
  27. j = value.index(')', i)
  28. val = value[i + 2:j]
  29. col, row = split_row_col(val)
  30. new_val = '%s%s' % (col, row+k)
  31. value = value.replace('?(%s)' % val, new_val)
  32. return value
  33. def get_field_aggregation(field):
  34. """ i..e, 'field@{sum}' """
  35. if field and '@{' in field and '}' in field:
  36. i = field.index('@{')
  37. j = field.index('}', i)
  38. cond = field[i + 2:j]
  39. try:
  40. if cond or cond == '':
  41. return (field[:i], cond)
  42. except Exception:
  43. return (field.replace('@{%s}' % cond, ''), False)
  44. return (field, False)
  45. def get_field_condition(field):
  46. """ i..e, 'field${value > 0 and value or False}' """
  47. if field and '${' in field and '}' in field:
  48. i = field.index('${')
  49. j = field.index('}', i)
  50. cond = field[i + 2:j]
  51. try:
  52. if cond or cond == '':
  53. return (field.replace('${%s}' % cond, ''), cond)
  54. except Exception:
  55. return (field, False)
  56. return (field, False)
  57. def get_field_style(field):
  58. """
  59. Available styles
  60. - font = bold, bold_red
  61. - fill = red, blue, yellow, green, grey
  62. - align = left, center, right
  63. - number = true, false
  64. i.e., 'field#{font=bold;fill=red;align=center;style=number}'
  65. """
  66. if field and '#{' in field and '}' in field:
  67. i = field.index('#{')
  68. j = field.index('}', i)
  69. cond = field[i + 2:j]
  70. try:
  71. if cond or cond == '':
  72. return (field.replace('#{%s}' % cond, ''), cond)
  73. except Exception:
  74. return (field, False)
  75. return (field, False)
  76. def get_field_style_cond(field):
  77. """ i..e, 'field#?object.partner_id and #{font=bold} or #{}?' """
  78. if field and '#?' in field and '?' in field:
  79. i = field.index('#?')
  80. j = field.index('?', i+2)
  81. cond = field[i + 2:j]
  82. try:
  83. if cond or cond == '':
  84. return (field.replace('#?%s?' % cond, ''), cond)
  85. except Exception:
  86. return (field, False)
  87. return (field, False)
  88. def fill_cell_style(field, field_style, styles):
  89. field_styles = field_style.split(';')
  90. for f in field_styles:
  91. (key, value) = f.split('=')
  92. if key not in styles.keys():
  93. raise ValidationError(_('Invalid style type %s' % key))
  94. if value.lower() not in styles[key].keys():
  95. raise ValidationError(
  96. _('Invalid value %s for style type %s' % (value, key)))
  97. cell_style = styles[key][value]
  98. if key == 'font':
  99. field.font = cell_style
  100. if key == 'fill':
  101. field.fill = cell_style
  102. if key == 'align':
  103. field.alignment = cell_style
  104. if key == 'style':
  105. if value == 'text':
  106. try:
  107. # In case value can't be encoded as utf, we do normal str()
  108. field.value = field.value.encode('utf-8')
  109. except Exception:
  110. field.value = str(field.value)
  111. field.number_format = cell_style
  112. def get_line_max(line_field):
  113. """ i.e., line_field = line_ids[100], max = 100 else 0 """
  114. if line_field and '[' in line_field and ']' in line_field:
  115. i = line_field.index('[')
  116. j = line_field.index(']')
  117. max_str = line_field[i + 1:j]
  118. try:
  119. if len(max_str) > 0:
  120. return (line_field[:i], int(max_str))
  121. else:
  122. return (line_field, False)
  123. except Exception:
  124. return (line_field, False)
  125. return (line_field, False)
  126. def get_groupby(line_field):
  127. """i.e., line_field = line_ids["a_id, b_id"], groupby = ["a_id", "b_id"]"""
  128. if line_field and '[' in line_field and ']' in line_field:
  129. i = line_field.index('[')
  130. j = line_field.index(']')
  131. groupby = literal_eval(line_field[i:j+1])
  132. return groupby
  133. return False
  134. def split_row_col(pos):
  135. match = re.match(r"([a-z]+)([0-9]+)", pos, re.I)
  136. if not match:
  137. raise ValidationError(_('Position %s is not valid') % pos)
  138. col, row = match.groups()
  139. return col, int(row)
  140. def openpyxl_get_sheet_by_name(book, name):
  141. """ Get sheet by name for openpyxl """
  142. i = 0
  143. for sheetname in book.sheetnames:
  144. if sheetname == name:
  145. return book.worksheets[i]
  146. i += 1
  147. raise ValidationError(_("'%s' sheet not found") % (name,))
  148. def xlrd_get_sheet_by_name(book, name):
  149. try:
  150. for idx in itertools.count():
  151. sheet = book.sheet_by_index(idx)
  152. if sheet.name == name:
  153. return sheet
  154. except IndexError:
  155. raise ValidationError(_("'%s' sheet not found") % (name,))
  156. def isfloat(input):
  157. try:
  158. float(input)
  159. return True
  160. except ValueError:
  161. return False
  162. def isinteger(input):
  163. try:
  164. int(input)
  165. return True
  166. except ValueError:
  167. return False
  168. def isdatetime(input):
  169. try:
  170. if len(input) == 10:
  171. dt.strptime(input, '%Y-%m-%d')
  172. elif len(input) == 19:
  173. dt.strptime(input, '%Y-%m-%d %H:%M:%S')
  174. else:
  175. return False
  176. return True
  177. except ValueError:
  178. return False
  179. def str_to_number(input):
  180. if isinstance(input, str):
  181. if ' ' not in input:
  182. if isdatetime(input):
  183. return parse(input)
  184. elif isinteger(input):
  185. if not (len(input) > 1 and input[:1] == '0'):
  186. return int(input)
  187. elif isfloat(input):
  188. if not (input.find(".") > 2 and input[:1] == '0'): # 00.123
  189. return float(input)
  190. return input
  191. def csv_from_excel(excel_content, delimiter, quote):
  192. decoded_data = base64.decodestring(excel_content)
  193. wb = xlrd.open_workbook(file_contents=decoded_data)
  194. sh = wb.sheet_by_index(0)
  195. content = StringIO()
  196. quoting = csv.QUOTE_ALL
  197. if not quote:
  198. quoting = csv.QUOTE_NONE
  199. if delimiter == " " and quoting == csv.QUOTE_NONE:
  200. quoting = csv.QUOTE_MINIMAL
  201. wr = csv.writer(content, delimiter=delimiter, quoting=quoting)
  202. for rownum in range(sh.nrows):
  203. row = []
  204. for x in sh.row_values(rownum):
  205. if quoting == csv.QUOTE_NONE and delimiter in x:
  206. raise ValidationError(
  207. _('Template with CSV Quoting = False, data must not '
  208. 'contain the same char as delimiter -> "%s"') %
  209. delimiter)
  210. row.append(x)
  211. wr.writerow(row)
  212. content.seek(0) # Set index to 0, and start reading
  213. out_file = base64.b64encode(content.getvalue().encode('utf-8'))
  214. return out_file
  215. def pos2idx(pos):
  216. match = re.match(r"([a-z]+)([0-9]+)", pos, re.I)
  217. if not match:
  218. raise ValidationError(_('Position %s is not valid') % (pos, ))
  219. col, row = match.groups()
  220. col_num = 0
  221. for c in col:
  222. if c in string.ascii_letters:
  223. col_num = col_num * 26 + (ord(c.upper()) - ord('A')) + 1
  224. return (int(row) - 1, col_num - 1)
  225. def _get_cell_value(cell, field_type=False):
  226. """ If Odoo's field type is known, convert to valid string for import,
  227. if not know, just get value as is """
  228. value = False
  229. datemode = 0 # From book.datemode, but we fix it for simplicity
  230. if field_type in ['date', 'datetime']:
  231. ctype = xlrd.sheet.ctype_text.get(cell.ctype, 'unknown type')
  232. if ctype == 'number':
  233. time_tuple = xlrd.xldate_as_tuple(cell.value, datemode)
  234. date = dt(*time_tuple)
  235. if field_type == 'date':
  236. value = date.strftime("%Y-%m-%d")
  237. elif field_type == 'datetime':
  238. value = date.strftime("%Y-%m-%d %H:%M:%S")
  239. else:
  240. value = cell.value
  241. elif field_type in ['integer', 'float']:
  242. value_str = str(cell.value).strip().replace(',', '')
  243. if len(value_str) == 0:
  244. value = ''
  245. elif value_str.replace('.', '', 1).isdigit(): # Is number
  246. if field_type == 'integer':
  247. value = int(float(value_str))
  248. elif field_type == 'float':
  249. value = float(value_str)
  250. else: # Is string, no conversion
  251. value = value_str
  252. elif field_type in ['many2one']:
  253. # If number, change to string
  254. if isinstance(cell.value, (int, float, complex)):
  255. value = str(cell.value)
  256. else:
  257. value = cell.value
  258. else: # text, char
  259. value = cell.value
  260. # If string, cleanup
  261. if isinstance(value, str):
  262. if value[-2:] == '.0':
  263. value = value[:-2]
  264. # Except boolean, when no value, we should return as ''
  265. if field_type not in ['boolean']:
  266. if not value:
  267. value = ''
  268. return value
  269. def _add_column(column_name, column_value, file_txt):
  270. i = 0
  271. txt_lines = []
  272. for line in file_txt.split('\n'):
  273. if line and i == 0:
  274. line = '"' + str(column_name) + '",' + line
  275. elif line:
  276. line = '"' + str(column_value) + '",' + line
  277. txt_lines.append(line)
  278. i += 1
  279. file_txt = '\n'.join(txt_lines)
  280. return file_txt
  281. def _add_id_column(file_txt):
  282. i = 0
  283. txt_lines = []
  284. for line in file_txt.split('\n'):
  285. if line and i == 0:
  286. line = '"id",' + line
  287. elif line:
  288. line = '%s.%s' % ('xls', uuid.uuid4()) + ',' + line
  289. txt_lines.append(line)
  290. i += 1
  291. file_txt = '\n'.join(txt_lines)
  292. return file_txt