From 8f9c94fe10799d170d2685eb1e13997e88bee4cf Mon Sep 17 00:00:00 2001 From: Ludovic Stephan Date: Wed, 16 Sep 2020 17:16:49 +0200 Subject: [PATCH] Plein de nettoyage partout --- kfet/statistic.py | 64 ++++++---------------------------- kfet/views.py | 87 ++++++++++++++++++++++++++--------------------- 2 files changed, 58 insertions(+), 93 deletions(-) diff --git a/kfet/statistic.py b/kfet/statistic.py index b2c1d882..1f66dd3f 100644 --- a/kfet/statistic.py +++ b/kfet/statistic.py @@ -1,6 +1,5 @@ from datetime import date, datetime, time, timedelta -from dateutil.parser import parse as dateutil_parse from dateutil.relativedelta import relativedelta from django.utils import timezone @@ -144,67 +143,24 @@ class MonthScale(Scale): return to_kfet_day(dt).replace(day=1) -def scale_url_params(scales_def, **other_url_params): +SCALE_CLASS_CHOICES = ((cls.name, cls.name) for cls in Scale.__subclasses__()) +SCALE_DICT = {cls.name: cls for cls in Scale.__subclasses__()} + + +def scale_url_params(scales_def): """ Convertit une spécification de scales en arguments GET utilisables par ScaleMixin. La spécification est de la forme suivante : - - scales_def : liste de champs de la forme (label, scale) + - scales_def : liste de champs de la forme (label, scale, scale_args, default) + - scale_args : arguments à passer à Scale.__init__ - - other_url_params : paramètres GET supplémentaires + - default : le graphe à montrer par défaut """ params_list = [] for label, cls, params, default in scales_def: - url_params = {"scale_name": cls.name} - url_params.update({"scale_" + key: value for key, value in params.items()}) - url_params.update(other_url_params) + url_params = {"scale-name": cls.name} + url_params.update({"scale-" + key: value for key, value in params.items()}) params_list.append(dict(label=label, url_params=url_params, default=default)) return params_list - - -class ScaleMixin(object): - def parse_scale_args(self): - """ - Récupère les paramètres de subdivision encodés dans une requête GET. - """ - scale_args = {} - - name = self.request.GET.get("scale_name", None) - if name is not None: - scale_args["name"] = name - - n_steps = self.request.GET.get("scale_n_steps", None) - if n_steps is not None: - scale_args["n_steps"] = int(n_steps) - - begin = self.request.GET.get("scale_begin", None) - if begin is not None: - scale_args["begin"] = dateutil_parse(begin) - - end = self.request.GET.get("scale_send", None) - if end is not None: - scale_args["end"] = dateutil_parse(end) - - last = self.request.GET.get("scale_last", None) - if last is not None: - scale_args["last"] = last in ["true", "True", "1"] and True or False - - return scale_args - - def get_context_data(self, *args, **kwargs): - # On n'hérite pas - - scale_args = self.parse_scale_args() - scale_name = scale_args.pop("name", None) - scale_cls = Scale.by_name(scale_name) - - if scale_cls is None: - self.scale = self.get_default_scale() - else: - self.scale = scale_cls(**scale_args) - - return {"labels": self.scale.get_labels()} - - def get_default_scale(self): - return DayScale(n_steps=7, last=True) diff --git a/kfet/views.py b/kfet/views.py index 3a3c8cd0..4e7dd49b 100644 --- a/kfet/views.py +++ b/kfet/views.py @@ -1,4 +1,3 @@ -import ast import heapq import statistics from collections import defaultdict @@ -12,6 +11,7 @@ from django.contrib.auth.decorators import login_required, permission_required from django.contrib.auth.mixins import PermissionRequiredMixin from django.contrib.auth.models import Permission, User from django.contrib.messages.views import SuccessMessageMixin +from django.core.exceptions import SuspiciousOperation from django.db import transaction from django.db.models import Count, F, Prefetch, Q, Sum from django.forms import formset_factory @@ -36,6 +36,7 @@ from kfet.forms import ( AccountNoTriForm, AccountPwdForm, AccountRestrictForm, + AccountStatForm, AccountTriForm, AddcostForm, ArticleForm, @@ -55,6 +56,7 @@ from kfet.forms import ( KPsulOperationGroupForm, OrderArticleForm, OrderArticleToInventoryForm, + StatScaleForm, TransferFormSet, UserForm, UserGroupForm, @@ -78,7 +80,7 @@ from kfet.models import ( Transfer, TransferGroup, ) -from kfet.statistic import DayScale, MonthScale, ScaleMixin, WeekScale, scale_url_params +from kfet.statistic import SCALE_DICT, DayScale, MonthScale, WeekScale, scale_url_params from shared.views import AutocompleteView from .auth import KFET_GENERIC_TRIGRAMME @@ -2295,6 +2297,26 @@ class UserAccountMixin: return obj +class ScaleMixin(object): + """Mixin pour utiliser les outils de `kfet.statistic`.""" + + def get_context_data(self, *args, **kwargs): + # On n'hérite pas + form = StatScaleForm(self.request.GET, prefix="scale") + + if not form.is_valid(): + raise SuspiciousOperation( + "Invalid StatScaleForm. Did someone tamper with the GET parameters ?" + ) + + scale_name = form.cleaned_data.pop("name") + scale_cls = SCALE_DICT.get(scale_name) + + self.scale = scale_cls(**form.cleaned_data) + + return {"labels": self.scale.get_labels()} + + # ----------------------- # Evolution Balance perso # ----------------------- @@ -2407,15 +2429,14 @@ class AccountStatBalance(UserAccountMixin, JSONDetailView): def get_context_data(self, *args, **kwargs): context = {} - last_days = self.request.GET.get("last_days", None) - if last_days is not None: - last_days = int(last_days) - begin_date = self.request.GET.get("begin_date", None) - end_date = self.request.GET.get("end_date", None) + form = AccountStatForm(self.request.GET) - changes = self.get_changes_list( - last_days=last_days, begin_date=begin_date, end_date=end_date - ) + if not form.is_valid(): + raise SuspiciousOperation( + "Invalid AccountStatForm. Did someone tamper with the GET parameters ?" + ) + + changes = self.get_changes_list(**form.cleaned_data) context["charts"] = [ {"color": "rgb(200, 20, 60)", "label": "Balance", "values": changes} @@ -2470,28 +2491,16 @@ class AccountStatOperation(UserAccountMixin, ScaleMixin, JSONDetailView): slug_url_kwarg = "trigramme" slug_field = "trigramme" - def get_operations(self, types=None): - # On selectionne les opérations qui correspondent - # à l'article en question et qui ne sont pas annulées - # puis on choisi pour chaques intervalle les opérations - # effectuées dans ces intervalles de temps - all_operations = ( - Operation.objects.filter(group__on_acc=self.object, canceled_at=None) - .values("article_nb", "group__at") - .order_by("group__at") - ) - if types is not None: - all_operations = all_operations.filter(type__in=types) - return all_operations - def get_context_data(self, *args, **kwargs): context = super().get_context_data(*args, **kwargs) - types = self.request.GET.get("types", None) - if types is not None: - types = ast.literal_eval(types) - - operations = self.get_operations(types=types) + operations = ( + Operation.objects.filter( + type=Operation.PURCHASE, group__on_acc=self.object, canceled_at=None + ) + .values("article_nb", "group__at") + .order_by("group__at") + ) # On compte les opérations nb_ventes = self.scale.chunkify_qs( operations, field="group__at", aggregate=Sum("article_nb") @@ -2508,7 +2517,7 @@ class AccountStatOperation(UserAccountMixin, ScaleMixin, JSONDetailView): # ------------------------ -# Article Satistiques Last +# Article Statistiques Last # ------------------------ @@ -2563,16 +2572,16 @@ class ArticleStatSales(ScaleMixin, JSONDetailView): .values("group__at", "article_nb") .order_by("group__at") ) - liq_only = all_purchases.filter(group__on_acc__trigramme="LIQ") - liq_exclude = all_purchases.exclude(group__on_acc__trigramme="LIQ") + cof_accts = all_purchases.filter(group__on_acc__profile__is_cof=True) + noncof_accts = all_purchases.exclude(group__on_acc__profile__is_cof=True) - nb_liq = scale.chunkify_qs( - liq_only, field="group__at", aggregate=Sum("article_nb") + nb_cof = scale.chunkify_qs( + cof_accts, field="group__at", aggregate=Sum("article_nb") ) - nb_accounts = scale.chunkify_qs( - liq_exclude, field="group__at", aggregate=Sum("article_nb") + nb_noncof = scale.chunkify_qs( + noncof_accts, field="group__at", aggregate=Sum("article_nb") ) - nb_ventes = [n1 + n2 for n1, n2 in zip(nb_liq, nb_accounts)] + nb_ventes = [n1 + n2 for n1, n2 in zip(nb_cof, nb_noncof)] context["charts"] = [ { @@ -2580,11 +2589,11 @@ class ArticleStatSales(ScaleMixin, JSONDetailView): "label": "Toutes consommations", "values": nb_ventes, }, - {"color": "rgb(54, 162, 235)", "label": "LIQ", "values": nb_liq}, + {"color": "rgb(54, 162, 235)", "label": "LIQ", "values": nb_cof}, { "color": "rgb(255, 205, 86)", "label": "Comptes K-Fêt", - "values": nb_accounts, + "values": nb_noncof, }, ] return context