""" Configuration de l'interface d'administration Django Créé par Marino ATOHOUN - FireWatch AI Project Amélioré par BlackBenAI Team pour un contrôle avancé """ 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 Contact, DetectionSession, Detection, AIModelStatus # Actions personnalisées @admin.action(description='Marquer comme lu') def make_read(modeladmin, request, queryset): queryset.update(is_read=True) @admin.action(description='Marquer comme non lu') def make_unread(modeladmin, request, queryset): queryset.update(is_read=False) @admin.register(Contact) class ContactAdmin(admin.ModelAdmin): """Administration des contacts - Par Marino ATOHOUN & BlackBenAI""" list_display = ('name', 'email', 'created_at', 'is_read', 'message_preview') list_filter = ('is_read', 'created_at') search_fields = ('name', 'email', 'message') readonly_fields = ('created_at',) list_editable = ('is_read',) ordering = ('-created_at',) actions = [make_read, make_unread] list_per_page = 20 def message_preview(self, obj): """Aperçu du message""" return obj.message[:50] + "..." if len(obj.message) > 50 else obj.message message_preview.short_description = "Aperçu du message" fieldsets = ( ('Informations de contact', { 'fields': ('name', 'email') }), ('Message', { 'fields': ('message',) }), ('Statut', { 'fields': ('is_read', 'created_at') }), ) class DetectionInline(admin.TabularInline): """Inline pour afficher les détections dans une session""" model = Detection extra = 0 readonly_fields = ('class_name', 'confidence', 'bbox_x', 'bbox_y', 'bbox_width', 'bbox_height', 'frame_number', 'timestamp') can_delete = False show_change_link = True @admin.register(DetectionSession) class DetectionSessionAdmin(admin.ModelAdmin): """Administration des sessions de détection - Par Marino ATOHOUN & BlackBenAI""" list_display = ('session_id_short', 'detection_type', 'created_at', 'is_processed', 'processing_time_display', 'detections_count', 'preview_image') list_filter = ('detection_type', 'is_processed', 'created_at') search_fields = ('session_id',) readonly_fields = ('session_id', 'created_at', 'processing_time', 'preview_original', 'preview_result') inlines = [DetectionInline] ordering = ('-created_at',) list_per_page = 20 date_hierarchy = 'created_at' def session_id_short(self, obj): return str(obj.session_id)[:8] + "..." session_id_short.short_description = "ID Session" def processing_time_display(self, obj): if obj.processing_time: return f"{obj.processing_time:.2f}s" return "-" processing_time_display.short_description = "Temps" def detections_count(self, obj): """Nombre de détections dans la session""" count = obj.detections.count() color = "green" if count > 0 else "gray" return format_html('{}', color, count) detections_count.short_description = "Détections" def preview_image(self, obj): if obj.result_file: return format_html('', obj.result_file.url) elif obj.original_file and obj.detection_type == 'image': return format_html('', obj.original_file.url) return "-" preview_image.short_description = "Aperçu" def preview_original(self, obj): if obj.original_file: if obj.detection_type == 'image': return format_html('', obj.original_file.url, obj.original_file.url) elif obj.detection_type == 'video': return format_html('', obj.original_file.url) return "Aucun fichier" preview_original.short_description = "Fichier original" def preview_result(self, obj): if obj.result_file: return format_html('', obj.result_file.url, obj.result_file.url) return "Aucun résultat" preview_result.short_description = "Résultat de détection" fieldsets = ( ('Informations de session', { 'fields': ('session_id', 'detection_type', 'created_at') }), ('Aperçu des fichiers', { 'fields': ('preview_original', 'preview_result'), 'description': 'Aperçu visuel des fichiers traités' }), ('Fichiers bruts', { 'fields': ('original_file', 'result_file'), 'classes': ('collapse',) }), ('Métriques de traitement', { 'fields': ('is_processed', 'processing_time') }), ) @admin.register(Detection) class DetectionAdmin(admin.ModelAdmin): """Administration des détections - Par Marino ATOHOUN & BlackBenAI""" list_display = ('id', 'session_link', 'class_name_colored', 'confidence_bar', 'frame_number', 'bbox_preview') list_filter = ('class_name', 'session__detection_type', 'session__created_at') search_fields = ('session__session_id', 'class_name') readonly_fields = ('session', 'class_name', 'confidence', 'bbox_x', 'bbox_y', 'bbox_width', 'bbox_height', 'bbox_visual') ordering = ('-session__created_at', 'frame_number') list_per_page = 50 def session_link(self, obj): url = reverse("admin:detection_detectionsession_change", args=[obj.session.id]) return format_html('Session {}', url, str(obj.session.session_id)[:8]) session_link.short_description = "Session" def class_name_colored(self, obj): colors = { 'fire': 'red', 'smoke': 'gray', 'person': 'orange', 'intrusion': 'orange', 'vehicle': 'blue' } color = colors.get(obj.class_name, 'black') return format_html('{}', color, obj.get_class_name_display()) class_name_colored.short_description = "Classe" def confidence_bar(self, obj): """Barre de progression visuelle pour la confiance""" percent = obj.confidence * 100 color = "green" if percent > 80 else "orange" if percent > 50 else "red" return format_html( '
' '
' '
{}%', f"{percent:.1f}", color, f"{percent:.1f}" ) confidence_bar.short_description = "Confiance" def bbox_preview(self, obj): """Aperçu textuel de la bounding box""" return f"x:{obj.bbox_x:.0f}, y:{obj.bbox_y:.0f} ({obj.bbox_width:.0f}x{obj.bbox_height:.0f})" bbox_preview.short_description = "Position" def bbox_visual(self, obj): """Représentation visuelle de la bbox (conceptuel)""" return format_html( '
' '
' '
', f"{(obj.bbox_x / 640) * 100:.1f}", f"{(obj.bbox_y / 480) * 100:.1f}", f"{(obj.bbox_width / 640) * 100:.1f}", f"{(obj.bbox_height / 480) * 100:.1f}" ) bbox_visual.short_description = "Visualisation BBox (Approx)" @admin.register(AIModelStatus) class AIModelStatusAdmin(admin.ModelAdmin): """Administration du statut des modèles IA - Par Marino ATOHOUN & BlackBenAI""" list_display = ('model_type', 'is_loaded_display', 'model_version', 'accuracy_percent', 'last_loaded', 'updated_at') list_filter = ('model_type', 'is_loaded') readonly_fields = ('created_at', 'updated_at', 'last_loaded') ordering = ('model_type',) actions = ['reload_models'] @admin.action(description='Recharger les modèles (Simulation)') def reload_models(self, request, queryset): # Ici on pourrait appeler une tâche Celery ou une fonction pour recharger le modèle rows_updated = queryset.update(is_loaded=False) # Simuler un déchargement pour forcer le rechargement self.message_user(request, f"{rows_updated} modèles marqués pour rechargement.") def is_loaded_display(self, obj): """Affichage coloré du statut de chargement""" if obj.is_loaded: return format_html('✓ Chargé') else: return format_html('✗ Non chargé') is_loaded_display.short_description = "Statut" def accuracy_percent(self, obj): """Affichage de la précision en pourcentage""" if obj.accuracy: return format_html( '
' '
' '
{}%', f"{obj.accuracy * 100:.1f}", f"{obj.accuracy * 100:.1f}" ) return "N/A" accuracy_percent.short_description = "Précision" fieldsets = ( ('Configuration du modèle', { 'fields': ('model_type', 'model_path', 'model_version') }), ('Statut opérationnel', { 'fields': ('is_loaded', 'last_loaded', 'accuracy') }), ('Métadonnées', { 'fields': ('created_at', 'updated_at'), 'classes': ('collapse',) }), ) # Par Marino ATOHOUN & BlackBenAI: Personnalisation de l'interface d'administration admin.site.site_header = "FireWatch AI - Administration" admin.site.site_title = "FireWatch AI Admin" admin.site.index_title = "Gestion BlackBenAI"