début de réorganisation de la partie statistique

This commit is contained in:
artus40
2017-08-14 19:12:18 +02:00
parent 0c887afaa3
commit c8c59b92a2
8 changed files with 116 additions and 111 deletions

View File

@@ -5,20 +5,24 @@ from . import managers
# Extends 'notes' module # Extends 'notes' module
class Observation(Note): class Observation(Note):
""" Note dans le cadre d'une rencontre """ """ Note dans le cadre d'une rencontre """
objects = managers.ObservationManager() objects = managers.ObservationManager()
rencontre = models.ForeignKey( 'maraudes.Rencontre', rencontre = models.ForeignKey('maraudes.Rencontre',
models.CASCADE, models.CASCADE,
related_name="observations" related_name="observations")
)
# Note attributes proxies # Note attributes proxies
def note_author(self): return self.rencontre.maraude.referent def note_author(self): return self.rencontre.maraude.referent
def note_date(self): return self.rencontre.date def note_date(self): return self.rencontre.date
def note_time(self): return self.rencontre.heure_debut def note_time(self): return self.rencontre.heure_debut
def note_labels(self): return [self.rencontre.lieu, self.rencontre.heure_debut] def note_labels(self): return [self.rencontre.lieu, self.rencontre.heure_debut]
def note_bg_colors(self): return ("info", "info") def note_bg_colors(self): return ("info", "info")
class Appel(Note): class Appel(Note):

View File

@@ -2,8 +2,6 @@ import datetime
import calendar import calendar
import logging import logging
logger = logging.getLogger(__name__)
from django.utils import timezone from django.utils import timezone
from django.shortcuts import redirect, reverse from django.shortcuts import redirect, reverse
from django.views import generic from django.views import generic
@@ -13,19 +11,21 @@ from django.contrib import messages
from utilisateurs.mixins import MaraudeurMixin from utilisateurs.mixins import MaraudeurMixin
from .models import ( Maraude, Maraudeur, from .models import (Maraude, Maraudeur,
CompteRendu, CompteRendu,
Rencontre, Lieu, Rencontre, Lieu,
Planning, ) Planning,)
from .notes import Signalement from .notes import Signalement
# Forms # Forms
from .forms import ( RencontreForm, from .forms import (RencontreForm,
ObservationInlineFormSet, ObservationInlineFormSet,
MaraudeHiddenDateForm, MonthSelectForm, MaraudeHiddenDateForm, MonthSelectForm,
AppelForm, SignalementForm, AppelForm, SignalementForm,
SendMailForm ) SendMailForm)
from notes.mixins import NoteFormMixin from notes.mixins import NoteFormMixin
logger = logging.getLogger(__name__)
def derniers_sujets_rencontres(): def derniers_sujets_rencontres():
""" Renvoie le 'set' des sujets rencontrés dans les deux dernières maraudes """ """ Renvoie le 'set' des sujets rencontrés dans les deux dernières maraudes """

View File

@@ -19,5 +19,9 @@
{% endblock %} {% endblock %}
{% block page_content %} {% block page_content %}
{{ par_heure.as_html }} {{ rencontres_par_heure.as_html }}
{{ rencontres_par_mois.as_html }}
<hr />
{{ rencontres_par_sujet.as_html }}
{% endblock %} {% endblock %}

View File

@@ -8,6 +8,5 @@
// Wait for the chart to finish drawing before calling the getImageURI() method. // Wait for the chart to finish drawing before calling the getImageURI() method.
google.visualization.events.addListener(chart, 'ready', function () { google.visualization.events.addListener(chart, 'ready', function () {
$("#image-{{ chart.get_html_id }}").attr("href", chart.getImageURI()); $("#image-{{ chart.get_html_id }}").attr("href", chart.getImageURI());
$("#wrapper-{{ chart.get_html_id}}").hide();
}); });
{% endblock %} {% endblock %}

View File

