aemo_fr/aemo/models.py

1447 lines
55 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

from collections import OrderedDict, namedtuple
from datetime import date, timedelta
from operator import attrgetter
from pathlib import Path
from django.contrib.auth.models import AbstractUser, Group, UserManager
from django.contrib.postgres.aggregates import StringAgg
from django.core.validators import MaxValueValidator
from django.db import models
from django.db.models import Count, F, OuterRef, Q, Subquery, Sum
from django.db.models.functions import Coalesce, ExtractMonth, ExtractYear
from django.db.utils import IntegrityError
from django.urls import reverse
from django.utils import timezone
from django.utils.functional import cached_property
from django_countries.fields import CountryField
from common import choices
from common.fields import ChoiceArrayField
from .utils import format_adresse, format_contact, format_d_m_Y, random_string_generator
class CercleScolaire(models.Model):
""" Les 7 cercles scolaires du canton"""
nom = models.CharField(max_length=50, unique=True)
telephone = models.CharField('tél.', max_length=35, blank=True)
class Meta:
verbose_name = 'Cercle scolaire'
verbose_name_plural = 'Cercles scolaires'
ordering = ('nom',)
def __str__(self):
return self.nom
class Service(models.Model):
""" Services sociaux en lien avec la Fondation Transit """
sigle = models.CharField('Sigle', max_length=20, unique=True)
nom_complet = models.CharField('Nom complet', max_length=80, blank=True)
class Meta:
ordering = ('sigle',)
def __str__(self):
return self.sigle
def save(self, *args, **kwargs):
self.sigle = self.sigle.upper()
super().save(*args, **kwargs)
class RoleManager(models.Manager):
def get_by_natural_key(self, nom):
return self.get(nom=nom)
class Role(models.Model):
NON_EDITABLE = ['Père', 'Mère', 'Enfant suivi', 'Enfant non-suivi']
ROLES_PARENTS = ['Père', 'Mère']
nom = models.CharField("Nom", max_length=50, unique=True)
est_famille = models.BooleanField("Famille", default=False)
est_intervenant = models.BooleanField("Intervenant", default=False)
est_editeur = models.BooleanField("Éditeur", default=False, help_text=(
"Un rôle éditeur donne le droit de modification des dossiers familles si "
"la personne est intervenante."
))
objects = RoleManager()
class Meta:
ordering = ('nom',)
def __str__(self):
return self.nom
@property
def editable(self):
return self.nom not in self.NON_EDITABLE
def natural_key(self):
return (self.nom,)
class Contact(models.Model):
prenom = models.CharField('Prénom', max_length=30)
nom = models.CharField('Nom', max_length=30)
rue = models.CharField('Rue', max_length=30, blank=True)
npa = models.CharField('NPA', max_length=4, blank=True)
localite = models.CharField('Localité', max_length=30, blank=True)
tel_prive = models.CharField('Tél. privé', max_length=30, blank=True)
tel_prof = models.CharField('Tél. prof.', max_length=30, blank=True)
email = models.EmailField(max_length=100, blank=True)
service = models.ForeignKey(Service, null=True, blank=True, on_delete=models.PROTECT)
roles = models.ManyToManyField(Role, related_name='contacts', blank=True, verbose_name='Rôles')
profession = models.CharField('Activité/prof.', max_length=100, blank=True)
remarque = models.TextField("Remarque", blank=True)
est_actif = models.BooleanField('actif', default=True)
class Meta:
verbose_name = 'Contact'
ordering = ('nom', 'prenom')
unique_together = ('nom', 'prenom', 'service')
def __str__(self):
sigle = self.service.sigle if self.service else ''
return '{} ({})'.format(self.nom_prenom, sigle) if self.service else self.nom_prenom
@property
def nom_prenom(self):
return '{} {}'.format(self.nom, self.prenom)
@property
def adresse(self):
return format_adresse(self.rue, self.npa, self.localite)
@cached_property
def all_roles(self):
return self.roles.all()
def has_role(self, roles=()):
"""Return True if user has at least one of the `roles` name."""
return bool(set(r.nom for r in self.all_roles).intersection(set(roles)))
def roles_str(self, sep=', '):
return sep.join([role.nom for role in self.all_roles])
@property
def contact(self):
return format_contact(self.tel_prof, self.email)
@classmethod
def membres_ope(cls):
return cls.objects.filter(service__sigle__startswith='OPE', est_actif=True).select_related('service')
class EquipeChoices(models.TextChoices):
MONTAGNES = 'montagnes', 'Montagnes et V-d-T'
LITTORAL = 'littoral', 'Littoral et V-d-R'
class Utilisateur(Contact, AbstractUser):
sigle = models.CharField(max_length=5, blank=True)
equipe = models.CharField("Équipe", max_length=10, choices=EquipeChoices.choices, blank=True)
date_desactivation = models.DateField('Date désactivation', null=True, blank=True)
taux_activite = models.PositiveSmallIntegerField(
"Taux dactivité (en %)", blank=True, default=0, validators=[MaxValueValidator(100)]
)
decharge = models.PositiveSmallIntegerField("Heures de décharge", blank=True, null=True)
objects = UserManager()
def __str__(self):
return self.nom_prenom
@property
def groupes(self):
return [item.name for item in self.groups.all()]
@property
def is_psy_or_educ(self):
return self.has_role({'Psy', 'Educ'})
@property
def is_responsable(self):
return self.has_role({'Responsable/coordinateur'})
@property
def is_admin_ipe(self):
return 'admin ipe' in self.groupes
@property
def initiales(self):
return self.sigle if self.sigle else f'{self.prenom[0]}{self.nom[0]}'
@property
def charge_max(self):
"""
Renvoie la charge maximale d'heures hebodmadaires de charge dossier en
fonction du taux d'activité.
"""
charge_map = {
100: 32, 90: 28, 80: 24, 75: 22, 70: 20, 60: 16, 0: 0,
}
charge = charge_map.get(self.taux_activite, int(32 * self.taux_activite / 100))
if self.decharge:
charge -= self.decharge
return charge
def prestations_gen(self):
"""Renvoie les prestations générales de lutilisateur."""
return self.prestations.filter(famille__isnull=True)
def temps_total_prestations(self, unite):
return self.prestations.model.temps_total(self.prestations, tous_utilisateurs=False)
def total_mensuel(self, unite, month, year):
prestations = self.prestations.filter(
date_prestation__year=year, date_prestation__month=month
)
return self.prestations.model.temps_total(prestations, tous_utilisateurs=False)
def totaux_mensuels(self, unite, year):
return [self.total_mensuel(unite, m, year) for m in range(1, 13)]
def total_annuel(self, unite, year):
prestations = self.prestations.filter(date_prestation__year=year)
return self.prestations.model.temps_total(prestations, tous_utilisateurs=False)
@classmethod
def _intervenants(cls, groupes=(), annee=None):
if annee is None:
# Utilisateurs actuellement actifs
utils = Utilisateur.objects.filter(date_desactivation__isnull=True)
else:
utils = Utilisateur.objects.filter(
Q(date_desactivation__isnull=True) | Q(date_desactivation__year__gte=annee)
)
return utils.filter(
groups__name__in=groupes,
is_superuser=False
).exclude(
groups__name__in=['direction']
).distinct()
@classmethod
def intervenants(cls, annee=None):
return cls._intervenants(
groupes=['aemo'],
annee=annee
)
class GroupInfo(models.Model):
group = models.OneToOneField(Group, on_delete=models.CASCADE)
description = models.TextField(blank=True)
def __str__(self):
return f"Description of group {self.group.name}"
class Region(models.Model):
nom = models.CharField(max_length=30, unique=True)
rue = models.CharField("Rue", max_length=100, blank=True)
def __str__(self):
return self.nom
def __lt__(self, other):
return self.nom < (other.nom if other else '')
class FamilleQuerySet(models.QuerySet):
def with_duos(self):
"""
Renvoie une liste des groupes Psy/Educ différents, sous forme de chaînes
de sigles séparées par "/".
Peut aussi renvoyer 1 ou 3 intervenants, le cas échéant.
"""
return self.annotate(
duo=StringAgg(
F('suivi__intervenant__intervenant__sigle'),
delimiter='/',
filter=(
Q(suivi__intervenant__date_fin__isnull=True) &
Q(suivi__intervenant__role__nom__in=['Educ', 'Psy'])
),
ordering='suivi__intervenant__intervenant__sigle',
)
)
def with_niveau_interv(self):
"""
Annote les familles avec le niveau dintervention le plus récent.
"""
return self.annotate(
niveau_interv=Subquery(
Niveau.objects.filter(
famille=OuterRef('pk')
).order_by('-date_debut').values('niveau_interv')[:1]
)
)
class FamilleManager(models.Manager):
def get_queryset(self):
return FamilleQuerySet(self.model, using=self._db).filter(
suivi__isnull=False
).prefetch_related(
models.Prefetch('membres', queryset=Personne.objects.select_related('role'))
)
def create_famille(self, equipe='', **kwargs):
kwargs.pop('_state', None)
famille = self.create(**kwargs)
Suivi.objects.create(
famille=famille,
equipe=equipe
)
famille.suivi.date_demande = date.today()
famille.suivi.save()
return famille
class Famille(models.Model):
created_at = models.DateTimeField(auto_now_add=True, editable=False)
archived_at = models.DateTimeField('Archivée le', blank=True, null=True)
nom = models.CharField('Nom de famille', max_length=40)
rue = models.CharField('Rue', max_length=60, blank=True)
npa = models.CharField('NPA', max_length=4, blank=True)
localite = models.CharField('Localité', max_length=30, blank=True)
region = models.ForeignKey(to=Region, null=True, blank=True, default=None, on_delete=models.SET_NULL)
telephone = models.CharField('Tél.', max_length=60, blank=True)
autorite_parentale = models.CharField("Autorité parentale", max_length=20,
choices=choices.AUTORITE_PARENTALE_CHOICES, blank=True)
monoparentale = models.BooleanField('Famille monoparent.', default=None, blank=True, null=True)
statut_marital = models.CharField("Statut marital", max_length=20, choices=choices.STATUT_MARITAL_CHOICES,
blank=True)
connue = models.BooleanField("famille déjà suivie", default=False)
accueil = models.BooleanField("famille d'accueil", default=False)
# Pour CIPE
besoins_part = models.BooleanField("famille à besoins particuliers", default=False)
sap = models.BooleanField(default=False, verbose_name="famille s@p")
garde = models.CharField(
'Type de garde', max_length=20, choices=choices.TYPE_GARDE_CHOICES, blank=True
)
provenance = models.CharField(
'Provenance', max_length=30, choices=choices.PROVENANCE_DESTINATION_CHOICES, blank=True)
destination = models.CharField(
'Destination', max_length=30, choices=choices.PROVENANCE_DESTINATION_CHOICES, blank=True)
statut_financier = models.CharField(
'Statut financier', max_length=30, choices=choices.STATUT_FINANCIER_CHOICES, blank=True)
remarques = models.TextField(blank=True)
objects = FamilleManager()
class Meta:
ordering = ('nom', 'npa')
permissions = (
("can_manage_waiting_list", "Gérer la liste d'attente"),
("can_archive", "Archiver les dossiers AEMO"),
('export_stats', 'Exporter les statistiques'),
)
def __str__(self):
return '{} - {}'.format(self.nom, self.adresse)
@property
def adresse(self):
return format_adresse(self.rue, self.npa, self.localite)
@property
def suivi(self):
return getattr(self, self.suivi_name) if self.suivi_name else None
@property
def suivi_url(self):
return reverse('famille-suivi', args=[self.pk])
@property
def edit_url(self):
return reverse('famille-edit', args=[self.pk])
@property
def add_person_url(self):
return reverse('personne-add', args=[self.pk])
def redirect_after_personne_creation(self, personne):
return self.edit_url
@property
def print_coords_url(self):
return reverse('print-coord-famille', args=[self.pk])
@classmethod
def actives(cls, date=None):
qs = Famille.objects.filter(suivi__isnull=False).order_by('nom', 'npa')
if date is not None:
return qs.filter(suivi__date_demande__lte=date).filter(
models.Q(suivi__date_fin_suivi__isnull=True) |
models.Q(suivi__date_fin_suivi__gte=date)
)
else:
return qs.filter(suivi__date_fin_suivi__isnull=True)
def interventions_actives(self, dt=None):
dt = dt or date.today()
return self.suivi.intervenant_set.filter(
Q(date_debut__lte=dt) & (
Q(date_fin__isnull=True) | Q(date_fin__gte=dt)
)
).select_related('intervenant', 'role')
def niveau_actuel(self):
try:
return [
niv.niveau_interv for niv in sorted(
self.niveaux.all(), key=attrgetter('date_debut')
)
][-1]
except IndexError:
return None
def membres_suivis(self):
if 'membres' in getattr(self, '_prefetched_objects_cache', {}):
return sorted([
pers for pers in self._prefetched_objects_cache['membres'] if pers.role.nom == "Enfant suivi"
], key=lambda p: p.date_naissance or date(1950, 1, 1))
else:
return self.membres.filter(role__nom="Enfant suivi").order_by('date_naissance')
def enfants_non_suivis(self):
if 'membres' in getattr(self, '_prefetched_objects_cache', {}):
return sorted([
pers for pers in self._prefetched_objects_cache['membres'] if pers.role.nom == "Enfant non-suivi"
], key=lambda p: p.date_naissance or date(1950, 1, 1))
else:
return self.membres.filter(role__nom='Enfant non-suivi').order_by('date_naissance')
def parents(self):
if 'membres' in getattr(self, '_prefetched_objects_cache', {}):
parents = [
pers for pers in self._prefetched_objects_cache['membres']
if pers.role.nom in Role.ROLES_PARENTS
]
else:
parents = [
pers for pers in self.membres.filter(role__nom__in=Role.ROLES_PARENTS)
]
return sorted(parents, key=lambda p: p.role.nom) # Mère avant Père
def autres_parents(self):
excluded_roles = Role.ROLES_PARENTS + ['Enfant suivi', 'Enfant non-suivi']
if 'membres' in getattr(self, '_prefetched_objects_cache', {}):
return [
pers for pers in self._prefetched_objects_cache['membres']
if pers.role.nom not in excluded_roles
]
else:
return self.membres.exclude(role__nom__in=excluded_roles)
def access_ok(self, user):
if user.has_perm('aemo.change_famille') or user.is_responsable:
return True
intervs = self.interventions_actives()
return not intervs or user in [interv.intervenant for interv in intervs]
def can_view(self, user):
return user.has_perm('aemo.view_famille')
def can_edit(self, user):
if user.has_perm('aemo.change_famille') or user.is_responsable:
return True
if not self.suivi.date_fin_suivi or self.suivi.date_fin_suivi >= date.today():
intervs = self.interventions_actives()
if (
self.can_view(user) and
(not intervs or user in [
interv.intervenant for interv in intervs if interv.role.est_editeur
])
):
return True
return False
def can_be_deleted(self, user):
if not user.has_perm('aemo.change_famille'):
return False
if self.suivi.motif_fin_suivi == 'erreur':
return True
return False
def can_be_archived(self, user):
if self.archived_at:
return False
closed_since = date.today() - self.suivi.date_fin_suivi
return (
user.has_perm('aemo.can_archive') and
self.suivi.date_fin_suivi is not None and
closed_since.days > 180 and
self.suivi.date_fin_suivi.year < date.today().year and
(date.today().month > 1 or closed_since.days > 400)
)
def temps_total_prestations(self, interv=None):
"""
Temps total des prestations liées à la famille, quel que soit le nombre
de membres suivis.
Filtré facultativement par intervenant.
"""
prest = self.prestations if interv is None else self.prestations.filter(intervenant=interv)
return prest.annotate(num_util=Count('intervenants')).aggregate(
total=Sum(F('duree') * F('num_util'), output_field=models.DurationField())
)['total'] or timedelta()
def temps_total_prestations_reparti(self):
"""
Temps total des prestations liées à la famille, divisé par le nombre d'enfants suivis.
"""
duree = self.temps_total_prestations()
duree //= (len(self.membres_suivis()) or 1)
return duree
def total_mensuel(self, date_debut_mois):
"""
Temps total de prestations sur un mois donné
"""
return self.prestations.filter(
date_prestation__month=date_debut_mois.month,
date_prestation__year=date_debut_mois.year
).aggregate(total=Sum('duree'))['total'] or timedelta()
def total_mensuel_par_prestation(self, prest_codes, date_debut_mois):
"""
Temps total d'évaluation sur un mois donné par prestation.
"""
return self.prestations.annotate(
num_util=Count('intervenants')
).filter(
lib_prestation__code__in=prest_codes,
date_prestation__month=date_debut_mois.month,
date_prestation__year=date_debut_mois.year
).aggregate(
total=Sum(F('duree') * F('num_util'), output_field=models.DurationField())
)['total'] or timedelta()
def total_mensuel_evaluation(self, date_debut_mois):
return self.total_mensuel_par_prestation(['aemo01'], date_debut_mois)
def total_mensuel_suivi(self, date_debut_mois):
return self.total_mensuel_par_prestation(['aemo02', 'aemo04', 'aemo05', 'aemo06', 'aemo07'], date_debut_mois)
def prestations_historiques(self):
return Prestation.prestations_historiques(self.prestations.all())
def prestations_du_mois(self):
"""
Retourne le détail des prestations pour cette famille
à partir du mois courant
"""
date_ref = date(date.today().year, date.today().month, 1)
return self.prestations.filter(date_prestation__gte=date_ref)
@classmethod
def suivis_en_cours(cls, debut, fin):
base = cls.objects.annotate(
date_deb=Coalesce(
'suivi__date_demande',
'suivi__date_debut_evaluation', 'suivi__date_debut_suivi'
),
).prefetch_related('membres')
return base.filter(
date_deb__lte=fin
).filter(
Q(**{'suivi__date_fin_suivi__isnull': True}) |
Q(**{'suivi__date_fin_suivi__gte': debut})
).exclude(**{'suivi__motif_fin_suivi': 'erreur'})
@classmethod
def suivis_nouveaux(cls, annee, mois):
base = cls.objects.annotate(
date_dem=F('suivi__date_demande')
).prefetch_related('membres')
return base.filter(**{
'date_dem__year': annee, 'date_dem__month': mois,
}).exclude(**{'suivi__motif_fin_suivi': 'erreur'})
@classmethod
def suivis_termines(cls, annee, mois):
base = cls.objects.all().prefetch_related('membres')
return base.filter(**{
'suivi__date_fin_suivi__year': annee,
'suivi__date_fin_suivi__month': mois,
}).exclude(**{'suivi__motif_fin_suivi': 'erreur'})
def can_be_reactivated(self, user):
return user.has_perm("aemo.change_famille") and self.suivi.date_fin_suivi is not None
def anonymiser(self):
# Famille
self.nom = random_string_generator()
self.rue = ''
self.telephone = ''
self.remarques = ''
self.archived_at = timezone.now()
self.save()
# Personne
for personne in self.membres.all().select_related('role'):
if personne.role.nom == 'Enfant suivi':
personne.nom = random_string_generator()
personne.prenom = random_string_generator()
personne.validite = None
personne.contractant = False
fields = ['rue', 'npa', 'localite', 'telephone', 'email', 'remarque', 'remarque_privee',
'profession', 'filiation', 'allergies', 'employeur', 'permis', 'animaux']
[setattr(personne, field, '') for field in fields]
personne.save()
personne.reseaux.clear()
if hasattr(personne, 'formation'):
personne.formation.cercle_scolaire = None
fields = ['college', 'classe', 'enseignant', 'creche', 'creche_resp', 'entreprise',
'maitre_apprentissage', 'remarque']
[setattr(personne.formation, field, '') for field in fields]
personne.formation.save()
else:
personne.delete()
# Suivi
fields = ['difficultes', 'aides', 'competences', 'autres_contacts', 'disponibilites', 'remarque',
'remarque_privee', 'motif_detail', 'referent_note', 'collaboration', 'ressources', 'crise',
'pers_famille_presentes', 'ref_presents', 'autres_pers_presentes']
[setattr(self.suivi, field, '') for field in fields]
self.suivi.save()
# Related
for doc in self.documents.all():
doc.delete()
for bilan in self.bilans.all():
bilan.delete()
for rapport in self.rapports.all():
rapport.delete()
self.prestations.all().update(texte='')
for prestation in self.prestations.exclude(fichier=''):
prestation.fichier.delete()
prestation.save()
class PersonneManager(models.Manager):
def create_personne(self, **kwargs):
kwargs.pop('_state', None)
pers = self.create(**kwargs)
return self.add_formation(pers)
def add_formation(self, pers):
if pers.role.nom == 'Enfant suivi' and not hasattr(pers, 'formation'):
moins_de_4ans = bool(pers.age and pers.age < 4)
Formation.objects.create(
personne=pers, statut='pre_scol' if moins_de_4ans else ''
)
if moins_de_4ans:
try:
pers.famille.suivi.demande_prioritaire = True
pers.famille.suivi.save()
except AttributeError:
pass
return pers
class Personne(models.Model):
""" Classe de base des personnes """
created_at = models.DateTimeField(auto_now_add=True, editable=False, null=True)
nom = models.CharField('Nom', max_length=30)
prenom = models.CharField('Prénom', max_length=30, blank=True)
date_naissance = models.DateField('Date de naissance', null=True, blank=True)
genre = models.CharField('Genre', max_length=1, choices=(('M', 'M'), ('F', 'F')), default='M')
rue = models.CharField('Rue', max_length=60, blank=True)
npa = models.CharField('NPA', max_length=4, blank=True)
localite = models.CharField('Localité', max_length=30, blank=True)
telephone = models.CharField('Tél.', max_length=60, blank=True)
email = models.EmailField('Courriel', blank=True)
pays_origine = CountryField('Nationalité', blank=True)
remarque = models.TextField(blank=True)
remarque_privee = models.TextField('Remarque privée', blank=True)
famille = models.ForeignKey(Famille, on_delete=models.CASCADE, related_name='membres')
role = models.ForeignKey('Role', on_delete=models.PROTECT)
profession = models.CharField('Profession', max_length=50, blank=True)
filiation = models.CharField('Filiation', max_length=80, blank=True)
decedee = models.BooleanField('Cette personne est décédée', default=False)
reseaux = models.ManyToManyField(Contact, blank=True)
allergies = models.TextField('Allergies', blank=True)
# Champs spécifiques SIFP
employeur = models.CharField('Adresse empl.', max_length=50, blank=True)
permis = models.CharField('Permis/séjour', max_length=30, blank=True)
validite = models.DateField('Date validité', blank=True, null=True)
animaux = models.BooleanField('Animaux', default=None, null=True)
objects = PersonneManager()
class Meta:
verbose_name = 'Personne'
ordering = ('nom', 'prenom')
def __str__(self):
return '{} {}'.format(self.nom, self.prenom)
@property
def nom_prenom(self):
return str(self)
@property
def adresse(self):
return format_adresse(self.rue, self.npa, self.localite)
@property
def age(self):
return self.age_a(date.today())
def age_str(self, date_=None, format_='auto'):
"""Renvoie '2 ans, 4 mois', '5 ans', '8 mois'.
Param:
'auto': application du format
- 'jour' si l'âge < 30 jours
- 'sem_jour' si l'âge < 77 jours (11 semaines)
- 'mois_sem' si l'âge < 690 jours (23 mois)
- 'an_mois': autres cas
'jour': âge en jours
'sem_jour': âge en semaines et jours
'mois_sem': âge en mois et semaines
"""
if not self.date_naissance:
# Pourrait disparaître si date_naissance devient non null
return ''
if format_ not in ['auto', 'jour', 'sem_jour', 'mois_sem', 'an_mois']:
raise ValueError('Paramètre erroné')
age_jours = self.age_jours(date_)
if (age_jours < 30 and format_ == 'auto') or format_ == 'jour':
age_str = f"{age_jours} jour{'s' if age_jours > 1 else ''}"
elif (age_jours < 77 and format_ == 'auto') or format_ == 'sem_jour':
sem, jours = age_jours // 7, age_jours % 7
age_str = f"{sem} sem."
age_str += f" {jours} jour{'s' if jours > 1 else ''}" if jours > 0 else ''
elif (age_jours < 690 and format_ == 'auto') or format_ == 'mois_sem':
mois, sem = age_jours // 30, int((age_jours % 30)/7)
age_str = f"{mois} mois"
age_str += f" {sem} sem." if sem > 0 else ''
else:
ans, mois = int(age_jours / 365.25), int((age_jours % 365.25) / 30)
age_str = f"{ans} an{'s' if ans > 1 else ''}"
age_str += " %d mois" % mois if mois > 0 else ''
return age_str
def age_a(self, date_):
if not self.date_naissance:
# Pourrait disparaître si date_naissance devient non null
return None
age = (date_ - self.date_naissance).days / 365.25
return int(age * 10) / 10 # 1 décimale arrondi vers le bas
def age_jours(self, date_=None):
if date_ is None:
date_ = date.today()
return (date_ - self.date_naissance).days
def age_mois(self, date_=None):
"""Âge en mois à la date_"""
if date_ is None:
date_ = date.today()
if not self.date_naissance or date_ < self.date_naissance:
return None
return self.age_jours(date_) / (365 / 12)
@property
def localite_display(self):
return '{} {}'.format(self.npa, self.localite)
@property
def edit_url(self):
return reverse('personne-edit', args=[self.famille_id, self.pk])
def can_edit(self, user):
return self.famille.can_edit(user)
def can_be_deleted(self, user):
return self.famille.can_be_deleted(user)
class Formation(models.Model):
FORMATION_CHOICES = (
('pre_scol', 'Pré-scolaire'),
('cycle1', 'Cycle 1'),
('cycle2', 'Cycle 2'),
('cycle3', 'Cycle 3'),
('apprenti', 'Apprentissage'),
('etudiant', 'Etudiant'),
('en_emploi', 'En emploi'),
('sans_emploi', 'Sans emploi'),
('sans_occupation', 'Sans occupation'),
)
personne = models.OneToOneField(Personne, on_delete=models.CASCADE)
statut = models.CharField('Scolarité', max_length=20, choices=FORMATION_CHOICES, blank=True)
cercle_scolaire = models.ForeignKey(
CercleScolaire, blank=True, null=True, on_delete=models.SET_NULL,
related_name='+', verbose_name='Cercle scolaire'
)
college = models.CharField('Collège', max_length=50, blank=True)
classe = models.CharField('Classe', max_length=50, blank=True)
enseignant = models.CharField('Enseignant', max_length=50, blank=True)
creche = models.CharField('Crèche', max_length=50, blank=True)
creche_resp = models.CharField('Resp.crèche', max_length=50, blank=True)
entreprise = models.CharField('Entreprise', max_length=50, blank=True)
maitre_apprentissage = models.CharField("Maître d'appr.", max_length=50, blank=True)
remarque = models.TextField(blank=True)
class Meta:
verbose_name = 'Scolarité'
def __str__(self):
return 'Scolarité de {}'.format(self.personne)
def can_edit(self, user):
return self.personne.famille.can_edit(user)
def delete(self, **kwargs):
if self.personne:
raise IntegrityError
else:
super().delete(**kwargs)
@property
def sse(self):
return "Champ à définir"
def pdf_data(self):
if self.personne.formation.statut == 'pre_scol':
data = [
['Crèche: {}'.format(self.creche), 'responsable: {}'.format(self.creche_resp)],
]
elif self.personne.formation.statut in ['cycle1', 'cycle2', 'cycle3', 'etudiant']:
data = [
['Cercle: {}'.format(self.cercle_scolaire), 'collège: {}'.format(self.college)],
['Classe: {}'.format(self.classe), 'Enseignant-e: {}'.format(self.enseignant)]
]
elif self.personne.formation.statut == 'apprenti':
data = [
['Employeur: {}'.format(self.entreprise), 'resp. apprenti-e: {}'.format(self.maitre_apprentissage)],
]
else:
data = [self.personne.formation.statut]
return data
def info_scol(self):
if self.statut == 'pre_scol':
creche = f"Crèche: {self.creche}" if self.creche else ''
resp = f" - resp.: {self.creche_resp}" if self.creche_resp else ''
data = ''.join([creche, resp])
elif self.statut in ['cycle1', 'cycle2', 'cycle3', 'etudiant']:
college = f"Collège: {self.college}" if self.college else ''
classe = f" - classe: {self.classe}" if self.classe else ''
ens = f" - ens.: {self.enseignant}" if self.enseignant else ''
data = ''.join([college, classe, ens])
elif self.statut == 'apprenti':
emp = f"Employeur: {self.entreprise}" if self.entreprise else ''
resp = f" - resp.: {self.maitre_apprentissage}" if self.maitre_apprentissage else ''
data = ''.join([emp, resp])
elif self.statut:
data = self.get_statut_display() if self.statut else ''
else:
data = "Formation: aucune info. saisie"
return data
class Document(models.Model):
famille = models.ForeignKey(Famille, related_name='documents', on_delete=models.CASCADE)
fichier = models.FileField("Nouveau fichier", upload_to='doc')
titre = models.CharField(max_length=100)
class Meta:
unique_together = ('famille', 'titre')
def __str__(self):
return self.titre
def delete(self):
Path(self.fichier.path).unlink(missing_ok=True)
return super().delete()
def can_edit(self, user):
return self.famille.can_edit(user)
def can_be_deleted(self, user):
return self.famille.can_be_deleted(user)
class Bilan(models.Model):
famille = models.ForeignKey(Famille, related_name='bilans', on_delete=models.CASCADE)
date = models.DateField("Date du bilan")
auteur = models.ForeignKey(
Utilisateur, related_name='bilans', on_delete=models.PROTECT, null=True,
)
objectifs = models.TextField("Objectifs")
rythme = models.TextField("Rythme et fréquence")
sig_famille = models.BooleanField("Apposer signature de la famille", default=True)
sig_interv = models.ManyToManyField(Utilisateur, blank=True, verbose_name="Signature des intervenants")
fichier = models.FileField("Fichier/image", blank=True, upload_to='bilans')
delete_urlname = 'famille-bilan-delete'
def __str__(self):
return "Bilan du {} pour la famille {}".format(self.date, self.famille)
def get_absolute_url(self):
return reverse('famille-bilan-view', args=[self.famille_id, self.pk])
def get_print_url(self):
return reverse('print-bilan', args=[self.pk])
def edit_url(self):
return reverse('famille-bilan-edit', args=[self.famille_id, self.pk])
def can_edit(self, user):
return self.famille.can_edit(user) or user in self.famille.suivi.intervenants.all()
def title(self):
return f"Bilan du {format_d_m_Y(self.date)}"
class Rapport(models.Model):
"""
Rapport est l'appellation historique, Résumé étant l'appellation moderne (dès février 2023).
Les champs «techniques» n'ont pas été renommés.
"""
famille = models.ForeignKey(Famille, related_name='rapports', on_delete=models.CASCADE)
date = models.DateField("Date du résumé")
auteur = models.ForeignKey(Utilisateur, on_delete=models.PROTECT)
pres_interv = models.ManyToManyField(
Utilisateur, blank=True, verbose_name="Intervenants cités dans le résumé", related_name='+'
)
sig_interv = models.ManyToManyField(
Utilisateur, blank=True, verbose_name="Signature des intervenants", related_name='+'
)
situation = models.TextField("Situation / contexte familial", blank=True)
observations = models.TextField("Observations, évolution et hypothèses", blank=True)
projet = models.TextField("Perspectives davenir", blank=True)
def __str__(self):
return "Résumé du {} pour la famille {}".format(format_d_m_Y(self.date), self.famille)
def get_absolute_url(self):
return reverse('famille-rapport-view', args=[self.famille.pk, self.pk])
def edit_url(self):
return reverse('famille-rapport-edit', args=[self.famille.pk, self.pk])
def get_print_url(self):
return reverse('famille-rapport-print', args=[self.famille.pk, self.pk])
def can_edit(self, user):
return self.famille.can_edit(user) or user in self.famille.suivi.intervenants.all()
def can_delete(self, user):
return self.famille.can_edit(user)
def title(self):
return f"Résumé du {format_d_m_Y(self.date)}"
def intervenants(self):
return [i.intervenant for i in self.famille.interventions_actives(self.date)]
class Niveau(models.Model):
INTERV_CHOICES = [(0, '0'), (1, '1'), (2, '2'), (3, '3')]
famille = models.ForeignKey(
Famille, related_name='niveaux', on_delete=models.CASCADE, verbose_name="Famille"
)
niveau_interv = models.PositiveSmallIntegerField("Niveau dintervention", choices=INTERV_CHOICES)
date_debut = models.DateField('Date début', blank=True, null=True)
date_fin = models.DateField('Date fin', blank=True, null=True)
def __str__(self):
return (
f"{self.famille.nom} - {self.niveau_interv} - du {format_d_m_Y(self.date_debut)} au "
f"{format_d_m_Y(self.date_fin)}"
)
class LibellePrestation(models.Model):
code = models.CharField('Code', max_length=6, unique=True)
nom = models.CharField('Nom', max_length=30)
actes = models.TextField('Actes à prester', blank=True)
class Meta:
ordering = ('code',)
def __str__(self):
return self.nom
class Prestation(models.Model):
famille = models.ForeignKey(Famille, related_name='prestations', null=True, blank=True,
on_delete=models.SET_NULL, verbose_name="Famille")
auteur = models.ForeignKey(Utilisateur, on_delete=models.PROTECT, verbose_name='auteur')
intervenants = models.ManyToManyField(Utilisateur, related_name='prestations')
date_prestation = models.DateField("date de lintervention")
duree = models.DurationField("durée")
lib_prestation = models.ForeignKey(
LibellePrestation, on_delete=models.SET_NULL, null=True, default=None,
related_name='prestations_%(app_label)s'
)
# Nombre de familles actives au moment de la prestation, utilisé pour ventiler les heures
# dans les statistiques.
familles_actives = models.PositiveSmallIntegerField(blank=True, default=0)
texte = models.TextField('Contenu', blank=True)
manque = models.BooleanField('Rendez-vous manqué', default=False)
fichier = models.FileField('Fichier/image', upload_to='prestations', blank=True)
# Nbre de jours maximum après fin d'un mois où il est encore possible de saisir
# des données du mois précédent.
DELAI_SAISIE_SUPPL = 14
class Meta:
ordering = ('-date_prestation',)
permissions = (
('edit_prest_prev_month', 'Modifier prestations du mois précédent'),
)
def __str__(self):
if self.famille:
return f'Prestation pour la famille {self.famille} le {self.date_prestation} : {self.duree}'
return f'Prestation générale le {self.date_prestation} : {self.duree}'
def can_edit(self, user):
return (
(user == self.auteur or user in self.intervenants.all() or
user.has_perm('aemo.edit_prest_prev_month')
) and self.check_date_allowed(user, self.date_prestation)
)
def edit_url(self):
return reverse('prestation-edit', args=[self.famille.pk if self.famille else 0, self.pk])
def save(self, *args, **kwargs):
if self.famille is None:
self.familles_actives = Famille.actives(self.date_prestation).count()
super().save(*args, **kwargs)
@classmethod
def check_date_allowed(cls, user, dt):
"""Contrôle si la date `dt` est située dans le mois courant + DELAI_SAISIE_SUPPL."""
today = date.today()
delai = cls.DELAI_SAISIE_SUPPL
if user.has_perm('aemo.edit_prest_prev_month'):
delai += 31
if today.day <= delai:
return dt >= (today - timedelta(days=delai + 1)).replace(day=1)
else:
return dt.year == today.year and dt.month == today.month
@classmethod
def prestations_historiques(cls, prestations):
"""
Renvoie un queryset avec toutes les prestations regroupées par annee/mois
et le total correspondant.
"""
return prestations.annotate(
annee=ExtractYear('date_prestation'),
mois=ExtractMonth('date_prestation')
).values('annee', 'mois').order_by('-annee', '-mois').annotate(total=Sum('duree'))
@classmethod
def temps_total(cls, prestations, tous_utilisateurs=False):
"""
Renvoie le temps total des `prestations` (QuerySet) sous forme de chaîne '12:30'.
Si tous_utilisateurs = True, multiplie le temps de chaque prestation par son
nombre d'intervenants.
"""
if prestations.count() > 0:
if tous_utilisateurs:
duree = prestations.annotate(num_util=Count('intervenants')).aggregate(
total=Sum(F('duree') * F('num_util'), output_field=models.DurationField())
)['total'] or timedelta()
else:
duree = prestations.aggregate(total=Sum('duree'))['total'] or timedelta()
else:
duree = timedelta()
return duree
@classmethod
def temps_total_general(cls, annee, familles=True):
"""
Renvoie le temps total des prestations (familiales ou générales selon
familles) pour l'année civile `annee`.
"""
prest_tot = cls.objects.filter(
famille__isnull=not familles,
date_prestation__year=annee
).aggregate(total=Sum('duree'))['total'] or timedelta()
return prest_tot
@classmethod
def temps_total_general_fam_gen(cls, annee):
"""
Renvoie le temps total des prestations familiales ET générales pour l'année civile `annee`.
"""
prest_tot = cls.objects.filter(
date_prestation__year=annee
).aggregate(total=Sum('duree'))['total'] or timedelta()
return prest_tot
@classmethod
def temps_totaux_mensuels(cls, annee):
"""
Renvoie une liste du total mensuel de toutes les prestations familiales
(sans prestations générales) pour l'année en cours (de janv. à déc.).
"""
data = []
for month in range(1, 13):
tot = cls.objects.filter(
famille__isnull=False,
date_prestation__year=annee, date_prestation__month=month,
).aggregate(total=Sum('duree'))['total'] or timedelta()
data.append(tot)
return data
class JournalAcces(models.Model):
"""Journalisation des accès aux familles."""
famille = models.ForeignKey(Famille, on_delete=models.CASCADE, related_name='+')
utilisateur = models.ForeignKey(Utilisateur, on_delete=models.CASCADE, related_name='+')
ordinaire = models.BooleanField(default=True)
quand = models.DateTimeField(auto_now_add=True)
def __str__(self):
return f"Accès de «{self.utilisateur}» à la famille «{self.famille}», le {self.quand}"
class Etape:
"""
1. Numéro d'ordre pour tri dans la vue
2. Code
3. Affichage dans tableau
4. Nom visible de l'étape
5. Code de l'étape suivante
6. Délai jusqu'à l'échéance (en jours)
7. Code de l'étape précédente
8. Date obligatoire précédente
9. Saisie obligatoire ou non (True / False)
"""
def __init__(self, num, code, abrev, nom, suivante, delai, precedente, preced_oblig, oblig):
self.num = num
self.code = code
self.abrev = abrev
self.nom = nom
self.suivante = suivante
self.delai = delai
self.precedente = precedente
self.preced_oblig = preced_oblig
self.oblig = oblig
def __str__(self):
return self.nom
def __repr__(self):
return '<Etape - {}/{}>'.format(self.num, self.code)
def date(self, suivi):
return getattr(suivi, 'date_{}'.format(self.code))
def delai_depuis(self, suivi, date_base):
"""Délai de cette étape à partir de la date date_base (en général date précédente étape)."""
if not date_base:
return None
return date_base + timedelta(days=self.delai)
def date_nom(self):
return 'date_{}'.format(self.code)
def etape_suivante(self, suivi):
return Suivi.WORKFLOW[self.suivante] if self.suivante else None
def etape_precedente(self):
return Suivi.WORKFLOW[self.precedente] if self.precedente else None
class EtapeDebutSuivi(Etape):
def date(self, suivi):
return suivi.debut_suivi_selon_niveau
class EtapeBilan(Etape):
delai_standard = 3 * 30 # 3 mois
delai_niveau3 = 30 # 1 mois
def date(self, suivi):
"""Date du dernier bilan"""
if self.etape_suivante(suivi).code == 'bilan_suivant':
return None
return suivi.date_dernier_bilan()
def etape_suivante(self, suivi):
# Soit bilan suivant, soit rapport
if suivi.date_dernier_rapport():
return Suivi.WORKFLOW['resume']
date_bilan = suivi.date_dernier_bilan()
prochain_rapport = suivi.date_prochain_rapport()
if date_bilan and prochain_rapport and (prochain_rapport - date_bilan) < timedelta(days=4 * 30):
return Suivi.WORKFLOW['resume']
else:
return Suivi.WORKFLOW['bilan_suivant']
def delai_depuis(self, suivi, date_base):
"""Délai de cette étape à partir de la date date_base (en général date précédente étape)."""
if not date_base:
return None
base = suivi.date_dernier_bilan() or date_base
delai = self.delai_niveau3 if suivi.famille.niveau_actuel() == 3 else self.delai_standard
return base + timedelta(days=delai)
class EtapeResume(Etape):
def date(self, suivi):
"""Date du dernier rapport"""
return suivi.date_dernier_rapport()
class EtapeFin(Etape):
delai_standard = 365 + 180 # 18 mois
delai_niveau3 = 270 # 9 mois
def delai_depuis(self, suivi, *args):
if not suivi.date_debut_suivi:
return None
debut_suivi = suivi.debut_suivi_selon_niveau
delai = self.delai_niveau3 if suivi.famille.niveau_actuel() == 3 else self.delai_standard
return debut_suivi + timedelta(days=delai)
Equipe = namedtuple("Equipe", ['code', 'nom', 'perm'])
EQUIPES = [
Equipe(code='montagnes', nom='Montagnes et V-d-T', perm=None),
Equipe(code='littoral', nom='Littoral et V-d-R', perm=None),
# Anciennes équipes, conservées pour anciens dossiers
Equipe(code='neuch_ville', nom='Neuchâtel-ville (archives)', perm=None),
Equipe(code='litt_est', nom='Littoral Est (archives)', perm=None),
Equipe(code='litt_ouest', nom='Littoral Ouest (archives)', perm=None),
]
class Suivi(models.Model):
WORKFLOW = OrderedDict([
('demande', Etape(
1, 'demande', 'dem', 'Demande déposée', 'debut_evaluation', 0, 'demande', 'demande', True
)),
('debut_evaluation', Etape(
2, 'debut_evaluation', 'deb_eva', "Début de lévaluation", 'fin_evaluation', 40, 'demande', 'demande', True
)),
('fin_evaluation', Etape(
3, 'fin_evaluation', "fin_eva", "Fin de lévaluation", 'debut_suivi', 40,
'debut_evaluation', 'debut_evaluation', True
)),
('debut_suivi', EtapeDebutSuivi(
4, 'debut_suivi', "dsuiv", 'Début du suivi', 'bilan_suivant', 40, 'fin_evaluation', 'fin_evaluation', False
)),
('bilan_suivant', EtapeBilan(
5, 'bilan_suivant', "bil", 'Bilan suivant', 'resume', 90, 'debut_suivi', 'fin_evaluation', False
)),
('resume', EtapeResume(
6, 'resume', 'rés', 'Résumé', 'fin_suivi', 90, 'bilan_suivant', 'fin_evaluation', False
)),
('fin_suivi', EtapeFin(
7, 'fin_suivi', "fsuiv", 'Fin du suivi', 'archivage', 365 + 180, 'resume', 'fin_evaluation', True
)),
('archivage', Etape(
8, 'arch_dossier', 'arch', 'Archivage du dossier', None, 0, 'fin_suivi', 'fin_suivi', False
)),
])
EQUIPES_CHOICES = [(equ.code, equ.nom) for equ in EQUIPES]
famille = models.OneToOneField(Famille, on_delete=models.CASCADE)
equipe = models.CharField('Équipe', max_length=15, choices=EQUIPES_CHOICES)
heure_coord = models.BooleanField("Heure de coordination", default=False)
difficultes = models.TextField("Difficultés", blank=True)
aides = models.TextField("Aides souhaitées", blank=True)
competences = models.TextField('Ressources/Compétences', blank=True)
dates_demande = models.CharField('Dates', max_length=128, blank=True)
autres_contacts = models.TextField("Autres services contactés", blank=True)
disponibilites = models.TextField('Disponibilités', blank=True)
remarque = models.TextField(blank=True)
remarque_privee = models.TextField('Remarque privée', blank=True)
service_orienteur = models.CharField(
"Orienté vers lAEMO par", max_length=15, choices=choices.SERVICE_ORIENTEUR_CHOICES, blank=True
)
service_annonceur = models.CharField('Service annonceur', max_length=60, blank=True)
motif_demande = ChoiceArrayField(
models.CharField(max_length=60, choices=choices.MOTIF_DEMANDE_CHOICES),
verbose_name="Motif de la demande", blank=True, null=True)
motif_detail = models.TextField('Motif', blank=True)
# Référents
intervenants = models.ManyToManyField(
Utilisateur, through='Intervenant', related_name='interventions', blank=True
)
ope_referent = models.ForeignKey(Contact, blank=True, null=True, related_name='+',
on_delete=models.SET_NULL, verbose_name='as. OPE')
ope_referent_2 = models.ForeignKey(
Contact, blank=True, null=True, related_name='+', on_delete=models.SET_NULL,
verbose_name='as. OPE 2'
)
mandat_ope = ChoiceArrayField(
models.CharField(max_length=65, choices=choices.MANDATS_OPE_CHOICES, blank=True),
verbose_name="Mandat OPE", blank=True, null=True,
)
sse_referent = models.ForeignKey(Contact, blank=True, null=True, related_name='+',
on_delete=models.SET_NULL, verbose_name='SSE')
referent_note = models.TextField('Autres contacts', blank=True)
collaboration = models.TextField('Collaboration', blank=True)
ressource = models.TextField('Ressource', blank=True)
crise = models.TextField('Gestion de crise', blank=True)
date_demande = models.DateField("Demande déposée le", blank=True, null=True, default=None)
date_debut_evaluation = models.DateField("Début de lévaluation le", blank=True, null=True, default=None)
date_fin_evaluation = models.DateField("Fin de lévaluation le", blank=True, null=True, default=None)
date_debut_suivi = models.DateField("Début du suivi le", blank=True, null=True, default=None)
date_fin_suivi = models.DateField("Fin du suivi le", blank=True, null=True, default=None)
demande_prioritaire = models.BooleanField("Demande prioritaire", default=False)
demarche = ChoiceArrayField(models.CharField(max_length=60, choices=choices.DEMARCHE_CHOICES, blank=True),
verbose_name="Démarche", blank=True, null=True)
pers_famille_presentes = models.CharField('Membres famille présents', max_length=200, blank=True)
ref_presents = models.CharField('Intervenants présents', max_length=250, blank=True)
autres_pers_presentes = models.CharField('Autres pers. présentes', max_length=100, blank=True)
motif_fin_suivi = models.CharField('Motif de fin de suivi', max_length=20,
choices=choices.MOTIFS_FIN_SUIVI_CHOICES, blank=True)
def __str__(self):
return 'Suivi pour la famille {} '.format(self.famille)
@property
def date_fin_theorique(self):
if self.date_fin_suivi:
return self.date_fin_suivi
if self.date_debut_suivi is None:
return None
return self.debut_suivi_selon_niveau + timedelta(days=548) # env. 18 mois
@cached_property
def debut_suivi_selon_niveau(self):
"""
Date de début de suivi tenant compte d'éventuel changement de niveau entre 2 et 3.
"""
debut = self.date_debut_suivi
if debut is None:
return debut
niv_prec = None
for niv in sorted(self.famille.niveaux.all(), key=lambda niv: niv.date_debut):
if (
(niv_prec in [1, 2] and niv.niveau_interv == 3) or
(niv_prec == 3 and niv.niveau_interv in [1, 2])
):
debut = niv.date_debut
niv_prec = niv.niveau_interv
return debut
def date_prochain_rapport(self):
if self.date_debut_suivi is None or self.date_fin_suivi is not None:
return None
date_dernier_rapport = self.date_dernier_rapport()
niveau = self.famille.niveau_actuel()
if date_dernier_rapport is None:
# Premier à 9 ou 18 mois, selon niveau
delai = 30 * 9 if niveau == 3 else 365 + 182
return self.date_debut_suivi + timedelta(days=delai)
else:
# Suivants tous les 9 ou 12 mois, selon niveau
delai = 30 * 9 if niveau == 3 else 365
return date_dernier_rapport + timedelta(days=delai)
def date_dernier_rapport(self):
# Using rapports.all() to leverage prefetchs
debut_suivi = self.debut_suivi_selon_niveau
return max([
rapp.date for rapp in self.famille.rapports.all() if rapp.date > debut_suivi
], default=None)
def date_dernier_bilan(self):
debut_suivi = self.debut_suivi_selon_niveau
# Using bilans.all() to leverage prefetchs
return max([
bilan.date for bilan in self.famille.bilans.all() if bilan.date > debut_suivi
], default=None)
@property
def ope_referent_display(self):
return self.ope_referent.nom_prenom if self.ope_referent else '-'
@cached_property
def etape(self):
"""Létape *terminée* du suivi."""
for key, etape in reversed(self.WORKFLOW.items()):
if key != 'archivage':
if etape.date(self):
return etape
@cached_property
def etape_suivante(self):
return self.etape.etape_suivante(self) if self.etape else None
def date_suivante(self):
etape_date = self.etape.date(self)
if not self.etape_suivante or not etape_date:
return None
return self.etape_suivante.delai_depuis(self, etape_date)
def get_mandat_ope_display(self):
dic = dict(choices.MANDATS_OPE_CHOICES)
return '; '.join([dic[value] for value in self.mandat_ope]) if self.mandat_ope else '-'
def get_motif_demande_display(self):
dic = dict(choices.MOTIF_DEMANDE_CHOICES)
return '; '.join([dic[value] for value in self.motif_demande]) if self.motif_demande else ''
@property
def ope_referents(self):
return [ope for ope in [self.ope_referent, self.ope_referent_2] if ope]
class IntervenantManager(models.Manager):
def actifs(self, _date=None):
return self.filter(Q(date_fin__isnull=True) | Q(date_fin__gt=_date or date.today()))
class Intervenant(models.Model):
"""
Modèle M2M entre Suivi et Utilisateur (utilisé par through de Suivi.intervenants).
"""
suivi = models.ForeignKey(Suivi, on_delete=models.CASCADE)
intervenant = models.ForeignKey(Utilisateur, on_delete=models.CASCADE)
role = models.ForeignKey(Role, on_delete=models.CASCADE)
date_debut = models.DateField('Date début', default=timezone.now)
# date_fin est utilisé pour les interventions s'arrêtant avant la fin du suivi.
date_fin = models.DateField('Date fin', null=True, blank=True)
objects = IntervenantManager()
def __str__(self):
return '{}, {} pour {}'.format(self.intervenant, self.role, self.suivi)
def can_edit(self, user):
return self.suivi.famille.can_edit(user)