271 lines
No EOL
11 KiB
Python
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 ''}" |