# forms.py from django import forms from django.utils.translation import gettext_lazy as _, get_language from .models import Observation, OBSERVATION_TYPE_CHOICES from common.models import Thematic from assets.models import AssetCategory class ObservationForm(forms.ModelForm): class Meta: model = Observation fields = ['description', 'latitude', 'longitude', 'thematic', 'category', 'observation_type', 'symptom', 'address'] widgets = { 'description': forms.Textarea(attrs={'class': 'form-control', 'rows': 3}), 'thematic': forms.Select(attrs={'class': 'form-control', 'id': 'id_thematic'}), 'category': forms.Select(attrs={'class': 'form-control', 'id': 'id_category', 'disabled': 'disabled'}), 'observation_type': forms.Select(attrs={'class': 'form-control', 'id': 'id_observation_type'}), 'symptom': forms.Select(attrs={'class': 'form-control', 'id': 'id_symptom', 'disabled': 'disabled'}), 'latitude': forms.HiddenInput(), 'longitude': forms.HiddenInput(), 'address': forms.HiddenInput(), } labels = { 'thematic': _('Thématique'), 'category': _('Catégorie'), 'observation_type': _('Type d\'observation'), 'symptom': _('Symptôme'), } def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) # Get current language lang = get_language() order_field = f'name_{lang}' if lang == 'nl' else 'name_fr' # Remove restrictions - allow all thematics, sorted by current language self.fields['thematic'].queryset = Thematic.objects.all().order_by(order_field) self.fields['thematic'].empty_label = _("--- Sélectionnez une thématique ---") # Initially show all categories, will be filtered by JavaScript based on selected thematic self.fields['category'].queryset = AssetCategory.objects.all().order_by(order_field) self.fields['category'].empty_label = _("--- Sélectionnez une catégorie ---") # Observation type choices with translations self.fields['observation_type'].choices = [ ('', _('--- Sélectionnez un type ---')), ('incident', _('Incident')), ('inventory', _('Inventaire')), ('remark', _('Remarque')), ] # Symptom field - initially empty, will be populated by JavaScript (and server on POST) from interventions.models import Symptom self.fields['symptom'].queryset = Symptom.objects.none() self.fields['symptom'].empty_label = _("--- Sélectionnez un symptôme ---") self.fields['symptom'].required = False # If we have an instance with a thematic, filter categories by that thematic if self.instance and self.instance.thematic_id: self.fields['category'].queryset = AssetCategory.objects.filter( thematic=self.instance.thematic ).order_by(order_field) # Enable category field if thematic is already selected self.fields['category'].widget.attrs.pop('disabled', None) # If observation type is incident and category exists, load symptoms if self.instance.observation_type == 'incident' and self.instance.category_id: self.fields['symptom'].queryset = Symptom.objects.filter( asset_categories=self.instance.category, is_active=True ).order_by(order_field) self.fields['symptom'].widget.attrs.pop('disabled', None) # If form is bound, adjust querysets based on submitted data so choices validate if self.is_bound: # Filter categories by posted thematic (for correct re-render and validation context) thematic_id = self.data.get('thematic') or self.data.get('id_thematic') if thematic_id: try: self.fields['category'].queryset = AssetCategory.objects.filter( thematic_id=int(thematic_id) ).order_by(order_field) # Enable category when thematic provided self.fields['category'].widget.attrs.pop('disabled', None) except ValueError: pass # Configure symptom queryset based on posted category when observation_type is incident # Prefer sub-category ID if provided obs_type = self.data.get('observation_type') sub_cat_id = self.data.get('obs_sub_category_id') category_id = sub_cat_id or self.data.get('category') if obs_type == 'incident' and category_id: try: cat_id_int = int(category_id) # If it's a sub-category, also include parent category symptoms cat_ids = [cat_id_int] if sub_cat_id: try: sub_cat_obj = AssetCategory.objects.get(pk=cat_id_int) if sub_cat_obj.parent_id: cat_ids.append(sub_cat_obj.parent_id) except AssetCategory.DoesNotExist: pass self.fields['symptom'].queryset = Symptom.objects.filter( asset_categories__id__in=cat_ids, is_active=True ).distinct().order_by(order_field) self.fields['symptom'].widget.attrs.pop('disabled', None) # Symptom required only for incident self.fields['symptom'].required = True except ValueError: # leave as none() pass else: # Not incident: ensure not required and empty queryset ok self.fields['symptom'].required = False def clean(self): cleaned = super().clean() # If a sub-category was selected, override category with it sub_cat_id = self.data.get('obs_sub_category_id') if sub_cat_id: try: sub_cat = AssetCategory.objects.get(pk=sub_cat_id) cleaned['category'] = sub_cat self.errors.pop('category', None) except (AssetCategory.DoesNotExist, ValueError): pass obs_type = cleaned.get('observation_type') if obs_type != 'incident': # Clear symptom if not incident cleaned['symptom'] = None else: # When incident, ensure symptom belongs to the selected category (or its parent) category = cleaned.get('category') symptom = cleaned.get('symptom') if category and symptom: from interventions.models import Symptom lang = get_language() order_field = f'name_{lang}' if lang == 'nl' else 'name_fr' cat_ids = [category.pk] if category.parent_id: cat_ids.append(category.parent_id) valid_qs = Symptom.objects.filter(asset_categories__id__in=cat_ids, is_active=True) if not valid_qs.filter(pk=symptom.pk).exists(): self.add_error('symptom', _("Sélectionnez un choix valide. Ce choix ne fait pas partie de ceux disponibles.")) return cleaned