# Copyright 2019 Ecosoft Co., Ltd (http://ecosoft.co.th/) # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html) import csv import itertools import logging import re import string import uuid from ast import literal_eval from datetime import datetime as dt from io import StringIO from dateutil.parser import parse from odoo import _ from odoo.exceptions import ValidationError _logger = logging.getLogger(__name__) try: import xlrd except ImportError: _logger.debug('Cannot import "xlrd". Please make sure it is installed.') def adjust_cell_formula(value, k): """Cell formula, i.e., if i=5, val=?(A11)+?(B12) -> val=A16+B17""" if isinstance(value, str): for i in range(value.count("?(")): if value and "?(" in value and ")" in value: i = value.index("?(") j = value.index(")", i) val = value[i + 2 : j] col, row = split_row_col(val) new_val = "{}{}".format(col, row + k) value = value.replace("?(%s)" % val, new_val) return value def get_field_aggregation(field): """i..e, 'field@{sum}'""" if field and "@{" in field and "}" in field: i = field.index("@{") j = field.index("}", i) cond = field[i + 2 : j] try: if cond or cond == "": return (field[:i], cond) except Exception: return (field.replace("@{%s}" % cond, ""), False) return (field, False) def get_field_condition(field): """i..e, 'field${value > 0 and value or False}'""" if field and "${" in field and "}" in field: i = field.index("${") j = field.index("}", i) cond = field[i + 2 : j] try: if cond or cond == "": return (field.replace("${%s}" % cond, ""), cond) except Exception: return (field, False) return (field, False) def get_field_style(field): """ Available styles - font = bold, bold_red - fill = red, blue, yellow, green, grey - align = left, center, right - number = true, false i.e., 'field#{font=bold;fill=red;align=center;style=number}' """ if field and "#{" in field and "}" in field: i = field.index("#{") j = field.index("}", i) cond = field[i + 2 : j] try: if cond or cond == "": return (field.replace("#{%s}" % cond, ""), cond) except Exception: return (field, False) return (field, False) def get_field_style_cond(field): """i..e, 'field#?object.partner_id and #{font=bold} or #{}?'""" if field and "#?" in field and "?" in field: i = field.index("#?") j = field.index("?", i + 2) cond = field[i + 2 : j] try: if cond or cond == "": return (field.replace("#?%s?" % cond, ""), cond) except Exception: return (field, False) return (field, False) def fill_cell_style(field, field_style, styles): field_styles = field_style.split(";") if field_style else [] for f in field_styles: (key, value) = f.split("=") if key not in styles.keys(): raise ValidationError(_("Invalid style type %s" % key)) if value.lower() not in styles[key].keys(): raise ValidationError( _("Invalid value {} for style type {}".format(value, key)) ) cell_style = styles[key][value] if key == "font": field.font = cell_style if key == "fill": field.fill = cell_style if key == "align": field.alignment = cell_style if key == "style": if value == "text": try: # In case value can't be encoded as utf, we do normal str() field.value = field.value.encode("utf-8") except Exception: field.value = str(field.value) field.number_format = cell_style def get_line_max(line_field): """i.e., line_field = line_ids[100], max = 100 else 0""" if line_field and "[" in line_field and "]" in line_field: i = line_field.index("[") j = line_field.index("]") max_str = line_field[i + 1 : j] try: if len(max_str) > 0: return (line_field[:i], int(max_str)) else: return (line_field, False) except Exception: return (line_field, False) return (line_field, False) def get_groupby(line_field): """i.e., line_field = line_ids["a_id, b_id"], groupby = ["a_id", "b_id"]""" if line_field and "[" in line_field and "]" in line_field: i = line_field.index("[") j = line_field.index("]") groupby = literal_eval(line_field[i : j + 1]) return groupby return False def split_row_col(pos): match = re.match(r"([a-z]+)([0-9]+)", pos, re.I) if not match: raise ValidationError(_("Position %s is not valid") % pos) col, row = match.groups() return col, int(row) def openpyxl_get_sheet_by_name(book, name): """Get sheet by name for openpyxl""" i = 0 for sheetname in book.sheetnames: if sheetname == name: return book.worksheets[i] i += 1 raise ValidationError(_("'%s' sheet not found") % (name,)) def xlrd_get_sheet_by_name(book, name): try: for idx in itertools.count(): sheet = book.sheet_by_index(idx) if sheet.name == name: return sheet except IndexError: raise ValidationError(_("'%s' sheet not found") % (name,)) def isfloat(input_val): try: float(input_val) return True except ValueError: return False def isinteger(input_val): try: int(input_val) return True except ValueError: return False def isdatetime(input_val): try: if len(input_val) == 10: dt.strptime(input_val, "%Y-%m-%d") elif len(input_val) == 19: dt.strptime(input_val, "%Y-%m-%d %H:%M:%S") else: return False return True except ValueError: return False def str_to_number(input_val): if isinstance(input_val, str): if " " not in input_val: if isdatetime(input_val): return parse(input_val) elif isinteger(input_val): if not (len(input_val) > 1 and input_val[:1] == "0"): return int(input_val) elif isfloat(input_val): if not (input_val.find(".") > 2 and input_val[:1] == "0"): return float(input_val) return input_val def csv_from_excel(excel_content, delimiter, quote): wb = xlrd.open_workbook(file_contents=excel_content) sh = wb.sheet_by_index(0) content = StringIO() quoting = csv.QUOTE_ALL if not quote: quoting = csv.QUOTE_NONE if delimiter == " " and quoting == csv.QUOTE_NONE: quoting = csv.QUOTE_MINIMAL wr = csv.writer(content, delimiter=delimiter, quoting=quoting) for rownum in range(sh.nrows): row = [] for x in sh.row_values(rownum): if quoting == csv.QUOTE_NONE and delimiter in x: raise ValidationError( _( "Template with CSV Quoting = False, data must not " 'contain the same char as delimiter -> "%s"' ) % delimiter ) row.append(x) wr.writerow(row) content.seek(0) # Set index to 0, and start reading out_file = content.getvalue().encode("utf-8") return out_file def pos2idx(pos): match = re.match(r"([a-z]+)([0-9]+)", pos, re.I) if not match: raise ValidationError(_("Position %s is not valid") % (pos,)) col, row = match.groups() col_num = 0 for c in col: if c in string.ascii_letters: col_num = col_num * 26 + (ord(c.upper()) - ord("A")) + 1 return (int(row) - 1, col_num - 1) def _get_cell_value(cell, field_type=False): """If Odoo's field type is known, convert to valid string for import, if not know, just get value as is""" value = False datemode = 0 # From book.datemode, but we fix it for simplicity if field_type in ["date", "datetime"]: ctype = xlrd.sheet.ctype_text.get(cell.ctype, "unknown type") if ctype in ("xldate", "number"): is_datetime = cell.value % 1 != 0.0 time_tuple = xlrd.xldate_as_tuple(cell.value, datemode) date = dt(*time_tuple) value = ( date.strftime("%Y-%m-%d %H:%M:%S") if is_datetime else date.strftime("%Y-%m-%d") ) else: value = cell.value elif field_type in ["integer", "float"]: value_str = str(cell.value).strip().replace(",", "") if len(value_str) == 0: value = "" elif value_str.replace(".", "", 1).isdigit(): # Is number if field_type == "integer": value = int(float(value_str)) elif field_type == "float": value = float(value_str) else: # Is string, no conversion value = value_str elif field_type in ["many2one"]: # If number, change to string if isinstance(cell.value, (int, float, complex)): value = str(cell.value) else: value = cell.value else: # text, char value = cell.value # If string, cleanup if isinstance(value, str): if value[-2:] == ".0": value = value[:-2] # Except boolean, when no value, we should return as '' if field_type not in ["boolean"]: if not value: value = "" return value def _add_column(column_name, column_value, file_txt): i = 0 txt_lines = [] for line in file_txt.split("\n"): if line and i == 0: line = '"' + str(column_name) + '",' + line elif line: line = '"' + str(column_value) + '",' + line txt_lines.append(line) i += 1 file_txt = "\n".join(txt_lines) return file_txt def _add_id_column(file_txt): i = 0 txt_lines = [] for line in file_txt.split("\n"): if line and i == 0: line = '"id",' + line elif line: line = "{}.{}".format("xls", uuid.uuid4()) + "," + line txt_lines.append(line) i += 1 file_txt = "\n".join(txt_lines) return file_txt