aemo_fr/aemo/forms.py

970 lines
36 KiB
Python
Raw Permalink Normal View History

2024-06-03 16:49:01 +02:00
from datetime import date, timedelta
import nh3
from dal import autocomplete
from dal.widgets import WidgetMixin
from tinymce.widgets import TinyMCE
from django import forms
from django.contrib import messages
from django.contrib.postgres.search import SearchQuery, SearchVector
from django.core.exceptions import ObjectDoesNotExist
from django.db.models import Count, Q
from django.utils.dates import MONTHS
from city_ch_autocomplete.forms import CityChField, CityChMixin
from common.choices import PROVENANCE_DESTINATION_CHOICES
from .models import (
Bilan, Contact, Document, EquipeChoices, Famille, Formation, Intervenant,
Niveau, Personne, Prestation, Region, Rapport, Role, Service, Suivi, Utilisateur,
)
from .utils import format_nom_prenom, ANTICIPATION_POUR_DEBUT_SUIVI
class BootstrapMixin:
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
for ffield in self.fields.values():
if (isinstance(ffield.widget, (PickSplitDateTimeWidget, PickDateWidget, WidgetMixin)) or
getattr(ffield.widget, '_bs_enabled', False)):
continue
elif isinstance(ffield.widget, (forms.Select, forms.NullBooleanSelect)):
self.add_attr(ffield.widget, 'form-select')
elif isinstance(ffield.widget, (forms.CheckboxInput, forms.RadioSelect)):
self.add_attr(ffield.widget, 'form-check-input')
else:
self.add_attr(ffield.widget, 'form-control')
@staticmethod
def add_attr(widget, class_name):
if 'class' in widget.attrs:
widget.attrs['class'] += f' {class_name}'
else:
widget.attrs.update({'class': class_name})
class BootstrapChoiceMixin:
"""
Mixin to customize choice widgets to set 'form-check' on container and
'form-check-input' on sub-options.
"""
_bs_enabled = True
def get_context(self, *args, **kwargs):
context = super().get_context(*args, **kwargs)
if 'class' in context['widget']['attrs']:
context['widget']['attrs']['class'] += ' form-check'
else:
context['widget']['attrs']['class'] = 'form-check'
return context
def create_option(self, *args, attrs=None, **kwargs):
attrs = attrs or {}
if 'class' in attrs:
attrs['class'] += ' form-check-input'
else:
attrs.update({'class': 'form-check-input'})
return super().create_option(*args, attrs=attrs, **kwargs)
class BSRadioSelect(BootstrapChoiceMixin, forms.RadioSelect):
pass
class ReadOnlyableMixin:
def __init__(self, *args, readonly=False, **kwargs):
self.readonly = readonly
super().__init__(*args, **kwargs)
if self.readonly:
for field in self.fields.values():
field.disabled = True
class BSCheckboxSelectMultiple(forms.CheckboxSelectMultiple):
"""
Custom widget to set 'form-check' on container and 'form-check-input' on sub-options.
"""
_bs_enabled = True
def get_context(self, *args, **kwargs):
context = super().get_context(*args, **kwargs)
context['widget']['attrs']['class'] = 'form-check'
return context
def create_option(self, *args, attrs=None, **kwargs):
attrs = attrs.copy() if attrs else {}
if 'class' in attrs:
attrs['class'] += ' form-check-input'
else:
attrs.update({'class': 'form-check-input'})
return super().create_option(*args, attrs=attrs, **kwargs)
class RichTextField(forms.CharField):
widget = TinyMCE
def __init__(self, *args, **kwargs):
kwargs['widget'] = self.widget
super().__init__(*args, **kwargs)
def clean(self, value):
value = super().clean(value)
return nh3.clean(
value, tags={'p', 'br', 'b', 'strong', 'u', 'i', 'em', 'ul', 'li'}
)
class HMDurationField(forms.DurationField):
"""A duration field taking HH:MM as input."""
widget = forms.TextInput(attrs={'placeholder': 'hh:mm'})
def to_python(self, value):
if value in self.empty_values or isinstance(value, timedelta):
return super().to_python(value)
value += ':00' # Simulate seconds
return super().to_python(value)
def prepare_value(self, value):
if isinstance(value, timedelta):
seconds = value.days * 24 * 3600 + value.seconds
hours = seconds // 3600
minutes = seconds % 3600 // 60
value = '{:02d}:{:02d}'.format(hours, minutes)
return value
class PickDateWidget(forms.DateInput):
class Media:
js = [
'admin/js/core.js',
'admin/js/calendar.js',
# Include the Django 3.2 version without today link.
'js/DateTimeShortcuts.js',
]
def __init__(self, attrs=None, **kwargs):
attrs = {'class': 'vDateField vDateField-rounded', 'size': '10', **(attrs or {})}
super().__init__(attrs=attrs, **kwargs)
class PickSplitDateTimeWidget(forms.SplitDateTimeWidget):
def __init__(self, attrs=None):
widgets = [PickDateWidget, forms.TimeInput(attrs={'class': 'TimeField'}, format='%H:%M')]
forms.MultiWidget.__init__(self, widgets, attrs)
class ContactForm(CityChMixin, BootstrapMixin, forms.ModelForm):
service = forms.ModelChoiceField(queryset=Service.objects.exclude(sigle='CRNE'), required=False)
roles = forms.ModelMultipleChoiceField(
label='Rôles',
queryset=Role.objects.exclude(est_famille=True).order_by('nom'),
widget=BSCheckboxSelectMultiple,
required=False
)
city_auto = CityChField(required=False)
postal_code_model_field = 'npa'
city_model_field = 'localite'
class Meta:
model = Contact
fields = [
'nom', 'prenom', 'profession', 'service', 'roles', 'rue', 'city_auto', 'npa', 'localite',
'tel_prof', 'tel_prive', 'email', 'remarque'
]
class RoleForm(BootstrapMixin, forms.ModelForm):
class Meta:
model = Role
fields = '__all__'
class ServiceForm(BootstrapMixin, forms.ModelForm):
class Meta:
model = Service
fields = ['sigle', 'nom_complet']
def clean_sigle(self):
return self.cleaned_data['sigle'].upper()
class FormationForm(BootstrapMixin, ReadOnlyableMixin, forms.ModelForm):
class Meta:
model = Formation
exclude = ('personne',)
class GroupSelectMultiple(BSCheckboxSelectMultiple):
option_template_name = "widgets/group_checkbox_option.html"
def create_option(self, name, value, *args, **kwargs):
try:
help_ = value.instance.groupinfo.description
except ObjectDoesNotExist:
help_= ''
return {
**super().create_option(name, value, *args, **kwargs),
'help': help_,
}
class UtilisateurForm(BootstrapMixin, forms.ModelForm):
roles = forms.ModelMultipleChoiceField(
label="Rôles",
queryset=Role.objects.exclude(
Q(est_famille=True) | Q(nom__in=['Personne significative', 'Référent'])
).order_by('nom'),
widget=BSCheckboxSelectMultiple,
required=False
)
class Meta:
model = Utilisateur
fields = [
'nom', 'prenom', 'sigle', 'profession', 'tel_prof', 'tel_prive', 'username',
'taux_activite', 'decharge', 'email', 'equipe', 'roles', 'groups'
]
widgets = {'groups': GroupSelectMultiple}
labels = {'profession': 'Titre'}
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.fields['groups'].queryset = self.fields['groups'].queryset.order_by('name')
class PersonneForm(CityChMixin, BootstrapMixin, ReadOnlyableMixin, forms.ModelForm):
role = forms.ModelChoiceField(label="Rôle", queryset=Role.objects.filter(est_famille=True), required=True)
city_auto = CityChField(required=False)
postal_code_model_field = 'npa'
city_model_field = 'localite'
class Meta:
model = Personne
fields = (
'role', 'nom', 'prenom', 'date_naissance', 'genre', 'filiation',
'rue', 'npa', 'localite', 'telephone',
'email', 'profession', 'pays_origine', 'decedee',
'allergies', 'remarque', 'remarque_privee',
)
widgets = {
'date_naissance': PickDateWidget,
}
labels = {
'remarque_privee': 'Remarque privée (pas imprimée)',
}
def __init__(self, **kwargs):
self.famille = kwargs.pop('famille', None)
role_id = kwargs.pop('role', None)
super().__init__(**kwargs)
if self.famille:
self.initial['nom'] = self.famille.nom
self.initial['rue'] = self.famille.rue
self.initial['npa'] = self.famille.npa
self.initial['localite'] = self.famille.localite
self.initial['telephone'] = self.famille.telephone
if role_id:
if role_id == 'ps': # personne significative
excl = Role.ROLES_PARENTS + ['Enfant suivi', 'Enfant non-suivi']
self.fields['role'].queryset = Role.objects.filter(
2024-06-04 12:12:00 +02:00
est_famille=True
2024-06-03 16:49:01 +02:00
).exclude(nom__in=excl)
elif role_id == 'parent':
self.fields['role'].queryset = Role.objects.filter(nom__in=Role.ROLES_PARENTS)
else:
self.role = Role.objects.get(pk=role_id)
if self.role.nom in Role.ROLES_PARENTS:
self.fields['role'].queryset = Role.objects.filter(nom=self.role.nom)
self.initial['genre'] = 'M' if self.role.nom == 'Père' else 'F'
elif self.role.nom in ['Enfant suivi', 'Enfant non-suivi']:
self.fields['role'].queryset = Role.objects.filter(nom__in=['Enfant suivi', 'Enfant non-suivi'])
self.fields['profession'].label = 'Profession/École'
self.initial['role'] = self.role.pk
else:
if self.instance.pk:
famille = self.instance.famille
# Cloisonnement des choix pour les rôles en fonction du rôle existant
if self.instance.role.nom in Role.ROLES_PARENTS:
self.fields['role'].queryset = Role.objects.filter(nom__in=Role.ROLES_PARENTS)
elif self.instance.role.nom in ['Enfant suivi', 'Enfant non-suivi']:
self.fields['role'].queryset = Role.objects.filter(nom__in=['Enfant suivi', 'Enfant non-suivi'])
else:
excl = ['Enfant suivi', 'Enfant non-suivi']
if len(famille.parents()) == 2:
excl.extend(Role.ROLES_PARENTS)
self.fields['role'].queryset = Role.objects.filter(
2024-06-04 12:12:00 +02:00
est_famille=True
2024-06-03 16:49:01 +02:00
).exclude(nom__in=excl)
def clean_nom(self):
return format_nom_prenom(self.cleaned_data['nom'])
def clean_prenom(self):
return format_nom_prenom(self.cleaned_data['prenom'])
def clean_localite(self):
localite = self.cleaned_data.get('localite')
if localite and localite[0].islower():
localite = localite[0].upper() + localite[1:]
return localite
def clean(self):
cleaned_data = super().clean()
if cleaned_data['decedee'] and cleaned_data['role'] == 'Enfant suivi':
raise forms.ValidationError('Un enfant décédé ne peut pas être «Enfant suivi»')
return cleaned_data
def save(self, **kwargs):
if self.instance.pk is None:
self.instance.famille = self.famille
pers = Personne.objects.create_personne(**self.instance.__dict__)
else:
pers = super().save(**kwargs)
pers = Personne.objects.add_formation(pers)
return pers
class AgendaFormBase(BootstrapMixin, forms.ModelForm):
destination = forms.ChoiceField(choices=PROVENANCE_DESTINATION_CHOICES, required=False)
class Meta:
fields = (
'date_demande', 'date_debut_evaluation', 'date_fin_evaluation',
'date_debut_suivi', 'date_fin_suivi', 'motif_fin_suivi', 'destination'
)
widgets = {field: PickDateWidget for field in fields[:-2]}
def __init__(self, *args, **kwargs):
destination = kwargs.pop('destination', '')
self.request = kwargs.pop('request', None)
super().__init__(*args, **kwargs)
if self.instance.date_debut_suivi is not None or self.instance.motif_fin_suivi:
for fname in ('date_demande', 'date_debut_evaluation', 'date_fin_evaluation'):
self.fields[fname].disabled = True
if self.instance.motif_fin_suivi:
self.fields['date_debut_suivi'].disabled = True
else:
# Choix 'Autres' obsolète (#435), pourrait être supprimé quand plus référencé
self.fields['motif_fin_suivi'].choices = [
ch for ch in self.fields['motif_fin_suivi'].choices if ch[0] != 'autres'
]
self.fields['destination'].choices = [('', '------')] + [
ch for ch in PROVENANCE_DESTINATION_CHOICES if (ch[0] != 'autre' or destination == 'autre')
]
self.initial['destination'] = destination
def clean(self):
cleaned_data = super().clean()
# Check date chronology
date_preced = None
for field_name in self._meta.fields[:-2]:
dt = cleaned_data.get(field_name)
if not dt:
continue
if date_preced and dt < date_preced:
raise forms.ValidationError(
"La date «{}» ne respecte pas lordre chronologique!".format(self.fields[field_name].label)
)
date_preced = dt
# Check mandatory dates
workflow = self._meta.model.WORKFLOW
for field, etape in reversed((workflow.items())):
if field == 'archivage':
continue
date_etape = cleaned_data.get(etape.date_nom())
etape_courante = etape
etape_preced_oblig = workflow[etape.preced_oblig]
date_preced_oblig = cleaned_data.get(etape_preced_oblig.date_nom())
while True:
etape_preced = workflow[etape_courante.precedente]
date_preced = cleaned_data.get(etape_preced.date_nom())
if date_preced is None and etape_courante.num > 1:
etape_courante = etape_preced
else:
break
if date_etape and date_preced_oblig is None:
raise forms.ValidationError("La date «{}» est obligatoire".format(etape_preced_oblig.nom))
# Check dates out of range
for field_name in self.changed_data:
value = self.cleaned_data.get(field_name)
if isinstance(value, date):
if value > date.today():
if field_name in ['date_debut_suivi', 'date_debut_evaluation', 'date_fin_evaluation']:
if value > date.today() + timedelta(days=ANTICIPATION_POUR_DEBUT_SUIVI):
self.add_error(
field_name,
forms.ValidationError(
"La saisie de cette date ne peut être "
f"anticipée de plus de {ANTICIPATION_POUR_DEBUT_SUIVI} jours !")
)
else:
self.add_error(field_name, forms.ValidationError("La saisie anticipée est impossible !"))
elif not Prestation.check_date_allowed(self.request.user, value):
if self.request and self.request.user.has_perm('aemo.change_famille'):
messages.warning(
self.request,
'Les dates saisies peuvent affecter les statistiques déjà communiquées !'
)
else:
self.add_error(
field_name,
forms.ValidationError("La saisie de dates pour le mois précédent nest pas permise !")
)
ddebut = cleaned_data.get('date_debut_suivi')
dfin = cleaned_data.get('date_fin_suivi')
motif = cleaned_data.get('motif_fin_suivi')
dest = cleaned_data.get('destination')
if ddebut and dfin and motif and dest: # dossier terminé
return cleaned_data
elif ddebut and (dfin is None or motif == '' or dest == ''): # suivi en cours
if any([dfin, motif, dest]):
raise forms.ValidationError(
"Les champs «Fin de l'accompagnement», «Motif de fin» et «Destination» "
"sont obligatoires pour fermer le dossier."
)
elif ddebut is None and dfin is None: # evaluation
if motif != '': # abandon
cleaned_data['date_fin_suivi'] = date.today()
return cleaned_data
def save(self):
instance = super().save()
if instance.date_fin_suivi:
instance.famille.destination = self.cleaned_data['destination']
instance.famille.save()
return instance
class PrestationRadioSelect(BSRadioSelect):
option_template_name = 'widgets/prestation_radio.html'
class PrestationForm(BootstrapMixin, forms.ModelForm):
class Meta:
model = Prestation
fields = ['date_prestation', 'duree', 'texte', 'manque', 'fichier', 'intervenants']
widgets = {
'date_prestation': PickDateWidget,
'intervenants': BSCheckboxSelectMultiple,
'lib_prestation': PrestationRadioSelect,
}
labels = {
'lib_prestation': 'Prestation',
}
field_classes = {
'duree': HMDurationField,
'texte': RichTextField,
}
def __init__(self, *args, famille=None, user, **kwargs):
self.user = user
self.famille = famille
super().__init__(*args, **kwargs)
self.fields['intervenants'].queryset = self.defaults_intervenants()
if famille:
intervenants = list(famille.suivi.intervenants.all())
if len(intervenants):
if self.user not in intervenants:
intervenants.insert(0, self.user)
self.fields['intervenants'].choices = [(i.pk, i.nom_prenom) for i in intervenants]
def defaults_intervenants(self):
return Utilisateur.intervenants()
def clean_date_prestation(self):
date_prestation = self.cleaned_data['date_prestation']
today = date.today()
if date_prestation > today:
raise forms.ValidationError("La saisie anticipée est impossible !")
if not Prestation.check_date_allowed(self.user, date_prestation):
raise forms.ValidationError(
"La saisie des prestations des mois précédents est close !"
)
return date_prestation
def clean_texte(self):
texte = self.cleaned_data['texte']
for snip in ('<p></p>', '<p><br></p>', '<p> </p>'):
while texte.startswith(snip):
texte = texte[len(snip):].strip()
for snip in ('<p></p>', '<p><br></p>', '<p> </p>'):
while texte.endswith(snip):
texte = texte[:-len(snip)].strip()
return texte
class DocumentUploadForm(BootstrapMixin, forms.ModelForm):
class Meta:
model = Document
fields = '__all__'
widgets = {'famille': forms.HiddenInput}
labels = {'fichier': ''}
class RapportEditForm(BootstrapMixin, forms.ModelForm):
class Meta:
model = Rapport
exclude = ['famille', 'auteur']
widgets = {
'famille': forms.HiddenInput,
'date': PickDateWidget,
'pres_interv': BSCheckboxSelectMultiple,
'sig_interv': BSCheckboxSelectMultiple,
}
field_classes = {
'situation': RichTextField,
'projet': RichTextField,
'observations': RichTextField,
}
field_order = [
'date', 'pres_interv', 'situation', 'observations', 'projet', 'sig_interv',
]
def __init__(self, user=None, **kwargs):
super().__init__(**kwargs)
if 'sig_interv' in self.fields:
interv_qs = Utilisateur.objects.filter(
pk__in=kwargs['initial']['famille'].suivi.intervenant_set.actifs(
self.instance.date or date.today()
).values_list('intervenant', flat=True)
)
self.fields['pres_interv'].queryset = interv_qs
self.fields['sig_interv'].queryset = interv_qs
class MonthSelectionForm(BootstrapMixin, forms.Form):
mois = forms.ChoiceField(choices=((mois_idx, MONTHS[mois_idx]) for mois_idx in range(1, 13)))
annee = forms.ChoiceField(
label='Année',
choices=((2019, 2019),
(2020, 2020),
(2021, 2021),
(2022, 2022),
(2023, 2023),
(2024, 2024))
)
class DateYearForm(forms.Form):
year = forms.ChoiceField(choices=[(str(y), str(y)) for y in range(2020, date.today().year + 1)])
def __init__(self, data=None, **kwargs):
if not data:
data = {'year': date.today().year}
super().__init__(data, **kwargs)
class ContactExterneAutocompleteForm(forms.ModelForm):
class Meta:
model = Personne
fields = ('reseaux',)
widgets = {'reseaux': autocomplete.ModelSelect2Multiple(url='contact-externe-autocomplete')}
labels = {'reseaux': 'Contacts'}
class ContactFilterForm(forms.Form):
service = forms.ModelChoiceField(label="Service", queryset=Service.objects.all(), required=False)
role = forms.ModelChoiceField(label="Rôle", queryset=Role.objects.exclude(est_famille=True), required=False)
texte = forms.CharField(
widget=forms.TextInput(attrs={'placeholder': 'Recherche…', 'autocomplete': 'off'}),
required=False
)
sort_by = forms.CharField(widget=forms.HiddenInput(), required=False)
sort_by_mapping = {
'nom': ['nom', 'prenom'],
'service': ['service'],
'role': ['roles__nom'],
'activite': ['profession'],
}
def filter(self, contacts):
if self.cleaned_data['service']:
contacts = contacts.filter(service=self.cleaned_data['service'])
if self.cleaned_data['role']:
contacts = contacts.filter(roles=self.cleaned_data['role'])
if self.cleaned_data['texte']:
contacts = contacts.filter(nom__icontains=self.cleaned_data['texte'])
if self.cleaned_data['sort_by']:
order_desc = self.cleaned_data['sort_by'].startswith('-')
contacts = contacts.order_by(*([
('-' if order_desc else '') + key
for key in self.sort_by_mapping.get(self.cleaned_data['sort_by'].strip('-'), [])
]))
return contacts
class FamilleAdresseForm(CityChMixin, BootstrapMixin, forms.ModelForm):
city_auto = CityChField(required=False)
postal_code_model_field = 'npa'
city_model_field = 'localite'
class Meta:
model = Famille
fields = ('rue', 'npa', 'localite')
def __init__(self, **kwargs):
self.famille = kwargs.pop('famille', None)
super().__init__(**kwargs)
membres = [
(m.pk, f"{m.nom_prenom} ({m.role}), {m.adresse}")
for m in self.famille.membres.all()
]
self.fields['membres'] = forms.MultipleChoiceField(
widget=BSCheckboxSelectMultiple,
choices=membres,
required=False
)
class FamilleForm(BootstrapMixin, ReadOnlyableMixin, forms.ModelForm):
equipe = forms.ChoiceField(label="Équipe", choices=[('', '------')] + Suivi.EQUIPES_CHOICES[:2])
# Concernant 'npa' & 'location', ils sont en lecture seule ici => pas besoin de faire de l'autocomplete
class Meta:
model = Famille
fields = ['nom', 'rue', 'npa', 'localite', 'telephone', 'region', 'autorite_parentale',
'monoparentale', 'statut_marital', 'connue', 'accueil', 'garde',
'provenance', 'statut_financier']
def __init__(self, **kwargs):
super().__init__(**kwargs)
if self.instance and self.instance.pk:
self.fields['rue'].widget.attrs['readonly'] = True
self.fields['npa'].widget.attrs['readonly'] = True
self.fields['localite'].widget.attrs['readonly'] = True
self.initial['equipe'] = kwargs['instance'].suivi.equipe
self.fields['region'] = forms.ModelChoiceField(
label='Région',
queryset=Region.objects.order_by('nom'),
required=False,
disabled=self.fields['region'].disabled,
widget=forms.Select(attrs={'class': 'form-select'})
)
self.fields['provenance'].choices = [
ch for ch in self.fields['provenance'].choices
if (ch[0] != 'autre' or self.instance.provenance == 'autre')
]
def save(self, **kwargs):
famille = super().save(**kwargs)
if famille.suivi.equipe != self.cleaned_data['equipe']:
famille.suivi.equipe = self.cleaned_data['equipe']
famille.suivi.save()
return famille
def clean_nom(self):
return format_nom_prenom(self.cleaned_data['nom'])
def clean_localite(self):
localite = self.cleaned_data.get('localite')
if localite and localite[0].islower():
localite = localite[0].upper() + localite[1:]
return localite
class FamilleCreateForm(CityChMixin, FamilleForm):
motif_detail = forms.CharField(
label="Motif de la demande", widget=forms.Textarea(), required=False
)
city_auto = CityChField(required=False)
postal_code_model_field = 'npa'
city_model_field = 'localite'
field_order = ['nom', 'rue', 'city_auto']
class Meta(FamilleForm.Meta):
exclude = ['typ', 'destination']
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['provenance'].choices = [
ch for ch in self.fields['provenance'].choices if ch[0] != 'autre'
]
def save(self, **kwargs):
famille = self._meta.model.objects.create_famille(
equipe=self.cleaned_data['equipe'], **self.instance.__dict__
)
famille.suivi.motif_detail = self.cleaned_data['motif_detail']
famille.suivi.save()
return famille
class SuiviForm(ReadOnlyableMixin, forms.ModelForm):
equipe = forms.TypedChoiceField(
choices=[('', '-------')] + Suivi.EQUIPES_CHOICES[:2], required=False
)
class Meta:
model = Suivi
fields = [
'equipe', 'heure_coord', 'ope_referent', 'ope_referent_2',
'mandat_ope', 'service_orienteur', 'service_annonceur', 'motif_demande',
'motif_detail', 'demande_prioritaire', 'demarche', 'collaboration',
'ressource', 'crise', 'remarque', 'remarque_privee',
]
widgets = {
'mandat_ope': BSCheckboxSelectMultiple,
'motif_demande': BSCheckboxSelectMultiple,
}
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.fields['ope_referent'].queryset = Contact.membres_ope()
self.fields['ope_referent_2'].queryset = Contact.membres_ope()
class IntervenantForm(BootstrapMixin, forms.ModelForm):
intervenant = forms.ModelChoiceField(
queryset=Utilisateur.objects.filter(groups__name__startswith='aemo').distinct().order_by('nom')
)
date_debut = forms.DateField(
label='Date de début', initial=date.today(), widget=PickDateWidget()
)
class Meta:
model = Intervenant
fields = ['intervenant', 'role', 'date_debut']
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['role'].queryset = Role.objects.filter(est_intervenant=True)
class IntervenantEditForm(BootstrapMixin, forms.ModelForm):
class Meta:
model = Intervenant
fields = ['intervenant', 'role', 'date_debut', 'date_fin']
widgets = {
'date_debut': PickDateWidget(),
'date_fin': PickDateWidget(),
}
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['intervenant'].disabled = True
self.fields['role'].disabled = True
class DemandeForm(BootstrapMixin, ReadOnlyableMixin, forms.ModelForm):
class Meta:
model = Suivi
fields = (
'dates_demande', 'ref_presents', 'pers_famille_presentes', 'autres_pers_presentes',
'difficultes', 'aides', 'competences', 'disponibilites', 'autres_contacts', 'remarque'
)
field_classes = {
'difficultes': RichTextField,
'aides': RichTextField,
'disponibilites': RichTextField,
'competences': RichTextField,
}
widgets = {
'autres_contacts': forms.Textarea(attrs={'cols': 120, 'rows': 4}),
'remarque': forms.Textarea(attrs={'cols': 120, 'rows': 4}),
}
class AgendaForm(ReadOnlyableMixin, AgendaFormBase):
ope_referent = forms.ModelChoiceField(
queryset=Contact.membres_ope(), required=False
)
class Meta(AgendaFormBase.Meta):
model = Suivi
class BilanForm(BootstrapMixin, forms.ModelForm):
class Meta:
model = Bilan
exclude = ['famille', 'auteur']
field_classes = {
'objectifs': RichTextField,
'rythme': RichTextField,
}
widgets = {
'famille': forms.HiddenInput,
'date': PickDateWidget,
'sig_interv': BSCheckboxSelectMultiple,
}
labels = {'fichier': ''}
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['sig_interv'].queryset = Utilisateur.objects.filter(
pk__in=kwargs['initial']['famille'].suivi.intervenant_set.actifs(
self.instance.date or date.today()
).values_list('intervenant', flat=True)
)
class NiveauForm(BootstrapMixin, forms.ModelForm):
class Meta:
model = Niveau
fields = ['niveau_interv', 'date_debut', 'date_fin']
widgets = {
'date_debut': PickDateWidget(),
'date_fin': PickDateWidget(),
}
def __init__(self, *args, **kwargs):
self.famille = kwargs.pop('famille')
super().__init__(*args, **kwargs)
if self.instance.pk is None:
self.fields.pop('date_fin')
if self.famille.niveaux.count() > 0:
self.fields['date_debut'].required = True
def clean_niveau_interv(self):
niv = self.cleaned_data['niveau_interv']
if self.instance.pk is None:
der_niv = self.famille.niveaux.last() if self.famille.niveaux.count() > 0 else None
if der_niv and der_niv.niveau_interv == niv:
raise forms.ValidationError(f"Le niveau {niv} est déjà actif.")
return niv
class EquipeFilterForm(forms.Form):
equipe = forms.ChoiceField(
label="Équipe", choices=[('', 'Toutes')] + EquipeChoices.choices, required=False,
widget=forms.Select(attrs={'class': 'form-select form-select-sm immediate-submit'}),
)
def filter(self, familles):
if self.cleaned_data['equipe']:
familles = familles.filter(suivi__equipe=self.cleaned_data['equipe'])
return familles
class FamilleFilterForm(EquipeFilterForm):
ressource = forms.ChoiceField(
label='Ressources',
required=False,
widget=forms.Select(attrs={'class': 'form-select form-select-sm immediate-submit'})
)
niveau = forms.ChoiceField(
label="Niv. d'interv.",
required=False,
widget=forms.Select(attrs={'class': 'form-select form-select-sm immediate-submit'})
)
interv = forms.ChoiceField(
label="Intervenant-e",
required=False,
widget=forms.Select(attrs={'class': 'form-select form-select-sm immediate-submit'})
)
nom = forms.CharField(
widget=forms.TextInput(
attrs={'placeholder': 'Nom de famille…', 'autocomplete': 'off',
'class': 'form-control form-control-sm inline'}),
required=False
)
duos = forms.ChoiceField(
label="Duos Educ/Psy",
required=False,
widget=forms.Select(attrs={'class': 'form-select form-select-sm immediate-submit'})
)
def __init__(self, **kwargs):
super().__init__(**kwargs)
intervenants = Utilisateur.objects.filter(is_active=True, groups__name='aemo')
self.fields['interv'].choices = [
('', 'Tout-e-s'), ('0', 'Aucun')] + [
(user.id, user.nom_prenom) for user in intervenants
]
self.fields['niveau'].choices = [
('', 'Tous')
] + [(key, val) for key, val in Niveau.INTERV_CHOICES]
self.fields['ressource'].choices = [
('', 'Toutes')
] + [(el.pk, el.nom) for el in Role.objects.filter(
nom__in=['ASE', 'IPE', 'Coach APA', 'Assistant-e social-e']
)]
self.fields['duos'].choices = [
('', 'Tous')
] + [
(duo, duo) for duo in Famille.objects.exclude(
suivi__date_fin_suivi__isnull=False
).with_duos().exclude(duo='').values_list('duo', flat=True).distinct().order_by('duo')
]
def filter(self, familles):
if self.cleaned_data['interv']:
if self.cleaned_data['interv'] == '0':
familles = familles.annotate(
num_interv=Count('suivi__intervenants', filter=(
Q(suivi__intervenant__date_fin__isnull=True) |
Q(suivi__intervenant__date_fin__gt=date.today())
))
).filter(num_interv=0)
else:
familles = familles.filter(
Q(suivi__intervenant__intervenant=self.cleaned_data['interv']) & (
Q(suivi__intervenant__date_fin__isnull=True) |
Q(suivi__intervenant__date_fin__gt=date.today())
)
)
if self.cleaned_data['nom']:
familles = familles.filter(nom__istartswith=self.cleaned_data['nom'])
familles = super().filter(familles)
if self.cleaned_data['niveau']:
familles = familles.with_niveau_interv().filter(niveau_interv=self.cleaned_data['niveau'])
if self.cleaned_data['ressource']:
ress = Intervenant.objects.actifs().filter(
role=self.cleaned_data['ressource'],
).values_list('intervenant', flat=True)
familles = familles.filter(suivi__intervenants__in=ress).distinct()
if self.cleaned_data['duos']:
familles = familles.with_duos().filter(duo=self.cleaned_data['duos'])
return familles
class JournalAuteurFilterForm(forms.Form):
recherche = forms.CharField(
widget=forms.TextInput(attrs={
'class': 'search-form-fields form-control d-inline-block',
'placeholder': 'recherche',
}),
required=False,
)
auteur = forms.ModelChoiceField(
queryset=Utilisateur.objects.all(),
widget=forms.Select(attrs={'class': 'search-form-fields form-select d-inline-block immediate-submit'}),
required=False,
)
def __init__(self, famille=None, **kwargs):
super().__init__(**kwargs)
self.fields['auteur'].queryset = Utilisateur.objects.filter(prestations__famille=famille).distinct()
def filter(self, prestations):
if self.cleaned_data['auteur']:
prestations = prestations.filter(auteur=self.cleaned_data['auteur'])
if self.cleaned_data['recherche']:
prestations = prestations.annotate(
search=SearchVector("texte", config="french_unaccent")
).filter(
search=SearchQuery(self.cleaned_data['recherche'], config="french_unaccent")
)
return prestations