from django.contrib import admin
from django.utils.html import format_html
from import_export.admin import ImportExportMixin, ImportExportModelAdmin
from simple_history.admin import SimpleHistoryAdmin
from .models import CloudKitConfiguration, CloudKitSyncState, CloudKitSyncJob
from .resources import CloudKitConfigurationResource, CloudKitSyncStateResource, CloudKitSyncJobResource
@admin.register(CloudKitConfiguration)
class CloudKitConfigurationAdmin(ImportExportMixin, SimpleHistoryAdmin):
resource_class = CloudKitConfigurationResource
list_display = [
'name',
'environment',
'container_id',
'is_active_badge',
'auto_sync_after_scrape',
'batch_size',
]
list_filter = ['environment', 'is_active']
search_fields = ['name', 'container_id']
readonly_fields = ['created_at', 'updated_at']
fieldsets = [
(None, {
'fields': ['name', 'environment', 'is_active']
}),
('CloudKit Credentials', {
'fields': ['container_id', 'key_id', 'private_key', 'private_key_path'],
'description': 'Enter your private key content directly OR provide a file path'
}),
('Sync Settings', {
'fields': ['batch_size', 'auto_sync_after_scrape']
}),
('Metadata', {
'fields': ['created_at', 'updated_at'],
'classes': ['collapse']
}),
]
actions = ['run_sync', 'test_connection']
def is_active_badge(self, obj):
if obj.is_active:
return format_html(
'● ACTIVE'
)
return format_html('○ Inactive')
is_active_badge.short_description = 'Status'
@admin.action(description='Run sync with selected configuration')
def run_sync(self, request, queryset):
from cloudkit.tasks import run_cloudkit_sync
for config in queryset:
run_cloudkit_sync.delay(config.id)
self.message_user(request, f'Started {queryset.count()} sync jobs.')
@admin.action(description='Test CloudKit connection')
def test_connection(self, request, queryset):
from django.contrib import messages
for config in queryset:
try:
client = config.get_client()
if client.test_connection():
self.message_user(
request,
f'✓ {config.name}: Connection successful!',
messages.SUCCESS
)
else:
self.message_user(
request,
f'✗ {config.name}: Connection failed',
messages.ERROR
)
except Exception as e:
self.message_user(
request,
f'✗ {config.name}: {str(e)}',
messages.ERROR
)
@admin.register(CloudKitSyncState)
class CloudKitSyncStateAdmin(ImportExportModelAdmin):
resource_class = CloudKitSyncStateResource
list_display = [
'record_id',
'record_type',
'sync_status_badge',
'last_synced',
'retry_count',
]
list_filter = ['sync_status', 'record_type']
search_fields = ['record_id', 'cloudkit_record_name']
ordering = ['-updated_at']
readonly_fields = [
'record_type',
'record_id',
'cloudkit_record_name',
'local_hash',
'remote_change_tag',
'last_synced',
'last_error',
'retry_count',
'created_at',
'updated_at',
]
actions = ['mark_pending', 'retry_failed']
def has_add_permission(self, request):
return False
def sync_status_badge(self, obj):
colors = {
'pending': '#f0ad4e',
'synced': '#5cb85c',
'failed': '#d9534f',
'deleted': '#999',
}
color = colors.get(obj.sync_status, '#999')
return format_html(
'{}',
color,
obj.sync_status.upper()
)
sync_status_badge.short_description = 'Status'
@admin.action(description='Mark selected as pending sync')
def mark_pending(self, request, queryset):
updated = queryset.update(sync_status='pending')
self.message_user(request, f'{updated} records marked as pending.')
@admin.action(description='Retry failed syncs')
def retry_failed(self, request, queryset):
updated = queryset.filter(sync_status='failed').update(
sync_status='pending',
retry_count=0
)
self.message_user(request, f'{updated} failed records queued for retry.')
@admin.register(CloudKitSyncJob)
class CloudKitSyncJobAdmin(ImportExportModelAdmin):
resource_class = CloudKitSyncJobResource
list_display = [
'id',
'configuration',
'status_badge',
'triggered_by',
'started_at',
'duration_display',
'records_summary',
]
list_filter = ['status', 'configuration', 'triggered_by']
date_hierarchy = 'created_at'
ordering = ['-created_at']
readonly_fields = [
'configuration',
'status',
'triggered_by',
'started_at',
'finished_at',
'duration_display',
'records_synced',
'records_created',
'records_updated',
'records_deleted',
'records_failed',
'sport_filter',
'record_type_filter',
'error_message',
'celery_task_id',
'created_at',
'updated_at',
]
def has_add_permission(self, request):
return False
def has_change_permission(self, request, obj=None):
return False
def status_badge(self, obj):
colors = {
'pending': '#999',
'running': '#f0ad4e',
'completed': '#5cb85c',
'failed': '#d9534f',
'cancelled': '#777',
}
color = colors.get(obj.status, '#999')
return format_html(
'{}',
color,
obj.status.upper()
)
status_badge.short_description = 'Status'
def records_summary(self, obj):
if obj.records_synced == 0 and obj.status != 'completed':
return '-'
return format_html(
''
'{} synced ({} new)',
obj.records_created, obj.records_updated, obj.records_deleted, obj.records_failed,
obj.records_synced, obj.records_created
)
records_summary.short_description = 'Records'