@@ -15,46 +15,26 @@
{% block breadcrumbs %} {% block breadcrumbs %}
{{ block.super }} {{ block.super }}
<li>Tests</li> <li>Données générales</li>
{% endblock %} {% endblock %}
{% block page_content %} {% block page_content %}
<div class="alert alert-info alert-dismissible">
<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">&times;</span></button> <div class="col-lg-8 text-right">
<p>Voici les données permettant une analyse statistiques des maraudes.</p> <h3 class="page-header">Données générales</h3>
<p>Vous pouvez sélectionner une période particulière ou l'ensemble des données</p> <table class="table table-bordered">
<p>Les données sont réparties en trois catégories, accessibles par le menu sur la gauche</p> <tr><th>...</th><th>Maraudes</th><th>Nombre de rencontres <span class="badge">moyenne par maraude</span></th><th>Personnes</th></tr>
<tr><th>Total</th><td>{{nbr_maraudes}}</td><td>{{nbr_rencontres}} <span class="badge">{{nbr_rencontres_moyenne }}</span></td><td>{{nbr_sujets}}</td></tr>
<tr><th>Soirée</th><td>{{nbr_maraudes_nuit}}</td><td>{{nbr_rencontres_nuit}} <span class="badge">{{nbr_rencontres_nuit_moyenne }}</span></td><td>{{nbr_sujets_nuit}}</td></tr>
<tr><th>Journée</th><td>{{nbr_maraudes_jour}}</td><td>{{nbr_rencontres_jour}} <span class="badge">{{nbr_rencontres_jour_moyenne }}</span></td><td>{{nbr_sujets_jour}}</td></tr>
</table>
</div> </div>
<div class="col-lg-4"> <div class="col-lg-4">
<h3 class="page-header">Données générales</h3> <div class="alert alert-info">
<ul class="list-group"> <p>Voici les données permettant une analyse statistiques des maraudes.</p>
<li class="list-group-item list-group-item-danger"> <p>Vous pouvez sélectionner une période particulière ou l'ensemble des données</p>
<span class="badge">{{ nbr_maraudes }}</span> <p>Les données sont réparties en trois catégories, accessibles par le menu sur la gauche</p>
Nombre de maraudes </div>
</li>
<li class="list-group-item">
<span class="badge">{{ nbr_maraudes_jour }}</span>
dont, Maraudes de journée
</li>
<li class="list-group-item list-group-item-danger">
<span class="badge">{{ nbr_rencontres }}</span>
Nombre total de rencontres
</li>
<li class="list-group-item">
<span class="badge">{{ moy_rencontres }}</span>
soit, en <strong>moyenne</strong> par maraude
</li>
<li class="list-group-item list-group-item-danger">
<span class="badge">{{ nbr_sujets_rencontres }}</span>
Nombre de sujets rencontrés
</li>
</ul>
</div> </div>
{% if rencontres_par_mois %}
<div class="col-lg-8">
<h3 class="page-header">Rencontres par mois</h3>
{{ rencontres_par_mois.as_html }}
</div>
{% endif %}
{% endblock %} {% endblock %}

View File

@@ -6,7 +6,6 @@
{% block sidebar %} {% block sidebar %}
{{ block.super }} {{ block.super }}
<hr />
<div class="panel panel-primary"> <div class="panel panel-primary">
<div class="panel-body text-right"> <div class="panel-body text-right">
{% include "statistiques/filter_form.html" %} {% include "statistiques/filter_form.html" %}
@@ -30,10 +29,6 @@
$("#tab-" + id).attr("class", "active"); $("#tab-" + id).attr("class", "active");
$("#wrapper-" + id).show(); $("#wrapper-" + id).show();
} }
/*$( function() {
hideAll();
});*/
</script> </script>
<ul class="nav nav-tabs"> <ul class="nav nav-tabs">
{% for title, graph in graphs %}<li role="presentation" id="tab-{{graph.get_html_id}}"><a href="#" onclick="showGraph('{{graph.get_html_id}}');">{{ title }}</a></li>{% endfor %} {% for title, graph in graphs %}<li role="presentation" id="tab-{{graph.get_html_id}}"><a href="#" onclick="showGraph('{{graph.get_html_id}}');">{{ title }}</a></li>{% endfor %}

View File

