Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[FIX] xlsx export: always show error column #82

Open
wants to merge 5 commits into
base: 14.0
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions oca_dependencies.txt
Original file line number Diff line number Diff line change
@@ -1 +1,4 @@
# See https://github.com/OCA/odoo-community.org/blob/master/website/Contribution/CONTRIBUTING.rst#oca_dependencies-txt
server-ux
server-tools
web
50 changes: 23 additions & 27 deletions pattern_import_export/models/ir_exports_line.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,7 @@
from odoo import _, api, fields, models
from odoo.exceptions import ValidationError

from odoo.addons.base_jsonify.models.ir_export import (
convert_dict,
partition,
update_dict,
)
from odoo.addons.base_jsonify.models.ir_export import convert_dict, update_dict

from .common import COLUMN_X2M_SEPARATOR, IDENTIFIER_SUFFIX

Expand Down Expand Up @@ -249,26 +245,26 @@ def _get_tab_name(self):
name = name[0:28] + "..."
return name

def _get_dict_parser_for_pattern(self):
parser = OrderedDict()
for rec in self:
names = rec.name.split("/")
options = {
"resolver": rec.resolver_id,
"function": rec.instance_method_name,
}
update_dict(parser, names, options)
if rec.sub_pattern_config_id:
last_item = parser
last_field = names[0]
for field in names[:-1]:
last_item = last_item[field]
last_field = field
sub_pattern_fields = rec.sub_pattern_config_id.export_fields
last_item[
last_field
] = sub_pattern_fields._get_dict_parser_for_pattern()
return (False, parser)

def _get_json_parser_for_pattern(self):
parser = {}
lang_to_lines = partition(self, lambda l: l.lang_id.code)
lang_parsers = {}
for lang in lang_to_lines:
dict_parser = OrderedDict()
for line in lang_to_lines[lang]:
names = line.name.split("/")
if line.target:
names = line.target.split("/")
function = line.instance_method_name
options = {"resolver": line.resolver_id, "function": function}
update_dict(dict_parser, names, options)
lang_parsers[lang] = convert_dict(dict_parser)
if list(lang_parsers.keys()) == [False]:
parser["fields"] = lang_parsers[False]
else:
parser["langs"] = lang_parsers
if self.export_id.global_resolver_id:
parser["resolver"] = self.global_resolver_id
if self.export_id.language_agnostic:
parser["language_agnostic"] = self.language_agnostic
return parser
return {"fields": convert_dict(self._get_dict_parser_for_pattern()[1])}
184 changes: 97 additions & 87 deletions pattern_import_export/tests/test_pattern_export.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,40 +12,111 @@ def _get_header(self, pattern_config, use_description=False):
def _get_data(self, pattern_config, records):
raise NotImplementedError

def _get_expected_results(self):
result = {
"expected_header_1": [
".id",
"name",
"street",
"country_id|code",
"category_id|1|name",
],
"expected_header_1_desc": [
"ID",
"Name",
"Street",
"Country|Country Code",
"Tags|1|Tag Name",
],
"expected_header_2": [".id", "name", "company_ids|1|name"],
"expected_header_3": [
".id",
"name",
"company_ids|1|name",
"company_ids|2|name",
"company_ids|3|name",
"company_ids|4|name",
"company_ids|5|name",
],
"expected_header_4": [
".id",
"name",
"child_ids|1|.id",
"child_ids|1|name",
"child_ids|1|street",
"child_ids|1|country_id|code",
"child_ids|1|category_id|1|name",
"child_ids|2|.id",
"child_ids|2|name",
"child_ids|2|street",
"child_ids|2|country_id|code",
"child_ids|2|category_id|1|name",
"child_ids|3|.id",
"child_ids|3|name",
"child_ids|3|street",
"child_ids|3|country_id|code",
"child_ids|3|category_id|1|name",
"country_id|code",
],
"expected_header_5": [
".id",
"name",
"child_ids|1|.id",
"child_ids|1|name",
"child_ids|1|street",
"child_ids|1|country_id|code",
"child_ids|1|category_id|1|name",
"child_ids|1|category_id|2|name",
"child_ids|1|category_id|3|name",
"child_ids|1|category_id|4|name",
"child_ids|1|category_id|5|name",
"child_ids|2|.id",
"child_ids|2|name",
"child_ids|2|street",
"child_ids|2|country_id|code",
"child_ids|2|category_id|1|name",
"child_ids|2|category_id|2|name",
"child_ids|2|category_id|3|name",
"child_ids|2|category_id|4|name",
"child_ids|2|category_id|5|name",
"child_ids|3|.id",
"child_ids|3|name",
"child_ids|3|street",
"child_ids|3|country_id|code",
"child_ids|3|category_id|1|name",
"child_ids|3|category_id|2|name",
"child_ids|3|category_id|3|name",
"child_ids|3|category_id|4|name",
"child_ids|3|category_id|5|name",
"country_id|code",
],
}
return result

