Ejemplo n.º 1
0
def attivita_scheda_turni_partecipa(request, me, pk=None, turno_pk=None):
    """
    Mostra la scheda "Informazioni" di una attivita'.
    """

    turno = get_object_or_404(Turno, pk=turno_pk)
    stato = turno.persona(me)

    if stato not in turno.TURNO_PUOI_PARTECIPARE:
        return errore_generico(request, me, titolo="Non puoi partecipare a questo turno",
                               messaggio="Siamo spiacenti, ma ci risulta che tu non possa "
                                         "richiedere partecipazione a questo turno. Vai "
                                         "all'elenco dei turni per maggiori informazioni "
                                         "sulla motivazione. ",
                               torna_titolo="Turni dell'attività",
                               torna_url=turno.url,
                               )

    p = Partecipazione(
        turno=turno,
        persona=me,
    )
    p.save()
    p.richiedi()

    return messaggio_generico(request, me, titolo="Ottimo! Richiesta inoltrata.",
                              messaggio="La tua richiesta è stata inoltrata ai referenti di "
                                        "questa attività, che potranno confermarla o negarla. "
                                        "Ti manderemo una e-mail non appena risponderanno alla "
                                        "tua richiesta. Puoi sempre controllare lo stato delle tue"
                                        "richieste di partecipazione da 'Attivita' > 'I miei turni'. ",
                              torna_titolo="Vai a 'I miei turni'",
                              torna_url="/attivita/storico/")
Ejemplo n.º 2
0
def crea_partecipazione(persona, turno):
    p = Partecipazione(
        turno=turno,
        persona=persona,
        stato=Partecipazione.RICHIESTA,
    )
    p.save()
    return p
Ejemplo n.º 3
0
def crea_partecipazione(persona, turno):
    p = Partecipazione(
        turno=turno,
        persona=persona,
        stato=Partecipazione.RICHIESTA,
    )
    p.save()
    return p
Ejemplo n.º 4
0
def permessi_persona(persona):
    """
    Permessi di ogni persona (nessuna delega).

    :param persona: La persona.
    :return: Lista di permessi.
    """
    from anagrafica.models import Sede
    from attivita.models import Attivita, Partecipazione
    from base.utils import poco_fa

    # Limiti di tempo per la centrale operativa
    quindici_minuti_fa = poco_fa() - timedelta(
        minutes=Attivita.MINUTI_CENTRALE_OPERATIVA)
    tra_quindici_minuti = poco_fa() + timedelta(
        minutes=Attivita.MINUTI_CENTRALE_OPERATIVA)

    # Sedi per le quali sto effettuando un servizio di centrale operativa
    sede_centrale_operativa = Sede.objects.filter(
        Partecipazione.con_esito(
            Partecipazione.ESITO_OK,
            persona=persona,
        ).via("attivita__turni__partecipazioni"),
        Q(
            Q(attivita__centrale_operativa=Attivita.CO_AUTO)
            | Q(attivita__centrale_operativa=Attivita.CO_MANUALE,
                attivita__turni__partecipazioni__centrale_operativa=True)),
        attivita__turni__inizio__lte=tra_quindici_minuti,
        attivita__turni__fine__gte=quindici_minuti_fa,
    )

    return [(GESTIONE_CENTRALE_OPERATIVA_SEDE, sede_centrale_operativa)]
