feat: add specific fields, templates, and migration for nature trees
This commit is contained in:
parent
c8e8223ffa
commit
894edc72fe
6 changed files with 1071 additions and 7 deletions
137
loko/assets/migrations/0026_add_naturetree_specific_fields.py
Normal file
137
loko/assets/migrations/0026_add_naturetree_specific_fields.py
Normal file
|
|
@ -0,0 +1,137 @@
|
|||
# Generated by Django 6.0.7 on 2026-09-12 12:14
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('assets', '0025_artworkfountain_external_reference_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterModelOptions(
|
||||
name='naturetree',
|
||||
options={'verbose_name': 'Arbre', 'verbose_name_plural': 'Arbres'},
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='natureassetmodel',
|
||||
name='foliage_type',
|
||||
field=models.CharField(blank=True, max_length=50, null=True, verbose_name='Type de feuillage'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='natureassetmodel',
|
||||
name='scientific_name',
|
||||
field=models.CharField(blank=True, max_length=200, null=True, verbose_name='Nom scientifique'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='naturetree',
|
||||
name='circumference',
|
||||
field=models.FloatField(blank=True, null=True, verbose_name='Circonférence à 1m30 (cm)'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='naturetree',
|
||||
name='crown_clearance',
|
||||
field=models.FloatField(blank=True, null=True, verbose_name='Hauteur sous couronne (m)'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='naturetree',
|
||||
name='crown_diameter',
|
||||
field=models.FloatField(blank=True, null=True, verbose_name='Diamètre du houppier (m)'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='naturetree',
|
||||
name='development_stage',
|
||||
field=models.CharField(blank=True, choices=[('young', 'Jeune plant'), ('semi_mature', 'Semi-mature / En croissance'), ('mature', 'Adulte / Mature'), ('senescent', 'Sénescent / Dépérissant'), ('remarkable', 'Vénérable / Remarquable')], default='mature', max_length=50, null=True, verbose_name='Stade de développement'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='naturetree',
|
||||
name='health_defects',
|
||||
field=models.TextField(blank=True, null=True, verbose_name='Défauts constatés & pathologies'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='naturetree',
|
||||
name='implantation_context',
|
||||
field=models.CharField(blank=True, choices=[('street', 'Alignement de voirie'), ('park', 'Parc / Espace vert'), ('square', 'Square / Place publique'), ('roundabout', 'Rond-point / Îlot central'), ('cemetery', 'Cimetière'), ('isolated', 'Isolé / Autre')], default='street', max_length=50, null=True, verbose_name="Contexte d'implantation"),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='naturetree',
|
||||
name='is_remarkable',
|
||||
field=models.BooleanField(default=False, verbose_name='Arbre remarquable / classé'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='naturetree',
|
||||
name='last_phytosanitary_date',
|
||||
field=models.DateField(blank=True, null=True, verbose_name='Dernier diagnostic phytosanitaire'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='naturetree',
|
||||
name='last_pruning_date',
|
||||
field=models.DateField(blank=True, null=True, verbose_name='Dernière taille / élagage'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='naturetree',
|
||||
name='mechanical_state',
|
||||
field=models.CharField(blank=True, choices=[('good', 'Bon (aucun défaut majeur)'), ('fair', 'Moyen (défauts mineurs)'), ('cavity', 'Présence de cavité(s)'), ('crack', 'Fissure(s) / Écorce incluse'), ('dead_branches', 'Branches mortes importantes'), ('critical', 'Critique (risque de rupture)')], default='good', max_length=50, null=True, verbose_name='État mécanique'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='naturetree',
|
||||
name='next_inspection_date',
|
||||
field=models.DateField(blank=True, null=True, verbose_name='Prochaine inspection préconisée'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='naturetree',
|
||||
name='pit_type',
|
||||
field=models.CharField(blank=True, choices=[('open_ground', 'Pleine terre / Engazonné'), ('vegetated', 'Pied végétalisé / Fleuri'), ('metal_grate', "Grille d'arbre métallique"), ('permeable_paving', 'Revêtement drainant / Pavés poreux'), ('compacted_soil', 'Sol compacté / Bitume')], default='open_ground', max_length=50, null=True, verbose_name='Type de pied / Fosse'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='naturetree',
|
||||
name='protection_type',
|
||||
field=models.CharField(blank=True, choices=[('none', 'Aucun (affranchi)'), ('staking', 'Tuteur (simple / bipode / tripode)'), ('corset', 'Corset métallique de protection'), ('guying', 'Haubanage'), ('trunk_protector', 'Gaine de protection du tronc')], default='none', max_length=50, null=True, verbose_name='Protection / Tuteurage'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='naturetree',
|
||||
name='pruning_frequency_years',
|
||||
field=models.IntegerField(blank=True, null=True, verbose_name='Périodicité de taille (années)'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='naturetree',
|
||||
name='pruning_type',
|
||||
field=models.CharField(blank=True, choices=[('free_crown', 'Port libre'), ('semi_free', 'Port semi-libre'), ('architectured_head', 'Taille architecturée (tête de chat / marotte)'), ('architectured_curtain', 'Taille en rideau / tonnelle'), ('formation', 'Taille de formation'), ('none', 'Non taillé / Entretien minimal')], default='free_crown', max_length=50, null=True, verbose_name='Type de conduite / Taille'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='naturetree',
|
||||
name='risk_level',
|
||||
field=models.CharField(blank=True, choices=[('low', 'Faible'), ('moderate', 'Modéré'), ('high', 'Élevé'), ('imminent_danger', 'Danger immédiat')], default='low', max_length=50, null=True, verbose_name='Niveau de risque'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='naturetree',
|
||||
name='tag_number',
|
||||
field=models.CharField(blank=True, max_length=100, null=True, verbose_name="N° de plaquette / bague d'inventaire"),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='naturetree',
|
||||
name='urban_constraints',
|
||||
field=models.CharField(blank=True, max_length=255, null=True, verbose_name='Contraintes urbaines'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='naturetree',
|
||||
name='vitality',
|
||||
field=models.CharField(blank=True, choices=[('strong', 'Forte (très vigoureux)'), ('normal', 'Normale'), ('weak', 'Faible (stress / manque de vigueur)'), ('declining', 'Dépérissant'), ('dead', 'Mort')], default='normal', max_length=50, null=True, verbose_name='Vigueur / Vitalité'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='naturetree',
|
||||
name='depth',
|
||||
field=models.FloatField(blank=True, null=True, verbose_name='Profondeur de fosse (m)'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='naturetree',
|
||||
name='height',
|
||||
field=models.FloatField(blank=True, null=True, verbose_name='Hauteur totale (m)'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='naturetree',
|
||||
name='protection_buffer',
|
||||
field=models.FloatField(blank=True, null=True, verbose_name='Rayon de protection racinaire (m)'),
|
||||
),
|
||||
]
|
||||
|
|
@ -70,6 +70,9 @@ class NatureLocation(AbstractLocation):
|
|||
|
||||
class NatureAssetModel(AbstractAssetModel):
|
||||
color = models.CharField(max_length=200, null=True, blank=True)
|
||||
scientific_name = models.CharField(max_length=200, null=True, blank=True, verbose_name=_("Nom scientifique"))
|
||||
foliage_type = models.CharField(max_length=50, null=True, blank=True, verbose_name=_("Type de feuillage"))
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Nature Asset Model"
|
||||
verbose_name_plural = "Nature Asset Models"
|
||||
|
|
@ -77,6 +80,73 @@ class NatureAssetModel(AbstractAssetModel):
|
|||
|
||||
|
||||
|
||||
TREE_DEVELOPMENT_STAGE_CHOICES = [
|
||||
('young', _("Jeune plant")),
|
||||
('semi_mature', _("Semi-mature / En croissance")),
|
||||
('mature', _("Adulte / Mature")),
|
||||
('senescent', _("Sénescent / Dépérissant")),
|
||||
('remarkable', _("Vénérable / Remarquable")),
|
||||
]
|
||||
|
||||
TREE_VITALITY_CHOICES = [
|
||||
('strong', _("Forte (très vigoureux)")),
|
||||
('normal', _("Normale")),
|
||||
('weak', _("Faible (stress / manque de vigueur)")),
|
||||
('declining', _("Dépérissant")),
|
||||
('dead', _("Mort")),
|
||||
]
|
||||
|
||||
TREE_MECHANICAL_STATE_CHOICES = [
|
||||
('good', _("Bon (aucun défaut majeur)")),
|
||||
('fair', _("Moyen (défauts mineurs)")),
|
||||
('cavity', _("Présence de cavité(s)")),
|
||||
('crack', _("Fissure(s) / Écorce incluse")),
|
||||
('dead_branches', _("Branches mortes importantes")),
|
||||
('critical', _("Critique (risque de rupture)")),
|
||||
]
|
||||
|
||||
TREE_RISK_LEVEL_CHOICES = [
|
||||
('low', _("Faible")),
|
||||
('moderate', _("Modéré")),
|
||||
('high', _("Élevé")),
|
||||
('imminent_danger', _("Danger immédiat")),
|
||||
]
|
||||
|
||||
TREE_IMPLANTATION_CHOICES = [
|
||||
('street', _("Alignement de voirie")),
|
||||
('park', _("Parc / Espace vert")),
|
||||
('square', _("Square / Place publique")),
|
||||
('roundabout', _("Rond-point / Îlot central")),
|
||||
('cemetery', _("Cimetière")),
|
||||
('isolated', _("Isolé / Autre")),
|
||||
]
|
||||
|
||||
TREE_PIT_TYPE_CHOICES = [
|
||||
('open_ground', _("Pleine terre / Engazonné")),
|
||||
('vegetated', _("Pied végétalisé / Fleuri")),
|
||||
('metal_grate', _("Grille d'arbre métallique")),
|
||||
('permeable_paving', _("Revêtement drainant / Pavés poreux")),
|
||||
('compacted_soil', _("Sol compacté / Bitume")),
|
||||
]
|
||||
|
||||
TREE_PROTECTION_CHOICES = [
|
||||
('none', _("Aucun (affranchi)")),
|
||||
('staking', _("Tuteur (simple / bipode / tripode)")),
|
||||
('corset', _("Corset métallique de protection")),
|
||||
('guying', _("Haubanage")),
|
||||
('trunk_protector', _("Gaine de protection du tronc")),
|
||||
]
|
||||
|
||||
TREE_PRUNING_CHOICES = [
|
||||
('free_crown', _("Port libre")),
|
||||
('semi_free', _("Port semi-libre")),
|
||||
('architectured_head', _("Taille architecturée (tête de chat / marotte)")),
|
||||
('architectured_curtain', _("Taille en rideau / tonnelle")),
|
||||
('formation', _("Taille de formation")),
|
||||
('none', _("Non taillé / Entretien minimal")),
|
||||
]
|
||||
|
||||
|
||||
BULB_TYPE_CHOICES = [
|
||||
('tulipes', _('Tulipes')),
|
||||
('jacynthes', _('Jacynthes')),
|
||||
|
|
@ -123,13 +193,54 @@ class NatureGreenSurfaceDocument(Document):
|
|||
class NatureTree(AbstractGeoAsset):
|
||||
model = models.ForeignKey(NatureAssetModel, on_delete=models.SET_NULL, null=True, blank=True, related_name="nature_trees")
|
||||
location = models.ForeignKey(NatureLocation, related_name="nature_trees", on_delete=models.SET_NULL, null=True, blank=True)
|
||||
height = models.FloatField(null=True, blank=True)
|
||||
depth = models.FloatField(null=True, blank=True)
|
||||
protection_buffer = models.FloatField(null=True, blank=True)
|
||||
|
||||
# Dendrométrie & Dimensions
|
||||
height = models.FloatField(null=True, blank=True, verbose_name=_("Hauteur totale (m)"))
|
||||
circumference = models.FloatField(null=True, blank=True, verbose_name=_("Circonférence à 1m30 (cm)"))
|
||||
crown_diameter = models.FloatField(null=True, blank=True, verbose_name=_("Diamètre du houppier (m)"))
|
||||
crown_clearance = models.FloatField(null=True, blank=True, verbose_name=_("Hauteur sous couronne (m)"))
|
||||
development_stage = models.CharField(max_length=50, choices=TREE_DEVELOPMENT_STAGE_CHOICES, default='mature', null=True, blank=True, verbose_name=_("Stade de développement"))
|
||||
depth = models.FloatField(null=True, blank=True, verbose_name=_("Profondeur de fosse (m)"))
|
||||
protection_buffer = models.FloatField(null=True, blank=True, verbose_name=_("Rayon de protection racinaire (m)"))
|
||||
|
||||
# Diagnostic Sanitaire & Sécurité
|
||||
vitality = models.CharField(max_length=50, choices=TREE_VITALITY_CHOICES, default='normal', null=True, blank=True, verbose_name=_("Vigueur / Vitalité"))
|
||||
mechanical_state = models.CharField(max_length=50, choices=TREE_MECHANICAL_STATE_CHOICES, default='good', null=True, blank=True, verbose_name=_("État mécanique"))
|
||||
health_defects = models.TextField(null=True, blank=True, verbose_name=_("Défauts constatés & pathologies"))
|
||||
risk_level = models.CharField(max_length=50, choices=TREE_RISK_LEVEL_CHOICES, default='low', null=True, blank=True, verbose_name=_("Niveau de risque"))
|
||||
last_phytosanitary_date = models.DateField(null=True, blank=True, verbose_name=_("Dernier diagnostic phytosanitaire"))
|
||||
next_inspection_date = models.DateField(null=True, blank=True, verbose_name=_("Prochaine inspection préconisée"))
|
||||
is_remarkable = models.BooleanField(default=False, verbose_name=_("Arbre remarquable / classé"))
|
||||
|
||||
# Implantation & Environnement urbain
|
||||
implantation_context = models.CharField(max_length=50, choices=TREE_IMPLANTATION_CHOICES, default='street', null=True, blank=True, verbose_name=_("Contexte d'implantation"))
|
||||
pit_type = models.CharField(max_length=50, choices=TREE_PIT_TYPE_CHOICES, default='open_ground', null=True, blank=True, verbose_name=_("Type de pied / Fosse"))
|
||||
protection_type = models.CharField(max_length=50, choices=TREE_PROTECTION_CHOICES, default='none', null=True, blank=True, verbose_name=_("Protection / Tuteurage"))
|
||||
urban_constraints = models.CharField(max_length=255, null=True, blank=True, verbose_name=_("Contraintes urbaines"))
|
||||
tag_number = models.CharField(max_length=100, null=True, blank=True, verbose_name=_("N° de plaquette / bague d'inventaire"))
|
||||
|
||||
# Conduite de taille & Travaux
|
||||
pruning_type = models.CharField(max_length=50, choices=TREE_PRUNING_CHOICES, default='free_crown', null=True, blank=True, verbose_name=_("Type de conduite / Taille"))
|
||||
pruning_frequency_years = models.IntegerField(null=True, blank=True, verbose_name=_("Périodicité de taille (années)"))
|
||||
last_pruning_date = models.DateField(null=True, blank=True, verbose_name=_("Dernière taille / élagage"))
|
||||
|
||||
# Références des choix pour utilisation simple dans les templates
|
||||
DEVELOPMENT_STAGE_CHOICES = TREE_DEVELOPMENT_STAGE_CHOICES
|
||||
VITALITY_CHOICES = TREE_VITALITY_CHOICES
|
||||
MECHANICAL_STATE_CHOICES = TREE_MECHANICAL_STATE_CHOICES
|
||||
RISK_LEVEL_CHOICES = TREE_RISK_LEVEL_CHOICES
|
||||
IMPLANTATION_CHOICES = TREE_IMPLANTATION_CHOICES
|
||||
PIT_TYPE_CHOICES = TREE_PIT_TYPE_CHOICES
|
||||
PROTECTION_CHOICES = TREE_PROTECTION_CHOICES
|
||||
PRUNING_CHOICES = TREE_PRUNING_CHOICES
|
||||
|
||||
indexes = [
|
||||
models.Index(fields=["code"]),
|
||||
]
|
||||
models.Index(fields=["code"]),
|
||||
]
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("Arbre")
|
||||
verbose_name_plural = _("Arbres")
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse('assets:nature_assets_detail', kwargs={'asset_model': 'naturetree', 'asset_id':self.id})
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
{% if model %}
|
||||
<div class="card mb-3">
|
||||
<div class="card-header bg-light d-flex justify-content-between align-items-center">
|
||||
<h5 class="mb-0">{% translate "Modèle" %}</h5>
|
||||
<h5 class="mb-0">{% if card_title %}{{ card_title }}{% else %}{% translate "Modèle" %}{% endif %}</h5>
|
||||
{% if can_edit_model %}
|
||||
<button type="button" class="btn btn-sm btn-outline-primary d-none" id="changeModelBtn" data-bs-toggle="modal" data-bs-target="#changeModelModal" data-edit-mode-only="true">
|
||||
<i class="bi bi-pencil me-1"></i>{% translate "Changer" %}
|
||||
|
|
|
|||
|
|
@ -117,6 +117,7 @@
|
|||
{% if allowed_update_fields and not mobile_view %}
|
||||
<div id="asset-cms-edit-bar" class="px-3 py-2 mb-3 shadow-sm d-flex align-items-center justify-content-between border-bottom bg-light">
|
||||
<div class="d-flex align-items-center gap-2">
|
||||
{% block asset_header_title %}
|
||||
<div>
|
||||
<h2 class="mb-0" style="color: var(--brand-color); font-size: 1.25rem;">
|
||||
{% translate "Asset" %} : {{ asset.code }}
|
||||
|
|
@ -132,6 +133,7 @@
|
|||
<span>{{ asset_verbose_name }}</span>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock asset_header_title %}
|
||||
</div>
|
||||
<div class="d-flex gap-2">
|
||||
{% if 'archive' in allowed_actions %}
|
||||
|
|
@ -157,6 +159,7 @@
|
|||
</div>
|
||||
{% else %}
|
||||
<div class="{% if mobile_view %}d-none{% else %}d-block{% endif %} mb-3">
|
||||
{% block asset_header_title_readonly %}
|
||||
<h2 class="mb-0" style="color: var(--brand-color);">
|
||||
{% if thematic.icon %}
|
||||
<i class="bi {{ thematic.icon }} me-2"></i>
|
||||
|
|
@ -173,6 +176,7 @@
|
|||
{% endif %}
|
||||
<span>{{ asset_verbose_name }}</span>
|
||||
</div>
|
||||
{% endblock asset_header_title_readonly %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
|
|
@ -203,6 +207,7 @@
|
|||
<div class="row {% if not mobile_view %}mt-4{% endif %}">
|
||||
<!-- Colonne principale : informations de base -->
|
||||
<div class="{% if mobile_view %}col-12{% else %}col-md-8{% endif %}">
|
||||
{% block main_asset_content %}
|
||||
<div class="card mb-3">
|
||||
<div class="card-header bg-light">
|
||||
<h5 class="mb-0">{% translate "Informations générales" %}</h5>
|
||||
|
|
@ -490,6 +495,7 @@
|
|||
</table>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock main_asset_content %}
|
||||
|
||||
<!-- Documents associés -->
|
||||
{% asset_documents asset %}
|
||||
|
|
@ -669,7 +675,11 @@
|
|||
<!-- Colonne latérale : modèle et localisation -->
|
||||
<div class="{% if mobile_view %}col-12{% else %}col-md-4{% endif %}">
|
||||
<!-- Modèle associé -->
|
||||
{% block model_card %}
|
||||
{% asset_model_card asset %}
|
||||
{% endblock model_card %}
|
||||
|
||||
{% block extra_side_cards %}{% endblock extra_side_cards %}
|
||||
|
||||
<!-- Localisation associée -->
|
||||
{% asset_location_link asset %}
|
||||
|
|
|
|||
803
loko/assets/templates/assets/naturetree_detail.html
Normal file
803
loko/assets/templates/assets/naturetree_detail.html
Normal file
|
|
@ -0,0 +1,803 @@
|
|||
{% extends "assets/detail_generic.html" %}
|
||||
{% load i18n %}
|
||||
{% load static %}
|
||||
{% load asset_tags %}
|
||||
|
||||
{# ══════════════════════════════════════════════════════════════════════════════ #}
|
||||
{# EN-TÊTE SPÉCIFIQUE ARBRE #}
|
||||
{# ══════════════════════════════════════════════════════════════════════════════ #}
|
||||
{% block asset_header_title %}
|
||||
<div class="d-flex align-items-center gap-3">
|
||||
<div class="rounded-circle bg-success-subtle p-2 d-flex align-items-center justify-content-center" style="width: 46px; height: 46px;">
|
||||
<i class="bi bi-tree-fill text-success" style="font-size: 1.6rem;"></i>
|
||||
</div>
|
||||
<div>
|
||||
<div class="d-flex align-items-center gap-2 flex-wrap">
|
||||
<h2 class="mb-0" style="color: var(--brand-color); font-size: 1.25rem;">
|
||||
{% translate "Arbre" %} : {{ asset.code }}
|
||||
</h2>
|
||||
{% if asset.name_fr %}
|
||||
<span class="text-secondary fw-semibold">— {{ asset.name_fr }}</span>
|
||||
{% elif asset.model %}
|
||||
<span class="text-secondary fw-semibold">— {{ asset.model.get_name }}</span>
|
||||
{% endif %}
|
||||
|
||||
{# Badges d'état en un coup d'œil #}
|
||||
{% if asset.status == 'active' %}
|
||||
<span class="badge bg-success ms-1">{{ asset.get_status_display }}</span>
|
||||
{% elif asset.status == 'damaged' or asset.status == 'to_replace' %}
|
||||
<span class="badge bg-warning text-dark ms-1">{{ asset.get_status_display }}</span>
|
||||
{% else %}
|
||||
<span class="badge bg-secondary ms-1">{{ asset.get_status_display }}</span>
|
||||
{% endif %}
|
||||
|
||||
{# Badge Vitalité #}
|
||||
{% if asset.vitality == 'strong' %}
|
||||
<span class="badge bg-success-subtle text-success border border-success ms-1"><i class="bi bi-heart-pulse-fill me-1"></i>{% translate "Vitalité forte" %}</span>
|
||||
{% elif asset.vitality == 'normal' %}
|
||||
<span class="badge bg-info-subtle text-info-emphasis border border-info ms-1"><i class="bi bi-heart-pulse me-1"></i>{% translate "Vitalité normale" %}</span>
|
||||
{% elif asset.vitality == 'weak' %}
|
||||
<span class="badge bg-warning-subtle text-warning-emphasis border border-warning ms-1"><i class="bi bi-exclamation-circle me-1"></i>{% translate "Vitalité faible" %}</span>
|
||||
{% elif asset.vitality == 'declining' or asset.vitality == 'dead' %}
|
||||
<span class="badge bg-danger ms-1"><i class="bi bi-exclamation-triangle-fill me-1"></i>{{ asset.get_vitality_display }}</span>
|
||||
{% endif %}
|
||||
|
||||
{# Badge Stade #}
|
||||
{% if asset.development_stage %}
|
||||
<span class="badge bg-light text-secondary border ms-1">{{ asset.get_development_stage_display }}</span>
|
||||
{% endif %}
|
||||
|
||||
{# Badge Risque #}
|
||||
{% if asset.risk_level == 'high' or asset.risk_level == 'imminent_danger' %}
|
||||
<span class="badge bg-danger ms-1"><i class="bi bi-shield-exclamation me-1"></i>{{ asset.get_risk_level_display }}</span>
|
||||
{% elif asset.risk_level == 'moderate' %}
|
||||
<span class="badge bg-warning text-dark ms-1">{{ asset.get_risk_level_display }}</span>
|
||||
{% endif %}
|
||||
|
||||
{# Badge Arbre Remarquable #}
|
||||
{% if asset.is_remarkable %}
|
||||
<span class="badge bg-warning text-dark ms-1 shadow-sm"><i class="bi bi-star-fill text-warning me-1"></i>{% translate "Arbre remarquable" %}</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="d-flex align-items-center gap-2 mt-1" style="font-size: 0.8rem; color: #6c757d;">
|
||||
<i class="bi bi-flower1"></i>
|
||||
{% if asset.model.scientific_name %}
|
||||
<em>{{ asset.model.scientific_name }}</em>
|
||||
<span>•</span>
|
||||
{% endif %}
|
||||
<span>{{ thematic.name|default:"Nature" }} — {% translate "Arbres" %}</span>
|
||||
{% if asset.location %}
|
||||
<span>•</span>
|
||||
<i class="bi bi-geo-alt"></i>
|
||||
<span>{{ asset.location.name }}</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock asset_header_title %}
|
||||
|
||||
{% block asset_header_title_readonly %}
|
||||
<div class="d-flex align-items-center gap-3">
|
||||
<div class="rounded-circle bg-success-subtle p-2 d-flex align-items-center justify-content-center" style="width: 46px; height: 46px;">
|
||||
<i class="bi bi-tree-fill text-success" style="font-size: 1.6rem;"></i>
|
||||
</div>
|
||||
<div>
|
||||
<div class="d-flex align-items-center gap-2 flex-wrap">
|
||||
<h2 class="mb-0" style="color: var(--brand-color); font-size: 1.25rem;">
|
||||
{% translate "Arbre" %} : {{ asset.code }}
|
||||
</h2>
|
||||
{% if asset.name_fr %}
|
||||
<span class="text-secondary fw-semibold">— {{ asset.name_fr }}</span>
|
||||
{% elif asset.model %}
|
||||
<span class="text-secondary fw-semibold">— {{ asset.model.get_name }}</span>
|
||||
{% endif %}
|
||||
|
||||
{# Badges d'état #}
|
||||
{% if asset.status == 'active' %}
|
||||
<span class="badge bg-success ms-1">{{ asset.get_status_display }}</span>
|
||||
{% elif asset.status == 'damaged' or asset.status == 'to_replace' %}
|
||||
<span class="badge bg-warning text-dark ms-1">{{ asset.get_status_display }}</span>
|
||||
{% else %}
|
||||
<span class="badge bg-secondary ms-1">{{ asset.get_status_display }}</span>
|
||||
{% endif %}
|
||||
|
||||
{# Badge Vitalité #}
|
||||
{% if asset.vitality == 'strong' %}
|
||||
<span class="badge bg-success-subtle text-success border border-success ms-1"><i class="bi bi-heart-pulse-fill me-1"></i>{% translate "Vitalité forte" %}</span>
|
||||
{% elif asset.vitality == 'normal' %}
|
||||
<span class="badge bg-info-subtle text-info-emphasis border border-info ms-1"><i class="bi bi-heart-pulse me-1"></i>{% translate "Vitalité normale" %}</span>
|
||||
{% elif asset.vitality == 'weak' %}
|
||||
<span class="badge bg-warning-subtle text-warning-emphasis border border-warning ms-1"><i class="bi bi-exclamation-circle me-1"></i>{% translate "Vitalité faible" %}</span>
|
||||
{% elif asset.vitality == 'declining' or asset.vitality == 'dead' %}
|
||||
<span class="badge bg-danger ms-1"><i class="bi bi-exclamation-triangle-fill me-1"></i>{{ asset.get_vitality_display }}</span>
|
||||
{% endif %}
|
||||
|
||||
{# Badge Stade #}
|
||||
{% if asset.development_stage %}
|
||||
<span class="badge bg-light text-secondary border ms-1">{{ asset.get_development_stage_display }}</span>
|
||||
{% endif %}
|
||||
|
||||
{# Badge Risque #}
|
||||
{% if asset.risk_level == 'high' or asset.risk_level == 'imminent_danger' %}
|
||||
<span class="badge bg-danger ms-1"><i class="bi bi-shield-exclamation me-1"></i>{{ asset.get_risk_level_display }}</span>
|
||||
{% elif asset.risk_level == 'moderate' %}
|
||||
<span class="badge bg-warning text-dark ms-1">{{ asset.get_risk_level_display }}</span>
|
||||
{% endif %}
|
||||
|
||||
{# Badge Arbre Remarquable #}
|
||||
{% if asset.is_remarkable %}
|
||||
<span class="badge bg-warning text-dark ms-1 shadow-sm"><i class="bi bi-star-fill text-warning me-1"></i>{% translate "Arbre remarquable" %}</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="d-flex align-items-center gap-2 mt-1" style="font-size: 0.85rem; color: #6c757d;">
|
||||
<i class="bi bi-flower1"></i>
|
||||
{% if asset.model.scientific_name %}
|
||||
<em>{{ asset.model.scientific_name }}</em>
|
||||
<span>•</span>
|
||||
{% endif %}
|
||||
<span>{{ thematic.name|default:"Nature" }} — {% translate "Arbres" %}</span>
|
||||
{% if asset.location %}
|
||||
<span>•</span>
|
||||
<i class="bi bi-geo-alt"></i>
|
||||
<span>{{ asset.location.name }}</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock asset_header_title_readonly %}
|
||||
|
||||
|
||||
{# ══════════════════════════════════════════════════════════════════════════════ #}
|
||||
{# COLONNE PRINCIPALE : CARTES MÉTIER ARBRE #}
|
||||
{# ══════════════════════════════════════════════════════════════════════════════ #}
|
||||
{% block main_asset_content %}
|
||||
|
||||
{# ── CARTE 1 : IDENTITÉ DE L'ARBRE ── #}
|
||||
<div class="card mb-3 shadow-sm border-0">
|
||||
<div class="card-header bg-light d-flex align-items-center justify-content-between">
|
||||
<h5 class="mb-0 fs-6 fw-bold" style="color: var(--brand-color);">
|
||||
<i class="bi bi-info-circle text-primary me-2"></i>{% translate "Identité & Informations générales" %}
|
||||
</h5>
|
||||
{% if asset.tag_number %}
|
||||
<span class="badge bg-secondary-subtle text-secondary border">
|
||||
<i class="bi bi-tag me-1"></i>{{ asset.tag_number }}
|
||||
</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<table class="table table-striped mb-0 align-middle">
|
||||
<tbody>
|
||||
<!-- Code -->
|
||||
<tr class="asset-editable-field" data-field="code" data-can-edit="{% if 'code' in allowed_update_fields %}true{% else %}false{% endif %}">
|
||||
<th style="width: 32%;">{% translate "Code d'inventaire" %}</th>
|
||||
<td>
|
||||
<div class="field-display"><span class="field-value fw-semibold">{{ asset.code }}</span></div>
|
||||
<div class="field-edit d-none">
|
||||
<input type="text" class="form-control form-control-sm" value="{{ asset.code }}">
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- N° de bague / Plaquette -->
|
||||
<tr class="asset-editable-field" data-field="tag_number" data-can-edit="{% if 'tag_number' in allowed_update_fields %}true{% else %}false{% endif %}">
|
||||
<th>{% translate "N° de plaquette / Bague" %}</th>
|
||||
<td>
|
||||
<div class="field-display"><span class="field-value">{{ asset.tag_number|default:"-" }}</span></div>
|
||||
<div class="field-edit d-none">
|
||||
<input type="text" class="form-control form-control-sm" value="{{ asset.tag_number|default:'' }}" placeholder="{% translate 'Ex: B-48010' %}">
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- Nom commun FR -->
|
||||
<tr class="asset-editable-field" data-field="name_fr" data-can-edit="{% if 'name_fr' in allowed_update_fields %}true{% else %}false{% endif %}">
|
||||
<th>{% translate "Nom commun (FR)" %}</th>
|
||||
<td>
|
||||
<div class="field-display"><span class="field-value">{{ asset.name_fr|default:"-" }}</span></div>
|
||||
<div class="field-edit d-none">
|
||||
<input type="text" class="form-control form-control-sm" value="{{ asset.name_fr|default:'' }}" placeholder="{% translate 'Ex: Érable plane' %}">
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- Nom commun NL -->
|
||||
<tr class="asset-editable-field" data-field="name_nl" data-can-edit="{% if 'name_nl' in allowed_update_fields %}true{% else %}false{% endif %}">
|
||||
<th>{% translate "Nom commun (NL)" %}</th>
|
||||
<td>
|
||||
<div class="field-display"><span class="field-value">{{ asset.name_nl|default:"-" }}</span></div>
|
||||
<div class="field-edit d-none">
|
||||
<input type="text" class="form-control form-control-sm" value="{{ asset.name_nl|default:'' }}" placeholder="{% translate 'Ex: Noorse esdoorn' %}">
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- Référence externe -->
|
||||
{% if asset.external_reference or 'external_reference' in allowed_update_fields %}
|
||||
<tr class="asset-editable-field" data-field="external_reference" data-can-edit="{% if 'external_reference' in allowed_update_fields %}true{% else %}false{% endif %}">
|
||||
<th>{% translate "Référence externe" %}</th>
|
||||
<td>
|
||||
<div class="field-display"><span class="field-value">{{ asset.external_reference|default:"-" }}</span></div>
|
||||
<div class="field-edit d-none">
|
||||
<input type="text" class="form-control form-control-sm" value="{{ asset.external_reference|default:'' }}">
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
|
||||
<!-- Date de plantation (remplace 'Installé le') -->
|
||||
<tr class="asset-editable-field" data-field="installation_date" data-can-edit="{% if 'installation_date' in allowed_update_fields %}true{% else %}false{% endif %}">
|
||||
<th>{% translate "Date de plantation" %}</th>
|
||||
<td>
|
||||
<div class="field-display">
|
||||
<span class="field-value">
|
||||
{% if asset.installation_date %}
|
||||
<i class="bi bi-calendar-event me-1 text-muted"></i>{{ asset.installation_date|date:"d/m/Y" }}
|
||||
{% else %}
|
||||
-
|
||||
{% endif %}
|
||||
</span>
|
||||
</div>
|
||||
<div class="field-edit d-none">
|
||||
<input type="date" class="form-control form-control-sm" value="{{ asset.installation_date|date:'Y-m-d'|default:'' }}">
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- Statut -->
|
||||
<tr class="asset-editable-field" data-field="status" data-can-edit="{% if 'status' in allowed_update_fields %}true{% else %}false{% endif %}">
|
||||
<th>{% translate "Statut" %}</th>
|
||||
<td>
|
||||
<div class="field-display">
|
||||
<span class="field-value">
|
||||
{% if asset.status == 'active' %}
|
||||
<span class="badge bg-success">{{ asset.get_status_display }}</span>
|
||||
{% elif asset.status == 'damaged' %}
|
||||
<span class="badge bg-warning">{{ asset.get_status_display }}</span>
|
||||
{% elif asset.status == 'to_replace' %}
|
||||
<span class="badge bg-danger">{{ asset.get_status_display }}</span>
|
||||
{% else %}
|
||||
<span class="badge bg-secondary">{{ asset.get_status_display }}</span>
|
||||
{% endif %}
|
||||
</span>
|
||||
</div>
|
||||
<div class="field-edit d-none">
|
||||
<select class="form-select form-select-sm">
|
||||
{% for value, label in status_choices %}
|
||||
<option value="{{ value }}" {% if value == asset.status %}selected{% endif %}>{{ label }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- Statut de validation -->
|
||||
{% if asset.validation_status is not None or 'validation_status' in allowed_update_fields %}
|
||||
<tr class="asset-editable-field" data-field="validation_status" data-can-edit="{% if 'validation_status' in allowed_update_fields %}true{% else %}false{% endif %}">
|
||||
<th>{% translate "Statut de validation" %}</th>
|
||||
<td>
|
||||
<div class="field-display">
|
||||
<span class="field-value">
|
||||
{% if asset.validation_status == 'validated' %}
|
||||
<span class="badge bg-success">{{ asset.get_validation_status_display }}</span>
|
||||
{% elif asset.validation_status == 'to_be_validated' %}
|
||||
<span class="badge bg-warning text-dark">{{ asset.get_validation_status_display }}</span>
|
||||
{% elif asset.validation_status == 'to_be_completed' %}
|
||||
<span class="badge bg-info text-dark">{{ asset.get_validation_status_display }}</span>
|
||||
{% elif asset.validation_status == 'rejected' %}
|
||||
<span class="badge bg-danger">{{ asset.get_validation_status_display }}</span>
|
||||
{% else %}
|
||||
<span class="badge bg-secondary">{{ asset.get_validation_status_display|default:"-" }}</span>
|
||||
{% endif %}
|
||||
</span>
|
||||
</div>
|
||||
<div class="field-edit d-none">
|
||||
<select class="form-select form-select-sm">
|
||||
<option value="">-</option>
|
||||
{% for value, label in validation_status_choices %}
|
||||
<option value="{{ value }}" {% if value == asset.validation_status %}selected{% endif %}>{{ label }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
|
||||
<!-- Catégorie -->
|
||||
<tr class="asset-editable-field" data-field="category" data-can-edit="{% if 'category' in allowed_update_fields %}true{% else %}false{% endif %}">
|
||||
<th>{% translate "Catégorie" %}</th>
|
||||
<td>
|
||||
<div class="field-display"><span class="field-value">{{ asset.category|default:"-" }}</span></div>
|
||||
<div class="field-edit d-none">
|
||||
<select class="form-select form-select-sm">
|
||||
<option value="">-</option>
|
||||
{% for cat in categories %}
|
||||
<option value="{{ cat.id }}" {% if cat.id == asset.category_id %}selected{% endif %}>{{ cat.get_name }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- Projet / Financement -->
|
||||
{% if asset.funding_program or 'funding_program' in allowed_update_fields %}
|
||||
<tr class="asset-editable-field" data-field="funding_program" data-can-edit="{% if 'funding_program' in allowed_update_fields %}true{% else %}false{% endif %}">
|
||||
<th>{% translate "Programme / Projet" %}</th>
|
||||
<td>
|
||||
<div class="field-display"><span class="field-value">{{ asset.funding_program|default:"-" }}</span></div>
|
||||
<div class="field-edit d-none">
|
||||
<input type="text" class="form-control form-control-sm" value="{{ asset.funding_program|default:'' }}">
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
|
||||
<!-- Note / Observations générales -->
|
||||
<tr class="asset-editable-field" data-field="note" data-can-edit="{% if 'note' in allowed_update_fields %}true{% else %}false{% endif %}">
|
||||
<th>{% translate "Note générale" %}</th>
|
||||
<td>
|
||||
<div class="field-display">
|
||||
<span class="field-value text-muted">{{ asset.note|default:"-"|linebreaksbr }}</span>
|
||||
</div>
|
||||
<div class="field-edit d-none">
|
||||
<textarea class="form-control form-control-sm" rows="2">{{ asset.note|default:'' }}</textarea>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
{# ── CARTE 2 : DENDROMÉTRIE & DIMENSIONS ── #}
|
||||
<div class="card mb-3 shadow-sm border-0">
|
||||
<div class="card-header bg-light">
|
||||
<h5 class="mb-0 fs-6 fw-bold" style="color: var(--brand-color);">
|
||||
<i class="bi bi-rulers text-success me-2"></i>{% translate "Fiche dendrométrique & Dimensions" %}
|
||||
</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
{# Mini-widgets d'indicateurs visuels #}
|
||||
<div class="row g-2 mb-3">
|
||||
<div class="col-4">
|
||||
<div class="p-2 border rounded text-center bg-light-subtle">
|
||||
<div class="small text-muted mb-1"><i class="bi bi-arrow-up-down text-success me-1"></i>{% translate "Hauteur" %}</div>
|
||||
<div class="fs-5 fw-bold text-success">
|
||||
{% if asset.height %}{{ asset.height }} <small class="text-muted fw-normal" style="font-size: 0.75rem;">m</small>{% else %}<span class="text-muted fs-6">—</span>{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-4">
|
||||
<div class="p-2 border rounded text-center bg-light-subtle">
|
||||
<div class="small text-muted mb-1"><i class="bi bi-circle text-primary me-1"></i>{% translate "Circonférence" %}</div>
|
||||
<div class="fs-5 fw-bold text-primary">
|
||||
{% if asset.circumference %}{{ asset.circumference }} <small class="text-muted fw-normal" style="font-size: 0.75rem;">cm</small>{% else %}<span class="text-muted fs-6">—</span>{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-4">
|
||||
<div class="p-2 border rounded text-center bg-light-subtle">
|
||||
<div class="small text-muted mb-1"><i class="bi bi-arrows-expand text-info me-1"></i>{% translate "Houppier" %}</div>
|
||||
<div class="fs-5 fw-bold text-info-emphasis">
|
||||
{% if asset.crown_diameter %}{{ asset.crown_diameter }} <small class="text-muted fw-normal" style="font-size: 0.75rem;">m</small>{% else %}<span class="text-muted fs-6">—</span>{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<table class="table table-striped mb-0 align-middle">
|
||||
<tbody>
|
||||
<!-- Hauteur totale -->
|
||||
<tr class="asset-editable-field" data-field="height" data-can-edit="{% if 'height' in allowed_update_fields %}true{% else %}false{% endif %}">
|
||||
<th style="width: 32%;">{% translate "Hauteur totale (m)" %}</th>
|
||||
<td>
|
||||
<div class="field-display"><span class="field-value">{% if asset.height %}{{ asset.height }} m{% else %}-{% endif %}</span></div>
|
||||
<div class="field-edit d-none">
|
||||
<input type="number" step="0.1" class="form-control form-control-sm" value="{{ asset.height|default:'' }}">
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- Circonférence du tronc -->
|
||||
<tr class="asset-editable-field" data-field="circumference" data-can-edit="{% if 'circumference' in allowed_update_fields %}true{% else %}false{% endif %}">
|
||||
<th>{% translate "Circonférence à 1,30 m (cm)" %}</th>
|
||||
<td>
|
||||
<div class="field-display"><span class="field-value">{% if asset.circumference %}{{ asset.circumference }} cm{% else %}-{% endif %}</span></div>
|
||||
<div class="field-edit d-none">
|
||||
<input type="number" step="1" class="form-control form-control-sm" value="{{ asset.circumference|default:'' }}" placeholder="{% translate 'En cm' %}">
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- Diamètre du houppier -->
|
||||
<tr class="asset-editable-field" data-field="crown_diameter" data-can-edit="{% if 'crown_diameter' in allowed_update_fields %}true{% else %}false{% endif %}">
|
||||
<th>{% translate "Diamètre du houppier (m)" %}</th>
|
||||
<td>
|
||||
<div class="field-display"><span class="field-value">{% if asset.crown_diameter %}{{ asset.crown_diameter }} m{% else %}-{% endif %}</span></div>
|
||||
<div class="field-edit d-none">
|
||||
<input type="number" step="0.5" class="form-control form-control-sm" value="{{ asset.crown_diameter|default:'' }}">
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- Hauteur sous couronne -->
|
||||
<tr class="asset-editable-field" data-field="crown_clearance" data-can-edit="{% if 'crown_clearance' in allowed_update_fields %}true{% else %}false{% endif %}">
|
||||
<th>{% translate "Hauteur sous couronne (gabarit)" %}</th>
|
||||
<td>
|
||||
<div class="field-display">
|
||||
<span class="field-value">
|
||||
{% if asset.crown_clearance %}{{ asset.crown_clearance }} m{% else %}-{% endif %}
|
||||
</span>
|
||||
</div>
|
||||
<div class="field-edit d-none">
|
||||
<input type="number" step="0.1" class="form-control form-control-sm" value="{{ asset.crown_clearance|default:'' }}" placeholder="{% translate 'Ex: 4.5' %}">
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- Stade de développement -->
|
||||
<tr class="asset-editable-field" data-field="development_stage" data-can-edit="{% if 'development_stage' in allowed_update_fields %}true{% else %}false{% endif %}">
|
||||
<th>{% translate "Stade de développement" %}</th>
|
||||
<td>
|
||||
<div class="field-display">
|
||||
<span class="field-value">
|
||||
{% if asset.development_stage %}
|
||||
<span class="badge bg-secondary-subtle text-secondary border">{{ asset.get_development_stage_display }}</span>
|
||||
{% else %}
|
||||
-
|
||||
{% endif %}
|
||||
</span>
|
||||
</div>
|
||||
<div class="field-edit d-none">
|
||||
<select class="form-select form-select-sm">
|
||||
<option value="">-</option>
|
||||
{% for val, lbl in asset.DEVELOPMENT_STAGE_CHOICES %}
|
||||
<option value="{{ val }}" {% if asset.development_stage == val %}selected{% endif %}>{{ lbl }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- Rayon de protection racinaire -->
|
||||
<tr class="asset-editable-field" data-field="protection_buffer" data-can-edit="{% if 'protection_buffer' in allowed_update_fields %}true{% else %}false{% endif %}">
|
||||
<th>{% translate "Protection racinaire (m)" %}</th>
|
||||
<td>
|
||||
<div class="field-display"><span class="field-value">{% if asset.protection_buffer %}{{ asset.protection_buffer }} m{% else %}-{% endif %}</span></div>
|
||||
<div class="field-edit d-none">
|
||||
<input type="number" step="0.5" class="form-control form-control-sm" value="{{ asset.protection_buffer|default:'' }}">
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- Profondeur de fosse -->
|
||||
<tr class="asset-editable-field" data-field="depth" data-can-edit="{% if 'depth' in allowed_update_fields %}true{% else %}false{% endif %}">
|
||||
<th>{% translate "Profondeur de fosse (m)" %}</th>
|
||||
<td>
|
||||
<div class="field-display"><span class="field-value">{% if asset.depth %}{{ asset.depth }} m{% else %}-{% endif %}</span></div>
|
||||
<div class="field-edit d-none">
|
||||
<input type="number" step="0.1" class="form-control form-control-sm" value="{{ asset.depth|default:'' }}">
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
{# ── CARTE 3 : DIAGNOSTIC SANITAIRE & SÉCURITÉ ── #}
|
||||
<div class="card mb-3 shadow-sm border-0">
|
||||
<div class="card-header bg-light d-flex align-items-center justify-content-between">
|
||||
<h5 class="mb-0 fs-6 fw-bold" style="color: var(--brand-color);">
|
||||
<i class="bi bi-shield-check text-warning me-2"></i>{% translate "Diagnostic sanitaire & Sécurité" %}
|
||||
</h5>
|
||||
{% if asset.is_remarkable %}
|
||||
<span class="badge bg-warning text-dark"><i class="bi bi-star-fill text-warning me-1"></i>Remarquable</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<table class="table table-striped mb-0 align-middle">
|
||||
<tbody>
|
||||
<!-- Vigueur / Vitalité -->
|
||||
<tr class="asset-editable-field" data-field="vitality" data-can-edit="{% if 'vitality' in allowed_update_fields %}true{% else %}false{% endif %}">
|
||||
<th style="width: 32%;">{% translate "Vigueur / Vitalité" %}</th>
|
||||
<td>
|
||||
<div class="field-display">
|
||||
<span class="field-value">
|
||||
{% if asset.vitality == 'strong' %}
|
||||
<span class="badge bg-success">{{ asset.get_vitality_display }}</span>
|
||||
{% elif asset.vitality == 'normal' %}
|
||||
<span class="badge bg-info text-dark">{{ asset.get_vitality_display }}</span>
|
||||
{% elif asset.vitality == 'weak' %}
|
||||
<span class="badge bg-warning text-dark">{{ asset.get_vitality_display }}</span>
|
||||
{% elif asset.vitality == 'declining' or asset.vitality == 'dead' %}
|
||||
<span class="badge bg-danger">{{ asset.get_vitality_display }}</span>
|
||||
{% else %}
|
||||
<span class="text-muted">-</span>
|
||||
{% endif %}
|
||||
</span>
|
||||
</div>
|
||||
<div class="field-edit d-none">
|
||||
<select class="form-select form-select-sm">
|
||||
<option value="">-</option>
|
||||
{% for val, lbl in asset.VITALITY_CHOICES %}
|
||||
<option value="{{ val }}" {% if asset.vitality == val %}selected{% endif %}>{{ lbl }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- État mécanique -->
|
||||
<tr class="asset-editable-field" data-field="mechanical_state" data-can-edit="{% if 'mechanical_state' in allowed_update_fields %}true{% else %}false{% endif %}">
|
||||
<th>{% translate "État mécanique" %}</th>
|
||||
<td>
|
||||
<div class="field-display">
|
||||
<span class="field-value">
|
||||
{% if asset.mechanical_state == 'good' %}
|
||||
<span class="text-success"><i class="bi bi-check-circle-fill me-1"></i>{{ asset.get_mechanical_state_display }}</span>
|
||||
{% elif asset.mechanical_state == 'fair' %}
|
||||
<span class="text-warning-emphasis"><i class="bi bi-info-circle-fill me-1"></i>{{ asset.get_mechanical_state_display }}</span>
|
||||
{% elif asset.mechanical_state %}
|
||||
<span class="text-danger"><i class="bi bi-exclamation-triangle-fill me-1"></i>{{ asset.get_mechanical_state_display }}</span>
|
||||
{% else %}
|
||||
-
|
||||
{% endif %}
|
||||
</span>
|
||||
</div>
|
||||
<div class="field-edit d-none">
|
||||
<select class="form-select form-select-sm">
|
||||
<option value="">-</option>
|
||||
{% for val, lbl in asset.MECHANICAL_STATE_CHOICES %}
|
||||
<option value="{{ val }}" {% if asset.mechanical_state == val %}selected{% endif %}>{{ lbl }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- Niveau de risque -->
|
||||
<tr class="asset-editable-field" data-field="risk_level" data-can-edit="{% if 'risk_level' in allowed_update_fields %}true{% else %}false{% endif %}">
|
||||
<th>{% translate "Niveau de risque" %}</th>
|
||||
<td>
|
||||
<div class="field-display">
|
||||
<span class="field-value">
|
||||
{% if asset.risk_level == 'low' %}
|
||||
<span class="badge bg-success-subtle text-success border border-success">{{ asset.get_risk_level_display }}</span>
|
||||
{% elif asset.risk_level == 'moderate' %}
|
||||
<span class="badge bg-warning text-dark">{{ asset.get_risk_level_display }}</span>
|
||||
{% elif asset.risk_level == 'high' or asset.risk_level == 'imminent_danger' %}
|
||||
<span class="badge bg-danger">{{ asset.get_risk_level_display }}</span>
|
||||
{% else %}
|
||||
-
|
||||
{% endif %}
|
||||
</span>
|
||||
</div>
|
||||
<div class="field-edit d-none">
|
||||
<select class="form-select form-select-sm">
|
||||
<option value="">-</option>
|
||||
{% for val, lbl in asset.RISK_LEVEL_CHOICES %}
|
||||
<option value="{{ val }}" {% if asset.risk_level == val %}selected{% endif %}>{{ lbl }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- Arbre remarquable -->
|
||||
<tr class="asset-editable-field" data-field="is_remarkable" data-can-edit="{% if 'is_remarkable' in allowed_update_fields %}true{% else %}false{% endif %}">
|
||||
<th>{% translate "Arbre remarquable / classé" %}</th>
|
||||
<td>
|
||||
<div class="field-display">
|
||||
<span class="field-value">
|
||||
{% if asset.is_remarkable %}
|
||||
<span class="badge bg-warning text-dark"><i class="bi bi-star-fill text-warning me-1"></i>{% translate "Oui" %}</span>
|
||||
{% else %}
|
||||
<span class="text-muted">{% translate "Non" %}</span>
|
||||
{% endif %}
|
||||
</span>
|
||||
</div>
|
||||
<div class="field-edit d-none">
|
||||
<select class="form-select form-select-sm">
|
||||
<option value="false" {% if not asset.is_remarkable %}selected{% endif %}>{% translate "Non" %}</option>
|
||||
<option value="true" {% if asset.is_remarkable %}selected{% endif %}>{% translate "Oui" %}</option>
|
||||
</select>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- Défauts constatés & Pathologies -->
|
||||
<tr class="asset-editable-field" data-field="health_defects" data-can-edit="{% if 'health_defects' in allowed_update_fields %}true{% else %}false{% endif %}">
|
||||
<th>{% translate "Défauts & Pathologies" %}</th>
|
||||
<td>
|
||||
<div class="field-display">
|
||||
<span class="field-value">{{ asset.health_defects|default:"-"|linebreaksbr }}</span>
|
||||
</div>
|
||||
<div class="field-edit d-none">
|
||||
<textarea class="form-control form-control-sm" rows="3" placeholder="{% translate 'Ex: Cavité au collet, branches mortes au houppier N-E, suie...' %}">{{ asset.health_defects|default:'' }}</textarea>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- Dernier diagnostic phytosanitaire -->
|
||||
<tr class="asset-editable-field" data-field="last_phytosanitary_date" data-can-edit="{% if 'last_phytosanitary_date' in allowed_update_fields %}true{% else %}false{% endif %}">
|
||||
<th>{% translate "Dernier diagnostic" %}</th>
|
||||
<td>
|
||||
<div class="field-display">
|
||||
<span class="field-value">
|
||||
{% if asset.last_phytosanitary_date %}
|
||||
<i class="bi bi-check2-circle text-success me-1"></i>{{ asset.last_phytosanitary_date|date:"d/m/Y" }}
|
||||
{% else %}
|
||||
-
|
||||
{% endif %}
|
||||
</span>
|
||||
</div>
|
||||
<div class="field-edit d-none">
|
||||
<input type="date" class="form-control form-control-sm" value="{{ asset.last_phytosanitary_date|date:'Y-m-d'|default:'' }}">
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- Prochaine inspection préconisée -->
|
||||
<tr class="asset-editable-field" data-field="next_inspection_date" data-can-edit="{% if 'next_inspection_date' in allowed_update_fields %}true{% else %}false{% endif %}">
|
||||
<th>{% translate "Prochaine inspection" %}</th>
|
||||
<td>
|
||||
<div class="field-display">
|
||||
<span class="field-value">
|
||||
{% if asset.next_inspection_date %}
|
||||
<i class="bi bi-clock-history text-info me-1"></i>{{ asset.next_inspection_date|date:"d/m/Y" }}
|
||||
{% else %}
|
||||
-
|
||||
{% endif %}
|
||||
</span>
|
||||
</div>
|
||||
<div class="field-edit d-none">
|
||||
<input type="date" class="form-control form-control-sm" value="{{ asset.next_inspection_date|date:'Y-m-d'|default:'' }}">
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
{# ── CARTE 4 : IMPLANTATION & CONDUITE DE TAILLE ── #}
|
||||
<div class="card mb-3 shadow-sm border-0">
|
||||
<div class="card-header bg-light">
|
||||
<h5 class="mb-0 fs-6 fw-bold" style="color: var(--brand-color);">
|
||||
<i class="bi bi-geo-alt text-info me-2"></i>{% translate "Implantation & Conduite de taille" %}
|
||||
</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<table class="table table-striped mb-0 align-middle">
|
||||
<tbody>
|
||||
<!-- Contexte d'implantation -->
|
||||
<tr class="asset-editable-field" data-field="implantation_context" data-can-edit="{% if 'implantation_context' in allowed_update_fields %}true{% else %}false{% endif %}">
|
||||
<th style="width: 32%;">{% translate "Contexte" %}</th>
|
||||
<td>
|
||||
<div class="field-display"><span class="field-value">{{ asset.get_implantation_context_display|default:"-" }}</span></div>
|
||||
<div class="field-edit d-none">
|
||||
<select class="form-select form-select-sm">
|
||||
<option value="">-</option>
|
||||
{% for val, lbl in asset.IMPLANTATION_CHOICES %}
|
||||
<option value="{{ val }}" {% if asset.implantation_context == val %}selected{% endif %}>{{ lbl }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- Type de pied / Fosse -->
|
||||
<tr class="asset-editable-field" data-field="pit_type" data-can-edit="{% if 'pit_type' in allowed_update_fields %}true{% else %}false{% endif %}">
|
||||
<th>{% translate "Type de pied / Fosse" %}</th>
|
||||
<td>
|
||||
<div class="field-display"><span class="field-value">{{ asset.get_pit_type_display|default:"-" }}</span></div>
|
||||
<div class="field-edit d-none">
|
||||
<select class="form-select form-select-sm">
|
||||
<option value="">-</option>
|
||||
{% for val, lbl in asset.PIT_TYPE_CHOICES %}
|
||||
<option value="{{ val }}" {% if asset.pit_type == val %}selected{% endif %}>{{ lbl }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- Protection / Tuteurage -->
|
||||
<tr class="asset-editable-field" data-field="protection_type" data-can-edit="{% if 'protection_type' in allowed_update_fields %}true{% else %}false{% endif %}">
|
||||
<th>{% translate "Protection / Tuteurage" %}</th>
|
||||
<td>
|
||||
<div class="field-display"><span class="field-value">{{ asset.get_protection_type_display|default:"-" }}</span></div>
|
||||
<div class="field-edit d-none">
|
||||
<select class="form-select form-select-sm">
|
||||
<option value="">-</option>
|
||||
{% for val, lbl in asset.PROTECTION_CHOICES %}
|
||||
<option value="{{ val }}" {% if asset.protection_type == val %}selected{% endif %}>{{ lbl }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- Contraintes urbaines -->
|
||||
<tr class="asset-editable-field" data-field="urban_constraints" data-can-edit="{% if 'urban_constraints' in allowed_update_fields %}true{% else %}false{% endif %}">
|
||||
<th>{% translate "Contraintes environnantes" %}</th>
|
||||
<td>
|
||||
<div class="field-display"><span class="field-value">{{ asset.urban_constraints|default:"-" }}</span></div>
|
||||
<div class="field-edit d-none">
|
||||
<input type="text" class="form-control form-control-sm" value="{{ asset.urban_constraints|default:'' }}" placeholder="{% translate 'Ex: Lignes aériennes, proximité façade...' %}">
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- Type de taille -->
|
||||
<tr class="asset-editable-field" data-field="pruning_type" data-can-edit="{% if 'pruning_type' in allowed_update_fields %}true{% else %}false{% endif %}">
|
||||
<th>{% translate "Conduite de taille" %}</th>
|
||||
<td>
|
||||
<div class="field-display"><span class="field-value fw-medium">{{ asset.get_pruning_type_display|default:"-" }}</span></div>
|
||||
<div class="field-edit d-none">
|
||||
<select class="form-select form-select-sm">
|
||||
<option value="">-</option>
|
||||
{% for val, lbl in asset.PRUNING_CHOICES %}
|
||||
<option value="{{ val }}" {% if asset.pruning_type == val %}selected{% endif %}>{{ lbl }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- Périodicité de taille -->
|
||||
<tr class="asset-editable-field" data-field="pruning_frequency_years" data-can-edit="{% if 'pruning_frequency_years' in allowed_update_fields %}true{% else %}false{% endif %}">
|
||||
<th>{% translate "Périodicité de taille" %}</th>
|
||||
<td>
|
||||
<div class="field-display">
|
||||
<span class="field-value">
|
||||
{% if asset.pruning_frequency_years %}
|
||||
{% blocktranslate with n=asset.pruning_frequency_years %}Tous les {{ n }} an(s){% endblocktranslate %}
|
||||
{% else %}
|
||||
-
|
||||
{% endif %}
|
||||
</span>
|
||||
</div>
|
||||
<div class="field-edit d-none">
|
||||
<input type="number" step="1" min="1" max="20" class="form-control form-control-sm" value="{{ asset.pruning_frequency_years|default:'' }}" placeholder="{% translate 'Nombre d années' %}">
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- Dernière taille / élagage -->
|
||||
<tr class="asset-editable-field" data-field="last_pruning_date" data-can-edit="{% if 'last_pruning_date' in allowed_update_fields %}true{% else %}false{% endif %}">
|
||||
<th>{% translate "Dernière taille" %}</th>
|
||||
<td>
|
||||
<div class="field-display">
|
||||
<span class="field-value">
|
||||
{% if asset.last_pruning_date %}
|
||||
{{ asset.last_pruning_date|date:"d/m/Y" }}
|
||||
{% else %}
|
||||
-
|
||||
{% endif %}
|
||||
</span>
|
||||
</div>
|
||||
<div class="field-edit d-none">
|
||||
<input type="date" class="form-control form-control-sm" value="{{ asset.last_pruning_date|date:'Y-m-d'|default:'' }}">
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock main_asset_content %}
|
||||
|
||||
|
||||
{# ══════════════════════════════════════════════════════════════════════════════ #}
|
||||
{# COLONNE LATÉRALE : ESSENCE BOTANIQUE #}
|
||||
{# ══════════════════════════════════════════════════════════════════════════════ #}
|
||||
{% block model_card %}
|
||||
{% asset_model_card asset card_title="Essence botanique" %}
|
||||
{% endblock model_card %}
|
||||
|
|
@ -32,7 +32,7 @@ def tojson(value):
|
|||
|
||||
|
||||
@register.inclusion_tag('assets/components/asset_model_card.html', takes_context=True)
|
||||
def asset_model_card(context, asset):
|
||||
def asset_model_card(context, asset, card_title=None):
|
||||
"""
|
||||
Display the model information for an asset in a Bootstrap card.
|
||||
Works with any asset that has a 'model' ForeignKey.
|
||||
|
|
@ -76,6 +76,8 @@ def asset_model_card(context, asset):
|
|||
if isinstance(field, excluded_model_field_types):
|
||||
continue
|
||||
value = getattr(model, field.name, None)
|
||||
if field.name == 'color' and not value:
|
||||
continue
|
||||
label = str(field.verbose_name).capitalize() if hasattr(field, 'verbose_name') else field.name
|
||||
display_value = str(value) if value is not None else '-'
|
||||
specific_fields.append({'label': label, 'value': value, 'display_value': display_value})
|
||||
|
|
@ -83,6 +85,7 @@ def asset_model_card(context, asset):
|
|||
return {
|
||||
'asset': asset,
|
||||
'model': model,
|
||||
'card_title': card_title,
|
||||
'can_edit_model': can_edit_model,
|
||||
'asset_models': asset_models,
|
||||
'asset_categories': asset_categories,
|
||||
|
|
|
|||
Loading…
Reference in a new issue