loko/streetup/assets/models/trafficlights.py
2026-07-22 14:48:40 +02:00

632 lines
No EOL
28 KiB
Python

# ---------------------
# Traffic Lights Models
# ---------------------
from django.contrib.gis.db import models
from django.urls import reverse
from django.contrib.contenttypes.models import ContentType
from django.utils.translation import gettext_lazy as _
from django.contrib.postgres.fields import ArrayField
from assets.models import AbstractLocation, AbstractAsset, AbstractGeoAsset, AbstractAssetModel
from common.models import Municipality, Document
from common.storages import PrivateMediaStorage
private_storage = PrivateMediaStorage()
TL_INTERSECTION_STATUS_CHOICES = {
'active': _('Actif'),
'in_preparation': _('En préparation'),
'under_repair': _('En cours de réparation'),
'voluntarily_out_of_service': _('Mis hors service volontairement'),
'inactive': _('Désactivé'),
'archived': _('Archivé'),
}
TL_VOLTAGE_CHOICES = {
'10V': '10 Volts',
'230V': '230 Volts',
'LED': 'LED',
}
class TrafficLightAbstractAsset(AbstractAsset):
def get_absolute_url(self):
content_type = ContentType.objects.get_for_model(self.__class__)
return reverse(
'assets:trafficlights_assets_detail',
kwargs={
'asset_model': content_type.model,
'asset_id': self.id
}
)
class Meta:
abstract = True
# Traffic Light Abstract Geo Asset
class TrafficLightAbstractGeoAsset(AbstractGeoAsset):
def get_absolute_url(self):
content_type = ContentType.objects.get_for_model(self.__class__)
return reverse(
'assets:trafficlights_assets_detail',
kwargs={
'asset_model': content_type.model,
'asset_id': self.id
}
)
class Meta:
abstract = True
# Traffic Light Intersection (crossroads, ...)
class TrafficLightIntersection(AbstractLocation):
geom = models.PointField(srid=3812, null=True,blank=True)
voltage_type = models.CharField(choices=TL_VOLTAGE_CHOICES, null=True,blank=True)
status = models.CharField(choices=TL_INTERSECTION_STATUS_CHOICES, default='active')
begin_time = models.DateTimeField(null=True,blank=True)
master = models.ForeignKey("self", on_delete=models.SET_NULL, null=True, blank=True, related_name="slaves")
municipality = models.ForeignKey(Municipality, on_delete=models.SET_NULL, null=True, blank=True, related_name="trafficlights_intersections")
current_index = models.CharField(max_length=100, null=True, blank=True)
is_radar = models.BooleanField(default=False, verbose_name=_("Est un radar ?"))
def get_active_assets(self):
# Retrieve associated poles
poles = list(self.poles.filter(status='active'))
radar_poles = list(self.radar_poles.filter(status='active'))
all_poles = poles + radar_poles
all_poles.sort(key=lambda p: p.code)
# Retrieve associated cables
cables = self.cables.filter(status='active').order_by('code')
# Retrieve associated lanterns via poles
lanterns = TrafficLightLantern.objects.filter(pole__intersection=self).filter(status='active').order_by('code')
# Retrieve associated controllers
controllers = self.controllers.filter(status='active').order_by('code')
# Retrieve associated detectors
detectors = self.detectors.filter(status='active').order_by('code')
# Retrieve associated electrical cabinets
electrical_cabinets = self.electrical_cabinets.filter(status='active').order_by('code')
# Retrieve associated radars
radars = self.radars.filter(status='active').order_by('code')
return {
"poles": all_poles,
"cables": cables,
"lanterns": lanterns,
"controllers": controllers,
"detectors": detectors,
"electrical_cabinets": electrical_cabinets,
"radars": radars,
}
def get_all_assets(self):
# Retrieve associated poles
poles = list(self.poles.all())
radar_poles = list(self.radar_poles.all())
all_poles = poles + radar_poles
all_poles.sort(key=lambda p: p.code)
# Retrieve associated cables
cables = self.cables.all().order_by('code')
# Retrieve associated lanterns via poles
lanterns = TrafficLightLantern.objects.filter(pole__intersection=self).all().order_by('code')
# Retrieve associated controllers
controllers = self.controllers.all().order_by('code')
# Retrieve associated detectors
detectors = self.detectors.all().order_by('code')
# Retrieve associated electrical cabinets
electrical_cabinets = self.electrical_cabinets.all().order_by('code')
# Retrieve associated radars
radars = self.radars.all().order_by('code')
return {
"poles": all_poles,
"cables": cables,
"lanterns": lanterns,
"controllers": controllers,
"detectors": detectors,
"electrical_cabinets": electrical_cabinets,
"radars": radars,
}
def get_direct_child_assets(self):
# Retrieve associated poles
poles = list(self.poles.all())
radar_poles = list(self.radar_poles.all())
all_poles = poles + radar_poles
all_poles.sort(key=lambda p: p.code)
# Retrieve associated cables
cables = self.cables.all().order_by('code')
# Retrieve associated lanterns via poles
lanterns = TrafficLightLantern.objects.filter(pole__intersection=self).all().order_by('code')
# Retrieve associated controllers
controllers = self.controllers.all().order_by('code')
# Retrieve associated detectors
detectors = self.detectors.all().order_by('code')
# Retrieve associated electrical cabinets
electrical_cabinets = self.electrical_cabinets.all().order_by('code')
# Retrieve associated radars
radars = self.radars.all().order_by('code')
return {
"poles": all_poles,
"cables": cables,
"lanterns": lanterns,
"controllers": controllers,
"detectors": detectors,
"electrical_cabinets": electrical_cabinets,
"radars": radars,
}
def get_controller_contract(self):
"""
Get the maintenance contract of the most recently installed active controller.
"""
latest_controller = self.controllers.filter(status='active').order_by('-installation_date').first()
return latest_controller.controller_maintenance_contract if latest_controller else None
@property
def is_connected_to_control_center(self):
"""
Is the most recently installed active controller connected to the control center.
"""
latest_controller = self.controllers.filter(status='active').order_by('-installation_date').first()
return latest_controller.connected_to_control_center if latest_controller else None
def get_absolute_url(self):
return reverse("assets:trafficlights_intersections_detail", kwargs={"intersection_id": self.id})
TL_DOCUMENT_TYPE_CHOICES = (
('as_built', 'As Built'),
('cross_plan', _('Plan de croix')),
('other', _('Autre')),
('programming_file', _('Fichier de programmation')),
('connexion_plan', _('Plan de raccordement')),
)
class TrafficLightIntersectionDocument(Document):
DOCUMENT_MANAGEMENT_SYNC = True
intersection = models.ForeignKey(TrafficLightIntersection, on_delete=models.CASCADE, related_name="documents", verbose_name="Associated Intersection")
programming = models.ForeignKey("TrafficLightProgramming", on_delete=models.SET_NULL, null=True, blank=True, related_name="documents", verbose_name=_("Programmation associée"))
file = models.FileField(upload_to="assets_documents/traffic_lights/", storage=private_storage, verbose_name="Fichier", max_length=255)
thumbnail = models.ImageField(upload_to="assets_documents/thumbnails/traffic_lights/", storage=private_storage, null=True, blank=True, max_length=255)
document_type = models.CharField(max_length=50, choices=TL_DOCUMENT_TYPE_CHOICES, default='other', verbose_name="Type de document")
def save(self, *args, **kwargs):
from assets.views.trafficlights import ensure_intersection_document_folder, generate_dxf_overlay_managed_document
# Determine if it's a new or changed file
is_dxf = self.file and self.file.name.lower().endswith('.dxf')
# Ensure the intersection folder exists
folder = ensure_intersection_document_folder(self.intersection)
# Call the parent save method
super().save(*args, **kwargs)
# Link the managed document to the intersection folder so it appears in the documents tree
if self.managed_document and folder:
self.managed_document.folders.add(folder)
# If it's a DXF, generate/update overlay in document management
if is_dxf and self.managed_document and not getattr(self, 'skip_overlay_generation', False):
try:
generate_dxf_overlay_managed_document(self.managed_document, user=self.uploaded_by)
except Exception as e:
import logging
logger = logging.getLogger(__name__)
logger.error(f"Error generating DXF overlay ManagedDocument: {e}")
class TrafficLightContract(models.Model):
intersection = models.ForeignKey(TrafficLightIntersection, on_delete=models.CASCADE, related_name="trafficlight_contracts")
contract = models.ForeignKey("contracts.Contract", on_delete=models.CASCADE, related_name="trafficlight_contracts")
status = models.CharField(max_length=50, choices=[('active', 'Actif'), ('inactive', 'Inactif'), ('archived', 'Archivé')], default='active')
maintenance_type = models.CharField(max_length=50, choices=[('infra', 'Infrastructure'), ('controller', 'Contrôleur de traffic'), ('other', 'Autre')], default='other')
def __str__(self):
return f"{self.intersection.code} - {self.contract.contract_number}"
class Meta:
verbose_name = "Traffic Lights Contract"
verbose_name_plural = "Traffic Lights Contracts"
unique_together = ('intersection', 'contract')
# Traffic Light Pole Model
class TrafficLightPoleModel(AbstractAssetModel):
colour = models.CharField(max_length=200, null=True,blank=True, verbose_name=_("Couleur"))
ral = models.CharField(max_length=200, null=True,blank=True, verbose_name=_("RAL"))
height = models.FloatField(null=True,blank=True, verbose_name=_("Hauteur (m)"))
# Traffic Light Pole
class TrafficLightPole(TrafficLightAbstractGeoAsset):
model = models.ForeignKey(TrafficLightPoleModel, on_delete=models.PROTECT)
intersection = models.ForeignKey(TrafficLightIntersection, related_name="poles", on_delete=models.PROTECT)
geom = models.PointField(srid=3812, null=True,blank=True)
arc_length = models.FloatField(null=True, blank=True, verbose_name=_("Longueur de l'arc (m)"))
extension = models.BooleanField(default=False, verbose_name=_("Extension"))
colour = models.CharField(max_length=200, null=True,blank=True, verbose_name=_("Couleur"))
ral = models.CharField(max_length=200, null=True,blank=True, verbose_name=_("RAL"))
@property
def sorted_lanterns(self):
return self.lanterns.order_by('code')
@property
def sorted_accessories(self):
return self.accessories.order_by('code')
@property
def sorted_radars(self):
return self.radars.order_by('code')
@property
def sorted_detectors(self):
return self.detectors.order_by('code')
@property
def sorted_signpanels(self):
return self.signpanels_attached.order_by('code')
def save(self, *args, **kwargs):
super().save(*args, **kwargs)
for radar in self.radars.all():
radar.save()
class Meta:
verbose_name = _("Traffic light pole")
verbose_name_plural = _("Traffic light poles")
# Traffic Light Cable Model
class TrafficLightCableModel(AbstractAssetModel):
number_of_strands = models.IntegerField(verbose_name=_("Nombre de brins"))
# Traffic Light Cable
class TrafficLightCable(TrafficLightAbstractGeoAsset):
model = models.ForeignKey(TrafficLightCableModel, on_delete=models.PROTECT)
intersection = models.ForeignKey(TrafficLightIntersection, related_name="cables", on_delete=models.PROTECT)
geom = models.LineStringField(srid=3812, null=True,blank=True)
class Meta:
verbose_name = _("Câble")
verbose_name_plural = _("Câbles")
# Traffic Light Lantern Model
class TrafficLightLanternModel(AbstractAssetModel):
voltage = models.IntegerField(verbose_name=_("Tension (V)"))
# Traffic Light Lantern
class TrafficLightLantern(TrafficLightAbstractAsset):
model = models.ForeignKey(TrafficLightLanternModel, on_delete=models.PROTECT)
pole = models.ForeignKey(TrafficLightPole, related_name="lanterns", on_delete=models.PROTECT)
orientation = models.FloatField(null=True, blank=True)
class Meta:
verbose_name = _("Lanterne")
verbose_name_plural = _("Lanternes")
# Traffic Light Controller Model
class TrafficLightControllerModel(AbstractAssetModel):
pass
# Traffic Light Controller
class TrafficLightController(TrafficLightAbstractGeoAsset):
model = models.ForeignKey(TrafficLightControllerModel, on_delete=models.PROTECT)
intersection = models.ForeignKey(TrafficLightIntersection, related_name="controllers", on_delete=models.PROTECT)
connected_to_control_center = models.BooleanField(default=False, verbose_name=_("Connecté à la centrale de contrôle"))
controller_maintenance_contract = models.ForeignKey("contracts.Contract", on_delete=models.SET_NULL, null=True, blank=True)
plan = models.CharField(max_length=255, null=True, blank=True, verbose_name=_("Plan"))
cabinet_type = models.CharField(max_length=255, null=True, blank=True, verbose_name=_("Type"))
power = models.CharField(max_length=255, null=True, blank=True, verbose_name=_("Puissance"))
firmware_version = models.CharField(max_length=255, null=True, blank=True, verbose_name=_("Version du firmware"))
rms = models.BooleanField(default=True, verbose_name=_("RMS"))
url = models.URLField(null=True, blank=True, verbose_name=_("URL"))
@property
def sorted_hardwares(self):
return self.hardwares.order_by('code')
@property
def sorted_detectors(self):
return self.detectors.order_by('code')
@property
def sorted_network_hardwares(self):
return self.network_hardwares.order_by('code')
@property
def sorted_voltage_hardwares(self):
return self.voltage_hardwares.order_by('code')
class Meta:
verbose_name = _("Contrôleur")
verbose_name_plural = _("Contrôleurs")
# Traffic Light Controller Hardware
class TrafficLightControllerHardware(TrafficLightAbstractGeoAsset):
controller = models.ForeignKey(TrafficLightController, related_name="hardwares", on_delete=models.CASCADE)
hardware = models.CharField(max_length=255, null=True, blank=True, verbose_name=_("Hardware"))
name = models.CharField(max_length=255, null=True, blank=True, verbose_name=_("Nom du matériel"))
description = models.TextField(null=True, blank=True, verbose_name=_("Description du matériel"))
class Meta:
verbose_name = _("Hardware contrôleur")
verbose_name_plural = _("Hardwares contrôleur")
# Traffic Light Controller Log File
class TrafficLightControllerLogFile(models.Model):
controller = models.ForeignKey(TrafficLightController, related_name="logs", on_delete=models.SET_NULL, null=True, blank=True)
file = models.FileField(upload_to="assets/traffic_lights/controller_logs/", storage=private_storage, verbose_name="Fichier de log", max_length=255)
filedate = models.DateTimeField(verbose_name=_("Date du log"), null=True, blank=True)
class Meta:
verbose_name = _("Fichier de log du contrôleur")
verbose_name_plural = _("Fichiers de log du contrôleur")
# Traffic Light Detector Model
class TrafficLightDetectorModel(AbstractAssetModel):
pass
# Traffic Light Detector
class TrafficLightDetector(TrafficLightAbstractGeoAsset):
name = models.CharField(max_length=255, null=True, blank=True, verbose_name=_("Nom du détecteur"))
hardware = models.CharField(max_length=255, null=True, blank=True, verbose_name=_("Hardware"))
model = models.ForeignKey(TrafficLightDetectorModel, on_delete=models.PROTECT)
intersection = models.ForeignKey(TrafficLightIntersection, related_name="detectors", on_delete=models.PROTECT)
controller = models.ForeignKey(TrafficLightController, related_name="detectors", on_delete=models.PROTECT, null=True, blank=True)
pole = models.ForeignKey(TrafficLightPole, related_name="detectors", on_delete=models.PROTECT, null=True, blank=True)
phases = ArrayField(models.CharField(max_length=50), null=True, blank=True, verbose_name=_("Phases associées"))
mentionned_on_plan = models.BooleanField(default=False, verbose_name=_("Mentionné sur le plan"))
class Meta:
verbose_name = _("Détecteur")
verbose_name_plural = _("Détecteurs")
# Traffic Light Network Hardware
class TrafficLightNetworkHardware(TrafficLightAbstractGeoAsset):
name = models.CharField(max_length=255, null=True, blank=True, verbose_name=_("Nom du matériel"))
hardware = models.CharField(max_length=255, null=True, blank=True, verbose_name=_("Hardware"))
intersection = models.ForeignKey(TrafficLightIntersection, related_name="network_hardwares", on_delete=models.PROTECT)
controller = models.ForeignKey(TrafficLightController, related_name="network_hardwares", on_delete=models.PROTECT, null=True, blank=True)
mentionned_on_plan = models.BooleanField(default=False, verbose_name=_("Mentionné sur le plan"))
def __str__(self):
return self.name or f"Matériel {self.id}"
class Meta:
verbose_name = _("Network Hardware")
verbose_name_plural = _("Network Hardwares")
# Traffic Light Voltage Hardware
class TrafficLightVoltageHardware(TrafficLightAbstractGeoAsset):
name = models.CharField(max_length=255, null=True, blank=True, verbose_name=_("Nom du matériel"))
hardware = models.CharField(max_length=255, null=True, blank=True, verbose_name=_("Hardware"))
intersection = models.ForeignKey(TrafficLightIntersection, related_name="voltage_hardwares", on_delete=models.PROTECT)
controller = models.ForeignKey(TrafficLightController, related_name="voltage_hardwares", on_delete=models.PROTECT, null=True, blank=True)
mentionned_on_plan = models.BooleanField(default=False, verbose_name=_("Mentionné sur le plan"))
def __str__(self):
return self.name or f"Matériel {self.id}"
class Meta:
verbose_name = _("Voltage Hardware")
verbose_name_plural = _("Voltage Hardwares")
# Traffic Light Electrical Cabinet Model
class TrafficLightElectricalCabinetModel(AbstractAssetModel):
width = models.FloatField(null=True, blank=True, verbose_name=_("Largeur (cm)"))
height = models.FloatField(null=True, blank=True, verbose_name=_("Hauteur (cm)"))
depth = models.FloatField(null=True, blank=True, verbose_name=_("Profondeur (cm)"))
ip_rating = models.CharField(max_length=10, null=True, blank=True, verbose_name=_("Indice de protection IP"))
CABINET_TYPE_CHOICES = (
('traffic_lights', 'Feux de signalisation'),
('mixed', 'Mixte'),
('other', 'Autre'),
)
KEY_TYPE_CHOICES = (
('bks', 'BKS'),
('bur16', 'BUR 16'),
('eu4', 'EU 4'),
('siemens', 'Siemens'),
('yale5', 'Yale 5'),
('58335', '58335'),
('no_key', 'Pas de clé'),
('other', 'Autre'),
)
# Traffic Light Electrical Cabinet
class TrafficLightElectricalCabinet(TrafficLightAbstractGeoAsset):
model = models.ForeignKey(TrafficLightElectricalCabinetModel, on_delete=models.SET_NULL, null=True, blank=True)
intersection = models.ForeignKey(TrafficLightIntersection, related_name="electrical_cabinets", on_delete=models.PROTECT)
geom = models.PointField(srid=3812, null=True, blank=True)
ean_code = models.CharField(max_length=50, null=True, blank=True, verbose_name=_("Code EAN"))
power_supply = models.CharField(max_length=100, null=True, blank=True, verbose_name=_("Alimentation électrique"))
meter_number = models.CharField(max_length=100, null=True, blank=True, verbose_name=_("Numéro de compteur"))
publicity = models.BooleanField(default=False, verbose_name=_("Publicité"))
key_type = models.CharField(max_length=100, null=True, blank=True, choices=KEY_TYPE_CHOICES, verbose_name=_("Type de clé"))
cabinet_type = models.CharField(max_length=100, null=True, blank=True, choices=CABINET_TYPE_CHOICES, verbose_name=_("Type d'armoire"), default='traffic_lights')
managed_by = models.CharField(max_length=255, null=True, blank=True, verbose_name=_("Géré par"))
meter_inside = models.BooleanField(default=False, verbose_name=_("Présence Compteur"))
differential_switch = models.BooleanField(default=False, verbose_name=_("Interrupteur différentiel"))
differential_switch_caliber = models.CharField(max_length=50, null=True, blank=True, verbose_name=_("Calibre du disjoncteur différentiel"))
differential_breaker = models.BooleanField(default=False, verbose_name=_("Disjoncteur différentiel"))
differential_breaker_caliber = models.CharField(max_length=50, null=True, blank=True, verbose_name=_("Calibre du disjoncteur"))
differential_caliber = models.CharField(max_length=50, null=True, blank=True, verbose_name=_("Calibre différentiel"))
departure_towards = models.CharField(max_length=50, null=True, blank=True, verbose_name=_("Départ vers"))
visual_conformity = models.CharField(max_length=255, null=True, blank=True, verbose_name=_("Conformité visuelle"))
cleanliness = models.CharField(max_length=255, null=True, blank=True, verbose_name=_("Propreté"))
class Meta:
verbose_name = _("Armoire")
verbose_name_plural = _("Armoires")
class TrafficLightElectricalCabinetContentModel(AbstractAssetModel):
pass
class TrafficLightElectricalCabinetContent(TrafficLightAbstractGeoAsset):
cabinet = models.ForeignKey(TrafficLightElectricalCabinet, related_name="contents", on_delete=models.CASCADE)
model = models.ForeignKey(TrafficLightElectricalCabinetContentModel, on_delete=models.SET_NULL, null=True, blank=True)
description = models.CharField(max_length=255, null=True, blank=True, verbose_name=_("Description du contenu"))
number = models.CharField(max_length=100, null=True, blank=True, verbose_name=_("Numéro de référence"))
ean = models.CharField(max_length=100, null=True, blank=True, verbose_name=_("Code EAN"))
class Meta:
verbose_name = _("Contenu d'armoire")
verbose_name_plural = _("Contenus d'armoires")
# Traffic Light Accessory Model
class TrafficLightAccessoryModel(AbstractAssetModel):
pass
# Traffic Light Accessory
class TrafficLightAccessory(TrafficLightAbstractGeoAsset):
model = models.ForeignKey(TrafficLightAccessoryModel, on_delete=models.PROTECT, null=True, blank=True)
pole = models.ForeignKey(TrafficLightPole, related_name="accessories", on_delete=models.PROTECT, null=True, blank=True)
intersection = models.ForeignKey(TrafficLightIntersection, related_name="accessories", on_delete=models.PROTECT, null=True, blank=True)
orientation = models.FloatField(null=True, blank=True)
class Meta:
verbose_name = _("Accessoire")
verbose_name_plural = _("Accessoires")
# Treffic 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"))
info = models.TextField(null=True, blank=True, verbose_name=_("Informations"))
def __str__(self):
return f"{self.index} - {self.intersection.code}"
class Meta:
ordering = ['-commissioning_date']
# Traffic Light Radar Pole Model
class TrafficLightRadarPoleModel(AbstractAssetModel):
pass
# Traffic Light Radar Pole
class TrafficLightRadarPole(TrafficLightAbstractGeoAsset):
model = models.ForeignKey(TrafficLightRadarPoleModel, on_delete=models.SET_NULL, null=True, blank=True)
intersection = models.ForeignKey(TrafficLightIntersection, related_name="radar_poles", on_delete=models.PROTECT)
colour = models.CharField(max_length=200, null=True,blank=True, verbose_name=_("Couleur"))
ral = models.CharField(max_length=200, null=True,blank=True, verbose_name=_("RAL"))
@property
def sorted_lanterns(self):
return []
@property
def sorted_accessories(self):
return []
@property
def sorted_radars(self):
return self.radars.order_by('code')
@property
def sorted_signpanels(self):
return []
def save(self, *args, **kwargs):
super().save(*args, **kwargs)
for radar in self.radars.all():
radar.save()
class Meta:
verbose_name = _("Radar Pole")
verbose_name_plural = _("Radar Poles")
# Traffic Light Radar Model
class TrafficLightRadarModel(AbstractAssetModel):
class Meta:
verbose_name = _("Traffic Light Radar Model")
verbose_name_plural = _("Traffic Light Radar Models")
# Traffic Light Radar
class TrafficLightRadar(TrafficLightAbstractGeoAsset):
model = models.ForeignKey(TrafficLightRadarModel, on_delete=models.PROTECT)
intersection = models.ForeignKey(TrafficLightIntersection, related_name="radars", on_delete=models.PROTECT)
# Physical Supports
pole = models.ForeignKey(TrafficLightRadarPole, verbose_name=_("Radar Pole (Support)"), related_name="radars", on_delete=models.SET_NULL, null=True, blank=True)
publiclighting_pole = models.ForeignKey('assets.PublicLightingPole', verbose_name=_("Public Lighting Pole (Support)"), related_name="radars", on_delete=models.SET_NULL, null=True, blank=True)
trafficlights_pole = models.ForeignKey(TrafficLightPole, verbose_name=_("Traffic Light Pole (Support)"), related_name="radars", on_delete=models.SET_NULL, null=True, blank=True)
# Electrical Connections / Power Sources
power_publiclighting_pole = models.ForeignKey('assets.PublicLightingPole', verbose_name=_("Public Lighting Pole (Power)"), related_name="powered_radars", on_delete=models.SET_NULL, null=True, blank=True)
power_trafficlights_pole = models.ForeignKey(TrafficLightPole, verbose_name=_("Traffic Light Pole (Power)"), related_name="powered_radars", on_delete=models.SET_NULL, null=True, blank=True)
class Meta:
verbose_name = _("Radar")
verbose_name_plural = _("Radars")
def clean(self):
super().clean()
support = self.pole or self.publiclighting_pole or self.trafficlights_pole
if support and support.geom:
self.geom = support.geom
if hasattr(support, 'lon'):
self.lon = support.lon
if hasattr(support, 'lat'):
self.lat = support.lat
if hasattr(support, 'geojson'):
self.geojson = support.geojson
def save(self, *args, **kwargs):
support = self.pole or self.publiclighting_pole or self.trafficlights_pole
if support and support.geom:
self.geom = support.geom
if hasattr(support, 'lon'):
self.lon = support.lon
if hasattr(support, 'lat'):
self.lat = support.lat
if hasattr(support, 'geojson'):
self.geojson = support.geojson
super().save(*args, **kwargs)