From 87b9db520f5c73be52d6a3e8841217b376544d59 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Delobelle?= Date: Sun, 2 Apr 2017 17:03:20 +0200 Subject: [PATCH] Refactor py base stats and account balance stats New mixin: PkUrlMixin - use with SingleObjectMixin standard django mixin (used by DetailView...) - `get_object` use field declared in `pk_url_kwarg` to get... the object SingleResumeStat - clean (part of) py code AccountStatBalanceList - renamed from `AccountStatBalanceAll` - url modified - add permission checking (only the connected user can get balance stats manifest) - clean py code AccountStatBalance - cleaner filtering management - merge urls using this class - clean py code --- kfet/urls.py | 13 +-- kfet/views.py | 270 +++++++++++++++++++++++++------------------------- 2 files changed, 141 insertions(+), 142 deletions(-) diff --git a/kfet/urls.py b/kfet/urls.py index 94a9eaec..ddd40ce6 100644 --- a/kfet/urls.py +++ b/kfet/urls.py @@ -82,15 +82,12 @@ urlpatterns = [ views.AccountStatLastDay.as_view(), name = 'kfet.account.stat.last.day'), - url('^accounts/(?P.{3})/stat/balance/$', - views.AccountStatBalanceAll.as_view(), - name = 'kfet.account.stat.balance'), - url('^accounts/(?P.{3})/stat/balance/d/(?P\d*)/$', + url(r'^accounts/(?P.{3})/stat/balance/list$', + views.AccountStatBalanceList.as_view(), + name='kfet.account.stat.balance.list'), + url(r'^accounts/(?P.{3})/stat/balance$', views.AccountStatBalance.as_view(), - name = 'kfet.account.stat.balance.days'), - url('^accounts/(?P.{3})/stat/balance/anytime/$', - views.AccountStatBalance.as_view(), - name = 'kfet.account.stat.balance.anytime'), + name='kfet.account.stat.balance'), # ----- # Checkout urls diff --git a/kfet/views.py b/kfet/views.py index 14fc0b3b..b0c90083 100644 --- a/kfet/views.py +++ b/kfet/views.py @@ -1,12 +1,13 @@ # -*- coding: utf-8 -*- +from urllib.parse import urlencode + from django.shortcuts import render, get_object_or_404, redirect from django.core.exceptions import PermissionDenied from django.core.cache import cache from django.views.generic import ListView, DetailView, TemplateView -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.views.generic.detail import BaseDetailView +from django.views.generic.edit import CreateView, UpdateView from django.core.urlresolvers import reverse, reverse_lazy from django.contrib import messages from django.contrib.messages.views import SuccessMessageMixin @@ -2041,49 +2042,46 @@ class JSONDetailView(JSONResponseMixin, BaseDetailView): return self.render_to_json_response(context) -class ObjectResumeStat(JSONDetailView): +class PkUrlMixin(object): + + def get_object(self, *args, **kwargs): + get_by = self.kwargs.get(self.pk_url_kwarg) + return get_object_or_404(self.model, **{self.pk_url_kwarg: get_by}) + + +class SingleResumeStat(JSONDetailView): """ Summarize all the stats of an object Handles JSONResponse + """ - context_object_name = '' id_prefix = '' - # 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 url_kwargs(self, **kwargs): - return [{}] * self.nb_stat + stats = [] + url_stat = None def get_context_data(self, **kwargs): # On n'hérite pas object_id = self.object.id - url_kwargs = self.url_kwargs() 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(self.stat_urls[i], - kwargs=dict( - self.get_object_url_kwargs(), - **url_kwargs[i] - ), - ), - } - prefix = "%s_%d" % (self.id_prefix, object_id) + stats = [] + prefix = '{}_{}'.format(self.id_prefix, object_id) + for i, stat_def in enumerate(self.stats): + url_pk = getattr(self.object, self.pk_url_kwarg) + url_params_d = stat_def.get('url_params', {}) + if len(url_params_d) > 0: + url_params = '?{}'.format(urlencode(url_params_d)) + else: + url_params = '' + stats.append({ + 'label': stat_def['label'], + 'btn': 'btn_{}_{}'.format(prefix, i), + 'url': '{url}{params}'.format( + url=reverse(self.url_stat, args=[url_pk]), + params=url_params, + ), + }) context['id_prefix'] = prefix context['content_id'] = "content_%s" % prefix context['stats'] = stats @@ -2100,39 +2098,47 @@ ID_PREFIX_ACC_BALANCE = "balance_acc" # Un résumé de toutes les vues ArticleStatBalance # REND DU JSON -class AccountStatBalanceAll(ObjectResumeStat): +class AccountStatBalanceList(PkUrlMixin, SingleResumeStat): model = Account context_object_name = 'account' - trigramme_url_kwarg = 'trigramme' + pk_url_kwarg = 'trigramme' + url_stat = 'kfet.account.stat.balance' id_prefix = ID_PREFIX_ACC_BALANCE - nb_stat = 5 + stats = [ + { + 'label': 'Tout le temps', + }, + { + 'label': '1 an', + 'url_params': {'last_days': 365}, + }, + { + 'label': '6 mois', + 'url_params': {'last_days': 183}, + }, + { + 'label': '3 mois', + 'url_params': {'last_days': 90}, + }, + { + 'label': '30 jours', + 'url_params': {'last_days': 30}, + }, + ] nb_default = 0 - stat_labels = ["Tout le temps", "1 an", "6 mois", "3 mois", "30 jours"] - stat_urls = ['kfet.account.stat.balance.anytime'] \ - + ['kfet.account.stat.balance.days'] * 4 - def get_object(self, **kwargs): - trigramme = self.kwargs.get(self.trigramme_url_kwarg) - return get_object_or_404(Account, trigramme=trigramme) - - def get_object_url_kwargs(self, **kwargs): - return {'trigramme': self.object.trigramme} - - def url_kwargs(self, **kwargs): - context_list = (super(AccountStatBalanceAll, self) - .url_kwargs(**kwargs)) - context_list[1] = {'nb_date': 365} - context_list[2] = {'nb_date': 183} - context_list[3] = {'nb_date': 90} - context_list[4] = {'nb_date': 30} - return context_list + def get_object(self, *args, **kwargs): + obj = super().get_object(*args, **kwargs) + if self.request.user != obj.user: + raise PermissionDenied + return obj @method_decorator(login_required) def dispatch(self, *args, **kwargs): - return super(AccountStatBalanceAll, self).dispatch(*args, **kwargs) + return super().dispatch(*args, **kwargs) -class AccountStatBalance(JSONDetailView): +class AccountStatBalance(PkUrlMixin, JSONDetailView): """ Returns a JSON containing the evolution a the personnal balance of a trigramme between timezone.now() and `nb_days` @@ -2141,44 +2147,38 @@ class AccountStatBalance(JSONDetailView): does not takes into account the balance offset """ model = Account - trigramme_url_kwarg = 'trigramme' - nb_date_url_kwargs = 'nb_date' + pk_url_kwarg = 'trigramme' context_object_name = 'account' - id_prefix = "" - 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): + def get_changes_list(self, last_days=None, begin_date=None, end_date=None): account = self.object - nb_date = self.kwargs.get(self.nb_date_url_kwargs, None) - end_date = this_morning() - if nb_date is None: - begin_date = timezone.datetime(year=1980, month=1, day=1) - anytime = True - else: - begin_date = this_morning() \ - - timezone.timedelta(days=int(nb_date)) - anytime = False - # On récupère les opérations + + # prepare filters + if end_date is None: + end_date = this_morning() + + if last_days is not None: + begin_date = end_date - timezone.timedelta(days=last_days) + + # prepare querysets # TODO: retirer les opgroup dont tous les op sont annulées - opgroups = list(OperationGroup.objects - .filter(on_acc=account) - .filter(at__gte=begin_date) - .filter(at__lte=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=begin_date) - .filter(group__at__lte=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=begin_date) - .filter(group__at__lte=end_date)) + opegroups = OperationGroup.objects.filter(on_acc=account) + recv_transfers = Transfer.objects.filter(to_acc=account, + canceled_at=None) + sent_transfers = Transfer.objects.filter(from_acc=account, + canceled_at=None) + + # apply filters + if begin_date is not None: + opegroups = opegroups.filter(at__gte=begin_date) + recv_transfers = recv_transfers.filter(group__at__gte=begin_date) + sent_transfers = sent_transfers.filter(group__at__gte=begin_date) + + if end_date is not None: + opegroups = opegroups.filter(at__lte=end_date) + recv_transfers = recv_transfers.filter(group__at__lte=end_date) + sent_transfers = sent_transfers.filter(group__at__lte=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, @@ -2188,72 +2188,74 @@ class AccountStatBalance(JSONDetailView): # sera mis à jour lors d'une # autre passe) # } - actions = [ - # Maintenant (à changer si on gère autre chose que now) - { + actions = [] + if begin_date is not None: + actions.append({ + 'at': begin_date.isoformat(), + 'amount': 0, + 'label': "début", + 'balance': 0, + }) + if end_date is not None: + actions.append({ 'at': end_date.isoformat(), - 'amout': 0, + 'amount': 0, 'label': "actuel", 'balance': 0, - } - ] + [ + }) + + actions += [ { - 'at': op.at.isoformat(), - 'amount': op.amount, - 'label': str(op), + 'at': ope_grp.at.isoformat(), + 'amount': ope_grp.amount, + 'label': str(ope_grp), 'balance': 0, - } for op in opgroups + } for ope_grp in opegroups ] + [ { 'at': tr.group.at.isoformat(), 'amount': tr.amount, - 'label': "%d€: %s -> %s" % (tr.amount, - tr.from_acc.trigramme, - tr.to_acc.trigramme), + 'label': str(tr), 'balance': 0, - } for tr in received_transfers + } for tr in recv_transfers ] + [ { 'at': tr.group.at.isoformat(), 'amount': -tr.amount, - 'label': "%d€: %s -> %s" % (tr.amount, - tr.from_acc.trigramme, - tr.to_acc.trigramme), + 'label': str(tr), 'balance': 0, - } for tr in emitted_transfers + } for tr in sent_transfers ] - if not anytime: - actions += [ - # Date de début : - { - 'at': 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'] + actions[i+1]['balance'] = \ + actions[i]['balance'] - actions[i+1]['amount'] return actions - def get_context_data(self, **kwargs): + def get_context_data(self, *args, **kwargs): context = {} - changes = self.get_changes_list() - nb_days = self.kwargs.get(self.nb_date_url_kwargs, None) - if nb_days is None: - nb_days_string = 'anytime' - else: - nb_days_string = str(int(nb_days)) - context['charts'] = [ { "color": "rgb(255, 99, 132)", - "label": "Balance", - "values": changes } ] + + 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) + + changes = self.get_changes_list( + last_days=last_days, + begin_date=begin_date, end_date=end_date, + ) + + context['charts'] = [{ + "color": "rgb(255, 99, 132)", + "label": "Balance", + "values": changes, + }] context['is_time_chart'] = True - context['min_date'] = changes[len(changes)-1]['at'] + context['min_date'] = changes[-1]['at'] context['max_date'] = changes[0]['at'] # TODO: offset return context @@ -2274,7 +2276,7 @@ ID_PREFIX_ACC_LAST_MONTHS = "last_months_acc" # Un résumé de toutes les vues ArticleStatLast # NE REND PAS DE JSON -class AccountStatLastAll(ObjectResumeStat): +class AccountStatLastAll(SingleResumeStat): model = Account context_object_name = 'account' trigramme_url_kwarg = 'trigramme' @@ -2419,7 +2421,7 @@ ID_PREFIX_ART_LAST_MONTHS = "last_months_art" # Un résumé de toutes les vues ArticleStatLast # NE REND PAS DE JSON -class ArticleStatLastAll(ObjectResumeStat): +class ArticleStatLastAll(SingleResumeStat): model = Article context_object_name = 'article' id_prefix = ID_PREFIX_ART_LAST