Ejemplo n.º 5
0
def attivita_scheda_turni_rimuovi(request, me, pk=None, turno_pk=None, partecipante_pk=None):

    turno = get_object_or_404(Turno, pk=turno_pk)
    stato = turno.persona(me)

    if stato != turno.TURNO_PRENOTATO_PUOI_RITIRARTI:
        return errore_generico(request, me, titolo="Non puoi ritirare la tua partecipazione",
                               messaggio="Una volta che la tua partecipazione è stata confermata, "
                                         "non puoi più ritirarla da Gaia. Se non puoi presentarti, "
                                         "scrivi a un referente dell'attività, che potrà valutare "
                                         "la situazione e rimuoverti dai partecipanti.",
                               torna_titolo="Torna al turno",
                               torna_url=turno.url)

    partecipazione = Partecipazione.con_esito_pending(turno=turno, persona=me).first()
    if not partecipazione:
        raise ValueError("TURNO_PRENOTATO_PUOI_RITIRARTI assegnato, ma nessuna partecipazione"
                         "trovata. ")

    partecipazione.autorizzazioni_ritira()
    return messaggio_generico(request, me, titolo="Richiesta ritirata.",
                              messaggio="La tua richiesta di partecipazione a questo turno "
                                        "è stata ritirata con successo.",
                              torna_titolo="Torna al turno",
                              torna_url=turno.url)
Ejemplo n.º 6
0
def attivita_scheda_turni_ritirati(request, me, pk=None, turno_pk=None):

    turno = get_object_or_404(Turno, pk=turno_pk)
    stato = turno.persona(me)

    if stato != turno.TURNO_PRENOTATO_PUOI_RITIRARTI:
        return errore_generico(
            request,
            me,
            titolo="Non puoi ritirare la tua partecipazione",
            messaggio="Una volta che la tua partecipazione è stata confermata, "
            "non puoi più ritirarla da Gaia. Se non puoi presentarti, "
            "scrivi a un referente dell'attività, che potrà valutare "
            "la situazione e rimuoverti dai partecipanti.",
            torna_titolo="Torna al turno",
            torna_url=turno.url)

    partecipazione = Partecipazione.con_esito_pending(turno=turno,
                                                      persona=me).first()
    if not partecipazione:
        raise ValueError(
            "TURNO_PRENOTATO_PUOI_RITIRARTI assegnato, ma nessuna partecipazione"
            "trovata. ")

    partecipazione.autorizzazioni_ritira()
    return messaggio_generico(
        request,
        me,
        titolo="Richiesta ritirata.",
        messaggio="La tua richiesta di partecipazione a questo turno "
        "è stata ritirata con successo.",
        torna_titolo="Torna al turno",
        torna_url=turno.url)
Ejemplo n.º 7
0
def co_poteri(request, me):
    sedi = me.oggetti_permesso(GESTIONE_POTERI_CENTRALE_OPERATIVA_SEDE)

    modulo = ModuloPoteri(request.GET or None)
    giorno = date.today()
    if modulo.is_valid():
        giorno = modulo.cleaned_data['giorno']

    minuti = Attivita.MINUTI_CENTRALE_OPERATIVA

    # Limiti di tempo per la centrale operativa
    giorno_inizio = datetime.combine(giorno, time(0, 0))
    giorno_fine = datetime.combine(giorno, time(23, 59))

    url = "/centrale-operativa/poteri/"

    ieri = "%s?giorno=%s" % (url, (giorno - timedelta(days=1)).strftime("%d/%m/%Y"))
    domani = "%s?giorno=%s" % (url, (giorno + timedelta(days=1)).strftime("%d/%m/%Y"))

    partecipazioni = Partecipazione.con_esito_ok().filter(
        turno__inizio__lte=giorno_fine,
        turno__fine__gte=giorno_inizio,
        turno__attivita__centrale_operativa=Attivita.CO_MANUALE,
        turno__attivita__sede__in=sedi,
    ).order_by('turno__inizio')

    contesto = {
        "partecipazioni": partecipazioni,
        "minuti": minuti,
        "modulo": modulo,
        "ieri": ieri,
        "domani": domani,
    }
    return "centrale_operativa_poteri.html", contesto