@@ -15,7 +15,7 @@ from .forms import StatistiquesForm, SelectRangeForm
from .charts import PieWrapper, ColumnWrapper from .charts import PieWrapper, ColumnWrapper
from maraudes.notes import Observation from maraudes.notes import Observation
from maraudes.models import Maraude from maraudes.models import Maraude, HORAIRES_APRESMIDI, HORAIRES_SOIREE
from notes.models import Sujet from notes.models import Sujet
### ###
@@ -62,8 +62,10 @@ class FilterMixin(generic.edit.FormMixin):
def get_fichestatistiques_queryset(self): def get_fichestatistiques_queryset(self):
return FicheStatistique.objects.filter(pk__in=self.get_observations_queryset().values_list('sujet')) return FicheStatistique.objects.filter(pk__in=self.get_observations_queryset().values_list('sujet'))
def get_sujets_queryset(self): def get_sujets_queryset(self, selection=None):
return Sujet.objects.filter(pk__in=self.get_observations_queryset().values_list('sujet')) if not selection:
selection = self.get_observations_queryset()
return Sujet.objects.filter(pk__in=selection.values_list('sujet'))
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs) context = super().get_context_data(**kwargs)
@@ -85,50 +87,36 @@ class DashboardView(FilterMixin, generic.TemplateView):
context['nbr_maraudes'] = maraudes.count() or NO_DATA context['nbr_maraudes'] = maraudes.count() or NO_DATA
context['nbr_maraudes_jour'] = maraudes.filter( context['nbr_maraudes_jour'] = maraudes.filter(
heure_debut=datetime.time(16,00) heure_debut=HORAIRES_APRESMIDI
).count() or NO_DATA ).count() or NO_DATA
context['nbr_maraudes_nuit'] = maraudes.filter(
heure_debut=HORAIRES_SOIREE
).count() or NO_DATA
context['nbr_rencontres'] = rencontres.count() or NO_DATA context['nbr_rencontres'] = rencontres.count() or NO_DATA
rencontres_jour = rencontres.filter(
rencontre__maraude__heure_debut=HORAIRES_APRESMIDI
)
rencontres_nuit = rencontres.filter(
rencontre__maraude__heure_debut=HORAIRES_SOIREE
)
context['nbr_rencontres_jour'] = rencontres_jour.count() or NO_DATA
context['nbr_rencontres_nuit'] = rencontres_nuit.count() or NO_DATA
for r, m in [
('nbr_rencontres', 'nbr_maraudes'),
('nbr_rencontres_nuit', 'nbr_maraudes_nuit'),
('nbr_rencontres_jour', 'nbr_maraudes_jour'),
]:
try: try:
context['moy_rencontres'] = int(context['nbr_rencontres'] / context['nbr_maraudes']) context['%s_moyenne' % r] = int(context[r] / context[m])
except (ZeroDivisionError, TypeError): except (ZeroDivisionError, TypeError):
context['moy_rencontres'] = NO_DATA context['%s_moyenne' % r] = NO_DATA
if self.year and not self.month: #Show rencontres_par_mois graph context['nbr_sujets'] = self.get_sujets_queryset(selection=rencontres).count()
par_mois = rencontres.order_by().annotate( context['nbr_sujets_jour'] = self.get_sujets_queryset(selection=rencontres_jour).count()
mois=ExtractMonth('created_date') context['nbr_sujets_nuit'] = self.get_sujets_queryset(selection=rencontres_nuit).count()
).values(
'mois'
).annotate(
nbr=Count('pk')
)
context['rencontres_par_mois'] = ColumnWrapper(
SimpleDataSource(
[("Mois", "Rencontres")] +
[(nom_mois[item['mois']], item['nbr']) for item in par_mois]
),
options = {
"title": "Nombre de rencontres par mois"
}
)
# Graph: Fréquence de rencontres par sujet
nbr_rencontres = rencontres.values('sujet').annotate(nbr=Count('pk')).order_by()
context['nbr_sujets_rencontres'] = nbr_rencontres.count()
categories = (
('Rencontre unique', (1,)),
('Entre 2 et 5 rencontres', range(2,6)),
('Entre 6 et 20 rencontres', range(6,20)),
('Plus de 20 rencontres', range(20,999)),
)
get_count_for_range = lambda rg: nbr_rencontres.filter(nbr__in=rg).count()
context['graph_rencontres'] = PieWrapper(
data= [('Type de rencontre', 'Nombre de sujets')] +
[(label, get_count_for_range(rg)) for label, rg in categories],
title= 'Fréquence de rencontres'
)
return context return context
@@ -245,7 +233,7 @@ class FrequentationStatsView(FilterMixin, generic.TemplateView):
par_heure = self.calculer_frequentation_par_quart_heure(observations, continu=False) par_heure = self.calculer_frequentation_par_quart_heure(observations, continu=False)
en_continu = self.calculer_frequentation_par_quart_heure(observations, continu=True) en_continu = self.calculer_frequentation_par_quart_heure(observations, continu=True)
context['par_heure'] = gchart.AreaChart( context['rencontres_par_heure'] = gchart.AreaChart(
SimpleDataSource( SimpleDataSource(
[("Heure", "Rencontres démarrées", "Au total (démarré + en cours)")] + [("Heure", "Rencontres démarrées", "Au total (démarré + en cours)")] +
[(heure, par_heure[heure], en_continu[heure]) for heure in sorted(par_heure.keys())] [(heure, par_heure[heure], en_continu[heure]) for heure in sorted(par_heure.keys())]
@@ -254,6 +242,41 @@ class FrequentationStatsView(FilterMixin, generic.TemplateView):
"title": "Fréquentation de la maraude en fonction de l'heure (par quart d'heure)" "title": "Fréquentation de la maraude en fonction de l'heure (par quart d'heure)"
} }
) )
par_mois = observations.order_by().annotate(
mois=ExtractMonth('created_date')
).values(
'mois'
).annotate(
nbr=Count('pk')
)
context['rencontres_par_mois'] = ColumnWrapper(
SimpleDataSource(
[("Mois", "Rencontres")] +
[(nom_mois[item['mois']], item['nbr']) for item in par_mois]
),
options = {
"title": "Nombre de rencontres par mois"
}
)
# Graph: Fréquence de rencontres par sujet
nbr_rencontres = observations.values('sujet').annotate(nbr=Count('pk')).order_by()
context['rencontres_par_sujet'] = nbr_rencontres.count()
categories = (
('Rencontre unique', (1,)),
('Entre 2 et 5 rencontres', range(2,6)),
('Entre 6 et 20 rencontres', range(6,20)),
('Plus de 20 rencontres', range(20,999)),
)
get_count_for_range = lambda rg: nbr_rencontres.filter(nbr__in=rg).count()
context['rencontres_par_sujet'] = PieWrapper(
data= [('Type de rencontre', 'Nombre de sujets')] +
[(label, get_count_for_range(rg)) for label, rg in categories],
title= 'Fréquence de rencontres'
)
return context return context