From 842137a4731da27666d9bb8515789a127b8f2ce5 Mon Sep 17 00:00:00 2001 From: david Date: Fri, 17 Sep 2021 17:30:47 +0200 Subject: [PATCH] [IMP][FIX] report_xlsx: handle duplicate name exceptions We want to avoid duplicated sheet names exceptions the same following the same philosophy that Odoo implements overriding the main library to avoid the 31 characters limit triming the strings before sending them to the library. In some cases, there's not much control over this as the reports send automated data and the potential exception is hidden underneath making it hard to debug the original issue. Even so, different names can become the same one as their strings are trimmed to those 31 character limit. This way, once we come across with a duplicated, we set that final 3 characters with a sequence that we evaluate on the fly. So for instance: - 'Sheet name' will be 'Sheet name~01' - The next 'Sheet name' will try to rename to 'Sheet name~01' as well and then that will give us 'Sheet name~02'. - And the next 'Sheet name' will try to rename to 'Sheet name~01' and then to 'Sheet name~02' and finally it will be able to 'Sheet name~03'. - An so on as many times as duplicated sheet names come to the workbook up to 100 for each sheet name. We set such limit as we don't want to truncate the strings too much and keeping in mind that this issue don't usually ocurrs. TT31938 --- report_xlsx/report/report_abstract_xlsx.py | 51 ++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/report_xlsx/report/report_abstract_xlsx.py b/report_xlsx/report/report_abstract_xlsx.py index 244d88dc..9190dd5b 100644 --- a/report_xlsx/report/report_abstract_xlsx.py +++ b/report_xlsx/report/report_abstract_xlsx.py @@ -2,6 +2,7 @@ # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). import logging +import re from io import BytesIO from odoo import models @@ -10,6 +11,56 @@ _logger = logging.getLogger(__name__) try: import xlsxwriter + + class PatchedXlsxWorkbook(xlsxwriter.Workbook): + def _check_sheetname(self, sheetname, is_chartsheet=False): + """We want to avoid duplicated sheet names exceptions the same following + the same philosophy that Odoo implements overriding the main library + to avoid the 31 characters limit triming the strings before sending them + to the library. + + In some cases, there's not much control over this as the reports send + automated data and the potential exception is hidden underneath making it + hard to debug the original issue. Even so, different names can become the + same one as their strings are trimmed to those 31 character limit. + + This way, once we come across with a duplicated, we set that final 3 + characters with a sequence that we evaluate on the fly. So for instance: + + - 'Sheet name' will be 'Sheet name~01' + - The next 'Sheet name' will try to rename to 'Sheet name~01' as well and + then that will give us 'Sheet name~02'. + - And the next 'Sheet name' will try to rename to 'Sheet name~01' and then + to 'Sheet name~02' and finally it will be able to 'Sheet name~03'. + - An so on as many times as duplicated sheet names come to the workbook up + to 100 for each sheet name. We set such limit as we don't want to truncate + the strings too much and keeping in mind that this issue don't usually + ocurrs. + """ + try: + return super()._check_sheetname(sheetname, is_chartsheet=is_chartsheet) + except xlsxwriter.exceptions.DuplicateWorksheetName: + pattern = re.compile(r"~[0-9]{2}$") + duplicated_secuence = ( + re.search(pattern, sheetname) and int(sheetname[-2:]) or 0 + ) + # Only up to 100 duplicates + deduplicated_secuence = "~{:02d}".format(duplicated_secuence + 1) + if duplicated_secuence > 99: + raise xlsxwriter.exceptions.DuplicateWorksheetName + if duplicated_secuence: + sheetname = re.sub(pattern, deduplicated_secuence, sheetname) + elif len(sheetname) <= 28: + sheetname += deduplicated_secuence + else: + sheetname = sheetname[:28] + deduplicated_secuence + # Refeed the method until we get an unduplicated name + return self._check_sheetname(sheetname, is_chartsheet=is_chartsheet) + + # "Short string" + + xlsxwriter.Workbook = PatchedXlsxWorkbook + except ImportError: _logger.debug("Can not import xlsxwriter`.")