Ejemplo n.º 8
0
def permessi_persona(persona):
    """
    Permessi di ogni persona (nessuna delega).

    :param persona: La persona.
    :return: Lista di permessi.
    """
    from anagrafica.models import Sede
    from attivita.models import Attivita, Partecipazione
    from base.utils import poco_fa

    # Limiti di tempo per la centrale operativa
    quindici_minuti_fa = poco_fa() - timedelta(minutes=Attivita.MINUTI_CENTRALE_OPERATIVA)
    tra_quindici_minuti = poco_fa() + timedelta(minutes=Attivita.MINUTI_CENTRALE_OPERATIVA)

    # Sedi per le quali sto effettuando un servizio di centrale operativa
    sede_centrale_operativa = Sede.objects.filter(
        Partecipazione.con_esito(Partecipazione.ESITO_OK,
                                 persona=persona,
                                 ).via("attivita__turni__partecipazioni"),
        Q(
            Q(attivita__centrale_operativa=Attivita.CO_AUTO)
            | Q(attivita__centrale_operativa=Attivita.CO_MANUALE,
                attivita__turni__partecipazioni__centrale_operativa=True)
        ),
        attivita__turni__inizio__lte=tra_quindici_minuti,
        attivita__turni__fine__gte=quindici_minuti_fa,
    )

    return [
        (GESTIONE_CENTRALE_OPERATIVA_SEDE, sede_centrale_operativa)
    ]
Ejemplo n.º 9
0
    def test_autorizzazioni_automatiche_scadute(self):
        presidente = crea_persona()
        persona, sede, app = crea_persona_sede_appartenenza(
            presidente=presidente)
        persona.email_contatto = email_fittizzia()
        persona.save()

        ora = timezone.now()

        area, attivita = crea_area_attivita(sede)

        domani_inizio = ora + timedelta(days=24)
        domani_fine = ora + timedelta(days=180)

        t1 = crea_turno(attivita, inizio=domani_inizio, fine=domani_fine)
        partecipazione = crea_partecipazione(persona, t1)
        attivita.centrale_operativa = Attivita.CO_AUTO
        attivita.save()
        self.assertEqual(0, Autorizzazione.objects.count())
        partecipazione.richiedi()
        self.assertNotIn(partecipazione, Partecipazione.con_esito_ok())
        self.assertEqual(0, len(mail.outbox))
        self.assertEqual(1, Autorizzazione.objects.count())
        autorizzazione = Autorizzazione.objects.first()
        self.assertNotEqual(autorizzazione.scadenza, None)
        autorizzazione.scadenza = timezone.now() - timedelta(days=10)
        autorizzazione.save()
        self.assertFalse(autorizzazione.concessa)
        Autorizzazione.gestisci_automatiche()
        self.assertEqual(1, len(mail.outbox))
        messaggio = mail.outbox[0]
        self.assertTrue(
            messaggio.subject.find(
                'Richiesta di partecipazione attività RESPINTA') > -1)
        self.assertFalse(
            messaggio.subject.find(
                'Richiesta di partecipazione attività APPROVATA') > -1)
        self.assertTrue(
            messaggio.body.find(
                'una tua richiesta è rimasta in attesa per 30 giorni e come da policy'
            ) == -1)
        self.assertTrue(autorizzazione.oggetto.automatica)
        Autorizzazione.gestisci_automatiche()
        self.assertEqual(1, len(mail.outbox))
        self.assertEqual(autorizzazione.concessa, None)
        self.assertIn(partecipazione, Partecipazione.con_esito_no())
Ejemplo n.º 10
0
 def risultati(self):
     qs_turni = self.args[0]
     return Persona.objects.filter(
         Partecipazione.con_esito_ok(turno__in=qs_turni).via("partecipazioni")
     ).prefetch_related(
         'appartenenze', 'appartenenze__sede',
         'utenza', 'numeri_telefono'
     ).distinct('cognome', 'nome', 'codice_fiscale')
Ejemplo n.º 11
0
 def risultati(self):
     qs_turni = self.args[0]
     return Persona.objects.filter(
         Partecipazione.con_esito_ok(
             turno__in=qs_turni).via("partecipazioni")).prefetch_related(
                 'appartenenze', 'appartenenze__sede', 'utenza',
                 'numeri_telefono').distinct('cognome', 'nome',
                                             'codice_fiscale')
