feat: implement dynamic inspection form partials with a registry and AJAX loading for asset-specific fields

This commit is contained in:
kdeterme 2026-08-08 21:56:38 +02:00
parent 2eceb7a9b6
commit 7243f33110
8 changed files with 122 additions and 58 deletions

View file

@ -997,17 +997,17 @@
document.getElementById('insp_target_name').textContent = props.name || props.name_fr || props.target_display || '-';
document.getElementById('insp_target_status').textContent = props.latest_result_status_display || props.status_display || props.status || '-';
const parkingFields = document.getElementById('parkingSpotSpecificFields');
if (parkingFields) {
if (props.model_name === 'parkingspot' || props.spot_type) {
parkingFields.classList.remove('d-none');
document.getElementById('insp_std_stands').value = '';
document.getElementById('insp_cargo_stands').value = '';
const markingSel = document.getElementById('insp_marking_quality');
if (markingSel) markingSel.value = '';
} else {
parkingFields.classList.add('d-none');
}
const container = document.getElementById('assetSpecificFieldsContainer');
if (container) {
container.innerHTML = '';
const modelName = props.model_name || '';
const url = `/inspections/api/partial-form/?content_type_id=${encodeURIComponent(ctId || '')}&object_id=${encodeURIComponent(objId)}&model_name=${encodeURIComponent(modelName)}`;
fetch(url)
.then(res => res.text())
.then(html => {
container.innerHTML = html;
})
.catch(err => console.error('Error loading inspection fields partial:', err));
}
// Réinitialisation de l'accumulateur de photos/fichiers

View file

@ -108,29 +108,11 @@
</div>
</div>
<!-- Champs spécifiques ParkingSpot (masqués par défaut) -->
<div id="parkingSpotSpecificFields" class="d-none border rounded p-2 mb-3 bg-white">
<span class="small fw-bold text-secondary mb-2 d-block"><i class="bi bi-p-square me-1"></i>{% translate "Contrôles spécifiques stationnement" %}</span>
<div class="row g-2 mb-2">
<div class="col-6">
<label class="form-label small mb-1">{% translate "Arceaux classiques" %}</label>
<input type="number" name="verified_standard_stand_count" id="insp_std_stands" class="form-control form-control-sm" min="0">
</div>
<div class="col-6">
<label class="form-label small mb-1">{% translate "Arceaux cargo" %}</label>
<input type="number" name="verified_cargo_stand_count" id="insp_cargo_stands" class="form-control form-control-sm" min="0">
</div>
</div>
<div class="mb-1">
<label class="form-label small mb-1">{% translate "Qualité marquage" %}</label>
<select name="verified_marking_quality" id="insp_marking_quality" class="form-select form-select-sm">
<option value="">{% translate "-- Sélectionner --" %}</option>
<option value="good">{% translate "Bon état" %}</option>
<option value="medium">{% translate "Moyen" %}</option>
<option value="damaged">{% translate "Dégradé" %}</option>
<option value="missing">{% translate "Absent / Effacé" %}</option>
</select>
</div>
<!-- Champs spécifiques au type d'asset -->
<div id="assetSpecificFieldsContainer">
{% if inspection_template %}
{% include inspection_template %}
{% endif %}
</div>
<!-- Option Création automatique d'une intervention -->

View file

@ -516,6 +516,8 @@ def asset_inspections(context, asset):
from inspections.models import Inspection
from assets.permissions import can_inspect_asset
from inspections.registry import get_inspection_partial_template
asset_ct = ContentType.objects.get_for_model(asset)
inspections = list(Inspection.objects.filter(
asset_content_type=asset_ct,
@ -532,10 +534,12 @@ def asset_inspections(context, asset):
return {
'asset': asset,
'asset_ct_id': asset_ct.id,
'asset_ct_model': asset_ct.model,
'asset_pk': asset.pk,
'inspections': inspections,
'can_inspect': can_insp,
'total_count': len(inspections),
'inspection_template': get_inspection_partial_template(asset),
}

View file

@ -0,0 +1,37 @@
import os
from django.template.loader import get_template
from django.template.exceptions import TemplateDoesNotExist
def get_inspection_partial_template(model_or_name):
"""
Retourne le chemin relatif du partial template d'inspection pour un type d'asset donné
si ce fichier existe (ex: 'inspections/components/asset_fields/_parkingspot.html').
Sinon retourne None.
"""
if not model_or_name:
return None
if hasattr(model_or_name, '_meta'):
model_name = model_or_name._meta.model_name.lower()
else:
model_name = str(model_or_name).lower()
template_path = f"inspections/components/asset_fields/_{model_name}.html"
try:
get_template(template_path)
return template_path
except TemplateDoesNotExist:
return None
# Registre des processeurs / formulaires d'inspection spécialisés par modèle
INSPECTION_FORM_REGISTRY = {}
def register_inspection_handler(model_name):
"""Décorateur pour enregistrer une classe/fonction de traitement d'inspection spécialisée."""
def decorator(cls_or_func):
INSPECTION_FORM_REGISTRY[model_name.lower()] = cls_or_func
return cls_or_func
return decorator

View file

@ -0,0 +1,28 @@
{% load i18n %}
<!-- Champs spécifiques ParkingSpot (Emplacement stationnement) -->
<div id="parkingSpotSpecificFields" class="border rounded p-2 mb-3 bg-white">
<span class="small fw-bold text-secondary mb-2 d-block">
<i class="bi bi-p-square me-1"></i>{% translate "Contrôles spécifiques stationnement" %}
</span>
<div class="row g-2 mb-2">
<div class="col-6">
<label class="form-label small mb-1">{% translate "Arceaux classiques" %}</label>
<input type="number" name="verified_standard_stand_count" id="insp_std_stands" class="form-control form-control-sm" min="0" value="{{ asset.standard_stand_count|default:'' }}">
</div>
<div class="col-6">
<label class="form-label small mb-1">{% translate "Arceaux cargo" %}</label>
<input type="number" name="verified_cargo_stand_count" id="insp_cargo_stands" class="form-control form-control-sm" min="0" value="{{ asset.cargo_stand_count|default:'' }}">
</div>
</div>
<div class="mb-1">
<label class="form-label small mb-1">{% translate "Qualité marquage" %}</label>
<select name="verified_marking_quality" id="insp_marking_quality" class="form-select form-select-sm">
<option value="">{% translate "-- Sélectionner --" %}</option>
<option value="good" {% if asset.marking_quality == 'good' %}selected{% endif %}>{% translate "Bon état" %}</option>
<option value="medium" {% if asset.marking_quality == 'medium' %}selected{% endif %}>{% translate "Moyen" %}</option>
<option value="damaged" {% if asset.marking_quality == 'damaged' %}selected{% endif %}>{% translate "Dégradé" %}</option>
<option value="missing" {% if asset.marking_quality == 'missing' %}selected{% endif %}>{% translate "Absent / Effacé" %}</option>
</select>
</div>
</div>

View file

@ -80,29 +80,11 @@
</div>
</div>
<!-- Champs spécifiques ParkingSpot (masqués par défaut) -->
<div id="parkingSpotSpecificFields" class="d-none border rounded p-2 mb-3 bg-white">
<span class="small fw-bold text-secondary mb-2 d-block"><i class="bi bi-p-square me-1"></i>{% translate "Contrôles spécifiques stationnement" %}</span>
<div class="row g-2 mb-2">
<div class="col-6">
<label class="form-label small mb-1">{% translate "Arceaux classiques" %}</label>
<input type="number" name="verified_standard_stand_count" id="insp_std_stands" class="form-control form-control-sm" min="0">
</div>
<div class="col-6">
<label class="form-label small mb-1">{% translate "Arceaux cargo" %}</label>
<input type="number" name="verified_cargo_stand_count" id="insp_cargo_stands" class="form-control form-control-sm" min="0">
</div>
</div>
<div class="mb-1">
<label class="form-label small mb-1">{% translate "Qualité marquage" %}</label>
<select name="verified_marking_quality" id="insp_marking_quality" class="form-select form-select-sm">
<option value="">{% translate "-- Sélectionner --" %}</option>
<option value="good">{% translate "Bon état" %}</option>
<option value="medium">{% translate "Moyen" %}</option>
<option value="damaged">{% translate "Dégradé" %}</option>
<option value="missing">{% translate "Absent / Effacé" %}</option>
</select>
</div>
<!-- Champs spécifiques au type d'asset -->
<div id="assetSpecificFieldsContainer">
{% if inspection_template %}
{% include inspection_template %}
{% endif %}
</div>
<!-- Option Création automatique d'une intervention -->

View file

@ -8,4 +8,5 @@ urlpatterns = [
path('quick-map/', views.quick_inspection_map, name='quick_inspection_map'),
path('api/create/', views.create_inspection_api, name='create_inspection_api'),
path('api/geojson/', views.inspections_geojson, name='inspections_geojson'),
path('api/partial-form/', views.get_asset_inspection_form_partial, name='asset_inspection_form_partial'),
]

View file

@ -1,5 +1,5 @@
from django.shortcuts import render, get_object_or_404, redirect
from django.http import JsonResponse, Http404, HttpResponseForbidden
from django.http import JsonResponse, Http404, HttpResponseForbidden, HttpResponse
from django.contrib.auth.decorators import login_required
from django.views.decorators.http import require_POST
from django.db.models import Q
@ -528,3 +528,33 @@ def inspections_geojson(request):
continue
return JsonResponse({'type': 'FeatureCollection', 'features': features[:1000]})
@login_required
def get_asset_inspection_form_partial(request):
"""
API endpoint qui renvoie le snippet HTML du partial form d'inspection pour un asset donné.
Paramètres GET : content_type_id & object_id (ou model_name).
"""
content_type_id = request.GET.get('content_type_id')
object_id = request.GET.get('object_id')
model_name = request.GET.get('model_name')
asset = None
if content_type_id and object_id:
try:
content_type = ContentType.objects.get(pk=content_type_id)
asset = content_type.get_object_for_this_type(pk=object_id)
model_name = content_type.model
except Exception:
pass
from .registry import get_inspection_partial_template
template_path = get_inspection_partial_template(asset or model_name)
if not template_path:
return HttpResponse('')
context = {'asset': asset}
return render(request, template_path, context)