"""
Admin interface for sync management
"""
from django.contrib import admin
from django_q.models import Task, Schedule
from django.utils.html import format_html
from django.urls import reverse
from django.utils.safestring import mark_safe
import json
class SyncTaskAdmin(admin.ModelAdmin):
"""Admin interface for monitoring sync tasks"""
list_display = [
'id', 'task_name', 'task_status', 'started_display',
'stopped_display', 'result_display', 'actions_display'
]
list_filter = ['success', 'stopped', 'group']
search_fields = ['name', 'func', 'group']
readonly_fields = [
'id', 'name', 'func', 'args', 'kwargs', 'started', 'stopped',
'result', 'success', 'group', 'attempt_count', 'retries',
'time_taken', 'stopped_early'
]
def task_name(self, obj):
"""Display task name with group if available"""
if obj.group:
return f"{obj.name} ({obj.group})"
return obj.name
task_name.short_description = 'Task Name'
def task_status(self, obj):
"""Display task status with color coding"""
if obj.success:
color = 'green'
status = 'SUCCESS'
elif obj.stopped:
color = 'red'
status = 'FAILED'
else:
color = 'orange'
status = 'PENDING'
return format_html(
'{}',
color, status
)
task_status.short_description = 'Status'
def started_display(self, obj):
"""Format started time"""
if obj.started:
return obj.started.strftime('%Y-%m-%d %H:%M:%S')
return '--'
started_display.short_description = 'Started'
def stopped_display(self, obj):
"""Format stopped time"""
if obj.stopped:
return obj.stopped.strftime('%Y-%m-%d %H:%M:%S')
return '--'
stopped_display.short_description = 'Stopped'
def result_display(self, obj):
"""Display result summary"""
if not obj.result:
return '--'
try:
result = json.loads(obj.result) if isinstance(obj.result, str) else obj.result
if isinstance(result, dict):
if 'summary' in result:
summary = result['summary']
return format_html(
"Sources: {}, Success: {}, Failed: {}",
summary.get('total_sources', 0),
summary.get('successful', 0),
summary.get('failed', 0)
)
elif 'error' in result:
return format_html(
'Error: {}',
result['error'][:100]
)
return str(result)[:100] + '...' if len(str(result)) > 100 else str(result)
except (json.JSONDecodeError, TypeError):
return str(obj.result)[:100] + '...' if len(str(obj.result)) > 100 else str(obj.result)
result_display.short_description = 'Result Summary'
def actions_display(self, obj):
"""Display action buttons"""
actions = []
if obj.group:
# Link to view all tasks in this group
url = reverse('admin:django_q_task_changelist') + f'?group__exact={obj.group}'
actions.append(
f'View Group'
)
return mark_safe(' '.join(actions))
actions_display.short_description = 'Actions'
def has_add_permission(self, request):
"""Disable adding tasks through admin"""
return False
def has_change_permission(self, request, obj=None):
"""Disable editing tasks through admin"""
return False
def has_delete_permission(self, request, obj=None):
"""Allow deleting tasks"""
return True
class SyncScheduleAdmin(admin.ModelAdmin):
"""Admin interface for managing scheduled sync tasks"""
list_display = [
'name', 'func', 'schedule_type', 'next_run_display',
'repeats_display', 'enabled_display'
]
list_filter = ['repeats', 'schedule_type', 'enabled']
search_fields = ['name', 'func']
readonly_fields = ['last_run', 'next_run']
fieldsets = (
('Basic Information', {
'fields': ('name', 'func', 'enabled')
}),
('Schedule Configuration', {
'fields': (
'schedule_type', 'repeats', 'cron', 'next_run',
'minutes', 'hours', 'days', 'weeks'
)
}),
('Task Arguments', {
'fields': ('args', 'kwargs'),
'classes': ('collapse',)
}),
('Runtime Information', {
'fields': ('last_run', 'next_run'),
'classes': ('collapse',)
})
)
def schedule_type_display(self, obj):
"""Display schedule type with icon"""
icons = {
'O': '🕐', # Once
'I': '🔄', # Interval
'C': '📅', # Cron
'D': '📆', # Daily
'W': '📋', # Weekly
'M': '📊', # Monthly
'Y': '📈', # Yearly
'H': '⏰', # Hourly
'Q': '📈', # Quarterly
}
icon = icons.get(obj.schedule_type, '❓')
type_names = {
'O': 'Once',
'I': 'Interval',
'C': 'Cron',
'D': 'Daily',
'W': 'Weekly',
'M': 'Monthly',
'Y': 'Yearly',
'H': 'Hourly',
'Q': 'Quarterly',
}
name = type_names.get(obj.schedule_type, obj.schedule_type)
return format_html('{} {}', icon, name)
schedule_type_display.short_description = 'Schedule Type'
def next_run_display(self, obj):
"""Format next run time"""
if obj.next_run:
return obj.next_run.strftime('%Y-%m-%d %H:%M:%S')
return '--'
next_run_display.short_description = 'Next Run'
def repeats_display(self, obj):
"""Display repeat count"""
if obj.repeats == -1:
return '∞ (Forever)'
return str(obj.repeats)
repeats_display.short_description = 'Repeats'
def enabled_display(self, obj):
"""Display enabled status with color"""
if obj.enabled:
return format_html(
'✓ Enabled'
)
else:
return format_html(
'✗ Disabled'
)
enabled_display.short_description = 'Status'
# Custom admin site for sync management
class SyncAdminSite(admin.AdminSite):
"""Custom admin site for sync management"""
site_header = 'ATS Sync Management'
site_title = 'Sync Management'
index_title = 'Sync Task Management'
def get_urls(self):
"""Add custom URLs for sync management"""
from django.urls import path
from django.shortcuts import render
from django.http import JsonResponse
from recruitment.candidate_sync_service import CandidateSyncService
urls = super().get_urls()
custom_urls = [
path('sync-dashboard/', self.admin_view(self.sync_dashboard), name='sync_dashboard'),
path('api/sync-stats/', self.admin_view(self.sync_stats), name='sync_stats'),
]
return custom_urls + urls
def sync_dashboard(self, request):
"""Custom sync dashboard view"""
from django_q.models import Task
from django.db.models import Count, Q
from django.utils import timezone
from datetime import timedelta
# Get sync statistics
now = timezone.now()
last_24h = now - timedelta(hours=24)
last_7d = now - timedelta(days=7)
# Task counts
total_tasks = Task.objects.filter(func__contains='sync_hired_candidates').count()
successful_tasks = Task.objects.filter(
func__contains='sync_hired_candidates',
success=True
).count()
failed_tasks = Task.objects.filter(
func__contains='sync_hired_candidates',
success=False,
stopped__isnull=False
).count()
pending_tasks = Task.objects.filter(
func__contains='sync_hired_candidates',
success=False,
stopped__isnull=True
).count()
# Recent activity
recent_tasks = Task.objects.filter(
func__contains='sync_hired_candidates'
).order_by('-started')[:10]
# Success rate over time
last_24h_tasks = Task.objects.filter(
func__contains='sync_hired_candidates',
started__gte=last_24h
)
last_24h_success = last_24h_tasks.filter(success=True).count()
last_7d_tasks = Task.objects.filter(
func__contains='sync_hired_candidates',
started__gte=last_7d
)
last_7d_success = last_7d_tasks.filter(success=True).count()
context = {
**self.each_context(request),
'title': 'Sync Dashboard',
'total_tasks': total_tasks,
'successful_tasks': successful_tasks,
'failed_tasks': failed_tasks,
'pending_tasks': pending_tasks,
'success_rate': (successful_tasks / total_tasks * 100) if total_tasks > 0 else 0,
'last_24h_success_rate': (last_24h_success / last_24h_tasks.count() * 100) if last_24h_tasks.count() > 0 else 0,
'last_7d_success_rate': (last_7d_success / last_7d_tasks.count() * 100) if last_7d_tasks.count() > 0 else 0,
'recent_tasks': recent_tasks,
}
return render(request, 'admin/sync_dashboard.html', context)
def sync_stats(self, request):
"""API endpoint for sync statistics"""
from django_q.models import Task
from django.utils import timezone
from datetime import timedelta
now = timezone.now()
last_24h = now - timedelta(hours=24)
stats = {
'total_tasks': Task.objects.filter(func__contains='sync_hired_candidates').count(),
'successful_24h': Task.objects.filter(
func__contains='sync_hired_candidates',
success=True,
started__gte=last_24h
).count(),
'failed_24h': Task.objects.filter(
func__contains='sync_hired_candidates',
success=False,
stopped__gte=last_24h
).count(),
'pending_tasks': Task.objects.filter(
func__contains='sync_hired_candidates',
success=False,
stopped__isnull=True
).count(),
}
return JsonResponse(stats)
# Create custom admin site
sync_admin_site = SyncAdminSite(name='sync_admin')
# Register models with custom admin site
sync_admin_site.register(Task, SyncTaskAdmin)
sync_admin_site.register(Schedule, SyncScheduleAdmin)
# Also register with default admin site for access
admin.site.register(Task, SyncTaskAdmin)
admin.site.register(Schedule, SyncScheduleAdmin)