Ejemplo n.º 12
0
 def risultati(self):
     qs_attivita = self.args[0]
     return Persona.objects.filter(
         Partecipazione.con_esito_ok(turno__attivita__in=qs_attivita).via("partecipazioni")
     ).prefetch_related(
         'appartenenze', 'appartenenze__sede',
         'utenza', 'numeri_telefono'
     ).annotate(
         partecipazioni__turno__nome=F('partecipazioni__turno__nome'),
         partecipazioni__turno__inizio=F('partecipazioni__turno__inizio'),
         partecipazioni__turno__fine=F('partecipazioni__turno__fine'),
     )  ## NO DISTINCT!
Ejemplo n.º 13
0
 def risultati(self):
     qs_attivita = self.args[0]
     return Persona.objects.filter(
         Partecipazione.con_esito_ok(
             turno__attivita__in=qs_attivita).via("partecipazioni")
     ).prefetch_related(
         'appartenenze', 'appartenenze__sede', 'utenza',
         'numeri_telefono').annotate(
             partecipazioni__turno__nome=F('partecipazioni__turno__nome'),
             partecipazioni__turno__inizio=F(
                 'partecipazioni__turno__inizio'),
             partecipazioni__turno__fine=F('partecipazioni__turno__fine'),
         )  ## NO DISTINCT!
Ejemplo n.º 14
0
    def test_autorizzazioni_automatiche_scadute(self):
        presidente = crea_persona()
        persona, sede, app = crea_persona_sede_appartenenza(presidente=presidente)
        persona.email_contatto = email_fittizzia()
        persona.save()

        ora = timezone.now()

        area, attivita = crea_area_attivita(sede)

        domani_inizio = ora + timedelta(days=24)
        domani_fine = ora + timedelta(days=180)

        t1 = crea_turno(attivita, inizio=domani_inizio, fine=domani_fine)
        partecipazione = crea_partecipazione(persona, t1)
        attivita.centrale_operativa = Attivita.CO_AUTO
        attivita.save()
        self.assertEqual(0, Autorizzazione.objects.count())
        partecipazione.richiedi()
        self.assertNotIn(partecipazione, Partecipazione.con_esito_ok())
        self.assertEqual(0, len(mail.outbox))
        self.assertEqual(1, Autorizzazione.objects.count())
        autorizzazione = Autorizzazione.objects.first()
        self.assertNotEqual(autorizzazione.scadenza, None)
        autorizzazione.scadenza = timezone.now() - timedelta(days=10)
        autorizzazione.save()
        self.assertFalse(autorizzazione.concessa)
        Autorizzazione.gestisci_automatiche()
        self.assertEqual(1, len(mail.outbox))
        messaggio = mail.outbox[0]
        self.assertTrue(messaggio.subject.find('Richiesta di partecipazione attività RESPINTA') > -1)
        self.assertFalse(messaggio.subject.find('Richiesta di partecipazione attività APPROVATA') > -1)
        self.assertTrue(messaggio.body.find('una tua richiesta è rimasta in attesa per 30 giorni e come da policy') == -1)
        self.assertTrue(autorizzazione.oggetto.automatica)
        Autorizzazione.gestisci_automatiche()
        self.assertEqual(1, len(mail.outbox))
        self.assertEqual(autorizzazione.concessa, None)
        self.assertIn(partecipazione, Partecipazione.con_esito_no())
