Remaster (#38)
* setup new 'statistiques' module * added 'graphos' package and created first test graph * put graphos in requirements, deleted local folder * added "load_csv" management command ! * added update of premiere_rencontre field in 'load_csv' management command * added missing urls.py file * added 'merge' action and view * added 'info_completed' ratio * linked sujets:merge views inside suivi:details * added link to maraudes:details in notes table headers, if any * Major reorganisation, moved 'suivi' and 'sujets' to 'notes', cleanup in 'maraudes', dropping 'website' mixins (mostly useless) * small cleanup * worked on Maraude and Sujet lists * corrected missing line in notes.__init__ * restored 'details' view for maraudes and sujets insie 'notes' module * worked on 'notes': added navigation between maraude's compte-rendu, right content in details, header to list tables * changed queryset for CompteRenduDetailsView to all notes of same date, minor layout changes * added right content to 'details-sujet', created 'statistiques' view and update templates * restored 'statistiques' ajax view in 'details-sujet', fixed 'merge_two' util function * added auto-creation of FicheStatistique (plus some tests), pagination for notes in 'details-sujet' * added error-prone cases in paginator * fixed non-working modals, added titles * added UpdateStatistiques capacity in CompteRenduCreate view * fixed missing AjaxTemplateMixin for CreateSujetView, worked on compte-rendu creation scripts * fixed MaraudeManager.all_of() for common Maraudeurs, added color hints in planning * re-instated statistiques module link and first test page * added FinalizeView to send a mail before finalizing compte-rendu * Added PieChart view for FicheStatistique fields * small style updates, added 'age' and 'genre' fields from sujets in statistiques.PieChartView * worked on statistiques, fixed small issues in 'notes' list views * small theme change * removed some dead code * fixed notes.tests, fixed statistiques.info_completed display, added filter in SujetLisView * added some tests * added customised admin templates * added authenticate in CustomAuthenticatationBackend, more verbose login thanks to messages * added django-nose for test coverage * Corrected raising exception on first migration On first migration, qs.exists() would previously be called and raising an Exception, sot he migrations would fail. * Better try block * cleaned up custom settings.py, added some overrides of django base_settings * corrected bad dictionnary key
This commit is contained in:
204
statistiques/views.py
Normal file
204
statistiques/views.py
Normal file
@@ -0,0 +1,204 @@
|
||||
import datetime
|
||||
|
||||
from django.shortcuts import render, redirect
|
||||
from django.contrib import messages
|
||||
from django.views import generic
|
||||
from django.db.models import (Field, CharField, NullBooleanField,
|
||||
Count,
|
||||
)
|
||||
from django.db.models.functions.datetime import ExtractMonth
|
||||
from graphos.sources.simple import SimpleDataSource
|
||||
from graphos.renderers import gchart
|
||||
|
||||
from .models import FicheStatistique
|
||||
from .forms import StatistiquesForm, SelectRangeForm
|
||||
from .charts import PieWrapper, ColumnWrapper
|
||||
|
||||
from maraudes.notes import Observation
|
||||
from maraudes.models import Maraude
|
||||
from notes.models import Sujet
|
||||
|
||||
###
|
||||
|
||||
|
||||
nom_mois = {
|
||||
1: "Janvier",
|
||||
2: "Février",
|
||||
3: "Mars",
|
||||
4: "Avril",
|
||||
5: "Mai",
|
||||
6: "Juin",
|
||||
7: "Juillet",
|
||||
8: "Août",
|
||||
9: "Septembre",
|
||||
10: "Octobre",
|
||||
11: "Novembre",
|
||||
12: "Décembre"
|
||||
}
|
||||
|
||||
|
||||
class FilterMixin(generic.edit.FormMixin):
|
||||
|
||||
form_class = SelectRangeForm
|
||||
|
||||
def get_initial(self):
|
||||
return {'month': self.request.GET.get('month', 0), 'year': self.request.GET.get('year', 0) }
|
||||
|
||||
def get(self, *args, **kwargs):
|
||||
self.year = int(self.request.GET.get('year', 0))
|
||||
self.month = int(self.request.GET.get('month', 0))
|
||||
return super().get(self, *args, **kwargs)
|
||||
|
||||
def _filters(self, prefix):
|
||||
return {'%s__%s' % (prefix, attr): getattr(self, attr) for attr in ('year', 'month')
|
||||
if getattr(self, attr) > 0 }
|
||||
|
||||
def get_observations_queryset(self):
|
||||
return Observation.objects.filter(**self._filters('created_date'))
|
||||
|
||||
def get_maraudes_queryset(self):
|
||||
return Maraude.objects.filter(**self._filters('date'))
|
||||
|
||||
def get_fichestatistiques_queryset(self):
|
||||
return FicheStatistique.objects.filter(pk__in=self.get_observations_queryset().values_list('sujet'))
|
||||
|
||||
def get_sujets_queryset(self):
|
||||
return Sujet.objects.filter(pk__in=self.get_observations_queryset().values_list('sujet'))
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
context['year'] = self.year
|
||||
context['month'] = self.month
|
||||
return context
|
||||
|
||||
|
||||
NO_DATA = "Aucune donnée"
|
||||
|
||||
class DashboardView(FilterMixin, generic.TemplateView):
|
||||
template_name = "statistiques/index.html"
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
|
||||
maraudes = self.get_maraudes_queryset()
|
||||
rencontres = self.get_observations_queryset()
|
||||
|
||||
context['nbr_maraudes'] = maraudes.count() or NO_DATA
|
||||
context['nbr_maraudes_jour'] = maraudes.filter(
|
||||
heure_debut=datetime.time(16,00)
|
||||
).count() or NO_DATA
|
||||
context['nbr_rencontres'] = rencontres.count() or NO_DATA
|
||||
try:
|
||||
context['moy_rencontres'] = int(context['nbr_rencontres'] / context['nbr_maraudes'])
|
||||
except (ZeroDivisionError, TypeError):
|
||||
context['moy_rencontres'] = NO_DATA
|
||||
|
||||
if self.year and not self.month: #Show rencontres_par_mois graph
|
||||
par_mois = rencontres.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 = 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
|
||||
|
||||
|
||||
|
||||
class PieChartView(FilterMixin, generic.TemplateView):
|
||||
template_name = "statistiques/typologie.html"
|
||||
|
||||
def get_graphs(self):
|
||||
sujets = self.get_sujets_queryset()
|
||||
# Insertion des champs 'âge' et 'genre' du modèle notes.Sujet
|
||||
for field in Sujet._meta.fields:
|
||||
if field.name == 'genre':
|
||||
yield str(field.verbose_name), PieWrapper(sujets, field)
|
||||
if field.name == 'age':
|
||||
categories = (
|
||||
('Mineurs', range(0,18)),
|
||||
('18-24', range(18,25)),
|
||||
('25-34', range(25,35)),
|
||||
('35-44', range(35,45)),
|
||||
('45-54', range(45,55)),
|
||||
('+ de 55', range(55,110)),
|
||||
)
|
||||
nbr_sujets = lambda rg: sujets.filter(age__in=rg).count()
|
||||
|
||||
yield "Âge", PieWrapper(
|
||||
data=[("age", "count")] +
|
||||
[(label, nbr_sujets(rg))
|
||||
for label, rg in categories] +
|
||||
[("Ne sait pas", sujets.filter(age=None).count())],
|
||||
title="Âge des sujets")
|
||||
|
||||
# Puis des champs du modèle statistiques.FicheStatistique
|
||||
# dans leur ordre de déclaration
|
||||
queryset = self.get_fichestatistiques_queryset()
|
||||
for field in FicheStatistique._meta.fields:
|
||||
if field.__class__ in (NullBooleanField, CharField):
|
||||
yield str(field.verbose_name), PieWrapper(queryset, field)
|
||||
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
context['graphs'] = [(title, graph) for title, graph in self.get_graphs()]
|
||||
context['queryset'] = self.get_fichestatistiques_queryset()
|
||||
return context
|
||||
|
||||
|
||||
|
||||
# AjaxMixin
|
||||
|
||||
class AjaxOrRedirectMixin:
|
||||
""" For view that should be retrieved by Ajax only. If not,
|
||||
redirects to the primary view where these are displayed """
|
||||
|
||||
def get(self, *args, **kwargs):
|
||||
""" Redirect to complete details view if request is not ajax """
|
||||
if not self.request.is_ajax():
|
||||
return redirect("notes:details-sujet", pk=self.get_object().pk)
|
||||
return super().get(*args, **kwargs)
|
||||
|
||||
|
||||
|
||||
class StatistiquesDetailsView(AjaxOrRedirectMixin, generic.DetailView):
|
||||
|
||||
model = FicheStatistique
|
||||
template_name = "statistiques/fiche_stats_details.html"
|
||||
|
||||
|
||||
|
||||
class StatistiquesUpdateView(AjaxOrRedirectMixin, generic.UpdateView):
|
||||
|
||||
model = FicheStatistique
|
||||
form_class = StatistiquesForm
|
||||
template_name = "statistiques/fiche_stats_update.html"
|
||||
Reference in New Issue
Block a user