loko/streetup/controls/forms.py
2026-07-22 14:48:40 +02:00

243 lines
No EOL
10 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

from django import forms
from django.utils.translation import get_language, gettext as _
from django.forms import inlineformset_factory, BaseInlineFormSet
from .models import Control, Remark, RemarkImage, ControlReferencePlan, ControlReportTemplate, ControlQuantity
from projects.models import ProjectPhase
from common.models import Thematic, UserContractAccess
from assets.models import AssetCategory
from contracts.models import Contract
from interventions.models import Intervention
class ControlForm(forms.ModelForm):
class Meta:
model = Control
fields = [
'description', 'status', 'control_type',
'project', 'phase',
'thematics', 'categories', 'interventions', 'visibility', 'shared_edition',
'location', 'lon', 'lat', 'geojson'
]
widgets = {
'description': forms.Textarea(attrs={'class': 'form-control', 'rows': 3, 'autocomplete': 'off'}),
'status': forms.Select(attrs={'class': 'form-select'}),
'control_type': forms.Select(attrs={'class': 'form-select'}),
'visibility': forms.Select(attrs={'class': 'form-select'}),
'shared_edition': forms.CheckboxInput(attrs={'class': 'form-check'}),
'thematics': forms.CheckboxSelectMultiple(),
'categories': forms.CheckboxSelectMultiple(),
'project': forms.Select(attrs={'class': 'form-select', 'id': 'project-select'}),
'phase': forms.Select(attrs={'class': 'form-select', 'id': 'phase-select'}),
'location': forms.TextInput(attrs={'class': 'form-control', 'autocomplete': 'off'}),
'interventions': forms.SelectMultiple(attrs={'style': 'display:none;'}),
'lon': forms.HiddenInput(),
'lat': forms.HiddenInput(),
'geojson': forms.HiddenInput(),
}
labels = {
'location': _('Localisation') + ' (' + _('facultatif') + ')',
'categories': _('Catégories') + ' (' + _('facultatif') + ')',
'interventions': _('Interventions liées') + ' (' + _('facultatif') + ')',
'project': _('Projet') + ' (' + _('facultatif') + ')',
'phase': _('Phase du projet') + ' (' + _('facultatif') + ')',
'description': _('Description') + ' (' + _('facultatif') + ')',
}
def __init__(self, *args, user=None, **kwargs):
super(ControlForm, self).__init__(*args, **kwargs)
self.fields['phase'].queryset = ProjectPhase.objects.none()
self.fields['categories'].queryset = AssetCategory.objects.none()
lang = get_language()
if lang == 'nl':
self.fields['thematics'].queryset = Thematic.objects.all().order_by('name_nl')
else:
self.fields['thematics'].queryset = Thematic.objects.all().order_by('name_fr')
# Si un utilisateur est passé au form et qu'il a une thématique par défaut
if (
not self.is_bound
and not self.instance.pk # uniquement à la création
and user
and hasattr(user, "config")
and user.config.default_thematic
and not self.initial.get('thematics')
):
self.initial['thematics'] = [user.config.default_thematic.id]
# Vérification des catégories pour la validation
if 'thematics' in self.data:
thematic_ids = self.data.getlist('thematics')
self.fields['categories'].queryset = AssetCategory.objects.filter(
thematic__id__in=thematic_ids
).distinct()
elif self.instance.pk:
# Si modification d’un objet existant, afficher toutes les catégories possibles
# en fonction des thématiques du contrôle (pas seulement les sélectionnées)
thematic_ids = list(self.instance.thematics.values_list('id', flat=True))
if thematic_ids:
self.fields['categories'].queryset = AssetCategory.objects.filter(
thematic__id__in=thematic_ids
).distinct()
else:
self.fields['categories'].queryset = AssetCategory.objects.none()
# Vérification des phases pour la validation
if 'project' in self.data and self.data.get('project'):
project_id = self.data.get('project')
self.fields['phase'].queryset = ProjectPhase.objects.filter(project_id=project_id)
elif self.instance.pk and self.instance.project:
# Si modification d'un objet existant
self.fields['phase'].queryset = ProjectPhase.objects.filter(project=self.instance.project)
# Interventions : éviter de charger toutes les options (liste potentiellement volumineuse)
if 'interventions' in self.data:
ids = [x for x in self.data.getlist('interventions') if str(x).strip().isdigit()]
self.fields['interventions'].queryset = Intervention.objects.filter(id__in=ids)
elif self.instance.pk:
self.fields['interventions'].queryset = self.instance.interventions.all()
else:
self.fields['interventions'].queryset = Intervention.objects.none()
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 RemarkForm(forms.ModelForm):
images = MultipleFileField(required=False)
class Meta:
model = Remark
fields = ["control", "status", "description", "images", "lon", "lat", "x", "y"]
widgets = {
"description": forms.Textarea(attrs={
"class": "form-control", # Ajoute la classe Bootstrap
"rows": 2, # Limite à deux lignes
}),
}
def __init__(self, *args, **kwargs):
super(RemarkForm, self).__init__(*args, **kwargs)
self.fields['images'].widget.attrs.update({'class': 'form-control'})
class ControlReferencePlanForm(forms.ModelForm):
class Meta:
model = ControlReferencePlan
fields = ["name", "plan"]
class ControlReportTemplateForm(forms.ModelForm):
class Meta:
model = ControlReportTemplate
fields = ['header_notes', 'footer_notes']
widgets = {
'header_notes': forms.Textarea(attrs={
'class': 'form-control',
'rows': 4,
'placeholder': _("Entrez les notes de début ..."),
}),
'footer_notes': forms.Textarea(attrs={
'class': 'form-control',
'rows': 4,
'placeholder': _("Entrez les notes de fin ..."),
}),
}
class ControlQuantityForm(forms.ModelForm):
# Champ “recherchable” pour le poste: on utilisera un input visible + un hidden pour l’ID
contract_post_label = forms.CharField(
required=False,
widget=forms.TextInput(attrs={
"class": "form-control",
"placeholder": _("Chercher un poste (autocomplete)..."),
"autocomplete": "off",
"autocorrect": "off", # (iOS)
"autocapitalize": "none",
"spellcheck": "false",
})
)
contract_post_id = forms.IntegerField(required=False, widget=forms.HiddenInput())
class Meta:
model = ControlQuantity
fields = [
"contract", "description",
"q1", "q2", "q3",
]
widgets = {
"contract": forms.Select(attrs={"class": "form-select", "id": "quantity-contract-select"}),
"description": forms.TextInput(attrs={"class": "form-control", "placeholder": _("Description si pas de marché")}),
"q1": forms.NumberInput(attrs={"class": "form-control", "step": "0.01", "inputmode": "decimal"}),
"q2": forms.NumberInput(attrs={"class": "form-control", "step": "0.01", "inputmode": "decimal"}),
"q3": forms.NumberInput(attrs={"class": "form-control", "step": "0.01", "inputmode": "decimal"}),
}
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if not self.is_bound and self.instance and self.instance.pk:
cp = getattr(self.instance, "contract_post", None)
if cp:
# ID caché
self.fields["contract_post_id"].initial = cp.id
# Libellé visible (NL/FR selon la langue active)
lang = (get_language() or "fr").lower()
desc = cp.description_nl if lang.startswith("nl") else cp.description_fr
label = f"{cp.order_number} - {cp.code} - {desc}"
self.fields["contract_post_label"].initial = label
def clean(self):
cleaned = super().clean()
# si contract_post_id est fourni, on le rattache dans la view (post-clean) pour éviter import circulaire
return cleaned
class ControlQuantityInlineFormSet(BaseInlineFormSet):
def __init__(self, *args, user=None, **kwargs):
super().__init__(*args, **kwargs)
qs = Contract.objects.none()
if user and hasattr(user, "config"):
allowed_ids = UserContractAccess.objects.filter(
user_config=user.config, can_view_posts=True
).values_list("contract_id", flat=True)
qs = Contract.objects.filter(id__in=allowed_ids).order_by("contract_number")
# Injecter le même queryset dans chaque form
for form in self.forms:
form.fields["contract"].queryset = qs
form.fields["contract"].empty_label = _("Sélectionner un marché")
QuantityFormSet = inlineformset_factory(
Remark,
ControlQuantity,
form=ControlQuantityForm,
formset=ControlQuantityInlineFormSet,
fields=["contract", "description", "q1", "q2", "q3"],
extra=0,
can_delete=True
)