diff --git a/loko/assets/migrations/0026_add_naturetree_specific_fields.py b/loko/assets/migrations/0026_add_naturetree_specific_fields.py new file mode 100644 index 0000000..707abf0 --- /dev/null +++ b/loko/assets/migrations/0026_add_naturetree_specific_fields.py @@ -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)'), + ), + ] diff --git a/loko/assets/models/nature.py b/loko/assets/models/nature.py index 7968d07..f65b28d 100644 --- a/loko/assets/models/nature.py +++ b/loko/assets/models/nature.py @@ -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}) diff --git a/loko/assets/templates/assets/components/asset_model_card.html b/loko/assets/templates/assets/components/asset_model_card.html index ff28351..778cc40 100644 --- a/loko/assets/templates/assets/components/asset_model_card.html +++ b/loko/assets/templates/assets/components/asset_model_card.html @@ -3,7 +3,7 @@ {% if model %}
-
{% translate "Modèle" %}
+
{% if card_title %}{{ card_title }}{% else %}{% translate "Modèle" %}{% endif %}
{% if can_edit_model %}
{% endif %} @@ -203,6 +207,7 @@
+ {% block main_asset_content %}
{% translate "Informations générales" %}
@@ -490,6 +495,7 @@
+ {% endblock main_asset_content %} {% asset_documents asset %} @@ -669,7 +675,11 @@
+ {% block model_card %} {% asset_model_card asset %} + {% endblock model_card %} + + {% block extra_side_cards %}{% endblock extra_side_cards %} {% asset_location_link asset %} diff --git a/loko/assets/templates/assets/naturetree_detail.html b/loko/assets/templates/assets/naturetree_detail.html new file mode 100644 index 0000000..c1fd2f4 --- /dev/null +++ b/loko/assets/templates/assets/naturetree_detail.html @@ -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 %} +
+
+ +
+
+
+

+ {% translate "Arbre" %} : {{ asset.code }} +

