loko/streetup/assets/forms.py

341 lines
No EOL
14 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.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',
},
}
class PublicBuildingForm(forms.ModelForm):
"""Formulaire de création et d'édition d'un Bâtiment Public."""
class Meta:
model = PublicBuilding
fields = [
'code', 'name_fr', 'name_nl', 'address', 'description',
'owner', 'manager', 'occupant_entity',
'usage_type', 'building_status',
'construction_year', 'floors_count', 'basement_count',
'total_surface_m2', 'usable_surface_m2',
'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'}),
'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'}),
'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['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['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