273 lines
No EOL
11 KiB
Python
273 lines
No EOL
11 KiB
Python
from django import forms
|
|
|
|
from assets.models import TrafficLightIntersectionDocument, PublicLightingStreetDocument, StructureDocument, ControlCenterDocument
|
|
from assets.models import SignStreetDocument, RoadStreetDocument, ITSLocationDocument
|
|
from assets.models import TrafficLightIntersection
|
|
from assets.models.its import ITSLocation
|
|
from assets.models.trafficlights import TL_INTERSECTION_STATUS_CHOICES, TL_VOLTAGE_CHOICES
|
|
|
|
|
|
class MultipleFileInput(forms.ClearableFileInput):
|
|
allow_multiple_selected = True
|
|
|
|
|
|
class MultipleFileField(forms.FileField):
|
|
def __init__(self, *args, **kwargs):
|
|
kwargs.setdefault("widget", MultipleFileInput())
|
|
super().__init__(*args, **kwargs)
|
|
|
|
def clean(self, data, initial=None):
|
|
single_file_clean = super().clean
|
|
if isinstance(data, (list, tuple)):
|
|
result = [single_file_clean(d, initial) for d in data]
|
|
else:
|
|
result = [single_file_clean(data, initial)]
|
|
return result
|
|
|
|
|
|
class IntersectionDocumentForm(forms.ModelForm):
|
|
documents = MultipleFileField(required=True, label="Documents")
|
|
|
|
class Meta:
|
|
model = TrafficLightIntersectionDocument
|
|
fields = ['documents']
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
super(IntersectionDocumentForm, self).__init__(*args, **kwargs)
|
|
self.fields['documents'].widget.attrs.update({'class': 'form-control'})
|
|
|
|
|
|
class StreetDocumentForm(forms.ModelForm):
|
|
documents = MultipleFileField(required=True, label="Documents")
|
|
|
|
class Meta:
|
|
model = PublicLightingStreetDocument
|
|
fields = ['documents']
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
super(StreetDocumentForm, self).__init__(*args, **kwargs)
|
|
self.fields['documents'].widget.attrs.update({'class': 'form-control'})
|
|
|
|
|
|
class StructureDocumentForm(forms.ModelForm):
|
|
documents = MultipleFileField(required=True, label="Documents")
|
|
|
|
class Meta:
|
|
model = StructureDocument
|
|
fields = ['documents']
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
super(StructureDocumentForm, self).__init__(*args, **kwargs)
|
|
self.fields['documents'].widget.attrs.update({'class': 'form-control'})
|
|
|
|
|
|
class ControlCenterDocumentForm(forms.ModelForm):
|
|
documents = MultipleFileField(required=True, label="Documents")
|
|
|
|
class Meta:
|
|
model = ControlCenterDocument
|
|
fields = ['documents']
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
super(ControlCenterDocumentForm, self).__init__(*args, **kwargs)
|
|
self.fields['documents'].widget.attrs.update({'class': 'form-control'})
|
|
|
|
|
|
class SignStreetDocumentForm(forms.ModelForm):
|
|
documents = MultipleFileField(required=True, label="Documents")
|
|
|
|
class Meta:
|
|
model = SignStreetDocument
|
|
fields = ['documents']
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
super(SignStreetDocumentForm, self).__init__(*args, **kwargs)
|
|
self.fields['documents'].widget.attrs.update({'class': 'form-control'})
|
|
|
|
|
|
class RoadStreetDocumentForm(forms.ModelForm):
|
|
documents = MultipleFileField(required=True, label="Documents")
|
|
|
|
class Meta:
|
|
model = RoadStreetDocument
|
|
fields = ['documents']
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
super(RoadStreetDocumentForm, self).__init__(*args, **kwargs)
|
|
self.fields['documents'].widget.attrs.update({'class': 'form-control'})
|
|
|
|
|
|
class ITSLocationDocumentForm(forms.ModelForm):
|
|
documents = MultipleFileField(required=True, label="Documents")
|
|
|
|
class Meta:
|
|
model = ITSLocationDocument
|
|
fields = ['documents']
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
super(ITSLocationDocumentForm, self).__init__(*args, **kwargs)
|
|
self.fields['documents'].widget.attrs.update({'class': 'form-control'})
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Generic Location Forms (for create / edit location functionality)
|
|
# ---------------------------------------------------------------------------
|
|
|
|
class TrafficLightIntersectionForm(forms.ModelForm):
|
|
"""Form to create or edit a TrafficLightIntersection (localisation Feux)."""
|
|
|
|
# Not a model field — computed from the linked controller.
|
|
# Presented as a 3-way choice (Yes / No / Unknown).
|
|
connected_to_control_center = forms.ChoiceField(
|
|
choices=[('', '—'), ('true', 'Oui'), ('false', 'Non')],
|
|
required=False,
|
|
label='Connexion à la centrale',
|
|
widget=forms.Select(attrs={'class': 'form-select'}),
|
|
)
|
|
|
|
class Meta:
|
|
model = TrafficLightIntersection
|
|
fields = [
|
|
'code', 'status',
|
|
'name_fr', 'name_nl',
|
|
'voltage_type', 'is_radar',
|
|
'municipality', 'master',
|
|
'current_index', 'begin_time',
|
|
'lat', 'lon',
|
|
]
|
|
widgets = {
|
|
'code': forms.TextInput(attrs={'class': 'form-control'}),
|
|
'name_fr': forms.TextInput(attrs={'class': 'form-control'}),
|
|
'name_nl': forms.TextInput(attrs={'class': 'form-control'}),
|
|
'status': forms.Select(attrs={'class': 'form-select'}),
|
|
'voltage_type': forms.Select(attrs={'class': 'form-select'}),
|
|
'is_radar': forms.CheckboxInput(attrs={'class': 'form-check-input'}),
|
|
'municipality': forms.Select(attrs={'class': 'form-select'}),
|
|
'master': forms.Select(attrs={'class': 'form-select'}),
|
|
'current_index': forms.TextInput(attrs={'class': 'form-control'}),
|
|
'begin_time': forms.DateTimeInput(attrs={'class': 'form-control', 'type': 'datetime-local'}, format='%Y-%m-%dT%H:%M'),
|
|
'lat': forms.NumberInput(attrs={'class': 'form-control', 'step': 'any'}),
|
|
'lon': forms.NumberInput(attrs={'class': 'form-control', 'step': 'any'}),
|
|
}
|
|
labels = {
|
|
'code': 'Code',
|
|
'name_fr': 'Nom (FR)',
|
|
'name_nl': 'Naam (NL)',
|
|
'status': 'Statut',
|
|
'voltage_type': 'Voltage',
|
|
'is_radar': 'Est un radar ?',
|
|
'municipality': 'Commune',
|
|
'master': 'Carrefour principal',
|
|
'current_index': 'Index',
|
|
'begin_time': 'Date de mise en service',
|
|
'lat': 'Latitude',
|
|
'lon': 'Longitude',
|
|
}
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
super().__init__(*args, **kwargs)
|
|
self.fields['municipality'].required = False
|
|
self.fields['master'].required = False
|
|
self.fields['current_index'].required = False
|
|
self.fields['begin_time'].required = False
|
|
self.fields['lat'].required = False
|
|
self.fields['lon'].required = False
|
|
self.fields['name_fr'].required = False
|
|
self.fields['name_nl'].required = False
|
|
# Exclude current instance from master choices to prevent self-referencing loops
|
|
if self.instance and self.instance.pk:
|
|
self.fields['master'].queryset = TrafficLightIntersection.objects.exclude(pk=self.instance.pk).order_by('code')
|
|
else:
|
|
self.fields['master'].queryset = TrafficLightIntersection.objects.all().order_by('code')
|
|
# Pre-populate connected_to_control_center from the computed property
|
|
if self.instance and self.instance.pk:
|
|
val = self.instance.is_connected_to_control_center
|
|
if val is True:
|
|
self.initial['connected_to_control_center'] = 'true'
|
|
elif val is False:
|
|
self.initial['connected_to_control_center'] = 'false'
|
|
else:
|
|
self.initial['connected_to_control_center'] = ''
|
|
# Enforce display order
|
|
from collections import OrderedDict
|
|
desired_order = [
|
|
'code', 'status',
|
|
'name_fr', 'name_nl',
|
|
'voltage_type', 'is_radar', 'connected_to_control_center',
|
|
'municipality', 'master',
|
|
'current_index', 'begin_time',
|
|
'lat', 'lon',
|
|
]
|
|
self.fields = OrderedDict(
|
|
(k, self.fields[k]) for k in desired_order if k in self.fields
|
|
)
|
|
|
|
def save(self, commit=True):
|
|
instance = super().save(commit=commit)
|
|
# Propagate connected_to_control_center to the latest active controller
|
|
raw = self.cleaned_data.get('connected_to_control_center', '')
|
|
if raw != '' and instance.pk:
|
|
controller = instance.controllers.filter(status='active').order_by('-installation_date').first()
|
|
if controller is not None:
|
|
controller.connected_to_control_center = (raw == 'true')
|
|
controller.save(update_fields=['connected_to_control_center'])
|
|
return instance
|
|
|
|
|
|
class ITSLocationForm(forms.ModelForm):
|
|
"""Form to create or edit an ITSLocation."""
|
|
|
|
# Hidden field: WGS84 GeoJSON polygon drawn by the user on the map.
|
|
# Accepted values: a GeoJSON Polygon or MultiPolygon string, or empty.
|
|
geom_geojson = forms.CharField(required=False, widget=forms.HiddenInput())
|
|
|
|
class Meta:
|
|
model = ITSLocation
|
|
fields = [
|
|
'code', 'name_fr', 'name_nl',
|
|
'is_main_location', 'parent_location',
|
|
]
|
|
widgets = {
|
|
'code': forms.TextInput(attrs={'class': 'form-control'}),
|
|
'name_fr': forms.TextInput(attrs={'class': 'form-control'}),
|
|
'name_nl': forms.TextInput(attrs={'class': 'form-control'}),
|
|
'is_main_location': forms.CheckboxInput(attrs={'class': 'form-check-input'}),
|
|
'parent_location': forms.Select(attrs={'class': 'form-select'}),
|
|
}
|
|
labels = {
|
|
'code': 'Code',
|
|
'name_fr': 'Nom (FR)',
|
|
'name_nl': 'Naam (NL)',
|
|
'is_main_location': 'Localisation principale',
|
|
'parent_location': 'Localisation parente',
|
|
}
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
super().__init__(*args, **kwargs)
|
|
self.fields['name_fr'].required = False
|
|
self.fields['name_nl'].required = False
|
|
self.fields['parent_location'].required = False
|
|
if self.instance and self.instance.pk:
|
|
self.fields['parent_location'].queryset = ITSLocation.objects.exclude(pk=self.instance.pk).order_by('code')
|
|
# Pre-populate geom_geojson with the existing geometry in WGS84
|
|
if self.instance.geom:
|
|
geom_wgs84 = self.instance.geom.transform(4326, clone=True)
|
|
self.initial['geom_geojson'] = geom_wgs84.geojson
|
|
else:
|
|
self.fields['parent_location'].queryset = ITSLocation.objects.all().order_by('code')
|
|
|
|
|
|
# Registry: maps location_type slug → form class + thematic code
|
|
# Used by the generic location_editing views.
|
|
LOCATION_FORM_REGISTRY = {
|
|
'trafficlightintersection': {
|
|
'form_class': TrafficLightIntersectionForm,
|
|
'thematic_code': 'trafficlights',
|
|
'label': 'Intersection',
|
|
},
|
|
'itslocation': {
|
|
'form_class': ITSLocationForm,
|
|
'thematic_code': 'its',
|
|
'label': 'Localisation ITS',
|
|
},
|
|
} |