feat: implement dynamic inspection form partials with a registry and AJAX loading for asset-specific fields
This commit is contained in:
parent
2eceb7a9b6
commit
7243f33110
8 changed files with 122 additions and 58 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 -->
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
37
streetup/inspections/registry.py
Normal file
37
streetup/inspections/registry.py
Normal 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
|
||||
|
|
@ -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>
|
||||
|
|
@ -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 -->
|
||||
|
|
|
|||
|
|
@ -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'),
|
||||
]
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue