143 lines
5.5 KiB
Python
143 lines
5.5 KiB
Python
from django.contrib.gis.db import models
|
|
from django.urls import reverse
|
|
from django.utils.translation import gettext_lazy as _
|
|
|
|
from assets.models import AbstractLocation, AbstractGeoAsset
|
|
from assets.models.sign import SignStreet, SignPole, SignPanel
|
|
|
|
|
|
SPOT_TYPE_CHOICES = (
|
|
('bicycle', _('Stationnement vélo')),
|
|
('scooter_drop_zone', _('Drop zone trottinettes / EDPM')),
|
|
('cargo_bike', _('Stationnement vélo cargo')),
|
|
('delivery', _('Zone de livraison / Chargement-déchargement')),
|
|
('pmr', _('Emplacement PMR')),
|
|
('kiss_and_ride', _('Arrêt minute / Court-durée (CD)')),
|
|
('car_parking', _('Stationnement automobile')),
|
|
('carsharing', _('Autopartage')),
|
|
('ev_charging', _('Recharge véhicule électrique')),
|
|
('other', _('Autre')),
|
|
)
|
|
|
|
STAND_TYPE_CHOICES = (
|
|
('normal', _('Arceau standard (U inversé)')),
|
|
('cargo', _('Arceau vélo cargo')),
|
|
('mixed', _('Mixte (normal et cargo)')),
|
|
('rack', _('Râteau / Pince-roue')),
|
|
('box', _('Box / Abri sécurisé')),
|
|
('other', _('Autre')),
|
|
)
|
|
|
|
MARKING_QUALITY_CHOICES = (
|
|
('good', _('Bon état')),
|
|
('medium', _('Moyen')),
|
|
('damaged', _('Dégradé')),
|
|
('missing', _('Absent / Effacé')),
|
|
)
|
|
|
|
|
|
class ParkingLocation(AbstractLocation):
|
|
"""
|
|
Modèle représentant un secteur ou zone d'ensemble de stationnement.
|
|
"""
|
|
geom = models.MultiPolygonField(srid=3812, null=True, blank=True, verbose_name=_("Emprise cartographique"))
|
|
description = models.TextField(null=True, blank=True, verbose_name=_("Description"))
|
|
address = models.CharField(max_length=500, null=True, blank=True, verbose_name=_("Adresse"))
|
|
|
|
class Meta:
|
|
verbose_name = _("Zone de stationnement")
|
|
verbose_name_plural = _("Zones de stationnement")
|
|
ordering = ['code', 'name_fr']
|
|
|
|
|
|
class ParkingSpot(AbstractGeoAsset):
|
|
"""
|
|
Modèle représentant un emplacement ou zone de stationnement vélo/trottinette/PMR/livraison.
|
|
Utilise une géométrie en Polygone.
|
|
"""
|
|
geom = models.PolygonField(srid=3812, null=True, blank=True, verbose_name=_("Emprise polygone"))
|
|
location = models.ForeignKey(
|
|
ParkingLocation,
|
|
on_delete=models.SET_NULL,
|
|
null=True,
|
|
blank=True,
|
|
related_name='parking_spots',
|
|
verbose_name=_("Zone / Localisation parente")
|
|
)
|
|
street = models.ForeignKey(
|
|
SignStreet,
|
|
on_delete=models.SET_NULL,
|
|
null=True,
|
|
blank=True,
|
|
related_name='parking_spots_new',
|
|
verbose_name=_("Rue")
|
|
)
|
|
spot_type = models.CharField(
|
|
max_length=50,
|
|
choices=SPOT_TYPE_CHOICES,
|
|
default='bicycle',
|
|
verbose_name=_("Type d'emplacement")
|
|
)
|
|
|
|
# Métriques et types d'arceaux vélos
|
|
standard_stand_count = models.IntegerField(default=0, verbose_name=_("Nombre d'arceaux vélos classiques"))
|
|
cargo_stand_count = models.IntegerField(default=0, verbose_name=_("Nombre d'arceaux vélos cargo"))
|
|
other_stand_count = models.IntegerField(default=0, verbose_name=_("Autres arceaux / agencements"))
|
|
stand_type = models.CharField(
|
|
max_length=50,
|
|
choices=STAND_TYPE_CHOICES,
|
|
default='normal',
|
|
verbose_name=_("Type d'arceau principal")
|
|
)
|
|
has_scooter_drop_zone = models.BooleanField(default=False, verbose_name=_("Drop zone trottinettes / EDPM"))
|
|
scooter_capacity = models.IntegerField(null=True, blank=True, verbose_name=_("Capacité estimée trottinettes"))
|
|
|
|
# Métriques générales & stationnement auto/livraison/PMR
|
|
capacity = models.IntegerField(null=True, blank=True, verbose_name=_("Capacité générale (nbr véhicules)"))
|
|
is_paid = models.BooleanField(default=False, verbose_name=_("Payant"))
|
|
time_limit_minutes = models.IntegerField(null=True, blank=True, verbose_name=_("Durée maximale (minutes)"))
|
|
marking_quality = models.CharField(
|
|
max_length=50,
|
|
choices=MARKING_QUALITY_CHOICES,
|
|
default='good',
|
|
verbose_name=_("Qualité du marquage au sol")
|
|
)
|
|
is_temporary = models.BooleanField(default=False, verbose_name=_("Temporaire (chantier/événement)"))
|
|
|
|
# Conformité & résultat d'inspection
|
|
is_compliant = models.BooleanField(default=True, verbose_name=_("Conforme aux spécifications"))
|
|
non_compliance_reason = models.TextField(null=True, blank=True, verbose_name=_("Motif de non-conformité"))
|
|
|
|
# Signalisation associée
|
|
poles = models.ManyToManyField(
|
|
SignPole,
|
|
related_name='parking_spots_attached_new',
|
|
blank=True,
|
|
verbose_name=_("Poteaux de signalisation associés")
|
|
)
|
|
panels = models.ManyToManyField(
|
|
SignPanel,
|
|
related_name='parking_spots_attached_new',
|
|
blank=True,
|
|
verbose_name=_("Panneaux de signalisation associés")
|
|
)
|
|
|
|
@property
|
|
def total_stand_count(self):
|
|
"""Retourne le nombre total d'arceaux (classiques + cargo + autres)."""
|
|
return (self.standard_stand_count or 0) + (self.cargo_stand_count or 0) + (self.other_stand_count or 0)
|
|
|
|
class Meta:
|
|
verbose_name = _("Emplacement de stationnement")
|
|
verbose_name_plural = _("Emplacements de stationnement")
|
|
ordering = ['code', 'name_fr']
|
|
|
|
def get_absolute_url(self):
|
|
return reverse('assets:parking_assets_detail', kwargs={'asset_model': 'parking_spot', 'asset_id': self.id})
|
|
|
|
def __str__(self):
|
|
type_label = self.get_spot_type_display()
|
|
name = self.get_name()
|
|
if name:
|
|
return f"{self.code} - {type_label} ({name})"
|
|
return f"{self.code} - {type_label}"
|