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 )