feat: add parser source dashboard and scheduling
All checks were successful
CI/CD Pipeline / Code Quality Checks (pull_request) Successful in 1m55s
CI/CD Pipeline / Run Tests (pull_request) Successful in 3m6s
CI/CD Pipeline / Run API Inventory E2E Tests (pull_request) Successful in 2m48s
CI/CD Pipeline / Telegram Notify Success (pull_request) Successful in 19s

This commit is contained in:
2026-04-28 01:08:51 +02:00
parent 1249071a33
commit 26d61f7181
58 changed files with 7713 additions and 141 deletions

View File

@@ -16,14 +16,14 @@ from apps.parsers.models import (
Proxy,
)
from apps.parsers.source_cards import SourceCardService
from django.db.models import Count, Max
from django.urls import NoReverseMatch, reverse
from registers.models import (
Organization,
Register,
RegisterUpload,
RegistryMembershipPeriod,
)
from django.db.models import Count, Max
from django.urls import NoReverseMatch, reverse
SOURCE_COLORS = (
"#49d0c8",
@@ -138,10 +138,7 @@ def build_admin_dashboard() -> dict[str, Any]:
source_cards = _build_source_cards()
source_mix = _build_source_mix(source_cards)
active_registry_orgs = (
RegistryMembershipPeriod.objects
.values("organization_id")
.distinct()
.count()
RegistryMembershipPeriod.objects.values("organization_id").distinct().count()
)
total_organizations = Organization.objects.count()
healthy_sources = sum(1 for card in source_cards if card["status"] == "success")

View File

@@ -8,6 +8,7 @@ They are easily testable and can manage transactions.
import logging
from typing import Any, Generic, TypeVar
import django
from apps.core.exceptions import NotFoundError
from django.db import models, transaction
from django.db.models import QuerySet
@@ -272,8 +273,13 @@ class BulkOperationsMixin:
"ignore_conflicts": ignore_conflicts,
}
# Django 4.1+ поддерживает update_conflicts
if update_conflicts and update_fields and unique_fields:
# Django 4.1+ поддерживает update_conflicts; проект закреплён на Django 3.x.
if (
django.VERSION >= (4, 1)
and update_conflicts
and update_fields
and unique_fields
):
kwargs["update_conflicts"] = True
kwargs["update_fields"] = update_fields
kwargs["unique_fields"] = unique_fields

View File

@@ -21,6 +21,7 @@ from apps.core.serializers import (
from django.conf import settings
from django.db import connection
from django.http import StreamingHttpResponse
from drf_yasg import openapi
from drf_yasg.utils import swagger_auto_schema
from rest_framework import status
from rest_framework.exceptions import ValidationError
@@ -354,6 +355,59 @@ class BackgroundJobStreamView(BackgroundJobStatusView):
return response
class BackgroundJobControlView(BackgroundJobStatusView):
"""Управление фоновой задачей: сейчас поддерживается revoke."""
@swagger_auto_schema(
tags=[JOBS_TAG],
operation_summary="Управление задачей",
operation_description="Отзывает Celery задачу по action=revoke.",
request_body=openapi.Schema(
type=openapi.TYPE_OBJECT,
properties={
"action": openapi.Schema(
type=openapi.TYPE_STRING,
enum=["revoke"],
),
"terminate": openapi.Schema(type=openapi.TYPE_BOOLEAN),
},
),
responses={
200: BackgroundJobSerializer,
400: CommonResponses.BAD_REQUEST,
403: CommonResponses.FORBIDDEN,
404: CommonResponses.NOT_FOUND,
**ErrorResponses.AUTHENTICATED,
},
)
def post(self, request: Request, task_id: str) -> Response:
from apps.core.models import JobStatus
from apps.core.services import BackgroundJobService
from celery import current_app
job = BackgroundJobService.get_by_task_id(task_id)
access_error = self._check_access(request, job)
if access_error is not None:
return access_error
action = request.data.get("action", "revoke")
if action != "revoke":
return Response(
{"detail": "Поддерживается только action=revoke"},
status=status.HTTP_400_BAD_REQUEST,
)
current_app.control.revoke(
task_id,
terminate=bool(request.data.get("terminate", False)),
)
if not job.is_finished:
job.status = JobStatus.REVOKED
job.progress_message = "Задача отозвана пользователем"
job.save(update_fields=["status", "progress_message", "updated_at"])
return Response(BackgroundJobSerializer(job).data)
class BackgroundJobListView(APIView):
"""
Список фоновых задач пользователя.