From ac7571282854f523ea1b9feae31f8d829d8ff501 Mon Sep 17 00:00:00 2001 From: Karl Determe Date: Tue, 25 Aug 2026 13:25:37 +0200 Subject: [PATCH] feat: add traffic light cross-plan import functionality with file upload and data preview/integration support --- .../trafficlights_import_cross_plan.html | 129 ++++++- loko/assets/tests.py | 84 +++++ loko/assets/views/trafficlights.py | 328 ++++++++++++++++-- 3 files changed, 508 insertions(+), 33 deletions(-) diff --git a/loko/assets/templates/assets/trafficlights_import_cross_plan.html b/loko/assets/templates/assets/trafficlights_import_cross_plan.html index f787f43..98e8cf4 100644 --- a/loko/assets/templates/assets/trafficlights_import_cross_plan.html +++ b/loko/assets/templates/assets/trafficlights_import_cross_plan.html @@ -215,6 +215,11 @@ {% translate "Câbles" %} {{ cables|length }} + @@ -264,6 +269,11 @@ {% elif item.status == 'db_only' %} {% translate "DB uniquement" %} {% endif %} + {% for diff in item.details_diff %} +
+ {{ diff }} +
+ {% endfor %} {{ item.deduced_model_name|default:"—" }} {{ item.db_model_name|default:"—" }} @@ -292,7 +302,13 @@ {% if item.status != 'db_only' %} - + {% for m in lantern_models %} {% endfor %} @@ -432,6 +459,11 @@ {% elif item.status == 'db_only' %} {% translate "DB uniquement" %} {% endif %} + {% for diff in item.details_diff %} +
+ {{ diff }} +
+ {% endfor %} {{ item.deduced_model_name|default:"—" }} {{ item.db_model_name|default:"—" }} @@ -460,7 +492,13 @@ {% if item.status != 'db_only' %} - + {% for m in cable_models %} {% endfor %} @@ -562,7 +611,74 @@ {% empty %} - {% translate "Aucun câble détecté." %} + {% translate "Aucun câble détecté." %} + + {% endfor %} + + + + + + +
+
+ + {% translate "Équipements associés existants en base (panneaux, caméras ITS, contrôleurs, armoires, etc.) non décrits dans le plan de croix Excel. Par défaut, ils sont conservés." %} + +
+
+ + + + + + + + + + + + + + {% for item in other_assets %} + + + + + + + + + + {% empty %} + + {% endfor %} @@ -883,6 +999,7 @@ document.addEventListener('DOMContentLoaded', () => { bindSelectAll('select-all-lanterns', 'lantern-checkbox'); bindSelectAll('select-all-detectors', 'detector-checkbox'); bindSelectAll('select-all-cables', 'cable-checkbox'); + bindSelectAll('select-all-other', 'other-checkbox'); // 2. Interactive Switch Logic between Table View and Map View const tableView = document.getElementById('table-view'); diff --git a/loko/assets/tests.py b/loko/assets/tests.py index e87a965..84f2771 100644 --- a/loko/assets/tests.py +++ b/loko/assets/tests.py @@ -1323,6 +1323,90 @@ class TrafficLightCrossPlanImportTest(TestCase): self.assertIsNotNone(new_active) self.assertEqual(old_lantern.replaced_by, new_active) + def test_other_associated_assets_preview_and_integration(self): + from assets.models import TrafficLightPole, TrafficLightController, TrafficLightControllerHardware, TrafficLightAccessory + from assets.models.sign import SignPanel + from assets.models.its import ITSGeoAsset + + pole = TrafficLightPole.objects.create( + intersection=self.intersection, + code="SWB01_A01", + status="active" + ) + ctrl = TrafficLightController.objects.create( + intersection=self.intersection, + code="SWB01_C01", + status="active" + ) + hw = TrafficLightControllerHardware.objects.create( + controller=ctrl, + code="SWB01_HW_1", + status="active" + ) + sign = SignPanel.objects.create( + trafficlight_pole=pole, + code="SWB01_SP01", + status="active" + ) + its = ITSGeoAsset.objects.create( + trafficlights_pole=pole, + code="SWB01_ITS01", + status="active" + ) + acc = TrafficLightAccessory.objects.create( + intersection=self.intersection, + pole=pole, + code="SWB01_ACC01", + status="active" + ) + + self.client.force_login(self.user) + import os + from django.conf import settings + from django.core.files.uploadedfile import SimpleUploadedFile + + real_excel_path = os.path.join(os.path.dirname(settings.BASE_DIR), 'external_files', '18ac47df841-2808f-DOC-23270.xlsx') + if not os.path.exists(real_excel_path): + self.skipTest("Real Excel file for test not found") + + with open(real_excel_path, 'rb') as f: + excel_content = f.read() + + uploaded_file = SimpleUploadedFile("test_cross_plan.xlsx", excel_content, content_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet") + + url_import = reverse("assets:import_cross_plan", args=[self.intersection.id]) + response = self.client.post(url_import, {"cross_plan_file_upload": uploaded_file}) + self.assertEqual(response.status_code, 200) + self.assertIn("other_assets", response.context) + other_assets = response.context["other_assets"] + self.assertTrue(len(other_assets) >= 4) + + # Test selective integration: archive controller, keep sign panel, replace pole + url_integrate = reverse("assets:integrate_cross_plan", args=[self.intersection.id]) + response_integrate = self.client.post(url_integrate, { + "cross_plan_filename": response.context["cross_plan_filename"], + f"selected_other_controller_{ctrl.id}": "on", + f"action_other_controller_{ctrl.id}": "archive", + f"selected_other_signpanel_{sign.id}": "on", + f"action_other_signpanel_{sign.id}": "keep", + "selected_pole_SWB01_A01": "on", + "action_pole_SWB01_A01": "replace", + "db_id_pole_SWB01_A01": str(pole.id) + }) + self.assertEqual(response_integrate.status_code, 302) + + ctrl.refresh_from_db() + self.assertEqual(ctrl.status, "archived") + hw.refresh_from_db() + self.assertEqual(hw.status, "archived") + + sign.refresh_from_db() + self.assertEqual(sign.status, "active") + pole.refresh_from_db() + self.assertEqual(pole.status, "archived") + # Sign panel should have been re-linked to new replacement pole + self.assertEqual(sign.trafficlight_pole, pole.replaced_by) + def test_parse_telecom_detectors(self): import openpyxl import tempfile diff --git a/loko/assets/views/trafficlights.py b/loko/assets/views/trafficlights.py index 8f7d958..173436a 100644 --- a/loko/assets/views/trafficlights.py +++ b/loko/assets/views/trafficlights.py @@ -4080,17 +4080,17 @@ def import_cross_plan(request, intersection_id): if code in existing_poles: ep = existing_poles[code] - is_diff = ep.model_id != db_model.id - status = 'modified' if is_diff else 'identical' + # Since cross plans do not specify pole physical models (height, color), keep existing DB model + status = 'identical' compared_poles.append({ 'code': code, 'short_code': dp['short_code'], 'name': ep.name_fr or dp.get('name', dp['short_code']), 'status': status, - 'deduced_model_name': model_name, + 'deduced_model_name': ep.model.name_fr if ep.model else model_name, 'db_model_name': ep.model.name_fr if ep.model else '', 'db_id': ep.id, - 'details_diff': f"Modèle différent (DB: {ep.model.name_fr if ep.model else 'Aucun'} vs Excel: {model_name})" if is_diff else '' + 'details_diff': [] }) # remove from processed existing_poles.pop(code) @@ -4103,7 +4103,7 @@ def import_cross_plan(request, intersection_id): 'deduced_model_name': model_name, 'db_model_name': '', 'db_id': None, - 'details_diff': '' + 'details_diff': [] }) # Add remaining existing active poles (orphans) for code, ep in existing_poles.items(): @@ -4115,7 +4115,7 @@ def import_cross_plan(request, intersection_id): 'deduced_model_name': '', 'db_model_name': ep.model.name_fr if ep.model else '', 'db_id': ep.id, - 'details_diff': _("Absent du fichier Excel (Sera conservé ou archivé)") + 'details_diff': [_("Absent du fichier Excel (présent en DB)")] }) # ── 2. CABLES COMPARISON ── @@ -4137,7 +4137,7 @@ def import_cross_plan(request, intersection_id): 'deduced_model_name': model_name, 'db_model_name': ec.model.name_fr if ec.model else '', 'db_id': ec.id, - 'details_diff': f"Modèle différent (DB: {ec.model.name_fr if ec.model else 'Aucun'} vs Excel: {model_name})" if is_diff else '', + 'details_diff': [f"Modèle : {ec.model.name_fr if ec.model else 'Aucun'} → {model_name}"] if is_diff else [], 'connected_poles': dc.get("connected_poles", []) }) existing_cables.pop(code) @@ -4149,7 +4149,7 @@ def import_cross_plan(request, intersection_id): 'deduced_model_name': model_name, 'db_model_name': '', 'db_id': None, - 'details_diff': '', + 'details_diff': [], 'connected_poles': dc.get("connected_poles", []) }) for code, ec in existing_cables.items(): @@ -4160,7 +4160,7 @@ def import_cross_plan(request, intersection_id): 'deduced_model_name': '', 'db_model_name': ec.model.name_fr if ec.model else '', 'db_id': ec.id, - 'details_diff': _("Absent du fichier Excel") + 'details_diff': [_("Absent du fichier Excel (présent en DB)")] }) # ── 3. LANTERNS COMPARISON ── @@ -4187,9 +4187,9 @@ def import_cross_plan(request, intersection_id): details = [] if is_rename: - details.append(f"Renommer le code (DB: {el.code} vs Excel: {code})") + details.append(f"Code : {el.code} → {code}") if is_model_diff: - details.append(f"Modèle différent (DB: {el.model.name_fr if el.model else 'Aucun'} vs Excel: {model_name})") + details.append(f"Modèle : {el.model.name_fr if el.model else 'Aucun'} → {model_name}") compared_lanterns.append({ 'code': code, @@ -4199,7 +4199,7 @@ def import_cross_plan(request, intersection_id): 'db_model_name': el.model.name_fr if el.model else '', 'db_id': el.id, 'pole_code': dl['pole_code'], - 'details_diff': ", ".join(details) if details else '' + 'details_diff': details }) existing_lanterns.pop(match_key) else: @@ -4211,7 +4211,7 @@ def import_cross_plan(request, intersection_id): 'db_model_name': '', 'db_id': None, 'pole_code': dl['pole_code'], - 'details_diff': '' + 'details_diff': [] }) for code, el in existing_lanterns.items(): compared_lanterns.append({ @@ -4222,7 +4222,7 @@ def import_cross_plan(request, intersection_id): 'db_model_name': el.model.name_fr if el.model else '', 'db_id': el.id, 'pole_code': el.pole.code, - 'details_diff': _("Absent du fichier Excel") + 'details_diff': [_("Absent du fichier Excel (présent en DB)")] }) # ── 4. DETECTORS COMPARISON ── @@ -4248,9 +4248,9 @@ def import_cross_plan(request, intersection_id): is_diff = ed.model_id != db_model.id details = [] if match_key != code: - details.append(f"Renommer le code (DB: {match_key} vs Excel: {code})") + details.append(f"Code : {match_key} → {code}") if is_diff: - details.append(f"Modèle différent (DB: {ed.model.name_fr if ed.model else 'Aucun'} vs Excel: {model_name})") + details.append(f"Modèle : {ed.model.name_fr if ed.model else 'Aucun'} → {model_name}") status = 'modified' if (is_diff or match_key != code) else 'identical' compared_detectors.append({ @@ -4261,7 +4261,7 @@ def import_cross_plan(request, intersection_id): 'db_model_name': ed.model.name_fr if ed.model else '', 'db_id': ed.id, 'pole_code': dd['pole_code'], - 'details_diff': ", ".join(details) if details else '' + 'details_diff': details }) existing_detectors.pop(match_key) else: @@ -4273,7 +4273,7 @@ def import_cross_plan(request, intersection_id): 'db_model_name': '', 'db_id': None, 'pole_code': dd['pole_code'], - 'details_diff': '' + 'details_diff': [] }) for code, ed in existing_detectors.items(): compared_detectors.append({ @@ -4284,9 +4284,212 @@ def import_cross_plan(request, intersection_id): 'db_model_name': ed.model.name_fr if ed.model else '', 'db_id': ed.id, 'pole_code': ed.pole.code if ed.pole else '', - 'details_diff': _("Absent du fichier Excel") + 'details_diff': [_("Absent du fichier Excel (présent en DB)")] }) + # ── 5. OTHER ASSOCIATED ASSETS (NOT IN CROSS PLAN) ── + other_assets = [] + try: + from assets.models.sign import SignPanel + for sp in SignPanel.objects.filter(trafficlight_pole__intersection=intersection, status='active').select_related('model', 'signpanel_type', 'trafficlight_pole'): + model_display = sp.model.name_fr if sp.model else (sp.signpanel_type.name_fr if sp.signpanel_type else (sp.signpanel_text or "—")) + other_assets.append({ + 'type': 'signpanel', + 'category_label': _("Panneau de signalisation"), + 'badge_class': 'bg-info text-dark', + 'id': sp.id, + 'unique_key': f"signpanel_{sp.id}", + 'code': sp.code or f"SP_{sp.id}", + 'name': sp.name_fr or sp.code or _("Panneau"), + 'model_name': model_display, + 'support': sp.trafficlight_pole.code if sp.trafficlight_pole else _("Poteau"), + 'status': 'db_only', + 'details_diff': [_("Présent en DB (attaché au poteau)")], + 'action_default': 'keep', + }) + except Exception: + pass + + try: + from assets.models.its import ITSGeoAsset + for its in ITSGeoAsset.objects.filter(trafficlights_pole__intersection=intersection, status='active').select_related('model', 'trafficlights_pole'): + other_assets.append({ + 'type': 'itsasset', + 'category_label': _("Caméra ANPR / ITS"), + 'badge_class': 'bg-primary', + 'id': its.id, + 'unique_key': f"itsasset_{its.id}", + 'code': its.code or f"ITS_{its.id}", + 'name': its.name_fr or its.code or _("Équipement ITS"), + 'model_name': its.model.name_fr if its.model else "—", + 'support': its.trafficlights_pole.code if its.trafficlights_pole else _("Poteau"), + 'status': 'db_only', + 'details_diff': [_("Présent en DB (attaché au poteau)")], + 'action_default': 'keep', + }) + except Exception: + pass + + try: + for c in TrafficLightController.objects.filter(intersection=intersection, status='active').select_related('model'): + hw_count = c.hardwares.filter(status='active').count() + diff_note = _("Présent en DB") + if hw_count > 0: + diff_note += f" ({hw_count} hardwares)" + other_assets.append({ + 'type': 'controller', + 'category_label': _("Contrôleur"), + 'badge_class': 'bg-secondary', + 'id': c.id, + 'unique_key': f"controller_{c.id}", + 'code': c.code or f"CTRL_{c.id}", + 'name': c.name_fr or c.code or _("Contrôleur"), + 'model_name': c.model.name_fr if c.model else "—", + 'support': _("Carrefour"), + 'status': 'db_only', + 'details_diff': [diff_note], + 'action_default': 'keep', + }) + except Exception: + pass + + try: + for nh in TrafficLightNetworkHardware.objects.filter(intersection=intersection, controller__isnull=True, status='active'): + other_assets.append({ + 'type': 'networkhardware', + 'category_label': _("Matériel Réseau"), + 'badge_class': 'bg-dark', + 'id': nh.id, + 'unique_key': f"networkhardware_{nh.id}", + 'code': nh.code or f"NH_{nh.id}", + 'name': nh.name or nh.hardware or _("Matériel réseau"), + 'model_name': nh.hardware or "—", + 'support': _("Carrefour"), + 'status': 'db_only', + 'details_diff': [_("Présent en DB (autonome)")], + 'action_default': 'keep', + }) + except Exception: + pass + + try: + for vh in TrafficLightVoltageHardware.objects.filter(intersection=intersection, controller__isnull=True, status='active'): + other_assets.append({ + 'type': 'voltagehardware', + 'category_label': _("Matériel Tension"), + 'badge_class': 'bg-warning text-dark', + 'id': vh.id, + 'unique_key': f"voltagehardware_{vh.id}", + 'code': vh.code or f"VH_{vh.id}", + 'name': vh.name or vh.hardware or _("Matériel tension"), + 'model_name': vh.hardware or "—", + 'support': vh.controller.code if vh.controller else _("Carrefour"), + 'status': 'db_only', + 'details_diff': [_("Présent en DB (lié au contrôleur)")], + 'action_default': 'keep', + }) + except Exception: + pass + + try: + for ec in TrafficLightElectricalCabinet.objects.filter(intersection=intersection, status='active').select_related('model'): + other_assets.append({ + 'type': 'electricalcabinet', + 'category_label': _("Armoire électrique"), + 'badge_class': 'bg-secondary', + 'id': ec.id, + 'unique_key': f"electricalcabinet_{ec.id}", + 'code': ec.code or f"CAB_{ec.id}", + 'name': ec.name_fr or ec.code or _("Armoire"), + 'model_name': ec.model.name_fr if ec.model else (ec.ean_code or "—"), + 'support': _("Carrefour"), + 'status': 'db_only', + 'details_diff': [_("Présent en DB")], + 'action_default': 'keep', + }) + except Exception: + pass + + try: + for acc in TrafficLightAccessory.objects.filter(intersection=intersection, status='active').select_related('model', 'pole'): + other_assets.append({ + 'type': 'accessory', + 'category_label': _("Accessoire"), + 'badge_class': 'bg-light text-dark border', + 'id': acc.id, + 'unique_key': f"accessory_{acc.id}", + 'code': acc.code or f"ACC_{acc.id}", + 'name': acc.name_fr or acc.code or _("Accessoire"), + 'model_name': acc.model.name_fr if acc.model else "—", + 'support': acc.pole.code if acc.pole else _("Carrefour"), + 'status': 'db_only', + 'details_diff': [_("Présent en DB")], + 'action_default': 'keep', + }) + except Exception: + pass + + try: + for rad in TrafficLightRadar.objects.filter(intersection=intersection, status='active').select_related('model', 'trafficlights_pole', 'pole'): + sup = rad.trafficlights_pole.code if rad.trafficlights_pole else (rad.pole.code if rad.pole else _("Carrefour")) + other_assets.append({ + 'type': 'radar', + 'category_label': _("Radar"), + 'badge_class': 'bg-danger', + 'id': rad.id, + 'unique_key': f"radar_{rad.id}", + 'code': rad.code or f"RAD_{rad.id}", + 'name': rad.name_fr or rad.code or _("Radar"), + 'model_name': rad.model.name_fr if rad.model else "—", + 'support': sup, + 'status': 'db_only', + 'details_diff': [_("Présent en DB")], + 'action_default': 'keep', + }) + except Exception: + pass + + try: + for prog in TrafficLightProgramming.objects.filter(intersection=intersection, status='active'): + other_assets.append({ + 'type': 'programming', + 'category_label': _("Programmation"), + 'badge_class': 'bg-info text-dark', + 'id': prog.id, + 'unique_key': f"programming_{prog.id}", + 'code': prog.code or f"PROG_{prog.id}", + 'name': prog.plan or prog.index or _("Programmation"), + 'model_name': prog.index or "—", + 'support': _("Carrefour"), + 'status': 'db_only', + 'details_diff': [_("Présent en DB")], + 'action_default': 'keep', + }) + except Exception: + pass + + try: + from assets.models.publiclighting import PublicLightingLightFixture + for lf in PublicLightingLightFixture.objects.filter(traffic_light_pole__intersection=intersection, status='active').select_related('model', 'traffic_light_pole'): + other_assets.append({ + 'type': 'lightfixture', + 'category_label': _("Luminaire EP"), + 'badge_class': 'bg-warning text-dark', + 'id': lf.id, + 'unique_key': f"lightfixture_{lf.id}", + 'code': lf.code or f"LF_{lf.id}", + 'name': lf.name_fr or lf.code or _("Luminaire"), + 'model_name': lf.model.name_fr if lf.model else "—", + 'support': lf.traffic_light_pole.code if lf.traffic_light_pole else _("Poteau"), + 'status': 'db_only', + 'details_diff': [_("Présent en DB (attaché au poteau)")], + 'action_default': 'keep', + }) + except Exception: + pass + + other_assets.sort(key=lambda x: (x['category_label'], x['code'])) + # Sort compared lists alphabetically by code for preview compared_poles.sort(key=lambda x: x['code']) compared_cables.sort(key=lambda x: x['code']) @@ -4308,6 +4511,7 @@ def import_cross_plan(request, intersection_id): 'cables': compared_cables, 'lanterns': compared_lanterns, 'detectors': compared_detectors, + 'other_assets': other_assets, 'pole_models': pole_models, 'lantern_models': lantern_models, 'detector_models': detector_models, @@ -4479,6 +4683,22 @@ def integrate_cross_plan(request, intersection_id): p.save() created_poles[dp["code"]] = p + # Re-link existing active attached assets (signs, ITS, accessories, radars, light fixtures) to new poles matching code + try: + from assets.models.sign import SignPanel + from assets.models.its import ITSGeoAsset + from assets.models.publiclighting import PublicLightingLightFixture + from assets.models.trafficlights import TrafficLightAccessory, TrafficLightRadar + + for code, new_pole in created_poles.items(): + SignPanel.objects.filter(trafficlight_pole__intersection=intersection, trafficlight_pole__code=code, status='active').update(trafficlight_pole=new_pole) + ITSGeoAsset.objects.filter(trafficlights_pole__intersection=intersection, trafficlights_pole__code=code, status='active').update(trafficlights_pole=new_pole) + TrafficLightAccessory.objects.filter(intersection=intersection, pole__code=code, status='active').update(pole=new_pole) + TrafficLightRadar.objects.filter(intersection=intersection, trafficlights_pole__code=code, status='active').update(trafficlights_pole=new_pole) + PublicLightingLightFixture.objects.filter(traffic_light_pole__intersection=intersection, traffic_light_pole__code=code, status='active').update(traffic_light_pole=new_pole) + except Exception: + pass + # Re-create all cables for dc in deduced["cables"]: db_model = resolve_cable_model(dc["model_name"]) @@ -4545,14 +4765,6 @@ def integrate_cross_plan(request, intersection_id): action = request.POST.get(f"action_pole_{code}", "keep") else: action = "keep" - selected_model_id = request.POST.get(f"model_pole_{code}") - - db_model = None - if selected_model_id: - db_model = TrafficLightPoleModel.objects.filter(pk=selected_model_id).first() - if not db_model: - db_model = resolve_pole_model(dp["model"]) - db_id = request.POST.get(f"db_id_pole_{code}") existing = None if db_id: @@ -4560,6 +4772,16 @@ def integrate_cross_plan(request, intersection_id): if not existing: existing = TrafficLightPole.objects.filter(intersection=intersection, code=code, status='active').first() + selected_model_id = request.POST.get(f"model_pole_{code}") + db_model = None + if selected_model_id: + db_model = TrafficLightPoleModel.objects.filter(pk=selected_model_id).first() + if not db_model: + if existing and existing.model: + db_model = existing.model + else: + db_model = resolve_pole_model(dp["model"]) + custom_name = request.POST.get(f"name_pole_{code}", dp.get('name', dp['short_code'])) if action == "add" or (action == "replace" and not existing): p = TrafficLightPole( @@ -4603,6 +4825,17 @@ def integrate_cross_plan(request, intersection_id): existing.replaced_by = new_pole existing.save() created_poles[code] = new_pole + try: + from assets.models.sign import SignPanel + from assets.models.its import ITSGeoAsset + from assets.models.publiclighting import PublicLightingLightFixture + SignPanel.objects.filter(trafficlight_pole=existing).update(trafficlight_pole=new_pole) + ITSGeoAsset.objects.filter(trafficlights_pole=existing).update(trafficlights_pole=new_pole) + TrafficLightAccessory.objects.filter(pole=existing).update(pole=new_pole) + TrafficLightRadar.objects.filter(trafficlights_pole=existing).update(trafficlights_pole=new_pole) + PublicLightingLightFixture.objects.filter(traffic_light_pole=existing).update(traffic_light_pole=new_pole) + except Exception: + pass else: # 'keep' if existing: @@ -4867,6 +5100,47 @@ def integrate_cross_plan(request, intersection_id): status='archived', updated_by=request.user ) + # --- 5. Other associated assets integration --- + for key in request.POST.keys(): + if key.startswith("action_other_"): + suffix = key.replace("action_other_", "") + if "_" in suffix: + asset_type, asset_id_str = suffix.split("_", 1) + is_selected = request.POST.get(f"selected_other_{suffix}") == "on" + action = request.POST.get(key, "keep") + if is_selected and action == "archive": + try: + asset_id = int(asset_id_str) + if asset_type == "signpanel": + from assets.models.sign import SignPanel + SignPanel.objects.filter(pk=asset_id).update(status='archived', updated_by=request.user) + elif asset_type == "itsasset": + from assets.models.its import ITSGeoAsset + ITSGeoAsset.objects.filter(pk=asset_id).update(status='archived', updated_by=request.user) + elif asset_type == "controller": + TrafficLightController.objects.filter(pk=asset_id).update(status='archived', updated_by=request.user) + from assets.models.trafficlights import TrafficLightControllerHardware + TrafficLightControllerHardware.objects.filter(controller_id=asset_id).update(status='archived', updated_by=request.user) + TrafficLightNetworkHardware.objects.filter(controller_id=asset_id).update(status='archived', updated_by=request.user) + TrafficLightVoltageHardware.objects.filter(controller_id=asset_id).update(status='archived', updated_by=request.user) + elif asset_type == "networkhardware": + TrafficLightNetworkHardware.objects.filter(pk=asset_id).update(status='archived', updated_by=request.user) + elif asset_type == "voltagehardware": + TrafficLightVoltageHardware.objects.filter(pk=asset_id).update(status='archived', updated_by=request.user) + elif asset_type == "electricalcabinet": + TrafficLightElectricalCabinet.objects.filter(pk=asset_id).update(status='archived', updated_by=request.user) + elif asset_type == "accessory": + TrafficLightAccessory.objects.filter(pk=asset_id).update(status='archived', updated_by=request.user) + elif asset_type == "radar": + TrafficLightRadar.objects.filter(pk=asset_id).update(status='archived', updated_by=request.user) + elif asset_type == "programming": + TrafficLightProgramming.objects.filter(pk=asset_id).update(status='archived', updated_by=request.user) + elif asset_type == "lightfixture": + from assets.models.publiclighting import PublicLightingLightFixture + PublicLightingLightFixture.objects.filter(pk=asset_id).update(status='archived', updated_by=request.user) + except (ValueError, TypeError, Exception): + pass + messages.success(request, "Les modifications d'assets ont été appliquées avec succès.") # Cleanup parsed file
{% translate "Catégorie" %}{% translate "Code Asset" %}{% translate "Nom / Modèle" %}{% translate "Support / Liaison" %}{% translate "Statut DB" %}{% translate "Décision" %}
+ + + {{ item.category_label }} + + {{ item.code }} + + +
{{ item.name }}
+ {% if item.model_name and item.model_name != "—" %} +
{{ item.model_name }}
+ {% endif %} +
+ {{ item.support }} + + {% translate "En base (Actif)" %} + {% for diff in item.details_diff %} +
+ {{ diff }} +
+ {% endfor %} +
+ +
{% translate "Aucun autre équipement associé en base pour ce carrefour." %}