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( ['

{}

'.format(item) for item in items if suivi.difficultes == ''] ) if suivi.aides == '': suivi.aides = ''.join( ['{}

'.format(item) for item in items if suivi.aides == ''] ) if suivi.disponibilites == '': suivi.disponibilites = ''.join( ['{}

'.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 = ( "

Observations:\n" "(relations et interactions dans la famille / disponibilité des parents / " "réponses données aux besoins des enfants / …)

" "

Discussion (thème-s abordé-s) / activités:

" "

Particularités en termes de ressources et/ou de limites:

" "

Ressentis de l’intervenant-e:

" "

Objectif-s traité-s:

" "

Objectif-s à suivre:

" ) 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'] = ( "

Besoin de l’enfant

\n" "

Objectifs de la famille

\n" "

Moyens

\n" "

Critères

" ) 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)