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)