feat(forms): unify F2-F6 upload contracts and add shared serializers

This commit is contained in:
2026-04-14 10:56:29 +02:00
parent ec913888a4
commit 903312670c
13 changed files with 606 additions and 262 deletions

View File

@@ -0,0 +1,216 @@
"""Shared upload serializers and response helpers for form upload endpoints."""
from __future__ import annotations
from datetime import datetime
from typing import Any
from uuid import uuid4
from django.utils import timezone
from rest_framework import serializers, status
from rest_framework.response import Response
MAX_UPLOAD_SIZE_BYTES = 50 * 1024 * 1024
ALLOWED_UPLOAD_EXTENSIONS = (".xlsx", ".xls")
_ROMAN_NUMBERS = {
1: "I",
2: "II",
3: "III",
4: "IV",
}
def _normalize_extension(name: str) -> str:
return name.rsplit(".", 1)[-1].lower() if "." in name else ""
def _validate_uploaded_file(value: Any) -> None:
if not hasattr(value, "name") or not value.name:
raise serializers.ValidationError("Файл не выбран")
extension = "." + _normalize_extension(value.name)
if extension not in ALLOWED_UPLOAD_EXTENSIONS:
raise serializers.ValidationError("Неподдерживаемый формат файла")
if value.size > MAX_UPLOAD_SIZE_BYTES:
raise serializers.ValidationError("Размер файла превышает 50MB")
def report_quarter_display(report_year: int, report_quarter: int) -> str:
return f"{_ROMAN_NUMBERS.get(report_quarter, report_quarter)} квартал {report_year}"
def report_half_year_display(report_year: int, report_half_year: int) -> str:
return f"{_ROMAN_NUMBERS.get(report_half_year, report_half_year)} полугодие {report_year}"
def report_annual_display(report_year: int) -> str:
return f"{report_year} год"
def build_upload_success_payload(
*,
form: str,
report_year: int,
status: str,
report_quarter: int | None = None,
report_half_year: int | None = None,
result: dict[str, Any] | None = None,
job_id: str | None = None,
created_at: datetime | None = None,
) -> dict[str, Any]:
created = created_at or timezone.now()
payload: dict[str, Any] = {
"upload_id": str(uuid4()),
"form": form,
"report_year": report_year,
"status": status,
"created_at": created.isoformat(),
}
if report_half_year is not None:
payload["report_half_year"] = report_half_year
payload["report_period_display"] = report_half_year_display(
report_year=report_year,
report_half_year=report_half_year,
)
elif report_quarter is not None:
payload["report_quarter"] = report_quarter
payload["report_period_display"] = report_quarter_display(
report_year=report_year,
report_quarter=report_quarter,
)
else:
payload["report_period_display"] = report_annual_display(report_year=report_year)
if result is not None:
payload["result"] = result
if job_id is not None:
payload["job_id"] = job_id
return payload
def build_upload_validation_payload(errors: dict[str, Any]) -> dict[str, Any]:
details: list[dict[str, Any]] = []
def _collect(field: str, value: Any) -> None:
if isinstance(value, dict):
for nested_field, nested_value in value.items():
nested_name = f"{field}.{nested_field}" if field else nested_field
_collect(nested_name, nested_value)
return
if isinstance(value, list):
for item in value:
_collect(field, item)
return
code = getattr(value, "code", None) or "invalid"
details.append(
{
"field": field,
"code": code,
"message": str(value),
}
)
for field, value in errors.items():
target_field = "non_field_errors" if field == "non_field_errors" else field
_collect(target_field, value)
return {
"error_code": "validation_error",
"error_message": "Validation failed",
"details": details,
}
def build_upload_error_payload(
*,
error_code: str,
error_message: str,
details: list[dict[str, Any]] | None = None,
) -> dict[str, Any]:
return {
"error_code": error_code,
"error_message": error_message,
"details": details or [],
}
def build_upload_error_response(
*,
error_code: str,
error_message: str,
details: list[dict[str, Any]] | None = None,
status_code: int = status.HTTP_400_BAD_REQUEST,
) -> Response:
return Response(
build_upload_error_payload(
error_code=error_code,
error_message=error_message,
details=details,
),
status=status_code,
)
def build_upload_validation_response(errors: dict[str, Any]) -> Response:
return Response(
build_upload_validation_payload(errors),
status=status.HTTP_400_BAD_REQUEST,
)
class BaseUploadSerializer(serializers.Serializer):
file = serializers.FileField(help_text="Excel файл (.xlsx, .xls)")
report_year = serializers.IntegerField(min_value=2000, help_text="Отчетный год")
def validate_file(self, value):
_validate_uploaded_file(value)
return value
class UploadQuarterSerializer(BaseUploadSerializer):
report_quarter = serializers.IntegerField(
min_value=1,
max_value=4,
required=True,
help_text="Отчетный квартал от 1 до 4",
)
class UploadHalfYearSerializer(BaseUploadSerializer):
report_half_year = serializers.IntegerField(
min_value=1,
max_value=2,
required=True,
help_text="Полугодие отчета: 1 или 2",
)
class UploadAnnualSerializer(BaseUploadSerializer):
"""Upload payload without period quarter (годовая форма)."""
class FieldErrorSerializer(serializers.Serializer):
field = serializers.CharField()
message = serializers.CharField()
class RowValidationErrorSerializer(serializers.Serializer):
row = serializers.IntegerField()
inn = serializers.CharField(allow_null=True)
kpp = serializers.CharField(allow_null=True)
organization_name = serializers.CharField(allow_null=True)
errors = FieldErrorSerializer(many=True)
class UploadParseResultSerializer(serializers.Serializer):
batch_id = serializers.IntegerField()
loaded_count = serializers.IntegerField()
skipped_count = serializers.IntegerField()
errors = RowValidationErrorSerializer(many=True)

