from django.contrib import admin from django.utils.html import format_html from django.utils.translation import gettext_lazy as _ from django import forms from .models import ( Observation, ObservationPhoto, ChecklistTemplate, ChecklistItem, ObservationChecklistResponse, ObservationAttributeChoiceSet, ObservationAttributeChoice ) from common.models import Thematic from common.private_files.helpers import build_private_url from assets.models import AssetCategory class ObservationPhotoInline(admin.TabularInline): model = ObservationPhoto extra = 0 readonly_fields = ['photo_preview'] def photo_preview(self, obj): if obj.photo and obj.pk: url = build_private_url("observations", "ObservationPhoto", obj.pk, "photo") return format_html('', url) return "-" photo_preview.short_description = "Aperçu de la photo" class ObservationChecklistResponseInline(admin.TabularInline): model = ObservationChecklistResponse extra = 0 readonly_fields = ['checklist_item', 'get_value_display'] fields = ['checklist_item', 'get_value_display', 'comment'] def get_value_display(self, obj): return obj.get_value() get_value_display.short_description = _("Valeur") @admin.register(Observation) class ObservationAdmin(admin.ModelAdmin): list_display = ('id', 'thematic', 'category', 'address', 'created_at', 'created_by', 'latitude', 'longitude') list_filter = ('thematic', 'category', 'created_at') search_fields = ('address', 'description', 'created_by__username') inlines = [ObservationPhotoInline, ObservationChecklistResponseInline] readonly_fields = ('geom', 'created_at') fieldsets = ( (None, { 'fields': ('status', 'observation_type', 'description', 'address', 'latitude', 'longitude', 'geom') }), ("Métadonnées", { 'fields': ('created_by', 'thematic', 'category'), }), ) @admin.register(ObservationPhoto) class ObservationPhotoAdmin(admin.ModelAdmin): list_display = ('id', 'observation', 'photo_thumbnail') readonly_fields = ('photo_thumbnail',) def photo_thumbnail(self, obj): if obj.photo and obj.pk: url = build_private_url("observations", "ObservationPhoto", obj.pk, "photo") return format_html('', url) return "-" photo_thumbnail.short_description = "Miniature" # ============================================================================ # Attribute Choice Sets Admin # ============================================================================ class ObservationAttributeChoiceInline(admin.TabularInline): model = ObservationAttributeChoice extra = 1 fields = ['order', 'value', 'label_fr', 'label_nl', 'bootstrap_icon', 'bootstrap_color', 'image', 'is_active'] ordering = ['order'] def get_readonly_fields(self, request, obj=None): # Une fois créé, la valeur (clé) ne doit pas être modifiée if obj: return ['value'] return [] @admin.register(ObservationAttributeChoiceSet) class ObservationAttributeChoiceSetAdmin(admin.ModelAdmin): list_display = ('name', 'description_short', 'choices_count', 'is_active', 'updated_at') list_filter = ('is_active',) search_fields = ('name', 'description') inlines = [ObservationAttributeChoiceInline] readonly_fields = ('created_at', 'updated_at') fieldsets = ( (None, { 'fields': ('name', 'description', 'is_active') }), (_("Métadonnées"), { 'fields': ('created_at', 'updated_at'), 'classes': ('collapse',) }), ) def description_short(self, obj): if obj.description: return obj.description[:50] + '...' if len(obj.description) > 50 else obj.description return "-" description_short.short_description = _("Description") def choices_count(self, obj): return obj.choices.filter(is_active=True).count() choices_count.short_description = _("Nb. de choix") @admin.register(ObservationAttributeChoice) class ObservationAttributeChoiceAdmin(admin.ModelAdmin): list_display = ('value', 'label_fr', 'choice_set', 'bootstrap_icon', 'color_preview', 'order', 'is_active') list_filter = ('choice_set', 'bootstrap_color', 'is_active') search_fields = ('value', 'label_fr', 'label_nl') ordering = ['choice_set', 'order'] def color_preview(self, obj): color_map = { 'primary': '#0d6efd', 'secondary': '#6c757d', 'success': '#198754', 'danger': '#dc3545', 'warning': '#ffc107', 'info': '#0dcaf0', 'light': '#f8f9fa', 'dark': '#212529', } bg_color = color_map.get(obj.bootstrap_color, '#0d6efd') return format_html( '{}', bg_color, obj.bootstrap_color ) color_preview.short_description = _("Couleur") # ============================================================================ # Checklist Admin # ============================================================================ class ChecklistItemInline(admin.TabularInline): model = ChecklistItem extra = 1 fields = ['order', 'name_fr', 'name_nl', 'item_type', 'choice_set', 'is_required'] ordering = ['order'] autocomplete_fields = ['choice_set'] class ChecklistTemplateAdminForm(forms.ModelForm): """Formulaire admin avec filtre thématique -> catégorie.""" thematic = forms.ModelChoiceField( queryset=Thematic.objects.all().order_by('name_fr'), required=False, label=_("Thématique"), help_text=_("Sélectionnez d'abord une thématique pour filtrer les catégories") ) class Meta: model = ChecklistTemplate fields = '__all__' def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) # Ordonner les catégories par nom self.fields['category'].queryset = AssetCategory.objects.all().order_by('name_fr') # Si on édite une instance existante, pré-sélectionner la thématique if self.instance and self.instance.pk and self.instance.category: self.fields['thematic'].initial = self.instance.category.thematic # Filtrer les catégories par cette thématique self.fields['category'].queryset = AssetCategory.objects.filter( thematic=self.instance.category.thematic ).order_by('name_fr') @admin.register(ChecklistTemplate) class ChecklistTemplateAdmin(admin.ModelAdmin): form = ChecklistTemplateAdminForm list_display = ('name_fr', 'category', 'observation_type', 'is_active', 'items_count', 'created_at') list_filter = ('category__thematic', 'category', 'observation_type', 'is_active') search_fields = ('name_fr', 'name_nl', 'description') inlines = [ChecklistItemInline] readonly_fields = ('created_at', 'updated_at') fieldsets = ( (None, { 'fields': ('name_fr', 'name_nl', 'description') }), (_("Configuration"), { 'fields': ('thematic', 'category', 'observation_type', 'is_active') }), (_("Métadonnées"), { 'fields': ('created_at', 'updated_at'), 'classes': ('collapse',) }), ) def items_count(self, obj): return obj.items.count() items_count.short_description = _("Nombre d'éléments") class Media: js = ('observations/admin/checklist_template.js',) @admin.register(ChecklistItem) class ChecklistItemAdmin(admin.ModelAdmin): list_display = ('name_fr', 'template', 'item_type', 'choice_set', 'is_required', 'order') list_filter = ('template', 'item_type', 'is_required', 'choice_set') search_fields = ('name_fr', 'name_nl', 'description_fr') ordering = ['template', 'order'] autocomplete_fields = ['choice_set'] fieldsets = ( (None, { 'fields': ('template', 'name_fr', 'name_nl', 'description_fr', 'description_nl') }), (_("Type de réponse"), { 'fields': ('item_type', 'choice_set', 'is_required', 'order') }), (_("Options textuelles (ancien système)"), { 'fields': ('choices_fr', 'choices_nl', 'choices_icons', 'choices_colors'), 'classes': ('collapse',), 'description': _("Utilisez ces champs uniquement si vous ne voulez pas utiliser un ensemble de choix réutilisable.") }), ) @admin.register(ObservationChecklistResponse) class ObservationChecklistResponseAdmin(admin.ModelAdmin): list_display = ('observation', 'checklist_item', 'get_value', 'created_at') list_filter = ('checklist_item__template', 'created_at') search_fields = ('observation__description', 'checklist_item__name_fr') readonly_fields = ('created_at', 'updated_at')