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

271 lines
No EOL
11 KiB
Python

# ----------------------
# Road Sign Models
# ----------------------
from django.contrib.gis.db import models
from django.urls import reverse
from django.utils.text import slugify
from django.utils.translation import get_language
from django.conf import settings
from django.utils.translation import gettext_lazy as _
import os
from assets.models import AbstractLocation, AbstractAsset, AbstractGeoAsset, AbstractAssetModel
from common.models import Municipality, Document
# Road Sign Street
class SignStreet(AbstractLocation):
geom = models.MultiPolygonField(srid=3812, null=True,blank=True)
municipality = models.ForeignKey(Municipality, on_delete=models.SET_NULL, null=True, blank=True, related_name="sign_streets")
road_code = models.CharField(max_length=50, null=True, blank=True)
section_code = models.CharField(max_length=50, null=True, blank=True)
street_level = models.CharField(max_length=50, null=True, blank=True)
def get_active_assets(self):
# Retrieve associated signs
signpoles = self.signpoles.filter(status='active')
signpanels = self.signpanels.filter(status='active')
return {
"signpoles": signpoles,
"signpanels": signpanels,
}
def get_all_assets(self):
# Retrieve associated signs
signpoles = self.signpoles.all()
signpanels = self.signpanels.all()
return {
"signpoles": signpoles,
"signpanels": signpanels,
}
def get_direct_child_assets(self):
# Retrieve associated signs
signpoles = self.signpoles.all()
signpanels = self.signpanels.all()
return {
"signpoles": signpoles,
"signpanels": signpanels,
}
def get_absolute_url(self):
return reverse("assets:sign_streets_detail", kwargs={"street_id": self.id})
def upload_to_signstreet(instance, filename):
location = instance.street
code = slugify(location.code or "unknown") # Fallback si code est None
return os.path.join("assets_documents", "sign", code, filename)
SIGN_DOCUMENT_TYPE_CHOICES = (
('as_built', 'As Built'),
('other', 'Autre'),
)
class SignStreetDocument(Document):
street = models.ForeignKey(SignStreet, on_delete=models.CASCADE, related_name="documents", verbose_name="Associated Street")
file = models.FileField(upload_to=upload_to_signstreet, verbose_name="Fichier", max_length=255)
thumbnail = models.ImageField(upload_to=upload_to_signstreet, null=True, blank=True, max_length=255)
document_type = models.CharField(max_length=50, choices=SIGN_DOCUMENT_TYPE_CHOICES, default='other', verbose_name="Type de document")
uploaded_by = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.SET_NULL,
null=True,
blank=True,
verbose_name="Ajouté par",
related_name="uploaded_sign_street_documents_assets",
)
# Road Sign Pole Model
class SignPoleModel(AbstractAssetModel):
color = models.CharField(max_length=200, null=True,blank=True)
ral = models.CharField(max_length=200, null=True,blank=True)
height = models.FloatField(null=True,blank=True)
diameter = models.FloatField(null=True,blank=True)
material = models.CharField(max_length=200, null=True,blank=True)
# Road Sign Pole
class SignPole(AbstractGeoAsset):
model = models.ForeignKey(SignPoleModel, on_delete=models.PROTECT, null=True, blank=True)
street = models.ForeignKey(SignStreet, related_name="signpoles", on_delete=models.PROTECT)
orientation = models.IntegerField(
null=True,
blank=True,
default=90,
help_text="Orientation en degrés (0-360)"
)
class Meta:
indexes = [
models.Index(fields=["code"]),
]
def get_absolute_url(self):
return reverse('assets:sign_assets_detail', kwargs={'asset_model': 'signpole', 'asset_id':self.id})
# Road Sign Panel Model
class SignPanelModel(AbstractAssetModel):
shape = models.CharField(max_length=200, null=True,blank=True)
color = models.CharField(max_length=200, null=True,blank=True)
ral = models.CharField(max_length=200, null=True,blank=True)
height = models.FloatField(null=True,blank=True)
width = models.FloatField(null=True,blank=True)
diameter = models.FloatField(null=True,blank=True)
material = models.CharField(max_length=200, null=True,blank=True)
# Road Sign Panel Type
class SignPanelType(models.Model):
code = models.CharField(max_length=100, unique=True)
name_fr = models.CharField(max_length=200)
name_nl = models.CharField(max_length=200)
description = models.TextField(null=True, blank=True)
sgv_picture = models.ImageField(null=True, blank=True, upload_to="sign/signpanels_types/")
default_width = models.FloatField(null=True, blank=True, verbose_name="Largeur par défaut (cm)")
default_height = models.FloatField(null=True, blank=True, verbose_name="Hauteur par défaut (cm)")
default_diameter = models.FloatField(null=True, blank=True, verbose_name="Diamètre par défaut (cm)")
def get_name(self):
lang = get_language()
name = getattr(self, f"name_{lang}", self.name_fr) or "" # Fallback to name_fr
return name
def __str__(self):
return f"{self.code} - {self.get_name()}"
SUPPORT_TYPE_CHOICES = (
('pole', 'Poteau'),
('lightingpole', 'Poteau d\'éclairage'),
('trafficlightpole', 'Poteau de signalisation lumineuse'),
('facade', 'Façade'),
('mast','Mât'),
('portico', 'Portique'),
('gallows', 'Potence'),
('hopper','Trémie'),
('other', 'Autre'),
)
# Road Sign Panel
class SignPanel(AbstractGeoAsset):
model = models.ForeignKey(SignPanelModel, on_delete=models.SET_NULL, null=True, blank=True)
signpanel_type = models.ForeignKey(SignPanelType, on_delete=models.SET_NULL, null=True, blank=True)
pole = models.ForeignKey(SignPole, related_name="signpanels", on_delete=models.SET_NULL, null=True, blank=True)
vertical_order = models.IntegerField(null=True, blank=True)
signpanel_text = models.CharField(max_length=1000, null=True, blank=True, choices=SUPPORT_TYPE_CHOICES)
signpanel_value = models.FloatField(null=True, blank=True)
orientation = models.IntegerField(null=True, blank=True, help_text="Orientation en degrés (0-360)")
support_type = models.CharField(max_length=200, null=True, blank=True, choices=SUPPORT_TYPE_CHOICES)
is_temporary = models.BooleanField(default=False)
street = models.ForeignKey(SignStreet, related_name="signpanels", on_delete=models.SET_NULL, null=True, blank=True)
trafficlight_pole = models.ForeignKey("assets.TrafficLightPole", related_name="signpanels_attached", on_delete=models.SET_NULL, null=True, blank=True)
publiclighting_pole = models.ForeignKey("assets.PublicLightingPole", related_name="signpanels_attached", on_delete=models.SET_NULL, null=True, blank=True)
class Meta:
indexes = [
models.Index(fields=["code"]),
]
verbose_name = _("Panneau de signalisation")
verbose_name_plural = _("Panneaux de signalisation")
def get_absolute_url(self):
return reverse('assets:sign_assets_detail', kwargs={'asset_model': 'signpanel', 'asset_id':self.id})
def get_width(self):
"""Retourne la largeur du panneau en cm (50 cm par défaut ou valeur par défaut du type de panneau)"""
width = getattr(self, 'width', None)
if width:
return width
model = getattr(self, 'model', None)
if model:
w = getattr(model, 'width', None) or getattr(model, 'diameter', None)
if w:
return w
if self.signpanel_type:
if self.signpanel_type.default_width:
return self.signpanel_type.default_width
if self.signpanel_type.default_diameter:
return self.signpanel_type.default_diameter
return 50.0 # Valeur par défaut
def get_height(self):
"""Retourne la hauteur du panneau en cm (égale à la largeur par défaut ou valeur par défaut du type de panneau)"""
height = getattr(self, 'height', None)
if height:
return height
model = getattr(self, 'model', None)
if model:
h = getattr(model, 'height', None)
if h:
return h
if self.signpanel_type:
if self.signpanel_type.default_height:
return self.signpanel_type.default_height
if self.signpanel_type.default_diameter:
return self.signpanel_type.default_diameter
return self.get_width() # Hauteur = largeur par défaut
def get_scale_factor(self):
"""Retourne le facteur d'échelle par rapport à la taille de référence (50 cm)"""
return self.get_width() / 50.0
def upload_to_police_regulation(instance, filename):
code = slugify(instance.reference or instance.title or "regulation")
return os.path.join("assets_documents", "sign", "police_regulations", code, filename)
class ComplementaryPoliceRegulation(Document):
title = models.CharField(max_length=255)
reference = models.CharField(max_length=100, null=True, blank=True)
decision_date = models.DateField(null=True, blank=True)
file = models.FileField(upload_to=upload_to_police_regulation, verbose_name="Fichier", max_length=255)
thumbnail = models.ImageField(upload_to=upload_to_police_regulation, null=True, blank=True, max_length=255)
panels = models.ManyToManyField(SignPanel, related_name="complementary_regulations", blank=True)
uploaded_by = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.SET_NULL,
null=True,
blank=True,
verbose_name="Ajouté par",
related_name="uploaded_complementary_police_regulations_assets",
)
class Meta:
ordering = ["-decision_date", "reference", "title"]
def __str__(self):
ref = f"{self.reference} - " if self.reference else ""
return f"{ref}{self.title}"
SPOT_TYPE_CHOICES = (
('car_parking', 'Place de stationnement'),
('bicycle_parking', 'Place de stationnement pour vélos'),
('loading_zone', 'Zone de chargement/déchargement'),
('bus_stop', 'Arrêt de bus'),
('taxi_stand', 'Station de taxis'),
('other', 'Autre'),
)
class SignParkingSpot(AbstractGeoAsset):
geom = models.PolygonField(srid=3812, null=True, blank=True)
street = models.ForeignKey(SignStreet, related_name="parking_spots", on_delete=models.SET_NULL, null=True, blank=True)
spot_type = models.CharField(max_length=50, choices=SPOT_TYPE_CHOICES, default='other', verbose_name="Type de place")
capacity = models.IntegerField(null=True, blank=True, help_text="Capacité de la place (ex: nombre de véhicules pouvant stationner)")
is_temporary = models.BooleanField(default=False, help_text="Indique si la place est temporaire (ex: zone de chantier)")
poles = models.ManyToManyField(SignPole, related_name="parking_spots_attached", blank=True)
panels = models.ManyToManyField(SignPanel, related_name="parking_spots_attached", blank=True)
entity = models.CharField(max_length=255, null=True, blank=True, help_text="Entité responsable de la place (ex: société de parking)")
def get_absolute_url(self):
return reverse('assets:sign_assets_detail', kwargs={'asset_model': 'parking_spot', 'asset_id':self.id})
def __str__(self):
return f"{self.get_spot_type_display()}{' - ' + self.get_name() if self.get_name() else ''}"