Add v2 registry uploads and source CSV exports
All checks were successful
CI/CD Pipeline / Quality Gate (push) Successful in 20s
CI/CD Pipeline / Build and Push Images (push) Successful in 10s
CI/CD Pipeline / Internal Notify (push) Successful in 0s
CI/CD Pipeline / Deploy Dev in Dokploy (push) Successful in 1s

This commit is contained in:
2026-05-07 14:39:20 +02:00
parent 507ae2063a
commit 15360a3c8e
13 changed files with 639 additions and 8 deletions

View File

@@ -0,0 +1,39 @@
"""API v2 routes for parser source exports."""
from apps.parsers.models import ParserLoadLog
from apps.parsers.source_registry import PARSER_SOURCES, ParserSourceDescriptor
from apps.parsers.views import SourceResultCsvDownloadView
from django.urls import path
app_name = "parser_sources"
def _download_source_descriptors():
"""Return unique source result routes that support CSV download."""
seen_routes = set()
for descriptor in PARSER_SOURCES.values():
if not descriptor.api_route or descriptor.api_route in seen_routes:
continue
seen_routes.add(descriptor.api_route)
if descriptor.source == ParserLoadLog.Source.FNS_REPORTS:
continue
yield descriptor
def _download_view(descriptor: ParserSourceDescriptor):
class SourceDownloadView(SourceResultCsvDownloadView):
source_key = descriptor.key
return SourceDownloadView.as_view()
urlpatterns = []
for source_descriptor in _download_source_descriptors():
route_name = source_descriptor.api_route.replace("/", "-")
urlpatterns.append(
path(
f"{source_descriptor.api_route}/download/",
_download_view(source_descriptor),
name=f"{route_name}-download",
)
)

View File

@@ -520,6 +520,7 @@ class ParserSourceSerializer(serializers.Serializer):
parser_strategy = serializers.CharField()
source_notes = serializers.CharField(allow_blank=True)
supports_file_upload = serializers.BooleanField()
api_route = serializers.CharField(allow_blank=True)
result_list_url = serializers.CharField()
result_detail_url = serializers.CharField()
upload_url = serializers.CharField(allow_blank=True)

View File

@@ -2295,6 +2295,79 @@ class SourceResultListView(APIView):
)
SOURCE_CSV_HEADERS = [
"id",
"load_batch",
"source",
"external_id",
"inn",
"ogrn",
"organisation_name",
"title",
"record_date",
"amount",
"status",
"url",
"payload",
"created_at",
"updated_at",
]
def _csv_cell(value) -> str:
if value is None:
return ""
if isinstance(value, dict):
return json.dumps(value, ensure_ascii=False, sort_keys=True)
if hasattr(value, "isoformat"):
return value.isoformat()
return str(value)
def _source_csv_filename(descriptor) -> str:
route_name = descriptor.api_route.replace("/", "-") or descriptor.key
return f"{get_valid_filename(route_name)}.csv"
class SourceResultCsvDownloadView(APIView):
"""CSV download результата конкретного источника API v2."""
permission_classes = [IsAuthenticated]
source_key = ""
def get(self, request: Request, source_key: str | None = None):
resolved_source_key = source_key or self.source_key
query_serializer = ParserResultQuerySerializer(data=request.query_params)
query_serializer.is_valid(raise_exception=True)
params = query_serializer.validated_data
params["include_payload"] = True
descriptor, queryset = _filter_result_queryset(resolved_source_key, params)
if descriptor is None:
return _source_not_found_response(resolved_source_key)
if descriptor.source == ParserLoadLog.Source.FNS_REPORTS:
return _source_not_found_response(resolved_source_key)
response = HttpResponse(content_type="text/csv; charset=utf-8")
response[
"Content-Disposition"
] = f'attachment; filename="{_source_csv_filename(descriptor)}"'
writer = csv.writer(response)
writer.writerow(SOURCE_CSV_HEADERS)
for record in queryset.iterator(chunk_size=1000):
row = _result_record_to_dict(
descriptor.source,
record,
include_payload=True,
)
writer.writerow(
[_csv_cell(row.get(header)) for header in SOURCE_CSV_HEADERS]
)
return response
class SourceResultDetailView(APIView):
"""GET одной записи результата источника."""