indent-export: отсупы в экспорте по глубине вложенности #196

Merged
tsygankoviva merged 1 commits from indent-export into test 2026-10-21 15:48:44 +03:00
2 changed files with 37 additions and 7 deletions

View File

@ -1,6 +1,7 @@
from __future__ import annotations
import abc
from copy import copy
from collections import defaultdict
from typing import Any, Callable
@ -255,6 +256,12 @@ class DepthSheetWriter(AbstractWriter):
normalized = self.row_normalizer(raw_data)
values = [self.value_serializer(normalized.get(key)) for key in self.columns]
worksheet.append(values)
for column, key in enumerate(self.columns, start=1):
if key in ("name", "header.name"):
cell = worksheet.cell(row=next_row, column=column)
alignment = copy(cell.alignment)
alignment.indent = outline_level
cell.alignment = alignment
self._track_row_widths(column_widths, values)
if len(row) > 3:
self._set_color_to_row(ws=worksheet, row=next_row, item_type=row[0])
@ -492,13 +499,14 @@ class XlsxDepthSheetWriter(AbstractWriter):
return current_column
def _row_format(self, palette: str, item_type: str):
cache_key = (palette, item_type)
def _row_format(self, palette: str, item_type: str, indent: int = 0):
cache_key = (palette, item_type, indent)
if cache_key not in self._format_cache:
self._format_cache[cache_key] = self.workbook.add_format(
{
"bg_color": f"#{PALETTE[palette][item_type]}",
"pattern": 1,
"indent": indent,
}
)
return self._format_cache[cache_key]
@ -531,6 +539,11 @@ class XlsxDepthSheetWriter(AbstractWriter):
values = [self.value_serializer(normalized.get(key)) for key in self.columns]
if column_formats is not None:
worksheet.write_row(output_row, 0, values)
for column, key in enumerate(self.columns):
if key in ("name", "header.name") and outline_level:
properties = dict(self.column_format_resolver(key, column) or {})
properties["indent"] = outline_level
worksheet.write(output_row, column, values[column], self._resolved_format(properties))
if self.fixed_column_width is None:
self._track_row_widths(column_widths, values)
output_row += 1
@ -540,12 +553,14 @@ class XlsxDepthSheetWriter(AbstractWriter):
for column, value in enumerate(values):
cell_format = None
column_key = self.columns[column]
indent = outline_level if column_key in ("name", "header.name") else 0
resolved_format = (
self.row_format_resolver(row, column_key, column)
if self.row_format_resolver
else None
)
if resolved_format:
resolved_format = {**resolved_format, "indent": indent}
cell_format = self._resolved_format(resolved_format)
palette = self.palette.get(column_key)
if (
@ -553,7 +568,9 @@ class XlsxDepthSheetWriter(AbstractWriter):
and palette
and item_type not in (None, "SUB_ITEM", "INPUT")
):
cell_format = self._row_format(palette, item_type)
cell_format = self._row_format(palette, item_type, indent)
if cell_format is None and indent:
cell_format = self._resolved_format({"indent": indent})
worksheet.write(output_row, column, value, cell_format)
if self.fixed_column_width is None:
self._track_row_widths(column_widths, values)

View File

@ -17,20 +17,27 @@ from src.services.export_service.export_writers import (
)
@pytest.mark.parametrize("backend", ["openpyxl", "xlsxwriter", "xlsxwriter_columns"])
def test_depth_writer_preserves_collapsible_row_hierarchy(backend):
@pytest.mark.parametrize("backend", ["openpyxl", "xlsxwriter", "xlsxwriter_columns", "xlsxwriter_rows", "xlsxwriter_palette"])
@pytest.mark.parametrize("name_key", ["name", "header.name"])
def test_depth_writer_preserves_collapsible_row_hierarchy(backend, name_key):
stream = BytesIO()
workbook = Workbook() if backend == "openpyxl" else XlsxWorkbook(stream)
worksheet = workbook.active if backend == "openpyxl" else workbook.add_worksheet()
options = {}
if backend == "xlsxwriter_columns":
options["column_format_resolver"] = lambda key, column: {"bold": True}
if backend == "xlsxwriter_rows":
options["row_format_resolver"] = lambda row, key, column: {"bold": True}
if backend == "xlsxwriter_palette":
options["colors_config"] = {"header": {"palette": "green"}}
writer_class = DepthSheetWriter if backend == "openpyxl" else XlsxDepthSheetWriter
if backend != "openpyxl":
options["workbook"] = workbook
writer = writer_class(
columns_dict={"header": {"name": "Header", "key": "", "children": {
"name": {"name": "Name"},
"name": {"name": "Name", "key": name_key},
"code": {"name": "Code"},
"contract.name": {"name": "Contract name"},
}}},
row_normalizer=lambda row: row,
value_serializer=lambda value: value,
@ -38,7 +45,7 @@ def test_depth_writer_preserves_collapsible_row_hierarchy(backend):
)
# Missing GROUP/SUB_ITEM levels and misleading numeric depths match frontend cases.
types = ["ROOT", "ITEM", "INPUT", "META", "INPUT", "ITEM", "SUB_ITEM", "INPUT", "ROOT"]
writer.write(worksheet, [(kind, 42, index, {"name": str(index)}) for index, kind in enumerate(types)])
writer.write(worksheet, [(kind, 42, index, {name_key: str(index), "code": index, "contract.name": "Contract"}) for index, kind in enumerate(types)])
if backend == "openpyxl":
workbook.save(stream)
else:
@ -49,6 +56,12 @@ def test_depth_writer_preserves_collapsible_row_hierarchy(backend):
assert [sheet.row_dimensions[index].outlineLevel for index in range(3, 11)] == [0, 1, 2, 2, 1, 2, 3, 0]
assert [sheet.cell(index, 1).value for index in range(3, 11)] == ["0", "1", "2", "4", "5", "6", "7", "8"]
assert all(not sheet.row_dimensions[index].hidden for index in range(3, 11))
assert [sheet.cell(index, 1).alignment.indent for index in range(3, 11)] == [0, 1, 2, 2, 1, 2, 3, 0]
assert all(sheet.cell(index, column).alignment.indent == 0 for index in range(3, 11) for column in (2, 3))
if backend in ("xlsxwriter_columns", "xlsxwriter_rows"):
assert sheet.cell(4, 1).font.bold is True
if backend == "xlsxwriter_palette":
assert sheet.cell(4, 1).fill.fgColor == sheet.cell(4, 2).fill.fgColor
def test_tabular_sheet_writer_writes_headers_and_rows():