diff --git a/partner_programs/serializers.py b/partner_programs/serializers.py index 3bd6a09a..43e2806f 100644 --- a/partner_programs/serializers.py +++ b/partner_programs/serializers.py @@ -80,7 +80,9 @@ class PartnerProgramForMemberSerializer(PartnerProgramBaseSerializerMixin): views_count = serializers.SerializerMethodField(method_name="count_views") links = serializers.SerializerMethodField(method_name="get_links") - is_user_manager = serializers.SerializerMethodField(method_name="get_is_user_manager") + is_user_manager = serializers.SerializerMethodField( + method_name="get_is_user_manager" + ) def count_views(self, program): return get_views_count(program) diff --git a/partner_programs/services.py b/partner_programs/services.py index b10e23c4..1aac24fc 100644 --- a/partner_programs/services.py +++ b/partner_programs/services.py @@ -1,6 +1,8 @@ import logging from collections import OrderedDict +from openpyxl.cell.cell import ILLEGAL_CHARACTERS_RE + from partner_programs.models import PartnerProgramUserProfile from project_rates.models import Criteria, ProjectScore @@ -132,6 +134,31 @@ def get_project_scores_info(self) -> dict[str, str]: ("team_size", "Количество человек в команде"), ("leader_full_name", "Имя фамилия лидера"), ] +EXCEL_CELL_MAX = 32767 # лимит символов в ячейке Excel + + +def sanitize_excel_value(value): + """ + Приводит значение к безопасному для openpyxl виду: + - None -> "" + - для строк: вычищает запрещённые символы, нормализует переносы строк, + и обрезает до лимита Excel (32767). + - для чисел/булевых оставляет как есть. + """ + if value is None: + return "" + + if isinstance(value, (int, float, bool)): + return value + + text = str(value) + text = text.replace("\r\n", "\n").replace("\r", "\n") + text = ILLEGAL_CHARACTERS_RE.sub(" ", text) + + if len(text) > EXCEL_CELL_MAX: + text = text[: EXCEL_CELL_MAX - 3] + "..." + + return text def _leader_full_name(user): diff --git a/partner_programs/views.py b/partner_programs/views.py index e985ff1c..2783bb14 100644 --- a/partner_programs/views.py +++ b/partner_programs/views.py @@ -45,6 +45,7 @@ BASE_COLUMNS, build_program_field_columns, row_dict_for_link, + sanitize_excel_value, ) from partner_programs.utils import filter_program_projects_by_field_name from projects.models import Project @@ -518,7 +519,9 @@ def get(self, request, pk: int): extra_field_keys_order=extra_keys_order, row_number=row_number, ) - ws.append([row_dict.get(key, "") for key, _ in header_pairs]) + raw_values = [row_dict.get(key, "") for key, _ in header_pairs] + safe_values = [sanitize_excel_value(v) for v in raw_values] + ws.append(safe_values) bio = io.BytesIO() wb.save(bio)