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.buildings import PublicBuilding 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', }, } from assets.models.buildings import PublicBuilding, BuildingAsset class PublicBuildingForm(forms.ModelForm): """Formulaire de création et d'édition d'une Localisation de Bâtiment Public.""" class Meta: model = PublicBuilding fields = [ 'code', 'name_fr', 'name_nl', 'parent_location', 'location_type', 'is_main_location', 'address', 'description', 'owner', 'manager', 'occupant_entity', 'usage_type', 'building_status', 'construction_year', 'floors_count', 'basement_count', 'total_surface_m2', 'usable_surface_m2', 'surface_m2', 'level_number', 'is_erp', 'erp_category', 'erp_type', 'max_capacity', 'pmr_accessible', 'dpe_rating', 'ges_rating', 'has_asbestos_diag', 'has_lead_diag', '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'}), 'parent_location': forms.Select(attrs={'class': 'form-select'}), 'location_type': forms.Select(attrs={'class': 'form-select'}), 'is_main_location': forms.CheckboxInput(attrs={'class': 'form-check-input'}), 'address': forms.TextInput(attrs={'class': 'form-control'}), 'description': forms.Textarea(attrs={'class': 'form-control', 'rows': 3}), 'owner': forms.TextInput(attrs={'class': 'form-control'}), 'manager': forms.Select(attrs={'class': 'form-select'}), 'occupant_entity': forms.TextInput(attrs={'class': 'form-control'}), 'usage_type': forms.Select(attrs={'class': 'form-select'}), 'building_status': forms.Select(attrs={'class': 'form-select'}), 'construction_year': forms.NumberInput(attrs={'class': 'form-control'}), 'floors_count': forms.NumberInput(attrs={'class': 'form-control'}), 'basement_count': forms.NumberInput(attrs={'class': 'form-control'}), 'total_surface_m2': forms.NumberInput(attrs={'class': 'form-control', 'step': 'any'}), 'usable_surface_m2': forms.NumberInput(attrs={'class': 'form-control', 'step': 'any'}), 'surface_m2': forms.NumberInput(attrs={'class': 'form-control', 'step': 'any'}), 'level_number': forms.NumberInput(attrs={'class': 'form-control'}), 'is_erp': forms.CheckboxInput(attrs={'class': 'form-check-input'}), 'erp_category': forms.Select(attrs={'class': 'form-select'}), 'erp_type': forms.Select(attrs={'class': 'form-select'}), 'max_capacity': forms.NumberInput(attrs={'class': 'form-control'}), 'pmr_accessible': forms.CheckboxInput(attrs={'class': 'form-check-input'}), 'dpe_rating': forms.Select(attrs={'class': 'form-select'}), 'ges_rating': forms.Select(attrs={'class': 'form-select'}), 'has_asbestos_diag': forms.CheckboxInput(attrs={'class': 'form-check-input'}), 'has_lead_diag': forms.CheckboxInput(attrs={'class': 'form-check-input'}), 'lat': forms.NumberInput(attrs={'class': 'form-control', 'step': 'any'}), 'lon': forms.NumberInput(attrs={'class': 'form-control', 'step': 'any'}), } 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 self.fields['location_type'].required = False self.fields['address'].required = False self.fields['description'].required = False self.fields['owner'].required = False self.fields['manager'].required = False self.fields['occupant_entity'].required = False self.fields['construction_year'].required = False self.fields['floors_count'].required = False self.fields['basement_count'].required = False self.fields['total_surface_m2'].required = False self.fields['usable_surface_m2'].required = False self.fields['surface_m2'].required = False self.fields['level_number'].required = False self.fields['erp_category'].required = False self.fields['erp_type'].required = False self.fields['max_capacity'].required = False self.fields['dpe_rating'].required = False self.fields['ges_rating'].required = False self.fields['lat'].required = False self.fields['lon'].required = False class BuildingAssetForm(forms.ModelForm): """Formulaire pour les assets/équipements physiques d'un bâtiment.""" class Meta: model = BuildingAsset fields = [ 'code', 'name_fr', 'name_nl', 'building', 'location', 'parent_asset', 'asset_type', 'category', 'status', 'obsolescence', 'installation_date', 'serial_number', 'last_control_date', 'next_control_date', 'note' ] widgets = { 'code': forms.TextInput(attrs={'class': 'form-control'}), 'name_fr': forms.TextInput(attrs={'class': 'form-control'}), 'name_nl': forms.TextInput(attrs={'class': 'form-control'}), 'building': forms.Select(attrs={'class': 'form-select'}), 'location': forms.Select(attrs={'class': 'form-select'}), 'parent_asset': forms.Select(attrs={'class': 'form-select'}), 'asset_type': forms.Select(attrs={'class': 'form-select'}), 'category': forms.Select(attrs={'class': 'form-select'}), 'status': forms.Select(attrs={'class': 'form-select'}), 'obsolescence': forms.Select(attrs={'class': 'form-select'}), 'installation_date': forms.DateTimeInput(attrs={'class': 'form-control', 'type': 'datetime-local'}), 'serial_number': forms.TextInput(attrs={'class': 'form-control'}), 'last_control_date': forms.DateInput(attrs={'class': 'form-control', 'type': 'date'}), 'next_control_date': forms.DateInput(attrs={'class': 'form-control', 'type': 'date'}), 'note': forms.Textarea(attrs={'class': 'form-control', 'rows': 3}), } def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) for field_name in ['name_fr', 'name_nl', 'location', 'parent_asset', 'category', 'installation_date', 'serial_number', 'last_control_date', 'next_control_date', 'note']: if field_name in self.fields: self.fields[field_name].required = False from common.models import Thematic from assets.models import AssetCategory t = Thematic.objects.filter(code='publicbuildings').first() if t and 'category' in self.fields: self.fields['category'].queryset = AssetCategory.objects.filter(thematic=t)