def _assert_result_expected_equal(self, expected, actual):
self.assertDictEqual(expected, actual)

def test_get_header1(self):
"""
Ensure the header is correctly generated
@return:
"""
headers = self._get_header(self.pattern_config)
expected_header = [
".id",
"name",
"street",
"country_id|code",
"category_id|1|name",
]
self.assertEqual(expected_header, headers)
self.assertEqual(self._get_expected_results()["expected_header_1"], headers)

def test_get_header1_descriptive(self):
headers = self._get_header(self.pattern_config, use_description=True)
expected_header = [
"ID",
"Name",
"Street",
"Country|Country Code",
"Tags|1|Tag Name",
]
self.assertEqual(expected_header, headers)
self.assertEqual(
self._get_expected_results()["expected_header_1_desc"], headers
)

def test_get_header2(self):
"""
Ensure the header is correctly generated in case of M2M with 1 occurrence
@return:
"""
headers = self._get_header(self.pattern_config_m2m)
expected_header = [".id", "name", "company_ids|1|name"]
self.assertEqual(expected_header, headers)
self.assertEqual(self._get_expected_results()["expected_header_2"], headers)

def test_get_header3(self):
"""
Expand All @@ -55,16 +126,7 @@ def test_get_header3(self):
export_fields_m2m = self.env.ref("pattern_import_export.demo_export_m2m_line_3")
export_fields_m2m.write({"number_occurence": 5})
headers = self._get_header(self.pattern_config_m2m)
expected_header = [
".id",
"name",
"company_ids|1|name",
"company_ids|2|name",
"company_ids|3|name",
"company_ids|4|name",
"company_ids|5|name",
]
self.assertEqual(expected_header, headers)
self.assertEqual(self._get_expected_results()["expected_header_3"], headers)

def test_get_header4(self):
"""
Expand All @@ -73,27 +135,7 @@ def test_get_header4(self):
@return:
"""
headers = self._get_header(self.pattern_config_o2m)
expected_header = [
".id",
"name",
"child_ids|1|.id",
"child_ids|1|name",
"child_ids|1|street",
"child_ids|1|country_id|code",
"child_ids|1|category_id|1|name",
"child_ids|2|.id",
"child_ids|2|name",
"child_ids|2|street",
"child_ids|2|country_id|code",
"child_ids|2|category_id|1|name",
"child_ids|3|.id",
"child_ids|3|name",
"child_ids|3|street",
"child_ids|3|country_id|code",
"child_ids|3|category_id|1|name",
"country_id|code",
]
self.assertEqual(expected_header, headers)
self.assertEqual(self._get_expected_results()["expected_header_4"], headers)

def test_get_header5(self):
"""
Expand All @@ -105,39 +147,7 @@ def test_get_header5(self):
export_fields_m2m = self.env.ref("pattern_import_export.demo_export_line_5")
export_fields_m2m.write({"number_occurence": 5})
headers = self._get_header(self.pattern_config_o2m)
expected_header = [
".id",
"name",
"child_ids|1|.id",
"child_ids|1|name",
"child_ids|1|street",
"child_ids|1|country_id|code",
"child_ids|1|category_id|1|name",
"child_ids|1|category_id|2|name",
"child_ids|1|category_id|3|name",
"child_ids|1|category_id|4|name",
"child_ids|1|category_id|5|name",
"child_ids|2|.id",
"child_ids|2|name",
"child_ids|2|street",
"child_ids|2|country_id|code",
"child_ids|2|category_id|1|name",
"child_ids|2|category_id|2|name",
"child_ids|2|category_id|3|name",
"child_ids|2|category_id|4|name",
"child_ids|2|category_id|5|name",
"child_ids|3|.id",
"child_ids|3|name",
"child_ids|3|street",
"child_ids|3|country_id|code",
"child_ids|3|category_id|1|name",
"child_ids|3|category_id|2|name",
"child_ids|3|category_id|3|name",
"child_ids|3|category_id|4|name",
"child_ids|3|category_id|5|name",
"country_id|code",
]
self.assertEqual(expected_header, headers)
self.assertEqual(self._get_expected_results()["expected_header_5"], headers)

