|
|
""" |
|
|
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 |
|
|
|
|
|
|
|
|
@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): |
|
|
|
|
|
rows_updated = queryset.update(is_loaded=False) |
|
|
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',) |
|
|
}), |
|
|
) |
|
|
|
|
|
|
|
|
|
|
|
admin.site.site_header = "FireWatch AI - Administration" |
|
|
admin.site.site_title = "FireWatch AI Admin" |
|
|
admin.site.index_title = "Gestion BlackBenAI" |
|
|
|