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

@@ -1,17 +1,17 @@
"""Admin для приложения backups."""
from urllib.parse import urlencode
from apps.backups.models import BackupExportJob
from apps.backups.serializers import BackupExportRequestSerializer
from apps.backups.services import BackupExportError, BackupExportJobService
from apps.core.services import BackgroundJobService
from django.contrib import admin
from django.contrib import messages
from django.contrib import admin, messages
from django.http import HttpResponse
from django.shortcuts import redirect
from django.urls import path, reverse
from django.utils.html import format_html
from django.utils import timezone
from urllib.parse import urlencode
from django.utils.html import format_html
@admin.register(BackupExportJob)
@@ -190,23 +190,29 @@ class BackupExportJobAdmin(admin.ModelAdmin):
)
return redirect(changelist_url)
actual_date = serializer.validated_data.get("actual_date") or timezone.localdate()
actual_date = (
serializer.validated_data.get("actual_date") or timezone.localdate()
)
try:
result = BackupExportJobService.check_or_start_job(
actual_date=actual_date,
requested_by_id=request.user.id if request.user.is_authenticated else None,
requested_by_id=request.user.id
if request.user.is_authenticated
else None,
)
except BackupExportError as exc:
self.message_user(request, f"Ошибка запуска резервного экспорта: {exc}", level=messages.ERROR)
self.message_user(
request,
f"Ошибка запуска резервного экспорта: {exc}",
level=messages.ERROR,
)
return redirect(changelist_url)
if result.action in {"started", "wait"}:
self.message_user(
request,
result.message,
level=messages.INFO
if result.action == "started"
else messages.WARNING,
level=messages.INFO if result.action == "started" else messages.WARNING,
)
return redirect(changelist_url)
@@ -216,10 +222,14 @@ class BackupExportJobAdmin(admin.ModelAdmin):
actual_date=result.actual_date
)
except BackupExportError as exc:
self.message_user(request, f"Ошибка загрузки backup: {exc}", level=messages.ERROR)
self.message_user(
request, f"Ошибка загрузки backup: {exc}", level=messages.ERROR
)
return redirect(changelist_url)
response = HttpResponse(artifact.archive_bytes, content_type="application/zip")
response = HttpResponse(
artifact.archive_bytes, content_type="application/zip"
)
response.status_code = 200
response[
"Content-Disposition"

View File

@@ -28,17 +28,17 @@ from apps.parsers.models import (
ManufacturerRecord,
ProcurementRecord,
)
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
from django.conf import settings
from django.db import IntegrityError, transaction
from django.db.models import Model, Q
from django.utils import timezone
from registers.models import (
Organization,
Register,
RegisterUpload,
RegistryMembershipPeriod,
)
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
from django.conf import settings
from django.db import IntegrityError, transaction
from django.db.models import Model
from django.utils import timezone
class BackupExportError(ValueError):
@@ -127,7 +127,10 @@ class BackupExportService:
@classmethod
def _get_active_organization_ids(cls, actual_date: date) -> list[int]:
return list(
RegistryMembershipPeriod.objects.all()
RegistryMembershipPeriod.objects.filter(
started_at__lte=actual_date,
)
.filter(Q(ended_at__isnull=True) | Q(ended_at__gt=actual_date))
.values_list("organization_id", flat=True)
.distinct()
)
@@ -141,7 +144,8 @@ class BackupExportService:
) -> dict:
active_memberships = RegistryMembershipPeriod.objects.filter(
organization_id__in=active_org_ids,
)
started_at__lte=actual_date,
).filter(Q(ended_at__isnull=True) | Q(ended_at__gt=actual_date))
register_ids = list(
active_memberships.values_list("registry_id", flat=True).distinct()

View File

@@ -3,7 +3,6 @@
from __future__ import annotations
import logging
import traceback
import uuid
from pathlib import Path
@@ -110,10 +109,9 @@ def generate_backup_for_date(self, job_id: int) -> dict:
job.actual_date,
job.id,
)
error_traceback = traceback.format_exc()
job.status = BackupExportJob.Status.FAILURE
job.error = str(exc)
job.completed_at = timezone.now()
job.save(update_fields=["status", "error", "completed_at", "updated_at"])
background_job.fail(error=str(exc), traceback_str=error_traceback)
background_job.fail(error=str(exc))
raise