aemo_fr/aemo/views.py

1674 lines
59 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.

import io
import calendar
import logging
from collections import OrderedDict
from datetime import date, timedelta
from operator import attrgetter
from dal import autocomplete
from two_factor.views import SetupView as TwoFactSetupView
from django.contrib import messages
from django.contrib.auth.mixins import PermissionRequiredMixin
from django.contrib.auth.models import Group, Permission
from django.contrib.auth.views import PasswordChangeView as AuthPasswordChangeView
from django.contrib.messages.views import SuccessMessageMixin
from django.contrib.postgres.aggregates import ArrayAgg
from django.core.exceptions import PermissionDenied
from django.db.models import (
Case, Count, DurationField, F, OuterRef, Prefetch, Q, Subquery, Sum, Value, When
)
from django.db.models.deletion import ProtectedError
from django.db.models.functions import Coalesce
from django.forms import HiddenInput, modelform_factory
from django.http import FileResponse, HttpResponseRedirect, JsonResponse
from django.shortcuts import get_object_or_404, render
from django.urls import reverse, reverse_lazy
from django.utils.crypto import get_random_string
from django.views.defaults import page_not_found as django_page_not_found
from django.views.generic import (
CreateView, DeleteView as DjangoDeleteView, DetailView, FormView, ListView, TemplateView,
UpdateView, View
)
from .export import ExportReporting
from . import forms
from .models import (
Bilan, CercleScolaire, Contact, Document, Famille, Formation, Intervenant, JournalAcces,
LibellePrestation, Niveau, Personne, Prestation, Rapport, Role, Service, Suivi,
Utilisateur
)
from .pdf import BilanPdf, CoordonneesFamillePdf, EvaluationPdf, JournalPdf, RapportPdf
from .utils import format_d_m_Y, is_ajax
logger = logging.getLogger('django')
MSG_READ_ONLY = "Vous n'avez pas les droits nécessaires pour modifier cette page"
MSG_ACCESS_DENIED = "Vous navez pas la permission daccéder à cette page."
class CreateUpdateView(UpdateView):
"""Mix generic Create and Update views."""
is_create = False
def get_object(self):
return None if self.is_create else super().get_object()
class DeleteView(DjangoDeleteView):
"""
Nous ne suivons pas la méthode Django d'afficher une page de confirmation
avant de supprimer un objet, mais nous avertissons avec un message JS avant
de POSTer directement la suppression. Pour cela, nous autorisons uniquement
la méthode POST.
"""
http_method_names = ['post']
class JournalAccesMixin:
"""
Classe Mixin pour journaliser les accès aux familles.
"""
def get(self, *args, **kwargs):
acces_ordinaire = self.famille.access_ok(self.request.user)
if not acces_ordinaire and not self.request.GET.get('confirm') == '1':
return render(
self.request, 'aemo/acces_famille.html',
{'url': self.request.path, 'famille': self.famille}
)
JournalAcces.objects.create(
famille=self.famille,
utilisateur=self.request.user,
ordinaire=acces_ordinaire
)
return super().get(*args, **kwargs)
class BasePDFView(View):
obj_class = None
pdf_class = None
produce_kwargs = {}
def get_object(self):
return get_object_or_404(self.obj_class, pk=self.kwargs[getattr(self, 'pk_url_kwarg', 'pk')])
def get(self, request, *args, **kwargs):
instance = self.get_object()
temp = io.BytesIO()
pdf = self.pdf_class(temp, instance, **self.produce_kwargs)
pdf.produce()
filename = pdf.get_filename()
temp.seek(0)
return FileResponse(temp, as_attachment=True, filename=filename)
class EquipeRequiredMixin:
def dispatch(self, request, *args, **kwargs):
self.famille = get_object_or_404(Famille, pk=kwargs['pk'])
self.check_access(request)
return super().dispatch(request, *args, **kwargs)
def check_access(self, request):
if not self.famille.can_view(request.user):
raise PermissionDenied(MSG_ACCESS_DENIED)
self.readonly = not self.famille.can_edit(request.user)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
if self.readonly:
messages.info(self.request, MSG_READ_ONLY)
context['can_edit'] = not self.readonly
context['famille'] = self.famille
return context
class CheckCanEditMixin:
def dispatch(self, request, *args, **kwargs):
if not self.get_object().can_edit(request.user):
raise PermissionDenied(MSG_ACCESS_DENIED)
return super().dispatch(request, *args, **kwargs)
class HomeView(TemplateView):
template_name = 'index.html'
class SetupView(TwoFactSetupView):
def get(self, request, *args, **kwargs):
# The original view is short-circuiting to complete is a device exists.
# We want to allow adding a second device if needed.
return super(TwoFactSetupView, self).get(request, *args, **kwargs)
class PasswordChangeView(AuthPasswordChangeView):
success_url = reverse_lazy('home')
def form_valid(self, form):
response = super().form_valid(form)
messages.success(self.request, "Votre mot de passe a bien été modifié.")
return response
class ContactCreateView(CreateView):
template_name = 'aemo/contact_edit.html'
model = Contact
form_class = forms.ContactForm
success_url = reverse_lazy('contact-list')
action = 'Création'
def form_valid(self, form):
contact = form.save()
for_pers = self.request.GET.get('forpers')
if for_pers:
pers = get_object_or_404(Personne, pk=for_pers)
pers.reseaux.add(contact)
return HttpResponseRedirect(reverse('personne-reseau-list', args=[pers.pk]))
return HttpResponseRedirect(self.success_url)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context.update({
'role_form': forms.RoleForm(),
'service_form': forms.ServiceForm(),
})
return context
class ContactListView(ListView):
template_name = 'aemo/contact_list.html'
model = Contact
paginate_by = 20
def get(self, request, *args, **kwargs):
self.filter_form = forms.ContactFilterForm(data=self.request.GET or None)
return super().get(request, *args, **kwargs)
def get_queryset(self):
contacts = Contact.objects.filter(est_actif=True).exclude(utilisateur__isnull=False).prefetch_related('roles')
if self.filter_form.is_bound and self.filter_form.is_valid():
contacts = self.filter_form.filter(contacts)
return contacts
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context.update({
'form': self.filter_form
})
return context
class ContactUpdateView(PermissionRequiredMixin, SuccessMessageMixin, UpdateView):
permission_required = 'aemo.change_contact'
template_name = 'aemo/contact_edit.html'
model = Contact
form_class = forms.ContactForm
success_url = reverse_lazy('contact-list')
success_message = 'Le contact %(nom)s %(prenom)s a bien été modifié'
action = 'Modification'
def delete_url(self):
return reverse('contact-delete', args=[self.object.pk])
class ContactDeleteView(PermissionRequiredMixin, DeleteView):
permission_required = 'aemo.delete_contact'
model = Contact
success_url = reverse_lazy('contact-list')
def form_valid(self, form):
self.object.est_actif = False
self.object.save()
messages.success(self.request, "Le contact %s a bien été archivé." % self.object)
return HttpResponseRedirect(self.success_url)
class ContactAutocompleteView(autocomplete.Select2QuerySetView):
ope = False
def get_queryset(self):
qs = Contact.membres_ope() if self.ope else Contact.objects.filter(est_actif=True)
if self.q:
qs = qs.filter(nom__istartswith=self.q)
return qs
class ContactExterneAutocompleteView(autocomplete.Select2QuerySetView):
def get_queryset(self):
qs = Contact.objects.\
filter(est_actif=True).\
exclude(service__sigle__startswith='OPE').\
exclude(service__sigle='CRNE')
if self.q:
qs = qs.filter(nom__istartswith=self.q)
return qs
class ContactTestDoublon(View):
def post(self, request, *args, **kwargs):
nom = request.POST.get('nom')
prenom = request.POST.get('prenom')
contacts = Contact.objects.filter(nom=nom, prenom=prenom)
data = ''
if contacts.exists():
data = [{'nom': c.nom, 'prenom': c.prenom} for c in contacts]
return JsonResponse(data, safe=False)
class ServiceCreateView(PermissionRequiredMixin, CreateView):
permission_required = 'aemo.add_service'
template_name = 'aemo/service_edit.html'
model = Service
form_class = forms.ServiceForm
success_url = reverse_lazy('service-list')
action = 'Création'
def form_valid(self, form):
if is_ajax(self.request):
service = form.save()
return JsonResponse({'pk': service.pk, 'sigle': service.sigle})
return super().form_valid(form)
def form_invalid(self, form):
if is_ajax(self.request):
return JsonResponse({'error': form.errors.as_text()})
return super().form_invalid(form)
class ServiceListView(ListView):
template_name = 'aemo/service_list.html'
model = Service
def get_queryset(self):
return Service.objects.exclude(sigle='FAMILLE')
class ServiceUpdateView(PermissionRequiredMixin, UpdateView):
permission_required = 'aemo.change_service'
template_name = 'aemo/service_edit.html'
model = Service
form_class = forms.ServiceForm
success_url = reverse_lazy('service-list')
action = 'Modification'
def delete_url(self):
return reverse('service-delete', args=[self.object.pk])
class ServiceDeleteView(PermissionRequiredMixin, DeleteView):
permission_required = 'aemo.delete_service'
model = Service
success_url = reverse_lazy('service-list')
class FormationView(SuccessMessageMixin, UpdateView):
template_name = 'aemo/formation_edit.html'
model = Formation
form_class = forms.FormationForm
success_message = 'Les modifications ont été enregistrées avec succès'
def get_object(self, queryset=None):
personne = get_object_or_404(Personne, pk=self.kwargs['pk'])
self.famille = get_object_or_404(Famille, pk=personne.famille_id)
return personne.formation
def get_form_kwargs(self):
return {
**super().get_form_kwargs(),
'readonly': not self.famille.can_edit(self.request.user)
}
def get_success_url(self):
return self.famille.edit_url
def get_context_data(self, **kwargs):
return {**super().get_context_data(**kwargs), 'famille': self.famille}
class PersonneBaseMixin:
template_name = 'aemo/personne_edit.html'
model = Personne
form_class = forms.PersonneForm
require_edit = False
def dispatch(self, request, *args, **kwargs):
self.famille = get_object_or_404(Famille, pk=kwargs['pk'])
if not self.famille.can_view(request.user):
raise PermissionDenied("Vous navez pas la permission daccéder à cette page.")
elif self.require_edit and not self.famille.can_edit(request.user):
raise PermissionDenied("Vous navez pas la permission daccéder à cette page.")
return super().dispatch(request, *args, **kwargs)
def get_context_data(self, **kwargs):
context = {**super().get_context_data(**kwargs), 'famille': self.famille}
if self.object and self.object.role.nom == 'Enfant suivi':
context['enfant'] = self.object
return context
def get_success_url(self):
return self.famille.edit_url
class PersonneCreateView(PersonneBaseMixin, CreateView):
action = 'Membre de la famille '
require_edit = True
def get_form_kwargs(self):
kwargs = super().get_form_kwargs()
kwargs['famille'] = self.famille
kwargs['role'] = self.request.GET.get('role', None)
return kwargs
def form_valid(self, form):
response = super().form_valid(form)
if self.object.famille.archived_at:
self.object.famille.archived_at = None
self.object.famille.save()
messages.info(self.request, "La famille a été sortie des archives")
return response
def get_success_url(self):
return self.famille.redirect_after_personne_creation(self.object)
class PersonneUpdateView(SuccessMessageMixin, PersonneBaseMixin, UpdateView):
pk_url_kwarg = 'obj_pk'
action = 'Modification'
success_message = 'Les modifications ont été enregistrées avec succès'
def get_form_kwargs(self):
return {**super().get_form_kwargs(), 'readonly': not self.famille.can_edit(self.request.user)}
def delete_url(self):
return reverse('personne-delete', args=[self.famille.pk, self.object.pk])
class PersonneDeleteView(PersonneBaseMixin, DeleteView):
require_edit = True
form_class = DeleteView.form_class
def get_object(self, *args, **kwargs):
pers = get_object_or_404(Personne, pk=self.kwargs['obj_pk'])
if pers.role.nom == "Enfant suivi":
raise PermissionDenied(
"Un enfant suivi ne peut pas être directement supprimé. Si cest "
"vraiment ce que vous voulez, mettez-le dabord comme Enfant non suivi."
)
return pers
class PersonneReseauView(DetailView):
template_name = 'aemo/personne_reseau_list.html'
model = Personne
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
famille = get_object_or_404(Famille, pk=self.object.famille_id)
context.update({
'famille': famille,
'form': forms.ContactExterneAutocompleteForm(),
'reseau': list(self.object.reseaux.all().order_by('nom')),
})
try:
pediatre = self.object.suivienfant.pediatre
except AttributeError:
pass
else:
if pediatre:
context['reseau'].append(pediatre)
return context
class PersonneReseauAdd(View):
def post(self, request, *args, **kwargs):
pers = get_object_or_404(Personne, pk=kwargs['pk'])
obj_pk = request.POST.getlist('contacts[]')
for obj in obj_pk:
contact = get_object_or_404(Contact, pk=obj)
pers.reseaux.add(contact)
return JsonResponse({'is_valid': True})
class PersonneReseauRemove(View):
def post(self, request, *args, **kwargs):
pers = get_object_or_404(Personne, pk=kwargs['pk'])
contact = get_object_or_404(Contact, pk=kwargs['obj_pk'])
pers.reseaux.remove(contact)
return HttpResponseRedirect(reverse('personne-reseau-list', args=[pers.pk]))
class UtilisateurListView(ListView):
template_name = 'aemo/utilisateur_list.html'
model = Utilisateur
is_active = True
paginate_by = 50
def get_queryset(self):
return Utilisateur.objects.filter(date_desactivation__isnull=self.is_active)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context.update({
'active_users': self.is_active,
})
return context
class UtilisateurReactivateView(PermissionRequiredMixin, View):
permission_required = 'aemo.delete_utilisateur'
def post(self, request, *args, **kwargs):
utilisateur = get_object_or_404(Utilisateur, pk=self.kwargs['pk'])
utilisateur.is_active = True
utilisateur.est_actif = True
utilisateur.date_desactivation = None
utilisateur.save()
return HttpResponseRedirect(reverse('utilisateur-list'))
class UtilisateurCreateView(SuccessMessageMixin, PermissionRequiredMixin, CreateView):
permission_required = 'aemo.add_utilisateur'
template_name = 'aemo/utilisateur_edit.html'
model = Utilisateur
form_class = forms.UtilisateurForm
success_url = reverse_lazy('utilisateur-list')
success_message = "Lutilisateur «%(username)s» a été créé avec le mot de passe «%(password)s»."
def form_valid(self, form):
form.instance.first_name = form.cleaned_data['prenom']
form.instance.last_name = form.cleaned_data['nom']
form.instance.service, _ = Service.objects.get_or_create(sigle='CRNE', defaults={'sigle': 'CRNE'})
pwd = get_random_string(length=10)
form.instance.set_password(pwd)
form.cleaned_data['password'] = pwd # for success message
return super().form_valid(form)
class UtilisateurUpdateView(PermissionRequiredMixin, UpdateView):
permission_required = 'aemo.change_utilisateur'
template_name = 'aemo/utilisateur_edit.html'
model = Utilisateur
form_class = forms.UtilisateurForm
success_url = reverse_lazy('utilisateur-list')
def delete_url(self):
return reverse('utilisateur-delete', args=[self.object.pk])
class UtilisateurPasswordReinitView(PermissionRequiredMixin, View):
permission_required = 'aemo.change_utilisateur'
def post(self, request, *args, **kwargs):
utilisateur = get_object_or_404(Utilisateur, pk=self.kwargs['pk'])
pwd = get_random_string(length=10)
utilisateur.set_password(pwd)
utilisateur.save()
messages.success(request, 'Le nouveau mot de passe de «%s» est «%s».' % (utilisateur, pwd))
return HttpResponseRedirect(reverse('utilisateur-edit', kwargs=self.kwargs))
class UtilisateurOtpDeviceReinitView(PermissionRequiredMixin, View):
permission_required = 'aemo.change_utilisateur'
def post(self, request, *args, **kwargs):
utilisateur = get_object_or_404(Utilisateur, pk=self.kwargs['pk'])
if utilisateur.totpdevice_set.exists():
utilisateur.totpdevice_set.all().delete()
messages.success(request, 'Le mobile de «%s» a été réinitialisé.' % utilisateur)
else:
messages.error(request, 'Aucune configuration mobile trouvée pour «%s»' % utilisateur)
return HttpResponseRedirect(reverse('utilisateur-edit', kwargs=self.kwargs))
class UtilisateurJournalAccesView(PermissionRequiredMixin, ListView):
permission_required = 'aemo.change_utilisateur'
template_name = 'aemo/utilisateur_journal.html'
paginate_by = 50
model = JournalAcces
def get_queryset(self):
return self.model.objects.filter(utilisateur_id=self.kwargs['pk']).order_by('-quand')
def get_context_data(self, **kwargs):
return {
**super().get_context_data(**kwargs),
'utilisateur': get_object_or_404(Utilisateur, pk=self.kwargs['pk']),
}
class UtilisateurChargeDossierView(PermissionRequiredMixin, TemplateView):
template_name = 'aemo/charge_utilisateurs.html'
permission_required = 'aemo.change_utilisateur'
def get_queryset(self):
self.filter_form = forms.EquipeFilterForm(data=self.request.GET or None)
qs_utilisateurs = Utilisateur.objects.filter(
roles__nom__in=['Educ', 'Psy'], date_desactivation__isnull=True
).exclude(
roles__nom__in=['Responsable/coordinateur']
).order_by('nom', 'prenom').distinct()
if self.filter_form.is_bound and self.filter_form.is_valid():
qs_utilisateurs = qs_utilisateurs.filter(equipe=self.filter_form.cleaned_data['equipe'])
# cf requete similaire dans FamilleListView
intervs = Intervenant.objects.filter(
Q(suivi__date_fin_suivi__isnull=True) & (
Q(date_fin__isnull=True) | Q(date_fin__gt=date.today())
)
).values('intervenant').annotate(
familles=ArrayAgg(
"suivi__famille_id",
distinct=True
),
nbre_coord=Count(
'id', filter=Q(suivi__heure_coord=True),
distinct=True
),
nbre_eval=Count('id', filter=(
Q(suivi__date_debut_suivi__isnull=True)
), distinct=True),
nbre_suivi=Count('id', filter=(
Q(suivi__date_debut_suivi__isnull=False)
), distinct=True),
).order_by('intervenant__id').values(
'intervenant__id', 'familles', 'nbre_coord', 'nbre_eval', 'nbre_suivi'
)
intervs_dict = {line['intervenant__id']: line for line in intervs}
familles = dict(Famille.objects.filter(
suivi__date_fin_suivi__isnull=True
).with_niveau_interv().values_list('pk', 'niveau_interv'))
utilisateurs = []
charge_map = {None: 0, 0: 0, 1: 1, 2: 2, 3: 5}
for util in qs_utilisateurs:
for key in 'familles', 'nbre_coord', 'nbre_eval', 'nbre_suivi':
setattr(util, key, intervs_dict.get(util.pk, {}).get(key, [] if key == 'familles' else 0))
util.charge = sum([charge_map[familles[fam_pk]] for fam_pk in util.familles])
if util.taux_activite == 0 and util.charge is None:
continue
util.heures = util.charge + util.nbre_coord if util.charge is not None else 0
util.charge_diff = util.charge_max - util.heures
utilisateurs.append(util)
return utilisateurs
def get_context_data(self, **kwargs):
return {
**super().get_context_data(**kwargs),
'utilisateurs': self.get_queryset(),
'filter_form': self.filter_form,
}
class UtilisateurDeleteView(PermissionRequiredMixin, DeleteView):
"""Archive, ne supprime pas réellement."""
permission_required = 'aemo.change_utilisateur'
model = Utilisateur
success_url = reverse_lazy('utilisateur-list')
def form_valid(self, form):
self.object.is_active = False # C'est ce flag qui empêche la connexion au système
self.object.est_actif = False
self.object.date_desactivation = date.today()
self.object.save()
return HttpResponseRedirect(self.success_url)
class UtilisateurAutocompleteView(autocomplete.Select2QuerySetView):
def get_queryset(self):
qs = Utilisateur.objects.filter(service__sigle='CRNE', date_desactivation__isnull=True)
if self.q:
qs = qs.filter(nom__istartswith=self.q)
return qs
class CercleScolaireListView(ListView):
template_name = 'aemo/cercle_scolaire_list.html'
model = CercleScolaire
class CercleScolaireUpdateView(PermissionRequiredMixin, UpdateView):
permission_required = 'aemo.change_cerclescolaire'
template_name = 'aemo/cercle_scolaire_edit.html'
model = CercleScolaire
fields = '__all__'
success_url = reverse_lazy('cercle-list')
def delete_url(self):
return reverse('cercle-delete', args=[self.object.pk])
class CercleScolaireCreateView(PermissionRequiredMixin, CreateView):
permission_required = 'aemo.add_cerclescolaire'
template_name = 'aemo/cercle_scolaire_edit.html'
model = CercleScolaire
fields = '__all__'
success_url = reverse_lazy('cercle-list')
class CercleScolaireDeleteView(PermissionRequiredMixin, DeleteView):
permission_required = 'aemo.delete_cerclescolaire'
model = CercleScolaire
success_url = reverse_lazy('cercle-list')
class RoleListView(ListView):
template_name = 'aemo/role_list.html'
model = Role
def get_context_data(self, **kwargs):
return {
**super().get_context_data(**kwargs),
'editeur_help': self.model._meta.get_field('est_editeur').help_text,
}
class RoleUpdateView(PermissionRequiredMixin, UpdateView):
permission_required = 'aemo.change_role'
template_name = 'aemo/role_edit.html'
model = Role
fields = '__all__'
success_url = reverse_lazy('role-list')
def delete_url(self):
if self.object.personne_set.count() == 0:
return reverse('role-delete', args=[self.object.pk])
class RoleCreateView(PermissionRequiredMixin, CreateView):
permission_required = 'aemo.add_role'
template_name = 'aemo/role_edit.html'
model = Role
fields = '__all__'
success_url = reverse_lazy('role-list')
def form_valid(self, form):
if is_ajax(self.request):
role = form.save()
return JsonResponse({'pk': role.pk, 'nom': role.nom})
return super().form_valid(form)
def form_invalid(self, form):
if is_ajax(self.request):
return JsonResponse({'error': form.errors.as_text()})
return super().form_invalid(form)
class RoleDeleteView(PermissionRequiredMixin, DeleteView):
permission_required = 'aemo.delete_role'
model = Role
success_url = reverse_lazy('role-list')
def form_valid(self, form):
try:
return super().form_valid(form)
except ProtectedError as e:
# TODO: Il y a certainement mieux...
messages.add_message(self.request, messages.ERROR, str(e), "Suppression impossible")
return HttpResponseRedirect(reverse('role-list'))
class FamilleCreateView(CreateView):
template_name = 'aemo/famille_edit.html'
model = Famille
form_class = forms.FamilleCreateForm
action = 'Nouvelle famille'
def dispatch(self, request, *args, **kwargs):
if not request.user.has_perm('aemo.add_famille'):
raise PermissionDenied(MSG_ACCESS_DENIED)
return super().dispatch(request, *args, **kwargs)
def form_valid(self, form):
famille = form.save()
return HttpResponseRedirect(reverse('famille-edit', args=[famille.pk]))
class FamilleUpdateView(EquipeRequiredMixin, SuccessMessageMixin, UpdateView):
template_name = 'aemo/famille_edit.html'
model = Famille
famille_model = Famille
form_class = forms.FamilleForm
context_object_name = 'famille'
title = 'Modification'
archive_url = None
success_message = 'La famille %(nom)s a bien été modifiée.'
def get_success_url(self):
return self.success_url or reverse('famille-edit', args=[self.object.pk])
def get_form_kwargs(self):
return {**super().get_form_kwargs(), 'readonly': self.readonly}
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context.update({
'enfant_suivi': Role.objects.get(nom='Enfant suivi'),
'enfant_non_suivi': Role.objects.get(nom='Enfant non-suivi'),
})
return context
class FamilleListView(ListView):
template_name = 'aemo/famille_list.html'
model = Famille
paginate_by = 20
mode = 'normal'
normal_cols = [
('Nom', 'nom'), ('Adresse', 'localite'), ('Réf. AEMO', 'referents'),
('Réf. OPE', 'referents_ope'), ('Statut', 'suivi'), ('Prior.', 'prioritaire'),
('Niveau', 'niveau_interv'),
]
attente_cols = [
('Nom', 'nom'), ('Adresse', 'localite'), ('Région', 'region'),
('Réf. AEMO', 'referents'), ('Réf. OPE', 'referents_ope'), ('Prior.', 'prioritaire'),
('Demande', 'date_demande'), ('Évaluation', 'evaluation'),
]
def get(self, request, *args, **kwargs):
if self.mode == 'attente':
self.paginate_by = None
form_class = forms.EquipeFilterForm
else:
form_class = forms.FamilleFilterForm
self.filter_form = form_class(data=self.request.GET or None)
return super().get(request, *args, **kwargs)
def get_queryset(self):
familles = super().get_queryset().with_niveau_interv().filter(
suivi__isnull=False, suivi__date_fin_suivi__isnull=True
).select_related(
'suivi__ope_referent', 'suivi__ope_referent_2'
).prefetch_related(
Prefetch(
'suivi__intervenant_set',
queryset=Intervenant.objects.actifs().select_related('intervenant', 'role')
),
Prefetch(
'niveaux', queryset=Niveau.objects.order_by('-date_debut')
),
'suivi__intervenants', 'bilans', 'rapports'
).order_by('nom', 'npa')
if self.mode == 'attente':
familles = familles.filter(
suivi__date_debut_suivi__isnull=True
).order_by(
'-suivi__demande_prioritaire', 'suivi__date_demande',
)
if self.filter_form.is_bound and self.filter_form.is_valid():
familles = self.filter_form.filter(familles)
return familles
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
if self.mode == 'attente':
cols = self.attente_cols
else:
cols = self.normal_cols
# cf requete similaire dans UtilisateurChargeDossierView
current_user = self.request.user
ma_charge = current_user.interventions.filter(
Q(intervenant__date_fin__isnull=True) | Q(intervenant__date_fin__gt=date.today())
).annotate(
niveau_interv=Subquery(
Niveau.objects.filter(
famille=OuterRef('famille_id')
).order_by('-date_debut').values('niveau_interv')[:1]
),
).aggregate(
charge=Sum(Case(
When(niveau_interv__lt=3, then='niveau_interv'),
When(niveau_interv=3, then=5),
), filter=Q(date_fin_suivi__isnull=True)),
nbre_coord=Count('heure_coord', filter=Q(heure_coord=True, date_fin_suivi__isnull=True)),
nbre_eval=Count('id', filter=(
Q(date_fin_suivi__isnull=True) & Q(date_debut_suivi__isnull=True)
)),
nbre_suivi=Count('id', filter=(
Q(date_fin_suivi__isnull=True) & Q(date_debut_suivi__isnull=False)
)),
)
context.update({
'labels': [c[0] for c in cols],
'col_keys': [c[1] for c in cols],
'form': self.filter_form,
'ma_charge': {
'heures': ma_charge['charge'] + ma_charge['nbre_coord'],
'nbre_eval': ma_charge['nbre_eval'],
'nbre_suivi': ma_charge['nbre_suivi'],
'charge_diff': current_user.charge_max - (ma_charge['charge'] + ma_charge['nbre_coord']),
} if ma_charge['charge'] is not None else '',
})
return context
class FamilleArchivableListe(View):
"""Return all family ids which are archivable by the current user."""
def get(self, request, *args, **kwargs):
data = [
famille.pk for famille in Famille.objects.filter(
archived_at__isnull=True, suivi__date_fin_suivi__isnull=False
)
if famille.can_be_archived(request.user)
]
return JsonResponse(data, safe=False)
class SuiviView(EquipeRequiredMixin, SuccessMessageMixin, JournalAccesMixin, UpdateView):
template_name = 'aemo/suivi_edit.html'
model = Suivi
famille_model = Famille
form_class = forms.SuiviForm
success_message = 'Les modifications ont été enregistrées avec succès'
def get_object(self, queryset=None):
return get_object_or_404(self.model, famille__pk=self.kwargs['pk'])
def get_form_kwargs(self):
return {**super().get_form_kwargs(), 'readonly': self.readonly}
def get_success_url(self):
return self.object.famille.suivi_url
def get_context_data(self, **kwargs):
return {
**super().get_context_data(**kwargs),
'famille': self.object.famille,
'intervenants': self.object.famille.interventions_actives().order_by('role'),
'niveaux': self.object.famille.niveaux.all().annotate(
date_fin_calc=Coalesce('date_fin', self.object.date_fin_suivi)
).order_by('pk'),
}
class SuiviIntervenantCreate(EquipeRequiredMixin, CreateView):
model = Intervenant
form_class = forms.IntervenantForm
template_name = 'aemo/form_in_popup.html'
titre_page = "Ajout dun intervenant"
titre_formulaire = "Intervenant"
def form_valid(self, form):
form.instance.suivi = Suivi.objects.get(famille__pk=self.kwargs['pk'])
return super().form_valid(form)
def get_success_url(self):
return reverse('famille-suivi', args=[self.kwargs['pk']])
class SuiviIntervenantUpdateView(EquipeRequiredMixin, UpdateView):
model = Intervenant
form_class = forms.IntervenantEditForm
template_name = 'aemo/form_in_popup.html'
titre_page = "Modification dune intervention"
titre_formulaire = "Intervenant"
pk_url_kwarg = 'obj_pk'
def get_success_url(self):
return reverse('famille-suivi', args=[self.kwargs['pk']])
class SuivisTerminesListView(FamilleListView):
template_name = 'aemo/suivis_termines_list.html'
def get_queryset(self):
familles = self.model.objects.filter(**{
'suivi__date_fin_suivi__isnull': False,
'archived_at__isnull': True
}).select_related('suivi')
if self.filter_form.is_bound and self.filter_form.is_valid():
familles = self.filter_form.filter(familles)
return familles
class AgendaSuiviView(EquipeRequiredMixin, SuccessMessageMixin, UpdateView):
template_name = 'aemo/agenda_suivi.html'
model = Suivi
famille_model = Famille
form_class = forms.AgendaForm
success_message = 'Les modifications ont été enregistrées avec succès'
def get_object(self, queryset=None):
return get_object_or_404(self.model, famille__pk=self.kwargs['pk'])
def get_form_kwargs(self):
return {**super().get_form_kwargs(),
'readonly': self.readonly,
'destination': self.get_object().famille.destination,
'request': self.request}
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
dfin = self.object.date_fin_suivi
ddebut = self.object.date_debut_suivi
context.update({
'bilans_et_rapports': sorted(
list(self.famille.bilans.all()) + list(self.famille.rapports.all()),
key=attrgetter('date'),
),
'mode': 'reactivation' if dfin else ('evaluation' if ddebut is None else 'suivi'),
'interv_temporaires': self.object.intervenant_set.filter(date_fin__isnull=False),
'niveaux': self.object.famille.niveaux.all().annotate(
date_fin_calc=Coalesce('date_fin', self.object.date_fin_suivi)
).order_by('pk'),
})
return context
def form_valid(self, form):
response = super().form_valid(form)
if (
'date_debut_suivi' in form.changed_data and form.cleaned_data['date_debut_suivi'] and
form.cleaned_data['date_debut_suivi'] < date.today()
):
# Contrôle attribution des prestations (accompagnement) depuis début suivi.
self.famille.prestations.filter(
date_prestation__gte=form.cleaned_data['date_debut_suivi'], lib_prestation__code='aemo01'
).update(lib_prestation=LibellePrestation.objects.get(code='aemo02'))
return response
def get_success_url(self):
if self.object.date_fin_suivi:
return reverse('famille-list')
return reverse('famille-agenda', args=[self.object.famille.pk])
class DemandeView(EquipeRequiredMixin, SuccessMessageMixin, UpdateView):
template_name = 'aemo/demande_edit.html'
model = Suivi
famille_model = Famille
form_class = forms.DemandeForm
success_message = 'Les modifications ont été enregistrées avec succès'
def get_form_kwargs(self):
return {**super().get_form_kwargs(), 'readonly': self.readonly}
def get_object(self, queryset=None):
suivi = self.famille.suivi
items = [
"Selon le professionnel",
"Selon les parents",
"Selon l'enfant",
]
if suivi.difficultes == '':
suivi.difficultes = ''.join(
['<p><strong>{}</strong></p><p></p>'.format(item) for item in items if suivi.difficultes == '']
)
if suivi.aides == '':
suivi.aides = ''.join(
['<strong>{}</strong></p><p></p>'.format(item) for item in items if suivi.aides == '']
)
if suivi.disponibilites == '':
suivi.disponibilites = ''.join(
['<strong>{}</strong></p><p></p>'.format(item) for item in ['Parents', 'Enfants']
if suivi.disponibilites == '']
)
return suivi
def get_success_url(self):
return reverse('demande', args=[self.object.famille.pk])
class EvaluationPDFView(BasePDFView):
obj_class = Famille
pdf_class = EvaluationPdf
class CoordonneesPDFView(BasePDFView):
obj_class = Famille
pdf_class = CoordonneesFamillePdf
class JournalPDFView(BasePDFView):
obj_class = Famille
pdf_class = JournalPdf
class BilanPDFView(BasePDFView):
obj_class = Bilan
pdf_class = BilanPdf
class PrestationMenu(ListView):
template_name = 'aemo/prestation_menu.html'
model = Prestation
paginate_by = 15
context_object_name = 'familles'
def get_queryset(self):
user = self.request.user
rdv_manques = dict(
Famille.actives().annotate(
rdv_manques=ArrayAgg(
'prestations__date_prestation',
filter=Q(prestations__manque=True),
ordering='prestations__date_prestation',
default=Value([])
)
).values_list('pk', 'rdv_manques')
)
familles = Famille.actives(
).annotate(
user_prest=Sum('prestations__duree',
filter=Q(**{'prestations__intervenants': user}))
).annotate(
aemo1=Sum('prestations__duree',
filter=(Q(**{'prestations__lib_prestation__code': 'aemo01'}))
) or None
).annotate(
aemo2=Sum('prestations__duree',
filter=(Q(**{'prestations__lib_prestation__code': 'aemo02'}))
) or None
)
for famille in familles:
famille.rdv_manques = [format_d_m_Y(rdv) for rdv in rdv_manques[famille.pk]]
return familles
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context.update({
'annee': date.today().year
})
return context
class PrestationBaseView:
model = Prestation
famille_model = Famille
form_class = forms.PrestationForm
template_name = 'aemo/prestation_edit.html'
def dispatch(self, *args, **kwargs):
self.famille = get_object_or_404(self.famille_model, pk=self.kwargs['pk']) if self.kwargs.get('pk') else None
return super().dispatch(*args, **kwargs)
def get_context_data(self, **kwargs):
return {**super().get_context_data(**kwargs), 'famille': self.famille}
def get_success_url(self):
if self.famille:
return reverse('journal-list', args=[self.famille.pk])
return reverse('prestation-gen-list')
class PrestationListView(PrestationBaseView, ListView):
template_name = 'aemo/prestation_list.html'
model = Prestation
paginate_by = 15
context_object_name = 'prestations'
def get(self, request, **kwargs):
self.filter_form = forms.JournalAuteurFilterForm(famille=self.famille, data=request.GET or None)
return super().get(request, **kwargs)
def get_queryset(self):
if self.famille:
prestations = self.famille.prestations.all().order_by('-date_prestation')
if self.filter_form.is_bound and self.filter_form.is_valid():
prestations = self.filter_form.filter(prestations)
return prestations
return self.request.user.prestations.filter(famille=None)
def get_context_data(self, **kwargs):
return super().get_context_data(**kwargs, filter_form=self.filter_form)
class PrestationCreateView(PrestationBaseView, CreateView):
def dispatch(self, request, *args, **kwargs):
if not request.user.has_perm('aemo.add_prestation'):
raise PermissionDenied("Vous n'avez pas les droits pour ajouter une prestation.")
return super().dispatch(request, *args, **kwargs)
def get_initial(self):
initial_text = (
"<p><b>Observations:\n"
"(relations et interactions dans la famille / disponibilité des parents / "
"réponses données aux besoins des enfants / …)</b></p><p></p>"
"<p><b>Discussion (thème-s abordé-s) / activités:</b></p><p></p>"
"<p><b>Particularités en termes de ressources et/ou de limites:</b></p><p></p>"
"<p><b>Ressentis de lintervenant-e:</b></p><p></p>"
"<p><b>Objectif-s traité-s:</b></p><p></p>"
"<p><b>Objectif-s à suivre:</b></p><p></p>"
)
initial = {
**super().get_initial(),
'intervenants': [self.request.user.pk],
'texte': initial_text if self.famille else '',
}
return initial
def get_form_kwargs(self):
return {**super().get_form_kwargs(), 'famille': self.famille, 'user': self.request.user}
def get_lib_prestation(self, date_prest):
"""
Renvoie la prestation en fonction de la famille et du rôle de l'utilisateur connecté.
"""
if self.famille is None:
code = 'aemo03' # Prestation générale
elif self.famille.suivi.date_debut_suivi and date_prest >= self.famille.suivi.date_debut_suivi:
code = 'aemo02' # Accompagnement
else:
code = 'aemo01' # Évaluation
return LibellePrestation.objects.get(code=code)
def form_valid(self, form):
if self.famille:
form.instance.famille = self.famille
form.instance.auteur = self.request.user
form.instance.lib_prestation = self.get_lib_prestation(form.cleaned_data['date_prestation'])
if 'duree' not in form.cleaned_data:
form.instance.duree = timedelta()
return super().form_valid(form)
class PrestationUpdateView(CheckCanEditMixin, PrestationBaseView, UpdateView):
pk_url_kwarg = 'obj_pk'
def get_form_kwargs(self):
return {**super().get_form_kwargs(), 'famille': self.famille, 'user': self.request.user}
def delete_url(self):
fam_id = self.famille.pk if self.famille else 0
return reverse('prestation-delete', args=[fam_id, self.object.pk])
class PrestationDeleteView(PrestationBaseView, DeleteView):
template_name = 'aemo/object_confirm_delete.html'
pk_url_kwarg = 'obj_pk'
form_class = DeleteView.form_class
class NiveauCreateUpdateView(CreateUpdateView):
model = Niveau
form_class = forms.NiveauForm
template_name = 'aemo/form_in_popup.html'
def dispatch(self, request, *args, **kwargs):
self.is_create = 'pk' not in kwargs
self.famille = get_object_or_404(Famille, pk=kwargs['obj_pk'])
self.titre_page = f"Famille: {self.famille.nom}"
self.titre_formulaire = "Nouveau niveau d'intervention" if self.is_create \
else "Modification de l'enregistrement"
return super().dispatch(request, *args, **kwargs)
def get_form_kwargs(self):
kwargs = super().get_form_kwargs()
kwargs['famille'] = self.famille
return kwargs
def form_valid(self, form):
if self.is_create:
der_niv = self.famille.niveaux.last() if self.famille.niveaux.count() > 0 else None
if der_niv:
der_niv.date_fin = form.cleaned_data['date_debut'] - timedelta(days=1)
der_niv.save()
form.instance.famille = self.famille
form.instance.date_fin = None
return super().form_valid(form)
def get_success_url(self):
return reverse('famille-suivi', args=[self.famille.pk])
def delete_url(self):
return reverse('niveau-delete', args=[self.famille.pk, self.object.pk])
class NiveauDeleteView(DeleteView):
template_name = 'aemo/object_confirm_delete.html'
form_class = DeleteView.form_class
model = Niveau
def get_success_url(self):
return reverse('famille-suivi', args=[self.kwargs['obj_pk']])
class DocumentBaseView:
template_name = 'aemo/document_upload.html'
titre_page = ''
titre_formulaire = ''
def dispatch(self, request, *args, **kwargs):
self.famille = get_object_or_404(Famille, pk=kwargs['pk'])
self.titre_page = self.titre_page.format(famille=self.famille.nom)
return super().dispatch(request, *args, **kwargs)
def get_success_url(self):
return self.famille.suivi_url
class DocumentUploadView(DocumentBaseView, CreateView):
form_class = forms.DocumentUploadForm
titre_page = 'Documents externes de la famille {famille}'
titre_formulaire = 'Nouveau document'
def get_initial(self):
initial = super().get_initial()
initial['famille'] = self.famille
return initial
def form_valid(self, form):
form.instance.famille = self.famille
form.instance.auteur = self.request.user
return super().form_valid(form)
class DocumentDeleteView(DeleteView):
model = Document
pk_url_kwarg = 'doc_pk'
def form_valid(self, form):
response = super().form_valid(form)
messages.success(self.request, "Le document a été supprimé avec succès")
return response
def get_success_url(self):
return self.request.META.get('HTTP_REFERER') or self.object.famille.suivi_url
class BilanEditView(DocumentUploadView, CreateUpdateView):
pk_url_kwarg = 'obj_pk'
titre_page = 'Bilan pour la famille {famille}'
titre_formulaire = 'Nouveau bilan'
model = Bilan
form_class = forms.BilanForm
def get_form_class(self):
if self.form_class:
return self.form_class
return modelform_factory(
model=self.model,
exclude=['famille', 'auteur'],
field_classes={
'objectifs': forms.RichTextField,
'rythme': forms.RichTextField,
},
widgets={
'famille': HiddenInput,
'date': forms.PickDateWidget,
},
labels={'fichier': ''},
)
def get_initial(self):
initial = super().get_initial()
if self.is_create:
initial['objectifs'] = (
"<p><b>Besoin de lenfant</b></p><p></p>\n"
"<p><b>Objectifs de la famille</b></p><p></p>\n"
"<p><b>Moyens</b></p><p></p>\n"
"<p><b>Critères</b></p><p></p>"
)
return initial
def get_success_url(self):
return reverse('famille-agenda', args=[self.famille.pk])
class BilanDetailView(DetailView):
model = Bilan
pk_url_kwarg = 'obj_pk'
template_name = 'aemo/bilan.html'
def get_context_data(self, **kwargs):
meta = self.model._meta
data_fields = [
f.name for f in meta.get_fields()
if f.name not in ['id', 'date', 'famille', 'auteur', 'fichier', 'phase', 'sig_famille', 'sig_interv']
]
return {
**super().get_context_data(**kwargs),
'famille': self.object.famille,
'data': [(meta.get_field(f).verbose_name, getattr(self.object, f)) for f in data_fields]
}
class BilanDeleteView(DeleteView):
model = Bilan
pk_url_kwarg = 'obj_pk'
def dispatch(self, request, *args, **kwargs):
self.famille = get_object_or_404(Famille, pk=kwargs['pk'])
if not self.get_object().can_edit(request.user):
raise PermissionDenied("Vous n'avez pas les droits de supprimer ce bilan.")
return super().dispatch(request, *args, **kwargs)
def get_success_url(self):
return reverse('famille-agenda', args=[self.famille.pk])
class BaseRapportView:
model = Rapport
def dispatch(self, request, *args, **kwargs):
self.famille = get_object_or_404(Famille, pk=kwargs['pk'])
return super().dispatch(request, *args, **kwargs)
class RapportCreateView(DocumentUploadView, BaseRapportView, CreateView):
form_class = forms.RapportEditForm
template_name = 'aemo/rapport_edit.html'
def get_form_class(self):
return modelform_factory(self.model, form=self.form_class)
def get_success_url(self):
return reverse('famille-agenda', args=[self.famille.pk])
class RapportDisplayView(BaseRapportView, DetailView):
pk_url_kwarg = 'obj_pk'
template_name = 'aemo/rapport.html'
def get_context_data(self, **kwargs):
meta = self.model._meta
fields = ['situation', 'observations', 'projet']
return {
**super().get_context_data(**kwargs),
'famille': self.object.famille,
'rapport': self.object,
'intervenants': ', '.join([i.nom_prenom for i in self.object.intervenants()]),
'enfants': '\n'.join(
[f"{enfant.nom_prenom} (*{format_d_m_Y(enfant.date_naissance)})"
for enfant in self.object.famille.membres_suivis()]
),
'data': [
(meta.get_field(f).verbose_name, getattr(self.object, f)) for f in fields
if getattr(self.object, f) or f in ['situation', 'projet']
]
}
class RapportUpdateView(DocumentUploadView, BaseRapportView, CreateUpdateView):
pk_url_kwarg = 'obj_pk'
form_class = forms.RapportEditForm
template_name = 'aemo/rapport_edit.html'
def get_form_class(self):
return modelform_factory(self.model, form=self.form_class)
def get_form_kwargs(self):
return {
**super().get_form_kwargs(),
'user': self.request.user,
}
def get_success_url(self):
return reverse('famille-agenda', args=[self.famille.pk])
class RapportDeleteView(BaseRapportView, DeleteView):
model = Rapport
pk_url_kwarg = 'obj_pk'
def form_valid(self, form):
if not self.object.can_edit(self.request.user):
raise PermissionDenied("Vous n'avez pas le droit de supprimer ce résumé.")
return super().form_valid(form)
def get_success_url(self):
return reverse('famille-agenda', args=[self.famille.pk])
class RapportPDFView(BaseRapportView, BasePDFView):
pdf_class = RapportPdf
pk_url_kwarg = 'obj_pk'
@property
def obj_class(self):
return self.model
class FamilleAdresseChangeView(UpdateView):
template_name = 'aemo/famille_adresse.html'
form_class = forms.FamilleAdresseForm
context_object_name = 'famille'
def dispatch(self, request, *args, **kwargs):
if not self.get_object().can_edit(request.user):
raise PermissionDenied("Vous navez pas la permission daccéder à cette page.")
return super().dispatch(request, *args, **kwargs)
def get_object(self):
return get_object_or_404(Famille, pk=self.kwargs['pk'])
def get_form_kwargs(self):
kwargs = super().get_form_kwargs()
kwargs['famille'] = self.get_object()
return kwargs
def form_valid(self, form):
famille = form.save()
for membre_id in form.cleaned_data['membres']:
membre = get_object_or_404(Personne, pk=membre_id)
membre.rue = famille.rue
membre.npa = famille.npa
membre.localite = famille.localite
membre.save()
return HttpResponseRedirect(famille.edit_url)
class FamilleAutoCompleteView(autocomplete.Select2QuerySetView):
def get_queryset(self):
qs = Famille.objects.filter(archived_at__isnull=True)
if self.q:
qs = qs.filter(nom__icontains=self.q)
return qs.prefetch_related('membres')
def get_result_label(self, result):
enfants = ", ".join(enf.prenom for enf in result.membres_suivis())
label = f'{result.nom}, {result.npa} {result.localite}'
if enfants:
label += f" ({enfants})"
return label
class PermissionOverview(TemplateView):
template_name = 'aemo/permissions.html'
perm_map = {'add': 'création', 'change': 'modification', 'delete': 'suppression', 'view': 'affichage'}
def get_context_data(self, **kwargs):
def verbose(perm):
return self.perm_map.get(perm.codename.split('_')[0], perm.codename)
context = super().get_context_data(**kwargs)
groups = Group.objects.all().prefetch_related('permissions')
grp_perms = {gr.name: [perm.codename for perm in gr.permissions.all()] for gr in groups}
objects = [CercleScolaire, Contact, Role, Service, Utilisateur]
all_perms = Permission.objects.filter(
content_type__app_label__in=['aemo'],
content_type__model__in=[o.__name__.lower() for o in objects]
)
def perms_for_model(model):
return [
(perm.codename, verbose(perm)) for perm in all_perms
if perm.content_type.model == model.__name__.lower()
]
perm_groups = OrderedDict()
# {'Contact': [('view_contact', 'affichage), ('change_contact', 'modification'), ...]}
for obj in objects:
perm_groups[obj._meta.verbose_name] = perms_for_model(obj)
perm_groups['AEMO'] = list(Suivi._meta.permissions)
perm_groups['AEMO'].extend([('delete_famille', 'Supprimer une famille')])
context.update({
'groups': groups,
'grp_perms': grp_perms,
'perms_by_categ': perm_groups,
})
return context
class ExportPrestationView(PermissionRequiredMixin, FormView):
permission_required = 'aemo.export_stats'
template_name = 'aemo/export.html'
form_class = forms.MonthSelectionForm
filename = 'aemo_reporting_{}'.format(date.strftime(date.today(), '%Y-%m-%d'))
def _prepare_query(self, query, prest_gen):
"""Return a list of families, sorted by prest and name."""
return query.annotate(
prest_gen=Value(prest_gen, output_field=DurationField())
).order_by('nom')
def get_initial(self):
initial = super().get_initial()
initial.update({
'mois': date.today().month - 1 if date.today().month > 1 else 12,
'annee': date.today().year if date.today().month > 1 else date.today().year - 1,
})
return initial
def form_valid(self, form):
mois = int(form.cleaned_data['mois'])
annee = int(form.cleaned_data['annee'])
debut_mois = date(annee, mois, 1)
fin_mois = date(annee, mois, calendar.monthrange(annee, mois)[1])
export = ExportReporting()
familles_en_cours = Famille.suivis_en_cours(debut_mois, fin_mois)
# Considérer au min. 1 enfant par famille, car les familles sans enfant
# suivi occupent aussi une ligne de la stat des familles en cours.
num_enfants = sum(
[len(fam.membres_suivis()) or 1 for fam in familles_en_cours]
)
# Calcul des prestations générales réparties sur chaque enfant suivi
# Renvoie une durée (timedelta()) par enfant suivi
prest_gen = Prestation.objects.filter(
date_prestation__month=mois,
date_prestation__year=annee,
famille=None
).annotate(
num_util=Count('intervenants')
).aggregate(
total=Sum(F('duree') * F('num_util'), output_field=DurationField())
)['total'] or timedelta()
logger.info("Total heures prest. gén. du mois: %s", prest_gen.total_seconds() / 3600)
prest_gen_par_enfant = prest_gen / (num_enfants or 1)
# Demandes en cours
export.produce_suivis(
'En_cours_{}'.format(debut_mois.strftime("%m.%Y")),
self._prepare_query(familles_en_cours, prest_gen_par_enfant),
debut_mois
)
# Nouvelles demandes
familles_nouvelles = Famille.suivis_nouveaux(annee, mois)
export.produce_nouveaux(
"Nouveaux_{}".format(debut_mois.strftime("%m.%Y")),
self._prepare_query(familles_nouvelles, prest_gen_par_enfant),
)
# Fins de suivis
familles_terminees = Famille.suivis_termines(annee, mois)
export.produce_termines(
"Terminés_{}".format(debut_mois.strftime("%m.%Y")),
self._prepare_query(familles_terminees, prest_gen_par_enfant),
)
export.produce_totaux("Totaux heures du mois")
return export.get_http_response(
'aemo_reporting_{}.xlsx'.format(date.strftime(debut_mois, '%m_%Y'))
)
class BasePrestationGeneraleEtPersonnelle(ListView):
def dispatch(self, request, *args, **kwargs):
str_curdate = request.GET.get('date', None)
if str_curdate:
cur_year = int(str_curdate[-4:])
cur_month = int(str_curdate[:2])
else:
today = date.today()
cur_year = today.year
cur_month = today.month
self.dfrom = date(cur_year, cur_month, 1)
self.dto = self.dfrom + timedelta(days=-1 + calendar.monthrange(self.dfrom.year, self.dfrom.month)[1])
return super().dispatch(request, *args, **kwargs)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
prev_month = self.dfrom - timedelta(days=2)
next_month = self.dfrom + timedelta(days=33)
context.update({
'current_date': self.dfrom,
'prev_month': prev_month if prev_month.year > 2019 else None,
'next_month': next_month if next_month.month <= date.today().month else None,
})
return context
class PrestationPersonnelleListView(BasePrestationGeneraleEtPersonnelle):
template_name = 'aemo/prestation_personnelle.html'
def get_queryset(self):
return self.request.user.prestations.filter(
date_prestation__gte=self.dfrom, date_prestation__lte=self.dto
)
def get_context_data(self, *args, **kwargs):
context = super().get_context_data(*args, **kwargs)
context.update({
'totaux': [(pp.code, self.get_queryset().filter(
lib_prestation__code=f"{pp.code}"
).aggregate(
tot=Sum('duree')
)['tot']) for pp in LibellePrestation.objects.all()],
'total_final': self.get_queryset().aggregate(tot=Sum('duree'))['tot'],
})
return context
class PrestationGeneraleListView(BasePrestationGeneraleEtPersonnelle):
model = Prestation
template_name = 'aemo/prestation_generale.html'
def get_queryset(self):
return super().get_queryset().filter(
famille=None,
date_prestation__gte=self.dfrom,
date_prestation__lte=self.dto
)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context.update({
'prestations': self.get_queryset(),
})
return context
class FamilleReactivationView(View):
def post(self, request, *args, **kwargs):
famille = get_object_or_404(Famille, pk=kwargs['pk'])
if famille.can_be_reactivated(request.user):
famille.suivi.date_fin_suivi = None
famille.suivi.motif_fin_suivi = ""
famille.suivi.save()
famille.destination = ''
famille.save()
return HttpResponseRedirect(reverse('famille-list'))
def page_not_found(request, *args, **kwargs):
if not request.user.is_authenticated:
kwargs['template_name'] = '404-public.html'
return django_page_not_found(request, *args, **kwargs)