Ejemplo n.º 15
0
def co_turni(request, me):
    sedi = me.oggetti_permesso(GESTIONE_CENTRALE_OPERATIVA_SEDE)
    tra_qualche_ora = poco_fa() + timedelta(hours=2)
    qualche_ora_fa = poco_fa() - timedelta(hours=2)
    partecipazioni = Partecipazione.con_esito_ok().filter(
            Q(turno__attivita__sede__in=sedi),                                       # Nelle mie sedi di competenza
            Q(turno__inizio__lte=tra_qualche_ora, turno__fine__gte=qualche_ora_fa)   # a) In corso, oppure
            | Q(turno__coturni__montato_da__isnull=False,
                turno__coturni__smontato_da__isnull=True)                            # d) Da smontare
        ).select_related('turno', 'turno__attivita', 'turno__attivita__sede')\
         .order_by('turno__inizio', 'turno__fine', 'turno__id', 'persona__id')\
         .distinct('turno__inizio', 'turno__fine', 'turno__id', 'persona__id')
    contesto = {
        "partecipazioni": partecipazioni,
    }
    return "centrale_operativa_turni.html", contesto
Ejemplo n.º 16
0
def co_poteri_switch(request, me, part_pk):
    sedi = me.oggetti_permesso(GESTIONE_POTERI_CENTRALE_OPERATIVA_SEDE)

    partecipazione = Partecipazione.con_esito_ok(
        turno__attivita__sede__in=sedi,
        pk=part_pk
    ).first()

    next = request.GET.get('next', default='/centrale-operativa/poteri/')

    if not partecipazione:
        return errore_generico(request, me, titolo="Partecipazione non trovata")

    partecipazione.centrale_operativa = not partecipazione.centrale_operativa
    partecipazione.save()

    return redirect(next)
Ejemplo n.º 17
0
def attivita_scheda_turni_partecipa(request, me, pk=None, turno_pk=None):
    """
    Mostra la scheda "Informazioni" di una attivita'.
    """

    turno = get_object_or_404(Turno, pk=turno_pk)
    stato = turno.persona(me)

    if stato not in turno.TURNO_PUOI_PARTECIPARE:
        return errore_generico(
            request,
            me,
            titolo="Non puoi partecipare a questo turno",
            messaggio="Siamo spiacenti, ma ci risulta che tu non possa "
            "richiedere partecipazione a questo turno. Vai "
            "all'elenco dei turni per maggiori informazioni "
            "sulla motivazione. ",
            torna_titolo="Turni dell'attività",
            torna_url=turno.url,
        )

    p = Partecipazione(
        turno=turno,
        persona=me,
    )
    p.save()
    p.richiedi()

    return messaggio_generico(
        request,
        me,
        titolo="Ottimo! Richiesta inoltrata.",
        messaggio="La tua richiesta è stata inoltrata ai referenti di "
        "questa attività, che potranno confermarla o negarla. "
        "Ti manderemo una e-mail non appena risponderanno alla "
        "tua richiesta. Puoi sempre controllare lo stato delle tue"
        "richieste di partecipazione da 'Attivita' > 'I miei turni'. ",
        torna_titolo="Vai a 'I miei turni'",
        torna_url="/attivita/storico/")
