feat: expand platform APIs, sources, and test coverage
Some checks failed
CI/CD Pipeline / Run Tests (pull_request) Successful in 1m53s
CI/CD Pipeline / Telegram Notify Success (push) Has been cancelled
CI/CD Pipeline / Run Tests (push) Has been cancelled
CI/CD Pipeline / Code Quality Checks (push) Has been cancelled
CI/CD Pipeline / Code Quality Checks (pull_request) Failing after 2m54s
CI/CD Pipeline / Telegram Notify Success (pull_request) Has been skipped
Some checks failed
CI/CD Pipeline / Run Tests (pull_request) Successful in 1m53s
CI/CD Pipeline / Telegram Notify Success (push) Has been cancelled
CI/CD Pipeline / Run Tests (push) Has been cancelled
CI/CD Pipeline / Code Quality Checks (push) Has been cancelled
CI/CD Pipeline / Code Quality Checks (pull_request) Failing after 2m54s
CI/CD Pipeline / Telegram Notify Success (pull_request) Has been skipped
This commit is contained in:
@@ -7,12 +7,16 @@ Views для приложения парсеров.
|
||||
|
||||
import hashlib
|
||||
import time
|
||||
import uuid
|
||||
from pathlib import Path
|
||||
|
||||
from apps.core.openapi import CommonResponses, ErrorResponses, swagger_tag
|
||||
from apps.core.response import api_response
|
||||
from apps.core.services import BackgroundJobService
|
||||
from apps.parsers.models import (
|
||||
FinancialReport,
|
||||
IndustrialCertificateRecord,
|
||||
IndustrialProductRecord,
|
||||
InspectionRecord,
|
||||
ManufacturerRecord,
|
||||
ParserLoadLog,
|
||||
@@ -24,12 +28,19 @@ from apps.parsers.serializers import (
|
||||
FinancialReportSerializer,
|
||||
FNSFileUploadSerializer,
|
||||
IndustrialCertificateSerializer,
|
||||
IndustrialProductSerializer,
|
||||
InspectionSerializer,
|
||||
ManufacturerSerializer,
|
||||
ParserLoadLogSerializer,
|
||||
ProcurementSerializer,
|
||||
ProxySerializer,
|
||||
SourceCardDetailSerializer,
|
||||
SourceCardRefreshRequestSerializer,
|
||||
SourceCardRefreshResponseSerializer,
|
||||
SourceCardSerializer,
|
||||
SourceTaskStatusSerializer,
|
||||
)
|
||||
from apps.parsers.source_cards import SourceCardService
|
||||
from apps.parsers.tasks import process_fns_file
|
||||
from django.conf import settings
|
||||
from django.db.models import Count
|
||||
@@ -50,6 +61,7 @@ MINPROMTORG_TAG = swagger_tag("Минпромторг", "minpromtorg")
|
||||
PROVERKI_TAG = swagger_tag("Единый реестр проверок", "inspections_registry")
|
||||
ZAKUPKI_TAG = swagger_tag("Государственные закупки", "public_procurements")
|
||||
FNS_TAG = swagger_tag("ФНС - Бухгалтерская отчетность", "fns_financial_reports")
|
||||
SOURCES_TAG = swagger_tag("Источники для фронта", "frontend_sources")
|
||||
SYSTEM_TAG = swagger_tag("Системные", "system")
|
||||
|
||||
|
||||
@@ -155,6 +167,66 @@ class ManufacturerViewSet(ReadOnlyModelViewSet):
|
||||
return super().retrieve(request, *args, **kwargs)
|
||||
|
||||
|
||||
class IndustrialProductViewSet(ReadOnlyModelViewSet):
|
||||
"""
|
||||
API для просмотра реестра промышленной продукции.
|
||||
|
||||
Данные загружаются из Минпромторга.
|
||||
Только чтение - добавление через парсер/админку.
|
||||
"""
|
||||
|
||||
queryset = IndustrialProductRecord.objects.all().order_by("-created_at")
|
||||
serializer_class = IndustrialProductSerializer
|
||||
permission_classes = [IsAuthenticated]
|
||||
filterset_fields = [
|
||||
"inn",
|
||||
"ogrn",
|
||||
"registry_number",
|
||||
"load_batch",
|
||||
"registry_organization",
|
||||
]
|
||||
search_fields = [
|
||||
"full_organisation_name",
|
||||
"product_name",
|
||||
"product_model",
|
||||
"registry_number",
|
||||
"inn",
|
||||
"ogrn",
|
||||
]
|
||||
|
||||
@swagger_auto_schema(
|
||||
tags=[MINPROMTORG_TAG],
|
||||
operation_summary="Список промышленной продукции",
|
||||
operation_description=(
|
||||
"Возвращает список записей реестра промышленной продукции "
|
||||
"Минпромторга.\n"
|
||||
"Поддерживает фильтрацию по: inn, ogrn, registry_number, load_batch.\n"
|
||||
"Поддерживает поиск по: full_organisation_name, product_name, "
|
||||
"product_model, registry_number, inn, ogrn."
|
||||
),
|
||||
responses={
|
||||
200: IndustrialProductSerializer(many=True),
|
||||
**ErrorResponses.AUTHENTICATED,
|
||||
},
|
||||
)
|
||||
def list(self, request, *args, **kwargs):
|
||||
return super().list(request, *args, **kwargs)
|
||||
|
||||
@swagger_auto_schema(
|
||||
tags=[MINPROMTORG_TAG],
|
||||
operation_summary="Детали записи промышленной продукции",
|
||||
operation_description=(
|
||||
"Возвращает информацию о конкретной записи реестра промышленной продукции."
|
||||
),
|
||||
responses={
|
||||
200: IndustrialProductSerializer,
|
||||
**ErrorResponses.AUTHENTICATED_NOT_FOUND,
|
||||
},
|
||||
)
|
||||
def retrieve(self, request, *args, **kwargs):
|
||||
return super().retrieve(request, *args, **kwargs)
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# Единый реестр проверок (proverki.gov.ru)
|
||||
# =============================================================================
|
||||
@@ -364,7 +436,7 @@ class FNSReportUploadView(APIView):
|
||||
"""
|
||||
|
||||
parser_classes = [MultiPartParser]
|
||||
permission_classes = [IsAuthenticated]
|
||||
permission_classes = [IsAdminUser]
|
||||
|
||||
@swagger_auto_schema(
|
||||
tags=[FNS_TAG],
|
||||
@@ -410,7 +482,7 @@ class FNSReportUploadView(APIView):
|
||||
),
|
||||
),
|
||||
400: CommonResponses.BAD_REQUEST,
|
||||
**ErrorResponses.AUTHENTICATED,
|
||||
**ErrorResponses.ADMIN,
|
||||
},
|
||||
)
|
||||
def post(self, request): # noqa
|
||||
@@ -479,9 +551,26 @@ class FNSReportUploadView(APIView):
|
||||
|
||||
# Ставим в очередь
|
||||
try:
|
||||
task = process_fns_file.delay(str(file_path))
|
||||
task_id = str(uuid.uuid4())
|
||||
BackgroundJobService.create_job(
|
||||
task_id=task_id,
|
||||
task_name="apps.parsers.tasks.process_fns_file",
|
||||
user_id=request.user.id,
|
||||
meta={
|
||||
"source": ParserLoadLog.Source.FNS_REPORTS,
|
||||
"file": file.name,
|
||||
},
|
||||
)
|
||||
task = process_fns_file.apply_async(
|
||||
args=[str(file_path)],
|
||||
kwargs={"requested_by_id": request.user.id},
|
||||
task_id=task_id,
|
||||
)
|
||||
except Exception:
|
||||
lock_path.unlink(missing_ok=True)
|
||||
from apps.core.models import BackgroundJob
|
||||
|
||||
BackgroundJob.objects.filter(task_id=task_id).delete()
|
||||
raise
|
||||
task_ids.append(task.id)
|
||||
queued += 1
|
||||
@@ -496,6 +585,111 @@ class FNSReportUploadView(APIView):
|
||||
)
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# Frontend-oriented source cards
|
||||
# =============================================================================
|
||||
|
||||
|
||||
class SourceCardListView(APIView):
|
||||
"""List of aggregated source cards for frontend pages."""
|
||||
|
||||
permission_classes = [IsAuthenticated]
|
||||
|
||||
@swagger_auto_schema(
|
||||
tags=[SOURCES_TAG],
|
||||
operation_summary="Список карточек источников",
|
||||
operation_description=(
|
||||
"Возвращает агрегированный список карточек источников данных "
|
||||
"для фронтенда."
|
||||
),
|
||||
responses={
|
||||
200: SourceCardSerializer(many=True),
|
||||
**ErrorResponses.AUTHENTICATED,
|
||||
},
|
||||
)
|
||||
def get(self, request):
|
||||
cards = SourceCardService.list_cards()
|
||||
serializer = SourceCardSerializer(cards, many=True)
|
||||
return api_response(serializer.data)
|
||||
|
||||
|
||||
class SourceTaskStatusListView(APIView):
|
||||
"""Tabular list of parsing source statuses for frontend screens."""
|
||||
|
||||
permission_classes = [IsAuthenticated]
|
||||
|
||||
@swagger_auto_schema(
|
||||
tags=[SOURCES_TAG],
|
||||
operation_summary="Статусы задач парсинга",
|
||||
operation_description=(
|
||||
"Возвращает табличный список статусов источников данных для экрана "
|
||||
"мониторинга парсинга."
|
||||
),
|
||||
responses={
|
||||
200: SourceTaskStatusSerializer(many=True),
|
||||
**ErrorResponses.AUTHENTICATED,
|
||||
},
|
||||
)
|
||||
def get(self, request):
|
||||
rows = SourceCardService.list_task_statuses()
|
||||
serializer = SourceTaskStatusSerializer(rows, many=True)
|
||||
return api_response(serializer.data)
|
||||
|
||||
|
||||
class SourceCardDetailView(APIView):
|
||||
"""Detailed frontend card for a single source."""
|
||||
|
||||
permission_classes = [IsAuthenticated]
|
||||
|
||||
@swagger_auto_schema(
|
||||
tags=[SOURCES_TAG],
|
||||
operation_summary="Детали карточки источника",
|
||||
operation_description=(
|
||||
"Возвращает детальную информацию по одной карточке источника, "
|
||||
"включая подисточники, последние загрузки и активные задачи."
|
||||
),
|
||||
responses={
|
||||
200: SourceCardDetailSerializer,
|
||||
**ErrorResponses.AUTHENTICATED_NOT_FOUND,
|
||||
},
|
||||
)
|
||||
def get(self, request, slug: str):
|
||||
card = SourceCardService.get_card(slug)
|
||||
serializer = SourceCardDetailSerializer(card)
|
||||
return api_response(serializer.data)
|
||||
|
||||
|
||||
class SourceCardRefreshView(APIView):
|
||||
"""Manual refresh trigger for a frontend source card."""
|
||||
|
||||
permission_classes = [IsAdminUser]
|
||||
|
||||
@swagger_auto_schema(
|
||||
tags=[SOURCES_TAG],
|
||||
operation_summary="Запуск обновления карточки источника",
|
||||
operation_description=(
|
||||
"Ставит обновление карточки в очередь и возвращает связанные task_id."
|
||||
),
|
||||
request_body=SourceCardRefreshRequestSerializer,
|
||||
responses={
|
||||
202: SourceCardRefreshResponseSerializer,
|
||||
400: CommonResponses.BAD_REQUEST,
|
||||
**ErrorResponses.ADMIN_NOT_FOUND,
|
||||
},
|
||||
)
|
||||
def post(self, request, slug: str):
|
||||
serializer = SourceCardRefreshRequestSerializer(data=request.data)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
|
||||
payload = SourceCardService.refresh_card(
|
||||
slug=slug,
|
||||
requested_by_id=request.user.id if request.user.is_authenticated else None,
|
||||
params=serializer.validated_data.get("params", {}),
|
||||
)
|
||||
output = SourceCardRefreshResponseSerializer(payload)
|
||||
return api_response(output.data, status_code=status.HTTP_202_ACCEPTED)
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# Системные (логи загрузок, прокси)
|
||||
# =============================================================================
|
||||
|
||||
Reference in New Issue
Block a user