loko/streetup/assets/models/parking.py

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}"