from django.contrib import admin from django.utils.html import format_html from django.urls import reverse from django.utils.safestring import mark_safe from .models import Website, CrawlTask, CrawledContent, CrawlLog, SearchKeyword, MediaFile from .tasks import crawl_websites_task @admin.register(Website) class WebsiteAdmin(admin.ModelAdmin): list_display = ['name', 'region', 'url', 'is_active', 'created_at'] list_filter = ['region', 'is_active', 'created_at'] search_fields = ['name', 'url', 'region'] list_editable = ['is_active'] ordering = ['region', 'name'] @admin.register(CrawlTask) class CrawlTaskAdmin(admin.ModelAdmin): list_display = ['name', 'status', 'created_by', 'progress_display', 'created_at', 'completed_at'] list_filter = ['status', 'created_by', 'created_at'] search_fields = ['name', 'keywords'] readonly_fields = ['created_at', 'started_at', 'completed_at', 'progress_display'] filter_horizontal = ['websites'] actions = ['execute_crawl_task'] def progress_display(self, obj): """显示任务进度""" if obj.status == 'completed': color = 'green' elif obj.status == 'failed': color = 'red' elif obj.status == 'running': color = 'orange' else: color = 'gray' return format_html( '{}%', color, f'{obj.progress_percentage:.1f} ({obj.crawled_pages}/{obj.total_pages})' ) progress_display.short_description = '进度' def execute_crawl_task(self, request, queryset): """执行选中的爬取任务""" for task in queryset: # 更新任务状态为pending task.status = 'pending' task.save() # 异步执行爬取任务 crawl_websites_task.delay(task.id) self.message_user(request, f"已启动 {queryset.count()} 个爬取任务。") execute_crawl_task.short_description = "执行选中的爬取任务" @admin.register(CrawledContent) class CrawledContentAdmin(admin.ModelAdmin): list_display = ['title_short', 'website', 'task', 'keywords_matched', 'media_count', 'publish_date', 'is_local_saved', 'created_at'] list_filter = ['website', 'task', 'created_at', 'publish_date', 'is_local_saved'] search_fields = ['title', 'content', 'keywords_matched'] readonly_fields = ['created_at', 'preview_content', 'media_files_display'] ordering = ['-created_at'] def title_short(self, obj): """显示缩短的标题""" return obj.title[:50] + '...' if len(obj.title) > 50 else obj.title title_short.short_description = '标题' def media_count(self, obj): """显示媒体文件数量""" count = obj.media_files.count() if count > 0: return format_html( '{}', count ) return "0" media_count.short_description = '媒体文件' def preview_content(self, obj): """预览内容""" if obj.is_local_saved: url = reverse('preview_crawled_content', args=[obj.id]) return format_html( '预览文章', url ) elif obj.content: return format_html( '
{}
', obj.get_preview_content(500) ) else: return "无内容" preview_content.short_description = '内容预览' def media_files_display(self, obj): """显示媒体文件列表""" media_files = obj.media_files.all() if not media_files: return "无媒体文件" html = "
" for media_file in media_files: if media_file.media_type == 'image': html += format_html( '
' '图片: {}
' '
' '大小: {}' '
', media_file.alt_text or '无标题', media_file.local_file.name, media_file.file_size_display ) elif media_file.media_type == 'video': html += format_html( '
' '视频:
' '
' '大小: {}' '
', media_file.local_file.name, media_file.mime_type, media_file.file_size_display ) else: html += format_html( '
' '{}: 下载
' '大小: {}' '
', media_file.get_media_type_display(), media_file.local_file.name, media_file.file_size_display ) html += "
" return format_html(html) media_files_display.short_description = '媒体文件' @admin.register(CrawlLog) class CrawlLogAdmin(admin.ModelAdmin): list_display = ['level', 'message_short', 'website', 'task', 'created_at'] list_filter = ['level', 'website', 'task', 'created_at'] search_fields = ['message'] readonly_fields = ['created_at'] ordering = ['-created_at'] def message_short(self, obj): """显示缩短的消息""" return obj.message[:100] + '...' if len(obj.message) > 100 else obj.message message_short.short_description = '消息' @admin.register(MediaFile) class MediaFileAdmin(admin.ModelAdmin): list_display = ['content', 'media_type', 'file_size_display', 'mime_type', 'created_at'] list_filter = ['media_type', 'created_at'] search_fields = ['content__title', 'original_url', 'alt_text'] readonly_fields = ['created_at', 'file_size_display', 'media_preview'] ordering = ['-created_at'] def media_preview(self, obj): """媒体文件预览""" if obj.media_type == 'image' and obj.local_file: return format_html( '', obj.local_file.name ) elif obj.media_type == 'video' and obj.local_file: return format_html( '', obj.local_file.name, obj.mime_type ) elif obj.media_type == 'audio' and obj.local_file: return format_html( '', obj.local_file.name, obj.mime_type ) else: return "无预览" media_preview.short_description = '预览' @admin.register(SearchKeyword) class SearchKeywordAdmin(admin.ModelAdmin): list_display = ['keyword', 'is_active', 'created_at', 'last_used'] list_filter = ['is_active', 'created_at', 'last_used'] search_fields = ['keyword'] list_editable = ['is_active'] ordering = ['-last_used', '-created_at']