242 lines
8.9 KiB
Python
242 lines
8.9 KiB
Python
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('<img src="{}" style="max-height: 150px;" />', 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('<img src="{}" style="max-height: 100px;" />', 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(
|
|
'<span style="background-color: {}; color: white; padding: 2px 8px; border-radius: 3px;">{}</span>',
|
|
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')
|
|
|