diff --git a/kfet/statistic.py b/kfet/statistic.py index 5b5e297a..8dd2e6d0 100644 --- a/kfet/statistic.py +++ b/kfet/statistic.py @@ -1,5 +1,7 @@ # -*- coding: utf-8 -*- +import ast + from dateutil.relativedelta import relativedelta from django.utils import timezone @@ -50,7 +52,7 @@ class StatScale(object): for cls in StatScale.__subclasses__(): if cls.name == name: return cls - raise Exception('scale %s not found' % name) + return None def get_from(self, dt): return self.std_chunk and self.get_chunk_start(dt) or dt @@ -114,32 +116,38 @@ class MonthStatScale(StatScale): return to_kfet_day(dt).replace(day=1) -def this_first_month_day(): - now = timezone.now() - first_day = timezone.datetime(year=now.year, - month=now.month, - day=1, - hour=KFET_WAKES_UP_AT) - return first_day +def stat_manifest(scales_def=None, scale_args=None, **url_params): + if scales_def is None: + scales_def = [] + if scale_args is None: + scale_args = {} + return [ + dict( + label=label, + url_params=dict( + scale=cls.name, + scale_args=scale_args, + **url_params, + ), + ) + for label, cls in scales_def + ] -def this_monday_morning(): - now = timezone.now() - monday = now - timezone.timedelta(days=now.isoweekday()-1) - monday_morning = timezone.datetime(year=monday.year, - month=monday.month, - day=monday.day, - hour=KFET_WAKES_UP_AT) - return monday_morning - - -def this_morning(): - now = timezone.now() - morning = timezone.datetime(year=now.year, - month=now.month, - day=now.day, - hour=KFET_WAKES_UP_AT) - return morning +def last_stats_manifest(scales_def=None, scale_args=None, **url_params): + scales_def = [ + ('Derniers mois', MonthStatScale, ), + ('Dernières semaines', WeekStatScale, ), + ('Derniers jours', DayStatScale, ), + ] + if scale_args is None: + scale_args = {} + scale_args.update(dict( + last=True, + n_steps=7, + )) + return stat_manifest(scales_def=scales_def, scale_args=scale_args, + **url_params) # Étant donné un queryset d'operations @@ -147,3 +155,38 @@ def this_morning(): def tot_ventes(queryset): res = queryset.aggregate(Sum('article_nb'))['article_nb__sum'] return res and res or 0 + + +class ScaleMixin(object): + + def get_context_data(self, *args, **kwargs): + context = super().get_context_data(*args, **kwargs) + + scale_name = self.request.GET.get('scale', None) + scale_args = self.request.GET.get('scale_args', None) + + cls = StatScale.by_name(scale_name) + if cls is None: + scale = self.get_default_scale() + else: + scale_args = self.request.GET.get('scale_args', {}) + if isinstance(scale_args, str): + scale_args = ast.literal_eval(scale_args) + scale = cls(**scale_args) + + self.scale = scale + context['labels'] = scale.get_labels() + return context + + def get_default_scale(self): + return DayStatScale(n_steps=7, last=True) + + def chunkify_qs(self, qs, scale, field=None): + if field is None: + field = 'at' + begin_f = '{}__gte'.format(field) + end_f = '{}__lte'.format(field) + return [ + qs.filter(**{begin_f: begin, end_f: end}) + for begin, end in scale + ] diff --git a/kfet/templates/kfet/account_read.html b/kfet/templates/kfet/account_read.html index 449914f9..3c2ccbcd 100644 --- a/kfet/templates/kfet/account_read.html +++ b/kfet/templates/kfet/account_read.html @@ -17,11 +17,11 @@ + +{% endblock %} diff --git a/kfet/urls.py b/kfet/urls.py index 2eeef513..0ffeb84f 100644 --- a/kfet/urls.py +++ b/kfet/urls.py @@ -140,20 +140,14 @@ urlpatterns = [ # Article - Update url('^articles/(?P\d+)/edit$', teamkfet_required(views.ArticleUpdate.as_view()), - name = 'kfet.article.update'), + name='kfet.article.update'), # Article - Statistics - url('^articles/(?P\d+)/stat/last/$', - views.ArticleStatLastAll.as_view(), - name = 'kfet.article.stat.last'), - url('^articles/(?P\d+)/stat/last/month/$', - views.ArticleStatLastMonth.as_view(), - name = 'kfet.article.stat.last.month'), - url('^articles/(?P\d+)/stat/last/week/$', - views.ArticleStatLastWeek.as_view(), - name = 'kfet.article.stat.last.week'), - url('^articles/(?P\d+)/stat/last/day/$', - views.ArticleStatLastDay.as_view(), - name = 'kfet.article.stat.last.day'), + url(r'^articles/(?P\d+)/stat/sales/list$', + views.ArticleStatSalesList.as_view(), + name='kfet.article.stat.sales.list'), + url(r'^articles/(?P\d+)/stat/sales$', + views.ArticleStatSales.as_view(), + name='kfet.article.stat.sales'), # ----- # K-Psul urls diff --git a/kfet/views.py b/kfet/views.py index 0b62cd2e..3b50cc0a 100644 --- a/kfet/views.py +++ b/kfet/views.py @@ -48,10 +48,8 @@ from decimal import Decimal import django_cas_ng import heapq import statistics -from kfet.statistic import DayStatScale, MonthStatScale, StatScale, WeekStatScale -from .statistic import ( - this_morning, this_monday_morning, this_first_month_day, tot_ventes, -) +from kfet.statistic import ScaleMixin, last_stats_manifest, tot_ventes + class Home(TemplateView): template_name = "kfet/home.html" @@ -2155,10 +2153,8 @@ class AccountStatBalance(PkUrlMixin, JSONDetailView): account = self.object # prepare filters - if end_date is None: - end_date = this_morning() - if last_days is not None: + end_date = timezone.now() begin_date = end_date - timezone.timedelta(days=last_days) # prepare querysets @@ -2289,43 +2285,7 @@ class AccountStatOperationList(PkUrlMixin, SingleResumeStat): pk_url_kwarg = 'trigramme' id_prefix = ID_PREFIX_ACC_LAST nb_default = 2 - stats = [ - { - 'label': 'Derniers mois', - 'url_params': dict( - types=[Operation.PURCHASE], - scale=MonthStatScale.name, - scale_args=dict( - last=True, - n_steps=7, - ), - ), - }, - { - 'label': 'Dernières semaines', - 'url_params': dict( - types=[Operation.PURCHASE], - last_days=49, - scale=WeekStatScale.name, - scale_args=dict( - last=True, - n_steps=7, - ), - ), - }, - { - 'label': 'Derniers jours', - 'url_params': dict( - types=[Operation.PURCHASE], - last_days=7, - scale=DayStatScale.name, - scale_args=dict( - last=True, - n_steps=7, - ), - ), - }, - ] + stats = last_stats_manifest(types=[Operation.PURCHASE]) url_stat = 'kfet.account.stat.operation' def get_object(self, *args, **kwargs): @@ -2339,7 +2299,7 @@ class AccountStatOperationList(PkUrlMixin, SingleResumeStat): return super().dispatch(*args, **kwargs) -class AccountStatOperation(PkUrlMixin, JSONDetailView): +class AccountStatOperation(ScaleMixin, PkUrlMixin, JSONDetailView): """ Returns a JSON containing the evolution a the personnal consommation of a trigramme at the diffent dates specified @@ -2359,38 +2319,24 @@ class AccountStatOperation(PkUrlMixin, JSONDetailView): .filter(group__on_acc=self.object) .filter(canceled_at=None) ) - operations = [] - for begin, end in scale: - operations.append(all_operations - .filter(group__at__gte=begin, - group__at__lte=end)) - return operations + chunks = self.chunkify_qs(all_operations, scale, field='group__at') + return chunks - def get_context_data(self, **kwargs): - context = {} - - scale = self.request.GET.get('scale', None) - if scale is None: - scale = DayStatScale(n_steps=7, last=True) - else: - scale_cls = StatScale.by_name(scale) - scale_args = self.request.GET.get('scale_args', '{}') - scale = scale_cls(**ast.literal_eval(scale_args)) - print(scale.datetimes) + def get_context_data(self, *args, **kwargs): + old_ctx = super().get_context_data(*args, **kwargs) + context = {'labels': old_ctx['labels']} + scale = self.scale types = self.request.GET.get('types', None) if types is not None: types = ast.literal_eval(types) operations = self.get_operations(types=types, scale=scale) - # On récupère les labels des dates - context['labels'] = scale.get_labels() # On compte les opérations nb_ventes = [] for chunk in operations: nb_ventes.append(tot_ventes(chunk)) - context['labels'] = scale.get_labels() context['charts'] = [{"color": "rgb(255, 99, 132)", "label": "NB items achetés", "values": nb_ventes}] @@ -2418,141 +2364,66 @@ ID_PREFIX_ART_LAST_MONTHS = "last_months_art" # Un résumé de toutes les vues ArticleStatLast # NE REND PAS DE JSON -class ArticleStatLastAll(SingleResumeStat): +class ArticleStatSalesList(SingleResumeStat): model = Article context_object_name = 'article' id_prefix = ID_PREFIX_ART_LAST - nb_stat = 3 nb_default = 2 - stat_labels = ["Derniers mois", "Dernières semaines", "Derniers jours"] - stat_urls = ['kfet.article.stat.last.month', - 'kfet.article.stat.last.week', - 'kfet.article.stat.last.day'] + url_stat = 'kfet.article.stat.sales' + stats = last_stats_manifest() - @method_decorator(login_required) + @method_decorator(teamkfet_required) def dispatch(self, *args, **kwargs): - return super(ArticleStatLastAll, self).dispatch(*args, **kwargs) + return super().dispatch(*args, **kwargs) -class ArticleStatLast(JSONDetailView): +class ArticleStatSales(ScaleMixin, JSONDetailView): """ Returns a JSON containing the consommation of an article at the diffent dates precised """ model = Article context_object_name = 'article' - end_date = timezone.now() - id_prefix = "" - 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) + def get_context_data(self, *args, **kwargs): + old_ctx = super().get_context_data(*args, **kwargs) + context = {'labels': old_ctx['labels']} + scale = self.scale - # 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): - 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]) - ) + all_operations = ( + Operation.objects + .filter(type=Operation.PURCHASE, + article=self.object, + canceled_at=None, + ) + ) + chunks = self.chunkify_qs(all_operations, scale, field='group__at') # 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['charts'] = [ { "color": "rgb(255, 99, 132)", - "label": "Toutes consommations", - "values": nb_ventes }, - { "color": "rgb(54, 162, 235)", - "label": "LIQ", - "values": nb_liq }, - { "color": "rgb(255, 205, 86)", - "label": "Comptes K-Fêt", - "values": nb_accounts } ] + nb_ventes = [] + nb_accounts = [] + nb_liq = [] + for qs in chunks: + nb_ventes.append( + tot_ventes(qs)) + nb_liq.append( + tot_ventes(qs.filter(group__on_acc__trigramme='LIQ'))) + nb_accounts.append( + tot_ventes(qs.exclude(group__on_acc__trigramme='LIQ'))) + context['charts'] = [{"color": "rgb(255, 99, 132)", + "label": "Toutes consommations", + "values": nb_ventes}, + {"color": "rgb(54, 162, 235)", + "label": "LIQ", + "values": nb_liq}, + {"color": "rgb(255, 205, 86)", + "label": "Comptes K-Fêt", + "values": nb_accounts}] return context - @method_decorator(login_required) + @method_decorator(teamkfet_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): - end_date = this_morning() - id_prefix = ID_PREFIX_ART_LAST_DAYS - - def get_dates(self, **kwargs): - return lastdays(7) - - def get_labels(self, **kwargs): - days = lastdays(7) - return daynames(days) - - -# Rend les ventes de 7 dernières semaines -# La semaine en cours n'est pas comprise -class ArticleStatLastWeek(ArticleStatLast): - end_date = this_monday_morning() - id_prefix = ID_PREFIX_ART_LAST_WEEKS - - def get_dates(self, **kwargs): - return lastweeks(7) - - def get_labels(self, **kwargs): - weeks = lastweeks(7) - return weeknames(weeks) - - -# Rend les ventes des 7 derniers mois -# Le mois en cours n'est pas compris -class ArticleStatLastMonth(ArticleStatLast): - end_date = this_monday_morning() - id_prefix = ID_PREFIX_ART_LAST_MONTHS - - def get_dates(self, **kwargs): - return lastmonths(7) - - def get_labels(self, **kwargs): - months = lastmonths(7) - return monthnames(months) + return super().dispatch(*args, **kwargs)