feat(parsers): add proverki.gov.ru parser with sync_inspections task
- Add InspectionRecord model with is_federal_law_248, data_year, data_month fields - Add ProverkiClient with Playwright support for JS-rendered portal - Add streaming XML parser for large files (>50MB) - Add sync_inspections task with incremental loading logic - Starts from 01.01.2025 if DB is empty - Loads both FZ-294 and FZ-248 inspections - Stops after 2 consecutive empty months - Add InspectionService methods: get_last_loaded_period, has_data_for_period - Add Minpromtorg parsers (certificates, manufacturers) - Add Django Admin for parser models - Update README with parsers documentation and changelog
This commit is contained in:
152
src/apps/core/admin.py
Normal file
152
src/apps/core/admin.py
Normal file
@@ -0,0 +1,152 @@
|
||||
"""
|
||||
Admin configuration for core app.
|
||||
"""
|
||||
|
||||
from apps.core.models import BackgroundJob
|
||||
from django.contrib import admin
|
||||
from django.utils.html import format_html
|
||||
|
||||
|
||||
@admin.register(BackgroundJob)
|
||||
class BackgroundJobAdmin(admin.ModelAdmin):
|
||||
"""Admin для фоновых задач."""
|
||||
|
||||
list_display = [
|
||||
"task_name_short",
|
||||
"status_badge",
|
||||
"progress_bar",
|
||||
"user_id",
|
||||
"started_at",
|
||||
"duration_display",
|
||||
"created_at",
|
||||
]
|
||||
list_filter = ["status", "task_name", "created_at"]
|
||||
search_fields = ["task_id", "task_name", "error"]
|
||||
readonly_fields = [
|
||||
"id",
|
||||
"task_id",
|
||||
"task_name",
|
||||
"status",
|
||||
"progress",
|
||||
"progress_message",
|
||||
"result",
|
||||
"error",
|
||||
"traceback",
|
||||
"started_at",
|
||||
"completed_at",
|
||||
"user_id",
|
||||
"meta",
|
||||
"created_at",
|
||||
"updated_at",
|
||||
]
|
||||
ordering = ["-created_at"]
|
||||
list_per_page = 50
|
||||
date_hierarchy = "created_at"
|
||||
|
||||
fieldsets = (
|
||||
(
|
||||
"Задача",
|
||||
{"fields": ("id", "task_id", "task_name", "user_id")},
|
||||
),
|
||||
(
|
||||
"Статус",
|
||||
{"fields": ("status", "progress", "progress_message")},
|
||||
),
|
||||
(
|
||||
"Результат",
|
||||
{"fields": ("result",), "classes": ("collapse",)},
|
||||
),
|
||||
(
|
||||
"Ошибка",
|
||||
{"fields": ("error", "traceback"), "classes": ("collapse",)},
|
||||
),
|
||||
(
|
||||
"Время",
|
||||
{"fields": ("started_at", "completed_at", "created_at", "updated_at")},
|
||||
),
|
||||
(
|
||||
"Метаданные",
|
||||
{"fields": ("meta",), "classes": ("collapse",)},
|
||||
),
|
||||
)
|
||||
|
||||
def task_name_short(self, obj):
|
||||
"""Сокращённое имя задачи."""
|
||||
name = obj.task_name or ""
|
||||
# Берём только последнюю часть пути
|
||||
parts = name.split(".")
|
||||
if len(parts) > 2:
|
||||
return parts[-1]
|
||||
return name
|
||||
|
||||
task_name_short.short_description = "Задача"
|
||||
task_name_short.admin_order_field = "task_name"
|
||||
|
||||
def status_badge(self, obj):
|
||||
"""Цветной бейдж статуса."""
|
||||
colors = {
|
||||
"pending": "#6c757d",
|
||||
"started": "#007bff",
|
||||
"success": "#28a745",
|
||||
"failure": "#dc3545",
|
||||
"revoked": "#ffc107",
|
||||
"retry": "#17a2b8",
|
||||
}
|
||||
color = colors.get(obj.status, "#6c757d")
|
||||
return format_html(
|
||||
'<span style="color: white; background: {}; padding: 3px 10px; '
|
||||
'border-radius: 3px;">{}</span>',
|
||||
color,
|
||||
obj.get_status_display(),
|
||||
)
|
||||
|
||||
status_badge.short_description = "Статус"
|
||||
status_badge.admin_order_field = "status"
|
||||
|
||||
def progress_bar(self, obj):
|
||||
"""Прогресс-бар."""
|
||||
progress = obj.progress or 0
|
||||
color = "#28a745" if progress == 100 else "#007bff"
|
||||
return format_html(
|
||||
'<div style="width: 100px; background: #e9ecef; border-radius: 3px;">'
|
||||
'<div style="width: {}px; background: {}; height: 18px; border-radius: 3px; '
|
||||
'text-align: center; color: white; font-size: 11px; line-height: 18px;">'
|
||||
"{}%</div></div>",
|
||||
progress,
|
||||
color,
|
||||
progress,
|
||||
)
|
||||
|
||||
progress_bar.short_description = "Прогресс"
|
||||
|
||||
def duration_display(self, obj):
|
||||
"""Длительность выполнения."""
|
||||
duration = obj.duration
|
||||
if duration is None:
|
||||
return "-"
|
||||
if duration < 60:
|
||||
return f"{duration:.1f} сек"
|
||||
return f"{duration / 60:.1f} мин"
|
||||
|
||||
duration_display.short_description = "Длительность"
|
||||
|
||||
def has_add_permission(self, request):
|
||||
"""Запретить создание записей вручную."""
|
||||
return False
|
||||
|
||||
def has_change_permission(self, request, obj=None):
|
||||
"""Запретить редактирование записей."""
|
||||
return False
|
||||
|
||||
actions = ["revoke_jobs"]
|
||||
|
||||
@admin.action(description="Отменить выбранные задачи")
|
||||
def revoke_jobs(self, request, queryset):
|
||||
from celery import current_app
|
||||
|
||||
count = 0
|
||||
for job in queryset.filter(status__in=["pending", "started"]):
|
||||
current_app.control.revoke(job.task_id, terminate=True)
|
||||
job.revoke()
|
||||
count += 1
|
||||
self.message_user(request, f"Отменено {count} задач")
|
||||
Reference in New Issue
Block a user