+ {% if asset.name_fr %} + — {{ asset.name_fr }} + {% elif asset.model %} + — {{ asset.model.get_name }} + {% endif %} + + {# Badges d'état en un coup d'œil #} + {% if asset.status == 'active' %} + {{ asset.get_status_display }} + {% elif asset.status == 'damaged' or asset.status == 'to_replace' %} + {{ asset.get_status_display }} + {% else %} + {{ asset.get_status_display }} + {% endif %} + + {# Badge Vitalité #} + {% if asset.vitality == 'strong' %} + {% translate "Vitalité forte" %} + {% elif asset.vitality == 'normal' %} + {% translate "Vitalité normale" %} + {% elif asset.vitality == 'weak' %} + {% translate "Vitalité faible" %} + {% elif asset.vitality == 'declining' or asset.vitality == 'dead' %} + {{ asset.get_vitality_display }} + {% endif %} + + {# Badge Stade #} + {% if asset.development_stage %} + {{ asset.get_development_stage_display }} + {% endif %} + + {# Badge Risque #} + {% if asset.risk_level == 'high' or asset.risk_level == 'imminent_danger' %} + {{ asset.get_risk_level_display }} + {% elif asset.risk_level == 'moderate' %} + {{ asset.get_risk_level_display }} + {% endif %} + + {# Badge Arbre Remarquable #} + {% if asset.is_remarkable %} + {% translate "Arbre remarquable" %} + {% endif %} +
+ +
+ + {% if asset.model.scientific_name %} + {{ asset.model.scientific_name }} + • + {% endif %} + {{ thematic.name|default:"Nature" }} — {% translate "Arbres" %} + {% if asset.location %} + • + + {{ asset.location.name }} + {% endif %} +
+
+
+{% endblock asset_header_title %} + +{% block asset_header_title_readonly %} +
+
+ +
+
+
+

+ {% translate "Arbre" %} : {{ asset.code }} +

+ {% if asset.name_fr %} + — {{ asset.name_fr }} + {% elif asset.model %} + — {{ asset.model.get_name }} + {% endif %} + + {# Badges d'état #} + {% if asset.status == 'active' %} + {{ asset.get_status_display }} + {% elif asset.status == 'damaged' or asset.status == 'to_replace' %} + {{ asset.get_status_display }} + {% else %} + {{ asset.get_status_display }} + {% endif %} + + {# Badge Vitalité #} + {% if asset.vitality == 'strong' %} + {% translate "Vitalité forte" %} + {% elif asset.vitality == 'normal' %} + {% translate "Vitalité normale" %} + {% elif asset.vitality == 'weak' %} + {% translate "Vitalité faible" %} + {% elif asset.vitality == 'declining' or asset.vitality == 'dead' %} + {{ asset.get_vitality_display }} + {% endif %} + + {# Badge Stade #} + {% if asset.development_stage %} + {{ asset.get_development_stage_display }} + {% endif %} + + {# Badge Risque #} + {% if asset.risk_level == 'high' or asset.risk_level == 'imminent_danger' %} + {{ asset.get_risk_level_display }} + {% elif asset.risk_level == 'moderate' %} + {{ asset.get_risk_level_display }} + {% endif %} + + {# Badge Arbre Remarquable #} + {% if asset.is_remarkable %} + {% translate "Arbre remarquable" %} + {% endif %} +
+ +
+ + {% if asset.model.scientific_name %} + {{ asset.model.scientific_name }} + • + {% endif %} + {{ thematic.name|default:"Nature" }} — {% translate "Arbres" %} + {% if asset.location %} + • + + {{ asset.location.name }} + {% endif %} +
+
+
+{% endblock asset_header_title_readonly %} + + +{# ══════════════════════════════════════════════════════════════════════════════ #} +{# COLONNE PRINCIPALE : CARTES MÉTIER ARBRE #} +{# ══════════════════════════════════════════════════════════════════════════════ #} +{% block main_asset_content %} + +{# ── CARTE 1 : IDENTITÉ DE L'ARBRE ── #} +
+
+
+ {% translate "Identité & Informations générales" %} +
+ {% if asset.tag_number %} + + {{ asset.tag_number }} + + {% endif %} +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + {% if asset.external_reference or 'external_reference' in allowed_update_fields %} + + + + + {% endif %} + + + + + + + + + + + + + + + {% if asset.validation_status is not None or 'validation_status' in allowed_update_fields %} + + + + + {% endif %} + + + + + + + + + {% if asset.funding_program or 'funding_program' in allowed_update_fields %} + + + + + {% endif %} + + + + + + + +
{% translate "Code d'inventaire" %} +
{{ asset.code }}
+
+ +
+
{% translate "N° de plaquette / Bague" %} +
{{ asset.tag_number|default:"-" }}
+
+ +
+
{% translate "Nom commun (FR)" %} +
{{ asset.name_fr|default:"-" }}
+
+ +
+
{% translate "Nom commun (NL)" %} +
{{ asset.name_nl|default:"-" }}
+
+ +
+
{% translate "Référence externe" %} +
{{ asset.external_reference|default:"-" }}
+
+ +
+
{% translate "Date de plantation" %} +
+ + {% if asset.installation_date %} + {{ asset.installation_date|date:"d/m/Y" }} + {% else %} + - + {% endif %} + +
+
+ +
+
{% translate "Statut" %} +
+ + {% if asset.status == 'active' %} + {{ asset.get_status_display }} + {% elif asset.status == 'damaged' %} + {{ asset.get_status_display }} + {% elif asset.status == 'to_replace' %} + {{ asset.get_status_display }} + {% else %} + {{ asset.get_status_display }} + {% endif %} + +
+
+ +
+
{% translate "Statut de validation" %} +
+ + {% if asset.validation_status == 'validated' %} + {{ asset.get_validation_status_display }} + {% elif asset.validation_status == 'to_be_validated' %} + {{ asset.get_validation_status_display }} + {% elif asset.validation_status == 'to_be_completed' %} + {{ asset.get_validation_status_display }} + {% elif asset.validation_status == 'rejected' %} + {{ asset.get_validation_status_display }} + {% else %} + {{ asset.get_validation_status_display|default:"-" }} + {% endif %} + +
+
+ +
+
{% translate "Catégorie" %} +
{{ asset.category|default:"-" }}
+
+ +
+
{% translate "Programme / Projet" %} +
{{ asset.funding_program|default:"-" }}
+
+ +
+
{% translate "Note générale" %} +
+ {{ asset.note|default:"-"|linebreaksbr }} +
+
+ +
+
+
+
+ + +{# ── CARTE 2 : DENDROMÉTRIE & DIMENSIONS ── #} +
+
+
+ {% translate "Fiche dendrométrique & Dimensions" %} +
+
+
+ {# Mini-widgets d'indicateurs visuels #} +
+
+
+
{% translate "Hauteur" %}
+
+ {% if asset.height %}{{ asset.height }} m{% else %}—{% endif %} +
+
+
+
+
+
{% translate "Circonférence" %}
+
+ {% if asset.circumference %}{{ asset.circumference }} cm{% else %}—{% endif %} +
+
+
+
+
+
{% translate "Houppier" %}
+
+ {% if asset.crown_diameter %}{{ asset.crown_diameter }} m{% else %}—{% endif %} +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
{% translate "Hauteur totale (m)" %} +
{% if asset.height %}{{ asset.height }} m{% else %}-{% endif %}
+
+ +
+
{% translate "Circonférence à 1,30 m (cm)" %} +
{% if asset.circumference %}{{ asset.circumference }} cm{% else %}-{% endif %}
+
+ +
+
{% translate "Diamètre du houppier (m)" %} +
{% if asset.crown_diameter %}{{ asset.crown_diameter }} m{% else %}-{% endif %}
+
+ +
+
{% translate "Hauteur sous couronne (gabarit)" %} +
+ + {% if asset.crown_clearance %}{{ asset.crown_clearance }} m{% else %}-{% endif %} + +
+
+ +
+
{% translate "Stade de développement" %} +
+ + {% if asset.development_stage %} + {{ asset.get_development_stage_display }} + {% else %} + - + {% endif %} + +
+
+ +
+
{% translate "Protection racinaire (m)" %} +
{% if asset.protection_buffer %}{{ asset.protection_buffer }} m{% else %}-{% endif %}
+
+ +
+
{% translate "Profondeur de fosse (m)" %} +
{% if asset.depth %}{{ asset.depth }} m{% else %}-{% endif %}
+
+ +
+
+
+
+ + +{# ── CARTE 3 : DIAGNOSTIC SANITAIRE & SÉCURITÉ ── #} +
+
+
+ {% translate "Diagnostic sanitaire & Sécurité" %} +
+ {% if asset.is_remarkable %} + Remarquable + {% endif %} +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
{% translate "Vigueur / Vitalité" %} +
+ + {% if asset.vitality == 'strong' %} + {{ asset.get_vitality_display }} + {% elif asset.vitality == 'normal' %} + {{ asset.get_vitality_display }} + {% elif asset.vitality == 'weak' %} + {{ asset.get_vitality_display }} + {% elif asset.vitality == 'declining' or asset.vitality == 'dead' %} + {{ asset.get_vitality_display }} + {% else %} + - + {% endif %} + +
+
+ +
+
{% translate "État mécanique" %} +
+ + {% if asset.mechanical_state == 'good' %} + {{ asset.get_mechanical_state_display }} + {% elif asset.mechanical_state == 'fair' %} + {{ asset.get_mechanical_state_display }} + {% elif asset.mechanical_state %} + {{ asset.get_mechanical_state_display }} + {% else %} + - + {% endif %} + +
+
+ +
+
{% translate "Niveau de risque" %} +
+ + {% if asset.risk_level == 'low' %} + {{ asset.get_risk_level_display }} + {% elif asset.risk_level == 'moderate' %} + {{ asset.get_risk_level_display }} + {% elif asset.risk_level == 'high' or asset.risk_level == 'imminent_danger' %} + {{ asset.get_risk_level_display }} + {% else %} + - + {% endif %} + +
+
+ +
+
{% translate "Arbre remarquable / classé" %} +
+ + {% if asset.is_remarkable %} + {% translate "Oui" %} + {% else %} + {% translate "Non" %} + {% endif %} + +
+
+ +
+
{% translate "Défauts & Pathologies" %} +
+ {{ asset.health_defects|default:"-"|linebreaksbr }} +
+
+ +
+
{% translate "Dernier diagnostic" %} +
+ + {% if asset.last_phytosanitary_date %} + {{ asset.last_phytosanitary_date|date:"d/m/Y" }} + {% else %} + - + {% endif %} + +
+
+ +
+
{% translate "Prochaine inspection" %} +
+ + {% if asset.next_inspection_date %} + {{ asset.next_inspection_date|date:"d/m/Y" }} + {% else %} + - + {% endif %} + +
+
+ +
+
+
+
+ + +{# ── CARTE 4 : IMPLANTATION & CONDUITE DE TAILLE ── #} +
+
+
+ {% translate "Implantation & Conduite de taille" %} +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
{% translate "Contexte" %} +
{{ asset.get_implantation_context_display|default:"-" }}
+
+ +
+
{% translate "Type de pied / Fosse" %} +
{{ asset.get_pit_type_display|default:"-" }}
+
+ +
+
{% translate "Protection / Tuteurage" %} +
{{ asset.get_protection_type_display|default:"-" }}
+
+ +
+
{% translate "Contraintes environnantes" %} +
{{ asset.urban_constraints|default:"-" }}
+
+ +
+
{% translate "Conduite de taille" %} +
{{ asset.get_pruning_type_display|default:"-" }}
+
+ +
+
{% translate "Périodicité de taille" %} +
+ + {% if asset.pruning_frequency_years %} + {% blocktranslate with n=asset.pruning_frequency_years %}Tous les {{ n }} an(s){% endblocktranslate %} + {% else %} + - + {% endif %} + +
+
+ +
+
{% translate "Dernière taille" %} +
+ + {% if asset.last_pruning_date %} + {{ asset.last_pruning_date|date:"d/m/Y" }} + {% else %} + - + {% endif %} + +
+
+ +
+
+
+
+ +{% endblock main_asset_content %} + + +{# ══════════════════════════════════════════════════════════════════════════════ #} +{# COLONNE LATÉRALE : ESSENCE BOTANIQUE #} +{# ══════════════════════════════════════════════════════════════════════════════ #} +{% block model_card %} +{% asset_model_card asset card_title="Essence botanique" %} +{% endblock model_card %} diff --git a/loko/assets/templatetags/asset_tags.py b/loko/assets/templatetags/asset_tags.py index 7c15ac2..316582b 100644 --- a/loko/assets/templatetags/asset_tags.py +++ b/loko/assets/templatetags/asset_tags.py @@ -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,