From 74f4d94f285f4a3b6a33a3dae37ed8ec8b3f2160 Mon Sep 17 00:00:00 2001 From: Qwann Date: Tue, 27 Dec 2016 15:44:58 +0100 Subject: [PATCH] first fixes --- kfet/statistic.py | 437 +++++++++++++++++++++++++++++++++++++++++----- kfet/views.py | 367 +------------------------------------- 2 files changed, 398 insertions(+), 406 deletions(-) diff --git a/kfet/statistic.py b/kfet/statistic.py index 94aa529b..d50f0298 100644 --- a/kfet/statistic.py +++ b/kfet/statistic.py @@ -1,44 +1,11 @@ # -*- coding: utf-8 -*- from django.utils import timezone - -french_days = { - 1: "lundi", - 2: "mardi", - 3: "mercredi", - 4: "jeudi", - 5: "vendredi", - 6: "samedi", - 7: "dimanche", - } - -french_months = { - 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", - } - - -def dayname(date): - return french_days[date.isoweekday()] - - -def weekname(date): - (_, a, _) = date.isocalendar() - week_num = a - return "semaine %d" % week_num - - -def monthname(date): - return french_months[date.month] +from django.utils.decorators import method_decorator +from django.views.generic import DetailView +from django.views.generic.list import BaseListView, MultipleObjectTemplateResponseMixin +from django.views.generic.detail import BaseDetailView, SingleObjectTemplateResponseMixin +from django.contrib.auth.decorators import login_required +from kfet.models import (Account, Article, Operation, OperationGroup, Transfer) # Pareil mais pour une liste de dates @@ -46,7 +13,7 @@ def monthname(date): def daynames(dates): names = {} for i in dates: - names[i] = dayname(dates[i]) + names[i] = dates[i].strftime("%A") return names @@ -55,7 +22,7 @@ def daynames(dates): def weeknames(dates): names = {} for i in dates: - names[i] = weekname(dates[i]) + names[i] = "Semaine " + dates[i].strftime("%W") return names @@ -64,7 +31,7 @@ def weeknames(dates): def monthnames(dates): names = {} for i in dates: - names[i] = monthname(dates[i]) + names[i] = dates[i].strftime("%B") return names @@ -135,3 +102,389 @@ def tot_ventes(queryset): for op in queryset: res += op.article_nb return res + + +# ----- +# Views +# ----- +# --------------- +# Vues génériques +# --------------- +# source : docs.djangoproject.com/fr/1.10/topics/class-based-views/mixins/ +class JSONResponseMixin(object): + """ + A mixin that can be used to render a JSON response. + """ + def render_to_json_response(self, context, **response_kwargs): + """ + Returns a JSON response, transforming 'context' to make the payload. + """ + return JsonResponse( + self.get_data(context), + **response_kwargs + ) + + def get_data(self, context): + """ + Returns an object that will be serialized as JSON by json.dumps(). + """ + # Note: This is *EXTREMELY* naive; in reality, you'll need + # to do much more complex handling to ensure that arbitrary + # objects -- such as Django model instances or querysets + # -- can be serialized as JSON. + return context + +class HybridDetailView(JSONResponseMixin, + SingleObjectTemplateResponseMixin, + BaseDetailView): + """ + Returns a DetailView as a html page except if it asked a JSON + file by the GET method in witch case it returns a JSON response. + """ + def render_to_response(self, context): + # Look for a 'format=json' GET argument + if self.request.GET.get('format') == 'json': + return self.render_to_json_response(context) + else: + return super(HybridDetailView, self).render_to_response(context) + + +class HybridListView(JSONResponseMixin, + MultipleObjectTemplateResponseMixin, + BaseListView): + """ + Returns a ListView as a html page except if it asked a JSON + file by the GET method in witch case it returns a JSON response. + """ + def render_to_response(self, context): + # Look for a 'format=json' GET argument + if self.request.GET.get('format') == 'json': + return self.render_to_json_response(context) + else: + return super(HybridListView, self).render_to_response(context) + +class ObjectResumeStat(DetailView): + """ + Summarize all the stats of an object + DOES NOT RETURN A JSON RESPONSE + """ + template_name = 'kfet/object_stat_resume.html' + context_object_name = 'lul' + id_prefix = 'id_a_definir' + # nombre de vues à résumer + nb_stat = 2 + # Le combienième est celui par defaut ? + # (entre 0 et nb_stat-1) + nb_default = 0 + stat_labels = ['stat_1', 'stat_2'] + stat_urls = ['url_1', 'url_2'] + + # sert à renverser les urls + # utile de le surcharger quand l'url prend d'autres arguments que l'id + def get_object_url_kwargs(self, **kwargs): + return {'pk': self.object.id} + + def get_context_data(self, **kwargs): + # On hérite pas + object_id = self.object.id + context = {} + stats = {} + for i in range(self.nb_stat): + stats[i] = { + 'label': self.stat_labels[i], + 'btn': "btn_%s_%d_%d" % (self.id_prefix, + object_id, + i), + 'url': reverse_lazy(self.stat_urls[i], + kwargs=self.get_object_url_kwargs()), + } + prefix = "%s_%d" % (self.id_prefix, object_id) + context['id_prefix'] = prefix + context['content_id'] = "content_%s" % prefix + context['stats'] = stats + context['default_stat'] = self.nb_default + context['object_id'] = object_id + return context + + +# ------------------ +# AccountStatBalance +# ------------------ +class AccountStatBalance(HybridDetailView): + """ + Returns a graph (or a JSON Response) of the evolution a the personnal + balance of a trigramm between begin_date and end_date + takes into account the Operations and the Transfers + does not takes intto account the balance offset + """ + model = Account + trigramme_url_kwarg = 'trigramme' + template_name = 'kfet/account_stat_balance.html' + context_object_name = 'account' + begin_date = this_morning() + end_date = timezone.now() # ne gère pas encore autre chose que now + anytime = False # un cas particulier + id_prefix = "lol" + + def get_object(self, **kwargs): + trigramme = self.kwargs.get(self.trigramme_url_kwarg) + return get_object_or_404(Account, trigramme=trigramme) + + def get_changes_list(self, **kwargs): + account = self.object + # On récupère les opérations + # TODO: retirer les opgroup dont tous les op sont annulées + opgroups = list(OperationGroup.objects + .filter(on_acc=account) + .filter(at__gte=self.begin_date) + .filter(at__lte=self.end_date)) + # On récupère les transferts reçus + received_transfers = list(Transfer.objects + .filter(to_acc=account) + .filter(canceled_at=None) + .filter(group__at__gte=self.begin_date) + .filter(group__at__lte=self.end_date)) + # On récupère les transferts émis + emitted_transfers = list(Transfer.objects + .filter(from_acc=account) + .filter(canceled_at=None) + .filter(group__at__gte=self.begin_date) + .filter(group__at__lte=self.end_date)) + # On transforme tout ça en une liste de dictionnaires sous la forme + # {'at': date, + # 'amount': changement de la balance (négatif si diminue la balance, + # positif si l'augmente), + # 'label': text descriptif, + # 'balance': état de la balance après l'action (0 pour le moment, + # sera mis à jour lors d'une + # autre passe) + # } + actions = [ + # Maintenant (à changer si on gère autre chose que now) + { + 'at': self.end_date.isoformat(), + 'amout': 0, + 'label': "actuel", + 'balance': 0, + } + ] + [ + { + 'at': op.at.isoformat(), + 'amount': op.amount, + 'label': str(op), + 'balance': 0, + } for op in opgroups + ] + [ + { + 'at': tr.group.at.isoformat(), + 'amount': tr.amount, + 'label': "%d€: %s -> %s" % (tr.amount, + tr.from_acc.trigramme, + tr.to_acc.trigramme), + 'balance': 0, + } for tr in received_transfers + ] + [ + { + 'at': tr.group.at.isoformat(), + 'amount': -tr.amount, + 'label': "%d€: %s -> %s" % (tr.amount, + tr.from_acc.trigramme, + tr.to_acc.trigramme), + 'balance': 0, + } for tr in emitted_transfers + ] + if not self.anytime: + actions += [ + # Date de début : + { + 'at': self.begin_date.isoformat(), + 'amount': 0, + 'label': "début", + 'balance': 0, + } + ] + # Maintenant on trie la liste des actions par ordre du plus récent + # an plus ancien et on met à jour la balance + actions = sorted(actions, key=lambda k: k['at'], reverse=True) + actions[0]['balance'] = account.balance + for i in range(len(actions)-1): + actions[i+1]['balance'] = actions[i]['balance'] \ + - actions[i+1]['amount'] + return actions + + def get_context_data(self, **kwargs): + context = {} + changes = self.get_changes_list() + context['changes'] = changes + context['chart_id'] = "%s_%s" % (self.id_prefix, + self.object.id) + context['min_date'] = changes[len(changes)-1]['at'] + context['max_date'] = changes[0]['at'] + # TODO: offset + return context + + @method_decorator(login_required) + def dispatch(self, *args, **kwargs): + return super(AccountStatBalance, self).dispatch(*args, **kwargs) + +# --------------- +# AccountStatLast +# --------------- +class AccountStatLast(HybridDetailView): + """ + Returns a graph (or a JSON Response) of the evolution a the personnal + consommation of a trigramm at the diffent dates precised + """ + model = Account + trigramme_url_kwarg = 'trigramme' + template_name = 'kfet/account_stat_last.html' + context_object_name = 'account' + end_date = timezone.now() + id_prefix = "lol" + + # doit rendre un dictionnaire des dates + # la première date correspond au début + # la dernière date est la fin de la dernière plage + def get_dates(self, **kwargs): + pass + + # doit rendre un dictionnaire des labels + # le dernier label ne sera pas utilisé + def get_labels(self, **kwargs): + pass + + def get_object(self, **kwargs): + trigramme = self.kwargs.get(self.trigramme_url_kwarg) + return get_object_or_404(Account, trigramme=trigramme) + + def sort_operations(self, **kwargs): + # On récupère les dates + dates = self.get_dates() + # On ajoute la date de fin + extended_dates = dates.copy() + extended_dates[len(dates)+1] = self.end_date + # 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(type='purchase') + .filter(group__on_acc=self.object) + .filter(canceled_at=None) + ) + operations = {} + for i in dates: + operations[i] = (all_operations + .filter(group__at__gte=extended_dates[i]) + .filter(group__at__lte=extended_dates[i+1]) + ) + return operations + + def get_context_data(self, **kwargs): + # On hérite + # en fait non, pas besoin et c'est chiant à dumper + # context = super(AccountStat, self).get_context_data(**kwargs) + context = {} + nb_ventes = {} + # On récupère les labels des dates + context['labels'] = self.get_labels().copy() + # On compte les opérations + operations = self.sort_operations() + for i in operations: + nb_ventes[i] = tot_ventes(operations[i]) + context['nb_ventes'] = nb_ventes + # ID unique + context['chart_id'] = "%s_%d" % (self.id_prefix, + self.object.id) + return context + + @method_decorator(login_required) + def dispatch(self, *args, **kwargs): + return super(AccountStatLast, self).dispatch(*args, **kwargs) + + +# --------------- +# ArticleStatLast +# --------------- +# Rend un graph des ventes sur une plage de temps à préciser. +# Le graphique distingue les ventes sur LIQ et sur les autres trigrammes +class ArticleStatLast(HybridDetailView): + """ + Returns a graph (or a JSON Response) of the consommation + of an article at the diffent dates precised + """ + model = Article + template_name = 'kfet/article_stat_last.html' + context_object_name = 'article' + end_date = timezone.now() + id_prefix = "lol" + + def render_to_response(self, context): + # Look for a 'format=json' GET argument + if self.request.GET.get('format') == 'json': + return self.render_to_json_response(context) + else: + return super(ArticleStatLast, self).render_to_response(context) + + # doit rendre un dictionnaire des dates + # la première date correspond au début + # la dernière date est la fin de la dernière plage + def get_dates(self, **kwargs): + pass + + # doit rendre un dictionnaire des labels + # le dernier label ne sera pas utilisé + def get_labels(self, **kwargs): + pass + def get_context_data(self, **kwargs): + # On hérite + # en fait non, pas besoin et c'est chiant à dumper + # context = super(ArticleStat, self).get_context_data(**kwargs) + context = {} + # On récupère les labels des dates + context['labels'] = self.get_labels().copy() + # On récupère les dates + dates = self.get_dates() + # On ajoute la date de fin + extended_dates = dates.copy() + extended_dates[len(dates)+1] = self.end_date + # 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(type='purchase') + .filter(article=self.object) + .filter(canceled_at=None) + ) + operations = {} + for i in dates: + operations[i] = (all_operations + .filter(group__at__gte=extended_dates[i]) + .filter(group__at__lte=extended_dates[i+1]) + ) + # On compte les opérations + nb_ventes = {} + nb_accounts = {} + nb_liq = {} + for i in operations: + nb_ventes[i] = tot_ventes(operations[i]) + nb_liq[i] = tot_ventes( + operations[i] + .filter(group__on_acc__trigramme='LIQ') + ) + nb_accounts[i] = tot_ventes( + operations[i] + .exclude(group__on_acc__trigramme='LIQ') + ) + context['nb_ventes'] = nb_ventes + context['nb_accounts'] = nb_accounts + context['nb_liq'] = nb_liq + # ID unique + context['chart_id'] = "%s_%d" % (self.id_prefix, + self.object.id) + return context + + @method_decorator(login_required) + def dispatch(self, *args, **kwargs): + return super(ArticleStatLast, self).dispatch(*args, **kwargs) diff --git a/kfet/views.py b/kfet/views.py index e2cb7a26..9cebfe0d 100644 --- a/kfet/views.py +++ b/kfet/views.py @@ -8,8 +8,6 @@ from django.shortcuts import render, get_object_or_404, redirect from django.core.exceptions import PermissionDenied, ValidationError from django.core.cache import cache from django.views.generic import ListView, DetailView -from django.views.generic.list import BaseListView, MultipleObjectTemplateResponseMixin -from django.views.generic.detail import BaseDetailView, SingleObjectTemplateResponseMixin from django.views.generic.edit import CreateView, UpdateView, DeleteView, FormView from django.core.urlresolvers import reverse_lazy from django.contrib import messages @@ -41,7 +39,9 @@ import statistics from .statistic import daynames, monthnames, weeknames, \ lastdays, lastweeks, lastmonths, \ this_morning, this_monday_morning, this_first_month_day, \ - tot_ventes + tot_ventes, \ + HybridDetailView, ObjectResumeStat, \ + ArticleStatLast, AccountStatBalance, AccountStatLast @login_required def home(request): @@ -1957,103 +1957,6 @@ class SupplierUpdate(SuccessMessageMixin, UpdateView): # ========== # Statistics # ========== - -# --------------- -# Vues génériques -# --------------- -# source : docs.djangoproject.com/fr/1.10/topics/class-based-views/mixins/ -class JSONResponseMixin(object): - """ - A mixin that can be used to render a JSON response. - """ - def render_to_json_response(self, context, **response_kwargs): - """ - Returns a JSON response, transforming 'context' to make the payload. - """ - return JsonResponse( - self.get_data(context), - **response_kwargs - ) - - def get_data(self, context): - """ - Returns an object that will be serialized as JSON by json.dumps(). - """ - # Note: This is *EXTREMELY* naive; in reality, you'll need - # to do much more complex handling to ensure that arbitrary - # objects -- such as Django model instances or querysets - # -- can be serialized as JSON. - return context - - -# Rend un DetailView en html sauf si on lui précise dans -# l'appel à get que l'on veut un json auquel cas il en rend un -class HybridDetailView(JSONResponseMixin, - SingleObjectTemplateResponseMixin, - BaseDetailView): - def render_to_response(self, context): - # Look for a 'format=json' GET argument - if self.request.GET.get('format') == 'json': - return self.render_to_json_response(context) - else: - return super(HybridDetailView, self).render_to_response(context) - - -# Rend un ListView en html sauf si on lui précise dans -# l'appel à get que l'on veut un json auquel cas il en rend un -class HybridListView(JSONResponseMixin, - MultipleObjectTemplateResponseMixin, - BaseListView): - def render_to_response(self, context): - # Look for a 'format=json' GET argument - if self.request.GET.get('format') == 'json': - return self.render_to_json_response(context) - else: - return super(HybridListView, self).render_to_response(context) - - -# Un résume des toutes les vues de stat d'un objet -# NE REND PAS DE JSON -class ObjectResumeStat(DetailView): - template_name = 'kfet/object_stat_resume.html' - context_object_name = 'lul' - id_prefix = 'id_a_definir' - # nombre de vues à résumer - nb_stat = 2 - # Le combienième est celui par defaut ? - # (entre 0 et nb_stat-1) - nb_default = 0 - stat_labels = ['stat_1', 'stat_2'] - stat_urls = ['url_1', 'url_2'] - - # sert à renverser les urls - # utile de le surcharger quand l'url prend d'autres arguments que l'id - def get_object_url_kwargs(self, **kwargs): - return {'pk': self.object.id} - - def get_context_data(self, **kwargs): - # On hérite pas - object_id = self.object.id - context = {} - stats = {} - for i in range(self.nb_stat): - stats[i] = { - 'label': self.stat_labels[i], - 'btn': "btn_%s_%d_%d" % (self.id_prefix, - object_id, - i), - 'url': reverse_lazy(self.stat_urls[i], - kwargs=self.get_object_url_kwargs()), - } - prefix = "%s_%d" % (self.id_prefix, object_id) - context['id_prefix'] = prefix - context['content_id'] = "content_%s" % prefix - context['stats'] = stats - context['default_stat'] = self.nb_default - context['object_id'] = object_id - return context - - # ----------------------- # Evolution Balance perso # ----------------------- @@ -2093,122 +1996,6 @@ class AccountStatBalanceAll(ObjectResumeStat): return super(AccountStatBalanceAll, self).dispatch(*args, **kwargs) -# Rend un graphe (ou un json) de l'évolution de la balance personelle -# entre begin_date et end_date -# prend en compte les opérations et les transferts -# ne prend pas en compte les balance offset (TODO?) -class AccountStatBalance(HybridDetailView): - model = Account - trigramme_url_kwarg = 'trigramme' - template_name = 'kfet/account_stat_balance.html' - context_object_name = 'account' - begin_date = this_morning() - end_date = timezone.now() # ne gère pas encore autre chose que now - anytime = False # un cas particulier - id_prefix = "lol" - - def get_object(self, **kwargs): - trigramme = self.kwargs.get(self.trigramme_url_kwarg) - return get_object_or_404(Account, trigramme=trigramme) - - def get_changes_list(self, **kwargs): - account = self.object - # On récupère les opérations - # TODO: retirer les opgroup dont tous les op sont annulées - opgroups = list(OperationGroup.objects - .filter(on_acc=account) - .filter(at__gte=self.begin_date) - .filter(at__lte=self.end_date)) - # On récupère les transferts reçus - received_transfers = list(Transfer.objects - .filter(to_acc=account) - .filter(canceled_at=None) - .filter(group__at__gte=self.begin_date) - .filter(group__at__lte=self.end_date)) - # On récupère les transferts émis - emitted_transfers = list(Transfer.objects - .filter(from_acc=account) - .filter(canceled_at=None) - .filter(group__at__gte=self.begin_date) - .filter(group__at__lte=self.end_date)) - # On transforme tout ça en une liste de dictionnaires sous la forme - # {'at': date, - # 'amount': changement de la balance (négatif si diminue la balance, - # positif si l'augmente), - # 'label': text descriptif, - # 'balance': état de la balance après l'action (0 pour le moment, - # sera mis à jour lors d'une - # autre passe) - # } - actions = [ - # Maintenant (à changer si on gère autre chose que now) - { - 'at': self.end_date.isoformat(), - 'amout': 0, - 'label': "actuel", - 'balance': 0, - } - ] + [ - { - 'at': op.at.isoformat(), - 'amount': op.amount, - 'label': str(op), - 'balance': 0, - } for op in opgroups - ] + [ - { - 'at': tr.group.at.isoformat(), - 'amount': tr.amount, - 'label': "%d€: %s -> %s" % (tr.amount, - tr.from_acc.trigramme, - tr.to_acc.trigramme), - 'balance': 0, - } for tr in received_transfers - ] + [ - { - 'at': tr.group.at.isoformat(), - 'amount': -tr.amount, - 'label': "%d€: %s -> %s" % (tr.amount, - tr.from_acc.trigramme, - tr.to_acc.trigramme), - 'balance': 0, - } for tr in emitted_transfers - ] - if not self.anytime: - actions += [ - # Date de début : - { - 'at': self.begin_date.isoformat(), - 'amount': 0, - 'label': "début", - 'balance': 0, - } - ] - # Maintenant on trie la liste des actions par ordre du plus récent - # an plus ancien et on met à jour la balance - actions = sorted(actions, key=lambda k: k['at'], reverse=True) - actions[0]['balance'] = account.balance - for i in range(len(actions)-1): - actions[i+1]['balance'] = actions[i]['balance'] \ - - actions[i+1]['amount'] - return actions - - def get_context_data(self, **kwargs): - context = {} - changes = self.get_changes_list() - context['changes'] = changes - context['chart_id'] = "%s_%s" % (self.id_prefix, - self.object.id) - context['min_date'] = changes[len(changes)-1]['at'] - context['max_date'] = changes[0]['at'] - # TODO: offset - return context - - @method_decorator(login_required) - def dispatch(self, *args, **kwargs): - return super(AccountStatBalance, self).dispatch(*args, **kwargs) - - # Rend l'évolution de la balance perso de ces 30 derniers jours class AccountStatBalanceMonth(AccountStatBalance): begin_date = this_morning() - timezone.timedelta(days=30) @@ -2275,73 +2062,6 @@ class AccountStatLastAll(ObjectResumeStat): return super(AccountStatLastAll, self).dispatch(*args, **kwargs) -class AccountStatLast(HybridDetailView): - model = Account - trigramme_url_kwarg = 'trigramme' - template_name = 'kfet/account_stat_last.html' - context_object_name = 'account' - end_date = timezone.now() - id_prefix = "lol" - - # doit rendre un dictionnaire des dates - # la première date correspond au début - # la dernière date est la fin de la dernière plage - def get_dates(self, **kwargs): - pass - - # doit rendre un dictionnaire des labels - # le dernier label ne sera pas utilisé - def get_labels(self, **kwargs): - pass - - def get_object(self, **kwargs): - trigramme = self.kwargs.get(self.trigramme_url_kwarg) - return get_object_or_404(Account, trigramme=trigramme) - - def sort_operations(self, **kwargs): - # On récupère les dates - dates = self.get_dates() - # On ajoute la date de fin - extended_dates = dates.copy() - extended_dates[len(dates)+1] = self.end_date - # 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(type='purchase') - .filter(group__on_acc=self.object) - .filter(canceled_at=None) - ) - operations = {} - for i in dates: - operations[i] = (all_operations - .filter(group__at__gte=extended_dates[i]) - .filter(group__at__lte=extended_dates[i+1]) - ) - return operations - - def get_context_data(self, **kwargs): - # On hérite - # en fait non, pas besoin et c'est chiant à dumper - # context = super(AccountStat, self).get_context_data(**kwargs) - context = {} - nb_ventes = {} - # On récupère les labels des dates - context['labels'] = self.get_labels().copy() - # On compte les opérations - operations = self.sort_operations() - for i in operations: - nb_ventes[i] = tot_ventes(operations[i]) - context['nb_ventes'] = nb_ventes - # ID unique - context['chart_id'] = "%s_%d" % (self.id_prefix, - self.object.id) - return context - - @method_decorator(login_required) - def dispatch(self, *args, **kwargs): - return super(AccountStatLast, self).dispatch(*args, **kwargs) # Rend les achats pour ce compte des 7 derniers jours @@ -2413,87 +2133,6 @@ class ArticleStatLastAll(ObjectResumeStat): return super(ArticleStatLastAll, self).dispatch(*args, **kwargs) -# Rend un graph des ventes sur une plage de temps à préciser. -# Le graphique distingue les ventes sur LIQ et sur les autres trigrammes -class ArticleStatLast(HybridDetailView): - model = Article - template_name = 'kfet/article_stat_last.html' - context_object_name = 'article' - end_date = timezone.now() - id_prefix = "lol" - - def render_to_response(self, context): - # Look for a 'format=json' GET argument - if self.request.GET.get('format') == 'json': - return self.render_to_json_response(context) - else: - return super(ArticleStatLast, self).render_to_response(context) - - # doit rendre un dictionnaire des dates - # la première date correspond au début - # la dernière date est la fin de la dernière plage - def get_dates(self, **kwargs): - pass - - # doit rendre un dictionnaire des labels - # le dernier label ne sera pas utilisé - def get_labels(self, **kwargs): - pass - - def get_context_data(self, **kwargs): - # On hérite - # en fait non, pas besoin et c'est chiant à dumper - # context = super(ArticleStat, self).get_context_data(**kwargs) - context = {} - # On récupère les labels des dates - context['labels'] = self.get_labels().copy() - # On récupère les dates - dates = self.get_dates() - # On ajoute la date de fin - extended_dates = dates.copy() - extended_dates[len(dates)+1] = self.end_date - # 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(type='purchase') - .filter(article=self.object) - .filter(canceled_at=None) - ) - operations = {} - for i in dates: - operations[i] = (all_operations - .filter(group__at__gte=extended_dates[i]) - .filter(group__at__lte=extended_dates[i+1]) - ) - # On compte les opérations - nb_ventes = {} - nb_accounts = {} - nb_liq = {} - for i in operations: - nb_ventes[i] = tot_ventes(operations[i]) - nb_liq[i] = tot_ventes( - operations[i] - .filter(group__on_acc__trigramme='LIQ') - ) - nb_accounts[i] = tot_ventes( - operations[i] - .exclude(group__on_acc__trigramme='LIQ') - ) - context['nb_ventes'] = nb_ventes - context['nb_accounts'] = nb_accounts - context['nb_liq'] = nb_liq - # ID unique - context['chart_id'] = "%s_%d" % (self.id_prefix, - self.object.id) - return context - - @method_decorator(login_required) - def dispatch(self, *args, **kwargs): - return super(ArticleStatLast, self).dispatch(*args, **kwargs) - - # Rend les ventes des 7 derniers jours # Aujourd'hui non compris class ArticleStatLastDay(ArticleStatLast):