From 94cb7148a07435fd8e1ac08f0b2e0afeceb5f182 Mon Sep 17 00:00:00 2001 From: kdeterme Date: Wed, 19 Aug 2026 11:30:42 +0200 Subject: [PATCH] feat: add support for TrafficLightProgramming management and textarea fields in generic asset templates --- ...rafficlightprogramming_options_and_more.py | 22 ++ ...3_add_trafficlight_programming_category.py | 52 ++++ loko/assets/models/trafficlights.py | 20 +- .../templates/assets/detail_generic.html | 2 + .../trafficlights_intersection_detail.html | 134 ++++++++- loko/assets/templatetags/asset_tags.py | 6 +- loko/assets/tests.py | 273 ++++++++++++++++++ loko/assets/urls.py | 1 + loko/assets/views/asset_editing.py | 53 +++- loko/assets/views/trafficlights.py | 93 +++++- 10 files changed, 638 insertions(+), 18 deletions(-) create mode 100644 loko/assets/migrations/0022_alter_trafficlightprogramming_options_and_more.py create mode 100644 loko/assets/migrations/0023_add_trafficlight_programming_category.py diff --git a/loko/assets/migrations/0022_alter_trafficlightprogramming_options_and_more.py b/loko/assets/migrations/0022_alter_trafficlightprogramming_options_and_more.py new file mode 100644 index 0000000..e5f12aa --- /dev/null +++ b/loko/assets/migrations/0022_alter_trafficlightprogramming_options_and_more.py @@ -0,0 +1,22 @@ +# Generated by Django 6.0.7 on 2026-08-19 09:20 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('assets', '0021_sync_asset_parent_data'), + ] + + operations = [ + migrations.AlterModelOptions( + name='trafficlightprogramming', + options={'ordering': ['-commissioning_date'], 'verbose_name': 'Programmation', 'verbose_name_plural': 'Programmations'}, + ), + migrations.AlterField( + model_name='trafficlightprogramming', + name='index', + field=models.CharField(blank=True, max_length=100, null=True, verbose_name='Index'), + ), + ] diff --git a/loko/assets/migrations/0023_add_trafficlight_programming_category.py b/loko/assets/migrations/0023_add_trafficlight_programming_category.py new file mode 100644 index 0000000..ebfd6ea --- /dev/null +++ b/loko/assets/migrations/0023_add_trafficlight_programming_category.py @@ -0,0 +1,52 @@ +from django.db import migrations + + +def create_programming_category(apps, schema_editor): + Thematic = apps.get_model('common', 'Thematic') + AssetCategory = apps.get_model('assets', 'AssetCategory') + ContentType = apps.get_model('contenttypes', 'ContentType') + + try: + thematic = Thematic.objects.get(code='trafficlights') + except Thematic.DoesNotExist: + thematic, _ = Thematic.objects.get_or_create( + code='trafficlights', + defaults={ + 'name_fr': 'Feux de signalisation', + 'name_nl': 'Verkeerslichten', + 'description': 'Gestion des carrefours à feux et équipements associés', + 'icon': 'bi-stoplights', + 'color': '#ffc107', + } + ) + + category, _cat_created = AssetCategory.objects.get_or_create( + code='TL_PROGRAMMING', + defaults={ + 'thematic': thematic, + 'name_fr': 'Programmation', + 'name_nl': 'Programmatie', + } + ) + + # Link ContentType for TrafficLightProgramming + try: + prog_ct = ContentType.objects.get(app_label='assets', model='trafficlightprogramming') + category.allowed_models.add(prog_ct) + except Exception: + pass + + +def remove_programming_category(apps, schema_editor): + AssetCategory = apps.get_model('assets', 'AssetCategory') + AssetCategory.objects.filter(code='TL_PROGRAMMING').delete() + + +class Migration(migrations.Migration): + dependencies = [ + ('assets', '0022_alter_trafficlightprogramming_options_and_more'), + ] + + operations = [ + migrations.RunPython(create_programming_category, remove_programming_category), + ] diff --git a/loko/assets/models/trafficlights.py b/loko/assets/models/trafficlights.py index 61819c2..97d3ef8 100644 --- a/loko/assets/models/trafficlights.py +++ b/loko/assets/models/trafficlights.py @@ -97,6 +97,9 @@ class TrafficLightIntersection(AbstractLocation): # Retrieve associated radars radars = self.radars.filter(status='active').order_by('code') + # Retrieve associated programmations + programmations = self.programmations.filter(status='active').order_by('code') + return { "poles": all_poles, "cables": cables, @@ -105,6 +108,7 @@ class TrafficLightIntersection(AbstractLocation): "detectors": detectors, "electrical_cabinets": electrical_cabinets, "radars": radars, + "programmations": programmations, } def get_all_assets(self): @@ -132,6 +136,9 @@ class TrafficLightIntersection(AbstractLocation): # Retrieve associated radars radars = self.radars.all().order_by('code') + # Retrieve associated programmations + programmations = self.programmations.all().order_by('code') + return { "poles": all_poles, "cables": cables, @@ -140,6 +147,7 @@ class TrafficLightIntersection(AbstractLocation): "detectors": detectors, "electrical_cabinets": electrical_cabinets, "radars": radars, + "programmations": programmations, } def get_direct_child_assets(self): @@ -167,6 +175,9 @@ class TrafficLightIntersection(AbstractLocation): # Retrieve associated radars radars = self.radars.all().order_by('code') + # Retrieve associated programmations + programmations = self.programmations.all().order_by('code') + return { "poles": all_poles, "cables": cables, @@ -175,6 +186,7 @@ class TrafficLightIntersection(AbstractLocation): "detectors": detectors, "electrical_cabinets": electrical_cabinets, "radars": radars, + "programmations": programmations, } def get_controller_contract(self): @@ -709,19 +721,21 @@ class TrafficLightAccessory(TrafficLightAbstractGeoAsset): -# Treffic Light Programmation +# Traffic Light Programmation class TrafficLightProgramming(TrafficLightAbstractAsset): intersection = models.ForeignKey(TrafficLightIntersection, on_delete=models.CASCADE, related_name="programmations") commissioning_date = models.DateTimeField(null=True, blank=True, verbose_name=_("Date de mise en service")) uncommissioning_date = models.DateTimeField(null=True, blank=True, verbose_name=_("Date de mise hors service")) plan = models.CharField(max_length=255, null=True, blank=True, verbose_name=_("Plan")) - index = models.CharField(max_length=100, verbose_name=_("Index")) + index = models.CharField(max_length=100, null=True, blank=True, verbose_name=_("Index")) info = models.TextField(null=True, blank=True, verbose_name=_("Informations")) def __str__(self): - return f"{self.index} - {self.intersection.code}" + return f"{self.index or self.code} - {self.intersection.code}" class Meta: + verbose_name = _("Programmation") + verbose_name_plural = _("Programmations") ordering = ['-commissioning_date'] diff --git a/loko/assets/templates/assets/detail_generic.html b/loko/assets/templates/assets/detail_generic.html index bf030bb..4b43268 100644 --- a/loko/assets/templates/assets/detail_generic.html +++ b/loko/assets/templates/assets/detail_generic.html @@ -462,6 +462,8 @@ {% elif field.field_type == 'date' %} + {% elif field.field_type == 'textarea' %} + {% else %} {% endif %} diff --git a/loko/assets/templates/assets/trafficlights_intersection_detail.html b/loko/assets/templates/assets/trafficlights_intersection_detail.html index cf65ce4..ceb3a81 100644 --- a/loko/assets/templates/assets/trafficlights_intersection_detail.html +++ b/loko/assets/templates/assets/trafficlights_intersection_detail.html @@ -850,7 +850,12 @@
{% translate "Programmations" %} - {{ prog_list|length }} + {{ prog_list|length }} + {% if 'create' in allowed_location_actions %} + + {% endif %}