View File

@@ -8,6 +8,11 @@ API формы Ф-2.
import logging
from apps.core.upload_contracts import (
build_upload_error_response,
build_upload_success_payload,
build_upload_validation_response,
)
from apps.core.viewsets import ReadOnlyViewSet
from apps.form_2.models import FormF2Record
from apps.form_2.serializers import (
@@ -42,7 +47,9 @@ class FormF2UploadView(APIView):
def post(self, request):
"""Загрузка и обработка файла."""
serializer = FormF2UploadSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
if not serializer.is_valid():
return build_upload_validation_response(serializer.errors)
file = serializer.validated_data["file"]
report_year = serializer.validated_data["report_year"]
@@ -59,11 +66,13 @@ class FormF2UploadView(APIView):
)
return Response(
{
"success": True,
"message": "Файл поставлен в очередь на обработку",
"task_id": task.id,
},
build_upload_success_payload(
form="f2",
report_year=report_year,
report_quarter=report_quarter,
status="queued",
job_id=task.id,
),
status=status.HTTP_202_ACCEPTED,
)
@@ -77,20 +86,21 @@ class FormF2UploadView(APIView):
result_serializer = FormF2ParseResultSerializer(result)
return Response(
{
"success": True,
"data": result_serializer.data,
},
build_upload_success_payload(
form="f2",
report_year=report_year,
report_quarter=report_quarter,
status="done",
result=result_serializer.data,
),
status=status.HTTP_200_OK,
)
except Exception as e:
logger.exception("Ошибка обработки файла Ф-2")
return Response(
{
"success": False,
"error": str(e),
},
status=status.HTTP_400_BAD_REQUEST,
return build_upload_error_response(
error_code="processing_error",
error_message=str(e),
status_code=status.HTTP_400_BAD_REQUEST,
)

View File

@@ -8,6 +8,10 @@
"""
from apps.form_2.models import FormF2Record
from apps.core.upload_contracts import (
UploadQuarterSerializer,
UploadParseResultSerializer,
)
from apps.organization.serializers import OrganizationSerializer
from rest_framework import serializers
@@ -51,52 +55,9 @@ class FormF2RecordListSerializer(serializers.ModelSerializer):
]
class FormF2UploadSerializer(serializers.Serializer):
"""Сериализатор загрузки файла формы Ф-2."""
file = serializers.FileField(help_text="Excel файл формы Ф-2 (.xlsx)")
report_year = serializers.IntegerField(min_value=2000, help_text="Отчетный год")
report_quarter = serializers.IntegerField(
min_value=1,
max_value=4,
required=False,
allow_null=True,
help_text="Отчетный квартал от 1 до 4. Пусто для годовой формы.",
)
def validate_file(self, value):
"""Валидация загруженного файла."""
if not value.name.endswith((".xlsx", ".xls")):
raise serializers.ValidationError(
"Неподдерживаемый формат файла. Используйте .xlsx или .xls"
)
# Ограничение размера: 50MB
if value.size > 50 * 1024 * 1024:
raise serializers.ValidationError("Размер файла превышает 50MB")
return value
class FormF2UploadSerializer(UploadQuarterSerializer):
"""Загрузка файла формы Ф-2 (квартальная отчетность)."""
class FieldErrorSerializer(serializers.Serializer):
"""Сериализатор ошибки поля."""
field = serializers.CharField()
message = serializers.CharField()
class RowValidationErrorSerializer(serializers.Serializer):
"""Сериализатор ошибки валидации строки."""
row = serializers.IntegerField()
inn = serializers.CharField(allow_null=True)
kpp = serializers.CharField(allow_null=True)
organization_name = serializers.CharField(allow_null=True)
errors = FieldErrorSerializer(many=True)
class FormF2ParseResultSerializer(serializers.Serializer):
class FormF2ParseResultSerializer(UploadParseResultSerializer):
"""Сериализатор результата парсинга."""
batch_id = serializers.IntegerField()
loaded_count = serializers.IntegerField()
skipped_count = serializers.IntegerField()
errors = RowValidationErrorSerializer(many=True)

View File

@@ -8,6 +8,11 @@ API формы Ф-3.
import logging
from apps.core.upload_contracts import (
build_upload_error_response,
build_upload_success_payload,
build_upload_validation_response,
)
from apps.core.viewsets import ReadOnlyViewSet
from apps.form_3.models import FormF3Record
from apps.form_3.serializers import (
@@ -41,7 +46,9 @@ class FormF3UploadView(APIView):
def post(self, request):
"""Загрузка и обработка файла."""
serializer = FormF3UploadSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
if not serializer.is_valid():
return build_upload_validation_response(serializer.errors)
file = serializer.validated_data["file"]
report_year = serializer.validated_data["report_year"]
@@ -57,11 +64,12 @@ class FormF3UploadView(APIView):
)
return Response(
{
"success": True,
"message": "Файл поставлен в очередь на обработку",
"task_id": task.id,
},
build_upload_success_payload(
form="f3",
report_year=report_year,
status="queued",
job_id=task.id,
),
status=status.HTTP_202_ACCEPTED,
)
@@ -74,20 +82,20 @@ class FormF3UploadView(APIView):
result_serializer = FormF3ParseResultSerializer(result)
return Response(
{
"success": True,
"data": result_serializer.data,
},
build_upload_success_payload(
form="f3",
report_year=report_year,
status="done",
result=result_serializer.data,
),
status=status.HTTP_200_OK,
)
except Exception as e:
logger.exception("Ошибка обработки файла Ф-3")
return Response(
{
"success": False,
"error": str(e),
},
status=status.HTTP_400_BAD_REQUEST,
return build_upload_error_response(
error_code="processing_error",
error_message=str(e),
status_code=status.HTTP_400_BAD_REQUEST,
)

View File

@@ -8,6 +8,10 @@
"""
from apps.form_3.models import FormF3Record
from apps.core.upload_contracts import (
UploadAnnualSerializer,
UploadParseResultSerializer,
)
from apps.organization.serializers import OrganizationSerializer
from rest_framework import serializers
@@ -50,51 +54,9 @@ class FormF3RecordListSerializer(serializers.ModelSerializer):
]
class FormF3UploadSerializer(serializers.Serializer):
"""Сериализатор загрузки файла формы Ф-3."""
file = serializers.FileField(help_text="Excel файл формы Ф-3 (.xlsx)")
report_year = serializers.IntegerField(min_value=2000, help_text="Отчетный год")
report_quarter = serializers.IntegerField(
min_value=1,
max_value=4,
required=False,
allow_null=True,
help_text="Отчетный квартал от 1 до 4. Пусто для годовой формы.",
)
def validate_file(self, value):
"""Валидация загруженного файла."""
if not value.name.endswith((".xlsx", ".xls")):
raise serializers.ValidationError(
"Неподдерживаемый формат файла. Используйте .xlsx или .xls"
)
if value.size > 50 * 1024 * 1024:
raise serializers.ValidationError("Размер файла превышает 50MB")
return value
class FormF3UploadSerializer(UploadAnnualSerializer):
"""Загрузка файла формы Ф-3 (годовая отчетность)."""
class FieldErrorSerializer(serializers.Serializer):
"""Сериализатор ошибки поля."""
field = serializers.CharField()
message = serializers.CharField()
class RowValidationErrorSerializer(serializers.Serializer):
"""Сериализатор ошибки валидации строки."""
row = serializers.IntegerField()
inn = serializers.CharField(allow_null=True)
kpp = serializers.CharField(allow_null=True)
organization_name = serializers.CharField(allow_null=True)
errors = FieldErrorSerializer(many=True)
class FormF3ParseResultSerializer(serializers.Serializer):
class FormF3ParseResultSerializer(UploadParseResultSerializer):
"""Сериализатор результата парсинга."""
batch_id = serializers.IntegerField()
loaded_count = serializers.IntegerField()
skipped_count = serializers.IntegerField()
errors = RowValidationErrorSerializer(many=True)