Ejemplo n.º 18
0
def attivita_statistiche(request, me):
    sedi = me.oggetti_permesso(GESTIONE_ATTIVITA_SEDE)

    modulo = ModuloStatisticheAttivita(request.POST or None,
                                       initial={"sedi": sedi})
    modulo.fields['sedi'].queryset = sedi

    statistiche = []
    chart = {}

    periodi = 12

    if modulo.is_valid():

        oggi = date.today()

        giorni = int(modulo.cleaned_data['periodo'])
        if giorni == modulo.SETTIMANA:
            etichetta = "sett."
        elif giorni == modulo.QUINDICI_GIORNI:
            etichetta = "fortn."
        elif giorni == modulo.MESE:
            etichetta = "mesi"
        else:
            raise ValueError("Etichetta mancante.")

        for periodo in range(periodi, 0, -1):

            dati = {}

            fine = oggi - timedelta(days=(giorni * periodo))
            inizio = fine - timedelta(days=giorni - 1)

            fine = datetime.combine(fine, time(23, 59, 59))
            inizio = datetime.combine(inizio, time(0, 0, 0))

            dati['inizio'] = inizio
            dati['fine'] = fine

            # Prima, ottiene tutti i queryset.
            qs_attivita = Attivita.objects.filter(stato=Attivita.VISIBILE,
                                                  sede__in=sedi)
            qs_turni = Turno.objects.filter(attivita__in=qs_attivita,
                                            inizio__lte=fine,
                                            fine__gte=inizio)
            qs_part = Partecipazione.con_esito_ok(turno__in=qs_turni)

            ore_di_servizio = qs_turni.annotate(
                durata=F('fine') - F('inizio')).aggregate(
                    totale_ore=Sum('durata'))['totale_ore'] or timedelta()
            ore_uomo_di_servizio = qs_part.annotate(
                durata=F('turno__fine') - F('turno__inizio')).aggregate(
                    totale_ore=Sum('durata'))['totale_ore'] or timedelta()

            # Poi, associa al dizionario statistiche.
            dati['etichetta'] = "%d %s fa" % (
                periodo,
                etichetta,
            )
            dati['num_turni'] = qs_turni.count()
            dati['ore_di_servizio'] = ore_di_servizio
            dati['ore_uomo_di_servizio'] = ore_uomo_di_servizio
            try:
                dati['rapporto'] = round(
                    ore_uomo_di_servizio / ore_di_servizio, 3)
            except ZeroDivisionError:
                dati['rapporto'] = 0

            statistiche.append(dati)

        chart['labels'] = json.dumps([x['etichetta'] for x in statistiche])
        chart['num_turni'] = json.dumps([x['num_turni'] for x in statistiche])
        chart['ore_di_servizio'] = json.dumps(
            [timedelta_ore(x['ore_di_servizio']) for x in statistiche])
        chart['ore_uomo_di_servizio'] = json.dumps(
            [timedelta_ore(x['ore_uomo_di_servizio']) for x in statistiche])
        chart['rapporto'] = json.dumps([x['rapporto'] for x in statistiche])

    contesto = {
        "modulo": modulo,
        "statistiche": statistiche,
        "chart": chart,
    }
    return 'attivita_statistiche.html', contesto
Ejemplo n.º 19
0
def statistiche_attivita_persona(persona, modulo):
    """
    Ritorna un dizionario di parametri, che puo' essere passato alla vista,
    con tutti i dati statistici relativi alle attivita' effettuate da una determinata
    persona.

    :param persona_id: ID della persona.
    :param modulo: Il form validato.
    :return: Un dizionario, o None se il form non e' valido.
    """

    if not modulo.is_valid():
        return None

    oggi = date.today()

    impostazioni = {
        # num_giorni: (nome, numero_periodi)
        modulo.SETTIMANA: ("sett.", 20),
        modulo.QUINDICI_GIORNI: ("fortn.", 20),
        modulo.MESE: ("mesi", 12),
        modulo.ANNO: ("anni", 4),
    }

    giorni = int(modulo.cleaned_data['periodo'])
    etichetta, periodi = impostazioni[giorni]

    statistiche = []
    chart = {}

    for periodo in range(periodi, -1, -1):

        dati = {}

        fine = oggi - timedelta(days=(giorni * periodo))
        inizio = fine - timedelta(days=giorni - 1)

        fine = datetime.combine(fine, time(23, 59, 59))
        inizio = datetime.combine(inizio, time(0, 0, 0))

        dati['inizio'] = inizio
        dati['fine'] = fine

        # Prima, ottiene tutti i queryset.
        qs_turni = Turno.objects.filter(inizio__lte=fine, fine__gte=inizio)
        qs_part = Partecipazione.con_esito_ok(turno__in=qs_turni, persona=persona)

        ore_di_servizio = qs_part.annotate(durata=F('turno__fine') - F('turno__inizio'))\
            .aggregate(totale_ore=Sum('durata'))['totale_ore'] or timedelta()

        # Poi, associa al dizionario statistiche.
        dati['etichetta'] = "%d %s fa" % (periodo, etichetta,)
        dati['num_turni'] = qs_part.count()
        dati['ore_di_servizio'] = ore_di_servizio
        dati['ore_di_servizio_int'] = round(ore_di_servizio.total_seconds() / 3600, 3)

        statistiche.append(dati)

    chart['labels'] = json.dumps([x['etichetta'] for x in statistiche])
    chart['num_turni'] = json.dumps([x['num_turni'] for x in statistiche])
    chart['ore_di_servizio'] = json.dumps([timedelta_ore(x['ore_di_servizio']) for x in statistiche])

    return {'statistiche': statistiche,
            'chart': chart}
