feat: export organizations to state corp v3
This commit is contained in:
@@ -148,6 +148,28 @@ class OrganizationSourceRecordExportRequestSerializer(serializers.Serializer):
|
||||
return value
|
||||
|
||||
|
||||
class OrganizationDirectoryImportUploadSerializer(serializers.Serializer):
|
||||
"""Request for uploading the canonical organization directory XLSX."""
|
||||
|
||||
file = serializers.FileField()
|
||||
|
||||
def validate_file(self, value):
|
||||
if not value.name.lower().endswith(".xlsx"):
|
||||
raise serializers.ValidationError("Поддерживаются только файлы .xlsx")
|
||||
return value
|
||||
|
||||
|
||||
class OrganizationDirectoryImportResponseSerializer(serializers.Serializer):
|
||||
"""Counters returned after canonical organization directory upload."""
|
||||
|
||||
success = serializers.BooleanField(read_only=True)
|
||||
message = serializers.CharField(read_only=True)
|
||||
scanned = serializers.IntegerField(read_only=True)
|
||||
created = serializers.IntegerField(read_only=True)
|
||||
updated = serializers.IntegerField(read_only=True)
|
||||
skipped = serializers.IntegerField(read_only=True)
|
||||
|
||||
|
||||
class OrganizationSourceExtensionSerializer(serializers.ModelSerializer):
|
||||
"""Compact source extension representation."""
|
||||
|
||||
|
||||
@@ -4,6 +4,9 @@ from __future__ import annotations
|
||||
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
from contextlib import suppress
|
||||
from tempfile import NamedTemporaryFile
|
||||
from typing import Any
|
||||
|
||||
from apps.core.openapi import swagger_tag
|
||||
@@ -17,7 +20,9 @@ from drf_yasg import openapi
|
||||
from drf_yasg.utils import swagger_auto_schema
|
||||
from rest_framework import status
|
||||
from rest_framework.decorators import action
|
||||
from rest_framework.exceptions import ValidationError
|
||||
from rest_framework.filters import OrderingFilter, SearchFilter
|
||||
from rest_framework.parsers import MultiPartParser
|
||||
from rest_framework.permissions import AllowAny, IsAdminUser, IsAuthenticated
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.viewsets import ReadOnlyModelViewSet
|
||||
@@ -26,6 +31,11 @@ from organizations.cache import (
|
||||
DEFAULT_ORGANIZATION_API_CACHE_TIMEOUT_SECONDS,
|
||||
ORGANIZATION_API_CACHE_PREFIX,
|
||||
get_organization_api_cache_version,
|
||||
invalidate_organization_api_cache,
|
||||
)
|
||||
from organizations.directory_import import (
|
||||
OrganizationDirectoryImportError,
|
||||
OrganizationDirectoryImportService,
|
||||
)
|
||||
from organizations.filters import OrganizationFilter
|
||||
from organizations.models import (
|
||||
@@ -35,6 +45,8 @@ from organizations.models import (
|
||||
SourceGroup,
|
||||
)
|
||||
from organizations.serializers import (
|
||||
OrganizationDirectoryImportResponseSerializer,
|
||||
OrganizationDirectoryImportUploadSerializer,
|
||||
OrganizationSerializer,
|
||||
OrganizationSourceExtensionSerializer,
|
||||
OrganizationSourceRecordExportRequestSerializer,
|
||||
@@ -355,6 +367,70 @@ class OrganizationViewSet(CachedReadOnlyMixin, ReadOnlyModelViewSet):
|
||||
)
|
||||
return Response(serializer.data)
|
||||
|
||||
@swagger_auto_schema(
|
||||
tags=[ORGANIZATIONS_TAG],
|
||||
operation_id="v2_organizations_import_directory",
|
||||
operation_summary="Загрузка справочника организаций",
|
||||
operation_description=(
|
||||
"Загружает XLSX сводного реестра организаций ОПК в каноническую "
|
||||
"таблицу organizations.Organization."
|
||||
),
|
||||
manual_parameters=[
|
||||
openapi.Parameter(
|
||||
name="file",
|
||||
in_=openapi.IN_FORM,
|
||||
type=openapi.TYPE_FILE,
|
||||
required=True,
|
||||
description="XLSX файл сводного реестра организаций",
|
||||
),
|
||||
],
|
||||
consumes=["multipart/form-data"],
|
||||
responses={
|
||||
201: OrganizationDirectoryImportResponseSerializer,
|
||||
400: "Некорректный XLSX файл",
|
||||
403: "Доступ только для администратора",
|
||||
},
|
||||
)
|
||||
@action(
|
||||
detail=False,
|
||||
methods=["post"],
|
||||
url_path="import-directory",
|
||||
parser_classes=[MultiPartParser],
|
||||
permission_classes=[IsAdminUser],
|
||||
)
|
||||
def import_directory(self, request, *args: Any, **kwargs: Any) -> Response:
|
||||
serializer = OrganizationDirectoryImportUploadSerializer(data=request.data)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
uploaded_file = serializer.validated_data["file"]
|
||||
|
||||
temp_path = ""
|
||||
try:
|
||||
with NamedTemporaryFile(suffix=".xlsx", delete=False) as temp_file:
|
||||
temp_path = temp_file.name
|
||||
for chunk in uploaded_file.chunks():
|
||||
temp_file.write(chunk)
|
||||
|
||||
result = OrganizationDirectoryImportService.import_xlsx(temp_path)
|
||||
except OrganizationDirectoryImportError as exc:
|
||||
raise ValidationError({"file": str(exc)}) from exc
|
||||
finally:
|
||||
if temp_path:
|
||||
with suppress(FileNotFoundError):
|
||||
os.unlink(temp_path)
|
||||
|
||||
invalidate_organization_api_cache()
|
||||
return Response(
|
||||
{
|
||||
"success": True,
|
||||
"message": "Справочник организаций успешно загружен",
|
||||
"scanned": result.scanned,
|
||||
"created": result.created,
|
||||
"updated": result.updated,
|
||||
"skipped": result.skipped,
|
||||
},
|
||||
status=status.HTTP_201_CREATED,
|
||||
)
|
||||
|
||||
|
||||
class OrganizationSourceExtensionViewSet(ReadOnlyModelViewSet):
|
||||
"""Read-only API for source extensions and their records."""
|
||||
|
||||
Reference in New Issue
Block a user