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
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:
@@ -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")
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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):
|
||||
"""
|
||||
Список фоновых задач пользователя.
|
||||
|
||||
Reference in New Issue
Block a user