Ejemplo n.º 20
0
def attivita_statistiche(request, me):
    sedi = me.oggetti_permesso(GESTIONE_ATTIVITA_SEDE)

    modulo = ModuloStatisticheAttivita(request.POST or None, initial={"sedi": sedi})
    modulo.fields['sedi'].queryset = sedi

    statistiche = []
    chart = {}

    periodi = 12

    if modulo.is_valid():

        oggi = date.today()

        giorni = int(modulo.cleaned_data['periodo'])
        if giorni == modulo.SETTIMANA:
            etichetta = "sett."
        elif giorni == modulo.QUINDICI_GIORNI:
            etichetta = "fortn."
        elif giorni == modulo.MESE:
            etichetta = "mesi"
        else:
            raise ValueError("Etichetta mancante.")

        for periodo in range(periodi, 0, -1):

            dati = {}

            fine = oggi - timedelta(days=(giorni*periodo))
            inizio = fine - timedelta(days=giorni-1)

            fine = datetime.combine(fine, time(23, 59, 59))
            inizio = datetime.combine(inizio, time(0, 0, 0))

            dati['inizio'] = inizio
            dati['fine'] = fine

            # Prima, ottiene tutti i queryset.
            qs_attivita = Attivita.objects.filter(stato=Attivita.VISIBILE, sede__in=sedi)
            qs_turni = Turno.objects.filter(attivita__in=qs_attivita, inizio__lte=fine, fine__gte=inizio)
            qs_part = Partecipazione.con_esito_ok(turno__in=qs_turni)

            ore_di_servizio = qs_turni.annotate(durata=F('fine') - F('inizio')).aggregate(totale_ore=Sum('durata'))['totale_ore'] or timedelta()
            ore_uomo_di_servizio = qs_part.annotate(durata=F('turno__fine') - F('turno__inizio')).aggregate(totale_ore=Sum('durata'))['totale_ore'] or timedelta()

            # Poi, associa al dizionario statistiche.
            dati['etichetta'] = "%d %s fa" % (periodo, etichetta,)
            dati['num_turni'] = qs_turni.count()
            dati['ore_di_servizio'] = ore_di_servizio
            dati['ore_uomo_di_servizio'] = ore_uomo_di_servizio
            try:
                dati['rapporto'] = round(ore_uomo_di_servizio / ore_di_servizio, 3)
            except ZeroDivisionError:
                dati['rapporto'] = 0

            statistiche.append(dati)

        chart['labels'] = json.dumps([x['etichetta'] for x in statistiche])
        chart['num_turni'] = json.dumps([x['num_turni'] for x in statistiche])
        chart['ore_di_servizio'] = json.dumps([timedelta_ore(x['ore_di_servizio']) for x in statistiche])
        chart['ore_uomo_di_servizio'] = json.dumps([timedelta_ore(x['ore_uomo_di_servizio']) for x in statistiche])
        chart['rapporto'] = json.dumps([x['rapporto'] for x in statistiche])

    contesto = {
        "modulo": modulo,
        "statistiche": statistiche,
        "chart": chart,
    }
    return 'attivita_statistiche.html', contesto