diff --git a/src/apps/backups/admin.py b/src/apps/backups/admin.py index 6092b9b..95721ed 100644 --- a/src/apps/backups/admin.py +++ b/src/apps/backups/admin.py @@ -1,7 +1,14 @@ """Admin для приложения backups.""" from apps.backups.models import BackupExportJob +from apps.backups.serializers import BackupExportRequestSerializer +from apps.backups.services import BackupExportError, BackupExportJobService from django.contrib import admin +from django.contrib import messages +from django.http import HttpResponse +from django.shortcuts import redirect +from django.urls import path, reverse +from django.utils import timezone @admin.register(BackupExportJob) @@ -36,3 +43,92 @@ class BackupExportJobAdmin(admin.ModelAdmin): "updated_at", ] ordering = ["-actual_date", "-created_at"] + change_list_template = "admin/backups/backupexportjob/change_list.html" + + def get_urls(self): + urls = super().get_urls() + custom_urls = [ + path( + "export/", + self.admin_site.admin_view(self.export_view), + name="backups_backupexportjob_export", + ), + ] + return custom_urls + urls + + def changelist_view(self, request, extra_context=None): + extra_context = extra_context or {} + extra_context["backup_export_url"] = reverse( + "admin:backups_backupexportjob_export" + ) + extra_context["backup_export_default_date"] = timezone.localdate().isoformat() + return super().changelist_view(request, extra_context=extra_context) + + def export_view(self, request): + changelist_url = reverse("admin:backups_backupexportjob_changelist") + + if request.method != "POST": + self.message_user( + request, + "Выгрузка backup доступна только через POST.", + level=messages.WARNING, + ) + return redirect(changelist_url) + + serializer = BackupExportRequestSerializer( + data={"actual_date": request.POST.get("actual_date")} + ) + if not serializer.is_valid(): + self.message_user( + request, + f"Некорректная дата: {serializer.errors}", + level=messages.ERROR, + ) + return redirect(changelist_url) + + 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, + ) + except BackupExportError as exc: + 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, + ) + return redirect(changelist_url) + + if result.action == "download": + try: + artifact = BackupExportJobService.consume_ready_archive( + actual_date=result.actual_date + ) + except BackupExportError as exc: + self.message_user(request, f"Ошибка загрузки backup: {exc}", level=messages.ERROR) + return redirect(changelist_url) + + response = HttpResponse(artifact.archive_bytes, content_type="application/zip") + response.status_code = 200 + response[ + "Content-Disposition" + ] = f'attachment; filename="{artifact.archive_filename}"' + response["X-Backup-SHA256"] = artifact.checksum_sha256 + response["X-Backup-Checksum-File"] = artifact.checksum_filename + response["X-Backup-Organizations"] = str(artifact.organizations_count) + response["X-Backup-Actual-Date"] = artifact.actual_date.isoformat() + return response + + self.message_user( + request, + f"Неожиданный статус задачи: {result.action}", + level=messages.ERROR, + ) + return redirect(changelist_url) diff --git a/src/apps/core/admin_dashboard.py b/src/apps/core/admin_dashboard.py index e9cae32..516a7cb 100644 --- a/src/apps/core/admin_dashboard.py +++ b/src/apps/core/admin_dashboard.py @@ -212,11 +212,26 @@ def build_admin_dashboard() -> dict[str, Any]: description="Синхронный импорт Excel по выбранному реестру", url_name="admin:registers_registerupload_upload_excel", ), + _build_quick_action( + label="Добавить загрузку реестра", + description="Создать запись загрузки реестра вручную", + url_name="admin:registers_registerupload_add", + ), _build_quick_action( label="ФНС Excel", description="Загрузить один или несколько файлов бухгалтерской отчётности", url_name="admin:parsers_financialreport_upload_excel", ), + _build_quick_action( + label="ФНС ZIP", + description="Загрузить архив файлов бухгалтерской отчетности", + url_name="admin:parsers_financialreport_upload_zip", + ), + _build_quick_action( + label="Выгрузить защищённый backup", + description="Сформировать архив с актуальными реестрами и связанными данными", + url_name="admin:backups_backupexportjob_changelist", + ), _build_quick_action( label="История обновлений", description="Проверить последние загрузки и ошибки по источникам", diff --git a/src/templates/admin/backups/backupexportjob/change_list.html b/src/templates/admin/backups/backupexportjob/change_list.html new file mode 100644 index 0000000..7e8f679 --- /dev/null +++ b/src/templates/admin/backups/backupexportjob/change_list.html @@ -0,0 +1,21 @@ +{% extends "admin/change_list.html" %} + +{% block object-tools-items %} +