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( famille=True ).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( famille=True ).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 l’ordre 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 n’est 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 ('

', '


', '

'): while texte.startswith(snip): texte = texte[len(snip):].strip() for snip in ('

', '


', '

'): 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