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', }, }