View File

@@ -2,6 +2,11 @@
import logging
from apps.core.upload_contracts import (
build_upload_error_response,
build_upload_success_payload,
build_upload_validation_response,
)
from apps.core.viewsets import ReadOnlyViewSet
from apps.form_4.models import FormF4Record
from apps.form_4.serializers import (
@@ -27,10 +32,13 @@ class FormF4UploadView(APIView):
def post(self, request):
serializer = FormF4UploadSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
if not serializer.is_valid():
return build_upload_validation_response(serializer.errors)
file = serializer.validated_data["file"]
report_year = serializer.validated_data["report_year"]
report_quarter = serializer.validated_data.get("report_quarter")
report_half_year = serializer.validated_data.get("report_half_year")
report_quarter = report_half_year
if file.size > BACKGROUND_THRESHOLD:
task = process_form_f4_file.delay(
@@ -40,11 +48,13 @@ class FormF4UploadView(APIView):
report_quarter,
)
return Response(
{
"success": True,
"message": "Файл поставлен в очередь",
"task_id": task.id,
},
build_upload_success_payload(
form="f4",
report_year=report_year,
report_half_year=report_half_year,
status="queued",
job_id=task.id,
),
status=status.HTTP_202_ACCEPTED,
)
@@ -55,12 +65,21 @@ class FormF4UploadView(APIView):
report_quarter=report_quarter,
)
return Response(
{"success": True, "data": FormF4ParseResultSerializer(result).data}
build_upload_success_payload(
form="f4",
report_year=report_year,
report_half_year=report_half_year,
status="done",
result=FormF4ParseResultSerializer(result).data,
),
status=status.HTTP_200_OK,
)
except Exception as e:
logger.exception("Ошибка обработки файла Ф-4")
return Response(
{"success": False, "error": str(e)}, status=status.HTTP_400_BAD_REQUEST
return build_upload_error_response(
error_code="processing_error",
error_message=str(e),
status_code=status.HTTP_400_BAD_REQUEST,
)

View File

@@ -1,6 +1,10 @@
"""Сериализаторы формы Ф-4."""
from apps.form_4.models import FormF4Record
from apps.core.upload_contracts import (
UploadHalfYearSerializer,
UploadParseResultSerializer,
)
from apps.organization.serializers import OrganizationSerializer
from rest_framework import serializers
@@ -39,40 +43,9 @@ class FormF4RecordListSerializer(serializers.ModelSerializer):
]
class FormF4UploadSerializer(serializers.Serializer):
file = serializers.FileField(help_text="Excel файл формы Ф-4 (.xlsx)")
report_year = serializers.IntegerField(min_value=2000, help_text="Отчетный год")
report_quarter = serializers.IntegerField(
min_value=1,
max_value=4,
required=False,
allow_null=True,
help_text="Отчетный квартал от 1 до 4. Пусто для годовой формы.",
)
def validate_file(self, value):
if not value.name.endswith((".xlsx", ".xls")):
raise serializers.ValidationError("Неподдерживаемый формат файла")
if value.size > 50 * 1024 * 1024:
raise serializers.ValidationError("Размер файла превышает 50MB")
return value
class FormF4UploadSerializer(UploadHalfYearSerializer):
"""Загрузка файла формы Ф-4 (полугодовая отчетность)."""
class FieldErrorSerializer(serializers.Serializer):
field = serializers.CharField()
message = serializers.CharField()
class RowValidationErrorSerializer(serializers.Serializer):
row = serializers.IntegerField()
inn = serializers.CharField(allow_null=True)
kpp = serializers.CharField(allow_null=True)
organization_name = serializers.CharField(allow_null=True)
errors = FieldErrorSerializer(many=True)
class FormF4ParseResultSerializer(serializers.Serializer):
batch_id = serializers.IntegerField()
loaded_count = serializers.IntegerField()
skipped_count = serializers.IntegerField()
errors = RowValidationErrorSerializer(many=True)
class FormF4ParseResultSerializer(UploadParseResultSerializer):
"""Сериализатор результата парсинга."""

