feat(admin): improve uploads and dashboard UX

This commit is contained in:
2026-03-23 16:07:11 +01:00
parent 45bca018b5
commit ef9763692d
22 changed files with 2531 additions and 212 deletions

View File

@@ -15,6 +15,7 @@ from apps.parsers.models import (
Proxy,
)
from apps.parsers.serializers import FNSFileUploadSerializer, FNSZipUploadSerializer
from apps.parsers.services import ProxyToolsSyncError, ProxyToolsSyncService
from django.contrib import admin, messages
from django.shortcuts import redirect
from django.template.response import TemplateResponse
@@ -26,6 +27,7 @@ from django.utils.html import format_html
class ProxyAdmin(admin.ModelAdmin):
"""Admin для прокси-серверов."""
change_list_template = "admin/parsers/proxy/change_list.html"
list_display = [
"address",
"country_code",
@@ -73,6 +75,78 @@ class ProxyAdmin(admin.ModelAdmin):
is_active_badge.short_description = "Статус"
is_active_badge.admin_order_field = "is_active"
def get_urls(self):
urls = super().get_urls()
custom_urls = [
path(
"sync-proxy-tools/",
self.admin_site.admin_view(self.sync_proxy_tools_view),
name="parsers_proxy_sync_proxy_tools",
),
]
return custom_urls + urls
def changelist_view(self, request, extra_context=None):
extra_context = extra_context or {}
extra_context["sync_proxy_tools_url"] = reverse(
"admin:parsers_proxy_sync_proxy_tools"
)
return super().changelist_view(request, extra_context=extra_context)
def sync_proxy_tools_view(self, request):
changelist_url = reverse("admin:parsers_proxy_changelist")
if request.method != "POST":
self.message_user(
request,
"Обновление списка прокси доступно только через POST.",
level=messages.WARNING,
)
return redirect(changelist_url)
try:
result = ProxyToolsSyncService.sync_ru_proxies()
except ProxyToolsSyncError as exc:
self.message_user(
request,
f"Ошибка обновления списка прокси: {exc}",
level=messages.ERROR,
)
return redirect(changelist_url)
if result.get("status") == "success":
self.message_user(
request,
(
"Список прокси обновлён: "
f"загружено {result['fetched']}, "
f"создано {result['created']}, "
f"обновлено {result['updated']}, "
f"деактивировано {result['deactivated']}."
),
level=messages.SUCCESS,
)
elif result.get("status") == "skipped":
reason = result.get("reason")
message = (
"Обновление списка прокси пропущено: не задан PROXY_TOOLS_API_KEY."
if reason == "missing_api_key"
else f"Обновление списка прокси пропущено: {reason}."
)
self.message_user(
request,
message,
level=messages.WARNING,
)
else:
self.message_user(
request,
"Обновление списка прокси завершилось без результата.",
level=messages.WARNING,
)
return redirect(changelist_url)
actions = ["activate_proxies", "deactivate_proxies", "reset_fail_count"]
@admin.action(description="Активировать выбранные прокси")
@@ -770,6 +844,45 @@ class FinancialReportAdmin(admin.ModelAdmin):
)
return super().changelist_view(request, extra_context=extra_context)
def _message_sync_upload_result(self, request, result, *, archive: bool) -> None:
noun = "файлов из архива" if archive else "файлов"
if result.processed:
self.message_user(
request,
f"Успешно обработано {noun}: {result.processed}.",
level=messages.SUCCESS,
)
if result.skipped:
self.message_user(
request,
f"Пропущено {noun}: {result.skipped}.",
level=messages.WARNING,
)
if result.failed:
self.message_user(
request,
f"Не удалось обработать {noun}: {result.failed}.",
level=messages.ERROR,
)
if result.invalid:
self.message_user(
request,
f"Невалидных элементов в архиве: {result.invalid}.",
level=messages.WARNING,
)
if not result.processed and not result.skipped and not result.failed:
empty_message = (
"Архив не содержит подходящих файлов."
if archive
else "Файлы не были обработаны."
)
self.message_user(
request,
empty_message,
level=messages.WARNING,
)
def upload_excel_view(self, request):
changelist_url = reverse("admin:parsers_financialreport_changelist")
@@ -786,39 +899,19 @@ class FinancialReportAdmin(admin.ModelAdmin):
return redirect(changelist_url)
try:
result = FNSUploadService.queue_uploaded_files(
result = FNSUploadService.process_uploaded_files_sync(
files=serializer.validated_data["files"],
requested_by_id=request.user.id,
)
except Exception as exc: # noqa: BLE001
self.message_user(
request,
f"Ошибка постановки файлов в очередь: {exc}",
f"Ошибка синхронной обработки файлов: {exc}",
level=messages.ERROR,
)
return redirect(changelist_url)
if result.queued:
self.message_user(
request,
"Файлов поставлено в очередь: "
f"{result.queued}. Task IDs: {', '.join(result.task_ids[:5])}",
level=messages.SUCCESS,
)
if result.skipped:
self.message_user(
request,
"Пропущено файлов: "
f"{result.skipped} (дубликаты или уже обрабатываются).",
level=messages.WARNING,
)
if not result.queued and not result.skipped:
self.message_user(
request,
"Файлы не были обработаны.",
level=messages.WARNING,
)
self._message_sync_upload_result(request, result, archive=False)
return redirect(changelist_url)
context = {
@@ -850,7 +943,7 @@ class FinancialReportAdmin(admin.ModelAdmin):
return redirect(changelist_url)
try:
result = FNSUploadService.queue_zip_archive(
result = FNSUploadService.process_zip_archive_sync(
archive_file=serializer.validated_data["file"],
requested_by_id=request.user.id,
)
@@ -862,32 +955,7 @@ class FinancialReportAdmin(admin.ModelAdmin):
)
return redirect(changelist_url)
if result.queued:
self.message_user(
request,
"Файлов из архива поставлено в очередь: "
f"{result.queued}. Task IDs: {', '.join(result.task_ids[:5])}",
level=messages.SUCCESS,
)
if result.skipped:
self.message_user(
request,
f"Пропущено файлов из архива: {result.skipped}.",
level=messages.WARNING,
)
if result.invalid:
self.message_user(
request,
f"Невалидных элементов в архиве: {result.invalid}.",
level=messages.WARNING,
)
if not result.queued and not result.skipped and not result.invalid:
self.message_user(
request,
"Архив не содержит подходящих файлов.",
level=messages.WARNING,
)
self._message_sync_upload_result(request, result, archive=True)
return redirect(changelist_url)
context = {