FireWatch / detection /admin.py
rinogeek's picture
Add application file
fc3299e
"""
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('<span style="color: {}; font-weight: bold;">{}</span>', color, count)
detections_count.short_description = "Détections"
def preview_image(self, obj):
if obj.result_file:
return format_html('<img src="{}" style="height: 50px; border-radius: 4px;" />', obj.result_file.url)
elif obj.original_file and obj.detection_type == 'image':
return format_html('<img src="{}" style="height: 50px; border-radius: 4px; opacity: 0.5;" />', 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('<a href="{}" target="_blank"><img src="{}" style="max-height: 300px; max-width: 100%; border-radius: 8px;" /></a>', obj.original_file.url, obj.original_file.url)
elif obj.detection_type == 'video':
return format_html('<video src="{}" controls style="max-height: 300px; max-width: 100%; border-radius: 8px;"></video>', 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('<a href="{}" target="_blank"><img src="{}" style="max-height: 300px; max-width: 100%; border-radius: 8px;" /></a>', 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('<a href="{}">Session {}</a>', 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('<span style="color: {}; font-weight: bold;">{}</span>', 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(
'<div style="width: 100px; background-color: #ddd; border-radius: 4px;">'
'<div style="width: {}%; background-color: {}; height: 10px; border-radius: 4px;"></div>'
'</div><span style="font-size: 0.8em;">{}%</span>',
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(
'<div style="position: relative; width: 200px; height: 150px; background: #f0f0f0; border: 1px solid #ccc;">'
'<div style="position: absolute; left: {}%; top: {}%; width: {}%; height: {}%; border: 2px solid red; background: rgba(255,0,0,0.2);"></div>'
'</div>',
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('<span style="color: green; font-weight: bold;">✓ Chargé</span>')
else:
return format_html('<span style="color: red; font-weight: bold;">✗ Non chargé</span>')
is_loaded_display.short_description = "Statut"
def accuracy_percent(self, obj):
"""Affichage de la précision en pourcentage"""
if obj.accuracy:
return format_html(
'<div style="width: 100px; background-color: #ddd; border-radius: 4px;">'
'<div style="width: {}%; background-color: blue; height: 10px; border-radius: 4px;"></div>'
'</div><span style="font-size: 0.8em;">{}%</span>',
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"