1674 lines
59 KiB
Python
1674 lines
59 KiB
Python
|
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 n’avez pas la permission d’accé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 n’avez pas la permission d’accéder à cette page.")
|
|||
|
elif self.require_edit and not self.famille.can_edit(request.user):
|
|||
|
raise PermissionDenied("Vous n’avez pas la permission d’accé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 c’est "
|
|||
|
"vraiment ce que vous voulez, mettez-le d’abord 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 = "L’utilisateur «%(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 d’un 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 d’une 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 l’intervenant-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 l’enfant</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 n’avez pas la permission d’accé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)
|