loko/loko/observations/forms.py

155 lines
7.3 KiB
Python

# 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