diff --git a/src/apps/core/excel.py b/src/apps/core/excel.py index 4645214..c725353 100644 --- a/src/apps/core/excel.py +++ b/src/apps/core/excel.py @@ -363,6 +363,8 @@ class BaseExcelParser(ABC, Generic[T]): self._workbook = openpyxl.load_workbook(content, read_only=True, data_only=True) self._sheet = self._workbook.active + if self._sheet.max_row is None or self._sheet.max_column is None: + self._sheet.calculate_dimension(force=True) def _parse_row(self, row_num: int) -> RowData | None: """Парсит одну строку Excel.""" diff --git a/tests/apps/core/test_excel.py b/tests/apps/core/test_excel.py index 36423ba..398e2f8 100644 --- a/tests/apps/core/test_excel.py +++ b/tests/apps/core/test_excel.py @@ -1,6 +1,9 @@ """Tests for core excel parser.""" +import re +from io import BytesIO from unittest.mock import MagicMock +from zipfile import ZIP_DEFLATED, ZipFile from apps.core.excel import ( BaseExcelParser, @@ -15,6 +18,7 @@ from apps.core.excel import ( validate_okpo, ) from django.test import TestCase +from openpyxl import Workbook class ValidatorsTest(TestCase): @@ -168,3 +172,48 @@ class BaseExcelParserTest(TestCase): self.assertEqual(len(mappings), 2) self.assertEqual(mappings[0].field_name, "inn") + + def test_parse_recovers_missing_read_only_worksheet_dimension(self): + workbook = Workbook() + sheet = workbook.active + sheet.append(["Организация", "ОКПО", "ОГРН", "ИНН"]) + sheet.append( + ["Тестовая организация", "12345678", "1234567890123", "1234567890"] + ) + source = BytesIO() + workbook.save(source) + workbook.close() + + source.seek(0) + without_dimension = BytesIO() + with ZipFile(source, "r") as input_archive, ZipFile( + without_dimension, + "w", + compression=ZIP_DEFLATED, + ) as output_archive: + for item in input_archive.infolist(): + content = input_archive.read(item.filename) + if item.filename == "xl/worksheets/sheet1.xml": + content = re.sub(rb"]*/>", b"", content, count=1) + output_archive.writestr(item, content) + without_dimension.seek(0) + + created_rows = [] + + class TestParser(BaseExcelParser): + def get_column_mappings(self): + return [] + + def get_next_batch_id(self) -> int: + return 1 + + def create_record(self, row_data, batch_id): + created_rows.append((row_data, batch_id)) + return MagicMock() + + result = TestParser().parse(without_dimension) + + self.assertEqual(result.loaded_count, 1) + self.assertEqual(result.skipped_count, 0) + self.assertEqual(created_rows[0][0].organization_name, "Тестовая организация") + self.assertEqual(created_rows[0][1], 1)