"""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) class BackupExportJobAdmin(admin.ModelAdmin): """Admin для задач backup-экспорта.""" list_display = [ "id", "actual_date", "status", "task_id", "requested_by", "organizations_count", "archive_size", "started_at", "completed_at", ] list_filter = ["status", "actual_date", "created_at", "completed_at"] search_fields = ["task_id", "checksum_sha256", "archive_filename"] readonly_fields = [ "task_id", "archive_path", "archive_filename", "checksum_filename", "checksum_sha256", "archive_size", "organizations_count", "error", "started_at", "completed_at", "created_at", "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)