""" 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)