def test_get_data_to_export1(self):
"""
Expand Down Expand Up @@ -169,7 +179,7 @@ def test_get_data_to_export1(self):
},
]
for result, expected_result in zip(results, expected_results):
self.assertDictEqual(expected_result, result)
self._assert_result_expected_equal(expected_result, result)

def test_get_data_to_export2(self):
"""
Expand All @@ -186,7 +196,7 @@ def test_get_data_to_export2(self):
]
results = self._get_data(self.pattern_config_m2m, self.env.user)
for result, expected_result in zip(results, expected_results):
self.assertDictEqual(expected_result, result)
self._assert_result_expected_equal(expected_result, result)

def test_get_data_to_export3(self):
"""
Expand All @@ -209,7 +219,7 @@ def test_get_data_to_export3(self):
]
results = self._get_data(self.pattern_config_m2m, self.env.user)
for result, expected_result in zip(results, expected_results):
self.assertDictEqual(expected_result, result)
self._assert_result_expected_equal(expected_result, result)

def test_get_data_to_export4(self):
"""
Expand Down Expand Up @@ -284,7 +294,7 @@ def test_get_data_to_export4(self):
]
results = self._get_data(self.pattern_config_o2m, self.partners)
for result, expected_result in zip(results, expected_results):
self.assertDictEqual(expected_result, result)
self._assert_result_expected_equal(expected_result, result)

def test_get_data_to_export5(self):
"""
Expand Down Expand Up @@ -361,7 +371,7 @@ def test_get_data_to_export5(self):

results = self._get_data(self.pattern_config_o2m, self.partners)
for result, expected_result in zip(results, expected_results):
self.assertDictEqual(expected_result, result)
self._assert_result_expected_equal(expected_result, result)

def test_get_data_to_export_is_key1(self):
"""
Expand Down
23 changes: 15 additions & 8 deletions pattern_import_export_xlsx/models/pattern_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,10 @@
from openpyxl.utils import get_column_letter, quote_sheetname
from openpyxl.worksheet.datavalidation import DataValidation

from odoo import fields, models
from odoo import _, fields, models

EXTRA_LINE_NUMBER = 1000
OFFSET_FOR_ERROR_COL = 1


class PatternConfig(models.Model):
Expand Down Expand Up @@ -40,15 +41,23 @@ def _build_main_sheet_structure(self, book):
"""
main_sheet = book["Sheet"]
main_sheet.title = self.name
# create error column here instead of
# pattern.file.xlsx_refresh_error_col for perf
# (only 1 cell affected)
main_sheet.cell(row=1, column=1, value=_("#Error"))
if self.use_description:
for col, header in enumerate(
self._get_header(use_description=True), start=1
self._get_header(use_description=True), start=1 + OFFSET_FOR_ERROR_COL
):
main_sheet.cell(row=1, column=col, value=header)
for col, header in enumerate(self._get_header(), start=1):
for col, header in enumerate(
self._get_header(), start=1 + OFFSET_FOR_ERROR_COL
):
main_sheet.cell(row=2, column=col, value=header)
else:
for col, header in enumerate(self._get_header(), start=1):
for col, header in enumerate(
self._get_header(), start=1 + OFFSET_FOR_ERROR_COL
):
main_sheet.cell(row=1, column=col, value=header)
return main_sheet

Expand All @@ -60,7 +69,7 @@ def _populate_main_sheet_rows(self, main_sheet, records):
for row, values in enumerate(
self._get_data_to_export(records), start=self.row_start_records
):
for col, header in enumerate(headers, start=1):
for col, header in enumerate(headers, start=1 + OFFSET_FOR_ERROR_COL):
main_sheet.cell(row=row, column=col, value=values.get(header, ""))

def _create_tabs(self, book, tabs):
Expand All @@ -79,8 +88,6 @@ def _create_validators(self, main_sheet, records, tabs):
apply validation to main sheet"""
main_sheet_length = len(records.ids) + EXTRA_LINE_NUMBER
for tab_name, tab in tabs.items():
# TODO support arbitrary columns/attributes instead of
# only name
col_letter_src = get_column_letter(1)
range_src = "${}$2:${}${}".format(
col_letter_src,
Expand All @@ -90,7 +97,7 @@ def _create_validators(self, main_sheet, records, tabs):
formula_range_src = "=" + quote_sheetname(tab_name) + "!" + range_src
validation = DataValidation(type="list", formula1=formula_range_src)
for idx_col in tab["idx_col_validator"]:
col_letter_dst = get_column_letter(idx_col)
col_letter_dst = get_column_letter(idx_col + OFFSET_FOR_ERROR_COL)
range_dst = "${}${}:${}${}".format(
col_letter_dst,
str(self.row_start_records),
Expand Down
Loading