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
This commit is contained in:
parent
f6022ecf7d
commit
87b9db520f
2 changed files with 141 additions and 142 deletions
13
kfet/urls.py
13
kfet/urls.py
|
@ -82,15 +82,12 @@ urlpatterns = [
|
|||
views.AccountStatLastDay.as_view(),
|
||||
name = 'kfet.account.stat.last.day'),
|
||||
|
||||
url('^accounts/(?P<trigramme>.{3})/stat/balance/$',
|
||||
views.AccountStatBalanceAll.as_view(),
|
||||
name = 'kfet.account.stat.balance'),
|
||||
url('^accounts/(?P<trigramme>.{3})/stat/balance/d/(?P<nb_date>\d*)/$',
|
||||
url(r'^accounts/(?P<trigramme>.{3})/stat/balance/list$',
|
||||
views.AccountStatBalanceList.as_view(),
|
||||
name='kfet.account.stat.balance.list'),
|
||||
url(r'^accounts/(?P<trigramme>.{3})/stat/balance$',
|
||||
views.AccountStatBalance.as_view(),
|
||||
name = 'kfet.account.stat.balance.days'),
|
||||
url('^accounts/(?P<trigramme>.{3})/stat/balance/anytime/$',
|
||||
views.AccountStatBalance.as_view(),
|
||||
name = 'kfet.account.stat.balance.anytime'),
|
||||
name='kfet.account.stat.balance'),
|
||||
|
||||
# -----
|
||||
# Checkout urls
|
||||
|
|
270
kfet/views.py
270
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
|
||||
|
|
Loading…
Reference in a new issue