View File

@@ -2,6 +2,11 @@
import logging
from apps.core.upload_contracts import (
build_upload_error_response,
build_upload_success_payload,
build_upload_validation_response,
)
from apps.core.viewsets import ReadOnlyViewSet
from apps.form_5.models import FormF5Record
from apps.form_5.serializers import (
@@ -27,9 +32,12 @@ class FormF5UploadView(APIView):
def post(self, request):
serializer = FormF5UploadSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
if not serializer.is_valid():
return build_upload_validation_response(serializer.errors)
file = serializer.validated_data["file"]
report_year = serializer.validated_data["report_year"]
report_quarter = serializer.validated_data.get("report_quarter")
if file.size > BACKGROUND_THRESHOLD:
@@ -40,11 +48,12 @@ class FormF5UploadView(APIView):
report_quarter,
)
return Response(
{
"success": True,
"message": "Файл поставлен в очередь",
"task_id": task.id,
},
build_upload_success_payload(
form="f5",
report_year=report_year,
status="queued",
job_id=task.id,
),
status=status.HTTP_202_ACCEPTED,
)
@@ -55,12 +64,20 @@ class FormF5UploadView(APIView):
report_quarter=report_quarter,
)
return Response(
{"success": True, "data": FormF5ParseResultSerializer(result).data}
build_upload_success_payload(
form="f5",
report_year=report_year,
status="done",
result=FormF5ParseResultSerializer(result).data,
),
status=status.HTTP_200_OK,
)
except Exception as e:
logger.exception("Ошибка обработки файла Ф-5")
return Response(
{"success": False, "error": str(e)}, status=status.HTTP_400_BAD_REQUEST
return build_upload_error_response(
error_code="processing_error",
error_message=str(e),
status_code=status.HTTP_400_BAD_REQUEST,
)

View File

@@ -1,6 +1,10 @@
"""Сериализаторы формы Ф-5."""
from apps.form_5.models import FormF5Record
from apps.core.upload_contracts import (
UploadAnnualSerializer,
UploadParseResultSerializer,
)
from apps.organization.serializers import OrganizationSerializer
from rest_framework import serializers
@@ -41,40 +45,9 @@ class FormF5RecordListSerializer(serializers.ModelSerializer):
]
class FormF5UploadSerializer(serializers.Serializer):
file = serializers.FileField(help_text="Excel файл формы Ф-5 (.xlsx)")
report_year = serializers.IntegerField(min_value=2000, help_text="Отчетный год")
report_quarter = serializers.IntegerField(
min_value=1,
max_value=4,
required=False,
allow_null=True,
help_text="Отчетный квартал от 1 до 4. Пусто для годовой формы.",
)
def validate_file(self, value):
if not value.name.endswith((".xlsx", ".xls")):
raise serializers.ValidationError("Неподдерживаемый формат файла")
if value.size > 50 * 1024 * 1024:
raise serializers.ValidationError("Размер файла превышает 50MB")
return value
class FormF5UploadSerializer(UploadAnnualSerializer):
"""Загрузка файла формы Ф-5 (годовая отчетность)."""
class FieldErrorSerializer(serializers.Serializer):
field = serializers.CharField()
message = serializers.CharField()
class RowValidationErrorSerializer(serializers.Serializer):
row = serializers.IntegerField()
inn = serializers.CharField(allow_null=True)
kpp = serializers.CharField(allow_null=True)
organization_name = serializers.CharField(allow_null=True)
errors = FieldErrorSerializer(many=True)
class FormF5ParseResultSerializer(serializers.Serializer):
batch_id = serializers.IntegerField()
loaded_count = serializers.IntegerField()
skipped_count = serializers.IntegerField()
errors = RowValidationErrorSerializer(many=True)
class FormF5ParseResultSerializer(UploadParseResultSerializer):
"""Сериализатор результата парсинга."""

