# --------------------- # 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)