""" Admin configuration for osint app. """ from django.contrib import admin from django.utils.html import format_html from django.urls import reverse from django.utils import timezone from datetime import timedelta from .models import ( OSINTTask, OSINTResult, OSINTConfiguration, SeedWebsite, OSINTKeyword, CrawledContent, AutoGeneratedReport ) @admin.register(OSINTTask) class OSINTTaskAdmin(admin.ModelAdmin): """OSINT task admin.""" list_display = ('report', 'task_type', 'status', 'created_at', 'completed_at') list_filter = ('task_type', 'status', 'created_at') search_fields = ('report__title', 'error_message') readonly_fields = ('created_at', 'started_at', 'completed_at') date_hierarchy = 'created_at' @admin.register(OSINTResult) class OSINTResultAdmin(admin.ModelAdmin): """OSINT result admin.""" list_display = ('report', 'source', 'data_type', 'confidence_level', 'is_verified', 'collected_at') list_filter = ('data_type', 'is_verified', 'collected_at') search_fields = ('report__title', 'source') readonly_fields = ('collected_at', 'updated_at') date_hierarchy = 'collected_at' @admin.register(OSINTConfiguration) class OSINTConfigurationAdmin(admin.ModelAdmin): """OSINT configuration admin.""" list_display = ('service_name', 'is_active', 'rate_limit', 'updated_at') list_filter = ('is_active',) search_fields = ('service_name',) @admin.register(SeedWebsite) class SeedWebsiteAdmin(admin.ModelAdmin): """Seed website admin.""" list_display = ('name', 'url', 'is_active', 'priority', 'last_crawled_at', 'pages_crawled', 'matches_found', 'status_indicator') list_filter = ('is_active', 'priority', 'created_at') search_fields = ('name', 'url', 'description') readonly_fields = ('last_crawled_at', 'pages_crawled', 'matches_found', 'created_at', 'updated_at') fieldsets = ( ('Basic Information', { 'fields': ('name', 'url', 'description', 'is_active', 'priority', 'created_by') }), ('Crawling Configuration', { 'fields': ('crawl_depth', 'crawl_interval_hours', 'allowed_domains', 'user_agent') }), ('Statistics', { 'fields': ('last_crawled_at', 'pages_crawled', 'matches_found'), 'classes': ('collapse',) }), ('Timestamps', { 'fields': ('created_at', 'updated_at'), 'classes': ('collapse',) }), ) date_hierarchy = 'created_at' def status_indicator(self, obj): """Show visual status indicator.""" if not obj.is_active: return format_html(' Inactive') if not obj.last_crawled_at: return format_html(' Never Crawled') hours_since = (timezone.now() - obj.last_crawled_at).total_seconds() / 3600 if hours_since > obj.crawl_interval_hours * 2: return format_html(' Overdue') elif hours_since > obj.crawl_interval_hours: return format_html(' Due Soon') else: return format_html(' Up to Date') status_indicator.short_description = 'Status' def save_model(self, request, obj, form, change): if not change: # New object obj.created_by = request.user super().save_model(request, obj, form, change) @admin.register(OSINTKeyword) class OSINTKeywordAdmin(admin.ModelAdmin): """OSINT keyword admin.""" list_display = ('name', 'keyword', 'keyword_type', 'is_active', 'confidence_score', 'auto_approve', 'match_count') list_filter = ('is_active', 'keyword_type', 'auto_approve', 'created_at') search_fields = ('name', 'keyword', 'description') readonly_fields = ('created_at', 'updated_at', 'match_count') fieldsets = ( ('Basic Information', { 'fields': ('name', 'keyword', 'description', 'keyword_type', 'is_active', 'created_by') }), ('Matching Configuration', { 'fields': ('case_sensitive', 'confidence_score', 'auto_approve') }), ('Statistics', { 'fields': ('match_count',), 'classes': ('collapse',) }), ('Timestamps', { 'fields': ('created_at', 'updated_at'), 'classes': ('collapse',) }), ) date_hierarchy = 'created_at' def match_count(self, obj): """Count how many times this keyword has matched.""" return obj.matched_contents.count() match_count.short_description = 'Total Matches' def save_model(self, request, obj, form, change): if not change: # New object obj.created_by = request.user super().save_model(request, obj, form, change) @admin.register(CrawledContent) class CrawledContentAdmin(admin.ModelAdmin): """Crawled content admin.""" list_display = ('title', 'url', 'seed_website', 'match_count', 'confidence_score', 'has_potential_scam', 'crawled_at') list_filter = ('has_potential_scam', 'seed_website', 'crawled_at', 'http_status') search_fields = ('title', 'url', 'content') readonly_fields = ('crawled_at', 'content_hash', 'http_status') fieldsets = ( ('Content Information', { 'fields': ('seed_website', 'url', 'title', 'content', 'html_content') }), ('Analysis', { 'fields': ('matched_keywords', 'match_count', 'confidence_score', 'has_potential_scam') }), ('Metadata', { 'fields': ('http_status', 'content_hash', 'crawled_at'), 'classes': ('collapse',) }), ) date_hierarchy = 'crawled_at' filter_horizontal = ('matched_keywords',) def get_queryset(self, request): return super().get_queryset(request).select_related('seed_website').prefetch_related('matched_keywords') @admin.register(AutoGeneratedReport) class AutoGeneratedReportAdmin(admin.ModelAdmin): """Auto-generated report admin.""" list_display = ('title', 'source_url', 'status', 'confidence_score', 'reviewed_by', 'reviewed_at', 'view_report_link') list_filter = ('status', 'confidence_score', 'created_at', 'reviewed_at') search_fields = ('title', 'description', 'source_url') readonly_fields = ('crawled_content', 'created_at', 'updated_at', 'published_at') fieldsets = ( ('Report Information', { 'fields': ('crawled_content', 'title', 'description', 'source_url') }), ('Analysis', { 'fields': ('matched_keywords', 'confidence_score') }), ('Review', { 'fields': ('status', 'review_notes', 'reviewed_by', 'reviewed_at', 'report') }), ('Publication', { 'fields': ('published_at',), 'classes': ('collapse',) }), ('Timestamps', { 'fields': ('created_at', 'updated_at'), 'classes': ('collapse',) }), ) date_hierarchy = 'created_at' filter_horizontal = ('matched_keywords',) actions = ['approve_reports', 'reject_reports', 'publish_reports'] def view_report_link(self, obj): """Link to the generated report if exists.""" if obj.report: url = reverse('admin:reports_scamreport_change', args=[obj.report.pk]) return format_html('View Report #{}', url, obj.report.pk) return '-' view_report_link.short_description = 'Linked Report' def get_queryset(self, request): return super().get_queryset(request).select_related( 'crawled_content', 'reviewed_by', 'report' ).prefetch_related('matched_keywords') @admin.action(description='Approve selected reports') def approve_reports(self, request, queryset): """Approve selected auto-generated reports.""" from django.utils import timezone updated = queryset.filter(status='pending').update( status='approved', reviewed_by=request.user, reviewed_at=timezone.now() ) self.message_user(request, f'{updated} reports approved.') @admin.action(description='Reject selected reports') def reject_reports(self, request, queryset): """Reject selected auto-generated reports.""" from django.utils import timezone updated = queryset.filter(status='pending').update( status='rejected', reviewed_by=request.user, reviewed_at=timezone.now() ) self.message_user(request, f'{updated} reports rejected.') @admin.action(description='Publish selected reports') def publish_reports(self, request, queryset): """Publish approved reports.""" from django.utils import timezone from reports.models import ScamReport from reports.models import ScamTag published = 0 for auto_report in queryset.filter(status='approved'): if not auto_report.report: # Create the actual scam report report = ScamReport.objects.create( title=auto_report.title, description=auto_report.description, reported_url=auto_report.source_url, scam_type='other', # Default type status='verified', # Auto-verified since reviewed verification_score=auto_report.confidence_score, is_public=True, is_anonymous=True, # System-generated is_auto_discovered=True, # Mark as auto-discovered reporter_ip=None, # System-generated ) auto_report.report = report auto_report.status = 'published' auto_report.published_at = timezone.now() auto_report.save() published += 1 self.message_user(request, f'{published} reports published.')