View File

@@ -2,6 +2,11 @@
import logging
from apps.core.upload_contracts import (
build_upload_error_response,
build_upload_success_payload,
build_upload_validation_response,
)
from apps.core.viewsets import ReadOnlyViewSet
from apps.form_6.models import FormF6Record
from apps.form_6.serializers import (
@@ -27,7 +32,9 @@ class FormF6UploadView(APIView):
def post(self, request):
serializer = FormF6UploadSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
if not serializer.is_valid():
return build_upload_validation_response(serializer.errors)
file = serializer.validated_data["file"]
report_year = serializer.validated_data["report_year"]
report_quarter = serializer.validated_data.get("report_quarter")
@@ -40,11 +47,12 @@ class FormF6UploadView(APIView):
report_quarter,
)
return Response(
{
"success": True,
"message": "Файл поставлен в очередь",
"task_id": task.id,
},
build_upload_success_payload(
form="f6",
report_year=report_year,
status="queued",
job_id=task.id,
),
status=status.HTTP_202_ACCEPTED,
)
@@ -55,12 +63,20 @@ class FormF6UploadView(APIView):
report_quarter=report_quarter,
)
return Response(
{"success": True, "data": FormF6ParseResultSerializer(result).data}
build_upload_success_payload(
form="f6",
report_year=report_year,
status="done",
result=FormF6ParseResultSerializer(result).data,
),
status=status.HTTP_200_OK,
)
except Exception as e:
logger.exception("Ошибка обработки файла Ф-6")
return Response(
{"success": False, "error": str(e)}, status=status.HTTP_400_BAD_REQUEST
return build_upload_error_response(
error_code="processing_error",
error_message=str(e),
status_code=status.HTTP_400_BAD_REQUEST,
)

View File

@@ -1,6 +1,10 @@
"""Сериализаторы формы Ф-6."""
from apps.form_6.models import FormF6Record
from apps.core.upload_contracts import (
UploadAnnualSerializer,
UploadParseResultSerializer,
)
from apps.organization.serializers import OrganizationSerializer
from rest_framework import serializers
@@ -40,40 +44,9 @@ class FormF6RecordListSerializer(serializers.ModelSerializer):
]
class FormF6UploadSerializer(serializers.Serializer):
file = serializers.FileField(help_text="Excel файл формы Ф-6 (.xlsx)")
report_year = serializers.IntegerField(min_value=2000, help_text="Отчетный год")
report_quarter = serializers.IntegerField(
min_value=1,
max_value=4,
required=False,
allow_null=True,
help_text="Отчетный квартал от 1 до 4. Пусто для годовой формы.",
)
def validate_file(self, value):
if not value.name.endswith((".xlsx", ".xls")):
raise serializers.ValidationError("Неподдерживаемый формат файла")
if value.size > 50 * 1024 * 1024:
raise serializers.ValidationError("Размер файла превышает 50MB")
return value
class FormF6UploadSerializer(UploadAnnualSerializer):
"""Загрузка файла формы Ф-6 (годовая отчетность)."""
class FieldErrorSerializer(serializers.Serializer):
field = serializers.CharField()
message = serializers.CharField()
class RowValidationErrorSerializer(serializers.Serializer):
row = serializers.IntegerField()
inn = serializers.CharField(allow_null=True)
kpp = serializers.CharField(allow_null=True)
organization_name = serializers.CharField(allow_null=True)
errors = FieldErrorSerializer(many=True)
class FormF6ParseResultSerializer(serializers.Serializer):
batch_id = serializers.IntegerField()
loaded_count = serializers.IntegerField()
skipped_count = serializers.IntegerField()
errors = RowValidationErrorSerializer(many=True)
class FormF6ParseResultSerializer(UploadParseResultSerializer):
"""Сериализатор результата парсинга."""