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 "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 }}
+
+
+
+
+ |
+
| {% 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 %}
+
+
+
+ |
+
| {% 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 %}
+
+
+
+
+
+ |
+
| {% 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 %}
+
+
+
+
+
+ |
+