resolved merge conflict in kfet views imports

This commit is contained in:
Aurélien Delobelle 2017-02-25 02:00:56 +01:00
commit 95b129e396
14 changed files with 28497 additions and 6 deletions

View file

@ -512,6 +512,10 @@ class OperationGroup(models.Model):
related_name = "+",
blank = True, null = True, default = None)
def __str__(self):
return ', '.join(map(str, self.opes.all()))
class Operation(models.Model):
PURCHASE = 'purchase'
DEPOSIT = 'deposit'
@ -556,6 +560,18 @@ class Operation(models.Model):
max_digits = 6, decimal_places = 2,
blank = True, null = True, default = None)
def __str__(self):
templates = {
self.PURCHASE: "{nb} {article.name} ({amount}€)",
self.DEPOSIT: "charge ({amount})",
self.WITHDRAW: "retrait ({amount})",
self.INITIAL: "initial ({amount})",
}
return templates[self.type].format(nb=self.article_nb,
article=self.article,
amount=self.amount)
class GlobalPermissions(models.Model):
class Meta:
managed = False

View file

@ -99,6 +99,25 @@ textarea {
font-weight:bold;
}
.nopadding {
padding: 0 !important;
}
.panel-md-margin{
background-color: white;
padding-left: 15px;
padding-right: 15px;
padding-bottom: 15px;
padding-top: 1px;
}
@media (min-width: 992px) {
.panel-md-margin{
margin:8px;
background-color: white;
}
}
.col-content-left, .col-content-right {
padding:0;
}
@ -165,6 +184,10 @@ textarea {
background:#fff;
}
.content-right-block-transparent > div:not(.buttons-title) {
background-color: transparent;
}
.content-right-block .buttons-title {
position:absolute;
top:8px;
@ -341,3 +364,11 @@ textarea {
.help h4 {
margin:15px 0;
}
/*
* Statistiques
*/
.stat-graph {
height: 100px;
}

File diff suppressed because it is too large Load diff

16
kfet/static/kfet/js/Chart.bundle.min.js vendored Normal file

File diff suppressed because one or more lines are too long

11557
kfet/static/kfet/js/Chart.js vendored Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,197 @@
(function($){
window.StatsGroup = function (url, target) {
// a class to properly display statictics
// url : points to an ObjectResumeStat that lists the options through JSON
// target : element of the DOM where to put the stats
var self = this;
var element = $(target);
var content = $("<div>");
var buttons;
function dictToArray (dict, start) {
// converts the dicts returned by JSONResponse to Arrays
// necessary because for..in does not guarantee the order
if (start === undefined) start = 0;
var array = new Array();
for (var k in dict) {
array[k] = dict[k];
}
array.splice(0, start);
return array;
}
function handleTimeChart (dict) {
// reads the balance data and put it into chartjs formatting
var data = dictToArray(dict, 0);
for (var i = 0; i < data.length; i++) {
var source = data[i];
data[i] = { x: new Date(source.at),
y: source.balance,
label: source.label }
}
return data;
}
function showStats () {
// CALLBACK : called when a button is selected
// shows the focus on the correct button
buttons.find(".focus").removeClass("focus");
$(this).addClass("focus");
// loads data and shows it
$.getJSON(this.stats_target_url + "?format=json",
displayStats);
}
function displayStats (data) {
// reads the json data and updates the chart display
var chart_datasets = [];
var charts = dictToArray(data.charts);
// are the points indexed by timestamps?
var is_time_chart = data.is_time_chart || false;
// reads the charts data
for (var i = 0; i < charts.length; i++) {
var chart = charts[i];
// format the data
var chart_data = is_time_chart ? handleTimeChart(chart.values) : dictToArray(chart.values, 1);
chart_datasets.push(
{
label: chart.label,
borderColor: chart.color,
backgroundColor: chart.color,
fill: is_time_chart,
lineTension: 0,
data: chart_data,
steppedLine: is_time_chart,
});
}
// options for chartjs
var chart_options =
{
responsive: true,
tooltips: {
mode: 'index',
intersect: false,
},
hover: {
mode: 'nearest',
intersect: false,
}
};
// additionnal options for time-indexed charts
if (is_time_chart) {
chart_options['scales'] = {
xAxes: [{
type: "time",
display: true,
scaleLabel: {
display: false,
labelString: 'Date'
},
time: {
tooltipFormat: 'll HH:mm',
displayFormats: {
'millisecond': 'SSS [ms]',
'second': 'mm:ss a',
'minute': 'DD MMM',
'hour': 'ddd h[h]',
'day': 'DD MMM',
'week': 'DD MMM',
'month': 'MMM',
'quarter': 'MMM',
'year': 'YYYY',
}
}
}],
yAxes: [{
display: true,
scaleLabel: {
display: false,
labelString: 'value'
}
}]
};
}
// global object for the options
var chart_model =
{
type: 'line',
options: chart_options,
data: {
labels: dictToArray(data.labels, 1),
datasets: chart_datasets,
}
};
// saves the previous charts to be destroyed
var prev_chart = content.children();
// creates a blank canvas element and attach it to the DOM
var canvas = $("<canvas>");
content.append(canvas);
// create the chart
var chart = new Chart(canvas, chart_model);
// clean
prev_chart.remove();
}
// initialize the interface
function initialize (data) {
// creates the bar with the buttons
buttons = $("<div>",
{class: "btn-group btn-group-justified",
role: "group",
"aria-label": "select-period"});
var to_click;
var context = dictToArray(data.stats);
for (var i = 0; i < context.length; i++) {
// creates the button
var btn_wrapper = $("<div>",
{class: "btn-group",
role:"group"});
var btn = $("<button>",
{class: "btn btn-primary",
type: "button"})
.text(context[i].label)
.prop("stats_target_url", context[i].url)
.on("click", showStats);
// saves the default option to select
if (i == data.default_stat || i == 0)
to_click = btn;
// append the elements to the parent
btn_wrapper.append(btn);
buttons.append(btn_wrapper);
}
// appends the contents to the DOM
element.append(buttons);
element.append(content);
// shows the default chart
to_click.click();
};
// constructor
(function () {
$.getJSON(url + "?format=json", initialize);
})();
};
})(jQuery);

102
kfet/statistic.py Normal file
View file

@ -0,0 +1,102 @@
# -*- coding: utf-8 -*-
from django.utils import timezone
from django.db.models import Sum
KFET_WAKES_UP_AT = 7
# donne le nom des jours d'une liste de dates
# dans un dico ordonné
def daynames(dates):
names = {}
for i in dates:
names[i] = dates[i].strftime("%A")
return names
# donne le nom des semaines une liste de dates
# dans un dico ordonné
def weeknames(dates):
names = {}
for i in dates:
names[i] = dates[i].strftime("Semaine %W")
return names
# donne le nom des mois d'une liste de dates
# dans un dico ordonné
def monthnames(dates):
names = {}
for i in dates:
names[i] = dates[i].strftime("%B")
return names
# rend les dates des nb derniers jours
# dans l'ordre chronologique
# aujourd'hui compris
# nb = 1 : rend hier
def lastdays(nb):
morning = this_morning()
days = {}
for i in range(1, nb+1):
days[i] = morning - timezone.timedelta(days=nb - i + 1)
return days
def lastweeks(nb):
monday_morning = this_monday_morning()
mondays = {}
for i in range(1, nb+1):
mondays[i] = monday_morning \
- timezone.timedelta(days=7*(nb - i + 1))
return mondays
def lastmonths(nb):
first_month_day = this_first_month_day()
first_days = {}
this_year = first_month_day.year
this_month = first_month_day.month
for i in range(1, nb+1):
month = ((this_month - 1 - (nb - i)) % 12) + 1
year = this_year + (nb - i) // 12
first_days[i] = timezone.datetime(year=year,
month=month,
day=1,
hour=KFET_WAKES_UP_AT)
return first_days
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 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
# Étant donné un queryset d'operations
# rend la somme des article_nb
def tot_ventes(queryset):
res = queryset.aggregate(Sum('article_nb'))['article_nb__sum']
return res and res or 0

View file

@ -10,6 +10,18 @@
<script type="text/javascript" src="{% static 'kfet/js/moment-timezone-with-data-2010-2020.js' %}"></script>
<script type="text/javascript" src="{% static 'kfet/js/kfet.js' %}"></script>
<script type="text/javascript" src="{% static 'kfet/js/history.js' %}"></script>
{% if account.user == request.user %}
<script type="text/javascript" src="{% static 'kfet/js/Chart.bundle.js' %}"></script>
<script type="text/javascript" src="{% static 'kfet/js/statistic.js' %}"></script>
<script>
jQuery(document).ready(function() {
var stat_last = new StatsGroup("{% url 'kfet.account.stat.last' trigramme=account.trigramme %}",
$("#stat_last"));
var stat_balance = new StatsGroup("{% url 'kfet.account.stat.balance' trigramme=account.trigramme %}",
$("#stat_balance"));
});
</script>
{% endif %}
{% endblock %}
{% block title %}
@ -51,6 +63,27 @@
</div>
</div>
{% endif %}
{% if account.user == request.user %}
<div class="content-right-block content-right-block-transparent">
<h2>Statistiques</h2>
<div class="row">
<div class="col-sm-12 nopadding">
<div class="panel-md-margin">
<h3>Ma balance</h3>
<div id="stat_balance" class"stat-graph"></div>
</div>
</div>
</div><!-- /row -->
<div class="row">
<div class="col-sm-12 nopadding">
<div class="panel-md-margin">
<h3>Ma consommation</h3>
<div id="stat_last" class"stat-graph"></div>
</div>
</div>
</div><!-- /row -->
</div>
{% endif %}
<div class="content-right-block">
<h2>Historique</h2>
<div id="history">

View file

@ -1,4 +1,5 @@
{% extends 'kfet/base.html' %}
{% load staticfiles %}
{% block title %}Informations sur l'article {{ article }}{% endblock %}
{% block content-header-title %}Article - {{ article.name }}{% endblock %}
@ -76,10 +77,32 @@
</tbody>
</table>
</div>
</div>
</div><!-- /row-->
</div>
<div class="content-right-block content-right-block-transparent">
<h2>Statistiques</h2>
<div class="row">
<div class="col-sm-12 nopadding">
<div class="panel-md-margin">
<h3>Ventes de {{ article.name }}</h3>
<div id="stat_last"></div>
</div>
</div>
</div><!-- /row -->
</div>
</div>
</div>
</div>
{% endblock %}
{% block extra_head %}
<script type="text/javascript" src="{% static 'kfet/js/Chart.bundle.js' %}"></script>
<script type="text/javascript" src="{% static 'kfet/js/statistic.js' %}"></script>
<script>
jQuery(document).ready(function() {
var stat_last = $("#stat_last");
var stat_last_url = "{% url 'kfet.article.stat.last' article.id %}";
STAT.get_thing(stat_last_url, stat_last, "Stat non trouvées :(");
});
</script>
{% endblock %}

View file

@ -0,0 +1,17 @@
{% extends "kfet/base.html" %}
{% load staticfiles %}
{% block title %}Accueil{% endblock %}
{% block content-header-title %}Accueil{% endblock %}
{% block content %}
<div class="row">
<div class="col-sm-4 col-md-3 col-content-left">
</div>
<div class="col-sm-8 col-md-9 col-content-right">
{% include 'kfet/base_messages.html' %}
</div>
</div>
{% endblock %}

View file

@ -0,0 +1,5 @@
from django.template.defaulttags import register
@register.filter
def get_item(dictionary, key):
return dictionary.get(key)

5
kfet/tests.py Normal file
View file

@ -0,0 +1,5 @@
# -*- coding: utf-8 -*-
from django.test import TestCase
# Écrire les tests ici

View file

@ -68,6 +68,30 @@ urlpatterns = [
(views.AccountNegativeList.as_view()),
name='kfet.account.negative'),
# Account - Statistics
url('^accounts/(?P<trigramme>.{3})/stat/last/$',
views.AccountStatLastAll.as_view(),
name = 'kfet.account.stat.last'),
url('^accounts/(?P<trigramme>.{3})/stat/last/month/$',
views.AccountStatLastMonth.as_view(),
name = 'kfet.account.stat.last.month'),
url('^accounts/(?P<trigramme>.{3})/stat/last/week/$',
views.AccountStatLastWeek.as_view(),
name = 'kfet.account.stat.last.week'),
url('^accounts/(?P<trigramme>.{3})/stat/last/day/$',
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*)/$',
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'),
# -----
# Checkout urls
# -----
@ -125,7 +149,20 @@ urlpatterns = [
# Article - Update
url('^articles/(?P<pk>\d+)/edit$',
teamkfet_required(views.ArticleUpdate.as_view()),
name='kfet.article.update'),
name = 'kfet.article.update'),
# Article - Statistics
url('^articles/(?P<pk>\d+)/stat/last/$',
views.ArticleStatLastAll.as_view(),
name = 'kfet.article.stat.last'),
url('^articles/(?P<pk>\d+)/stat/last/month/$',
views.ArticleStatLastMonth.as_view(),
name = 'kfet.article.stat.last.month'),
url('^articles/(?P<pk>\d+)/stat/last/week/$',
views.ArticleStatLastWeek.as_view(),
name = 'kfet.article.stat.last.week'),
url('^articles/(?P<pk>\d+)/stat/last/day/$',
views.ArticleStatLastDay.as_view(),
name = 'kfet.article.stat.last.day'),
# -----
# K-Psul urls

View file

@ -4,8 +4,10 @@ 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
from django.views.generic.edit import CreateView, UpdateView
from django.core.urlresolvers import reverse_lazy
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, reverse_lazy
from django.contrib import messages
from django.contrib.messages.views import SuccessMessageMixin
from django.contrib.auth import authenticate, login
@ -18,6 +20,7 @@ from django.db.models import F, Sum, Prefetch, Count
from django.db.models.functions import Coalesce
from django.utils import timezone
from django.utils.crypto import get_random_string
from django.utils.decorators import method_decorator
from gestioncof.models import CofProfile, Clipper
from kfet.decorators import teamkfet_required
from kfet.models import (
@ -43,11 +46,15 @@ from decimal import Decimal
import django_cas_ng
import heapq
import statistics
from .statistic import daynames, monthnames, weeknames, \
lastdays, lastweeks, lastmonths, \
this_morning, this_monday_morning, this_first_month_day, \
tot_ventes
@login_required
def home(request):
return render(request, "kfet/base.html")
return render(request, "kfet/home.html")
@teamkfet_required
@ -826,6 +833,8 @@ class ArticleUpdate(SuccessMessageMixin, UpdateView):
# Updating
return super(ArticleUpdate, self).form_valid(form)
# -----
# K-Psul
# -----
@ -1975,3 +1984,591 @@ class SupplierUpdate(SuccessMessageMixin, UpdateView):
return self.form_invalid(form)
# Updating
return super(SupplierUpdate, self).form_valid(form)
# ==========
# 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
class JSONDetailView(JSONResponseMixin,
BaseDetailView):
"""
Returns a DetailView that renders a JSON
"""
def render_to_response(self, context):
return self.render_to_json_response(context)
class HybridDetailView(JSONResponseMixin,
SingleObjectTemplateResponseMixin,
BaseDetailView):
"""
Returns a DetailView as an html page except if a JSON file is requested
by the GET method in which 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 an html page except if a JSON file is requested
by the GET method in which 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(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
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)
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
# -----------------------
ID_PREFIX_ACC_BALANCE = "balance_acc"
# Un résumé de toutes les vues ArticleStatBalance
# REND DU JSON
class AccountStatBalanceAll(ObjectResumeStat):
model = Account
context_object_name = 'account'
trigramme_url_kwarg = 'trigramme'
id_prefix = ID_PREFIX_ACC_BALANCE
nb_stat = 5
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
@method_decorator(login_required)
def dispatch(self, *args, **kwargs):
return super(AccountStatBalanceAll, self).dispatch(*args, **kwargs)
class AccountStatBalance(JSONDetailView):
"""
Returns a JSON containing the evolution a the personnal
balance of a trigramme between timezone.now() and `nb_days`
ago (specified to the view as an argument)
takes into account the Operations and the Transfers
does not takes into account the balance offset
"""
model = Account
trigramme_url_kwarg = 'trigramme'
nb_date_url_kwargs = 'nb_date'
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):
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
# 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))
# 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': 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 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']
return actions
def get_context_data(self, **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 } ]
context['is_time_chart'] = True
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)
# ------------------------
# Consommation personnelle
# ------------------------
ID_PREFIX_ACC_LAST = "last_acc"
ID_PREFIX_ACC_LAST_DAYS = "last_days_acc"
ID_PREFIX_ACC_LAST_WEEKS = "last_weeks_acc"
ID_PREFIX_ACC_LAST_MONTHS = "last_months_acc"
# Un résumé de toutes les vues ArticleStatLast
# NE REND PAS DE JSON
class AccountStatLastAll(ObjectResumeStat):
model = Account
context_object_name = 'account'
trigramme_url_kwarg = 'trigramme'
id_prefix = ID_PREFIX_ACC_LAST
nb_stat = 3
nb_default = 2
stat_labels = ["Derniers mois", "Dernières semaines", "Derniers jours"]
stat_urls = ['kfet.account.stat.last.month',
'kfet.account.stat.last.week',
'kfet.account.stat.last.day']
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}
@method_decorator(login_required)
def dispatch(self, *args, **kwargs):
return super(AccountStatLastAll, self).dispatch(*args, **kwargs)
class AccountStatLast(JSONDetailView):
"""
Returns a JSON containing the evolution a the personnal
consommation of a trigramme at the diffent dates specified
"""
model = Account
trigramme_url_kwarg = 'trigramme'
context_object_name = 'account'
end_date = timezone.now()
id_prefix = ""
# 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):
return {}
# 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):
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['charts'] = [ { "color": "rgb(255, 99, 132)",
"label": "NB items achetés",
"values": nb_ventes } ]
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
# Aujourd'hui non compris
class AccountStatLastDay(AccountStatLast):
end_date = this_morning()
id_prefix = ID_PREFIX_ACC_LAST_DAYS
def get_dates(self, **kwargs):
return lastdays(7)
def get_labels(self, **kwargs):
days = lastdays(7)
return daynames(days)
# Rend les achats de ce compte des 7 dernières semaines
# La semaine en cours n'est pas comprise
class AccountStatLastWeek(AccountStatLast):
end_date = this_monday_morning()
id_prefix = ID_PREFIX_ACC_LAST_WEEKS
def get_dates(self, **kwargs):
return lastweeks(7)
def get_labels(self, **kwargs):
weeks = lastweeks(7)
return weeknames(weeks)
# Rend les achats de ce compte des 7 derniers mois
# Le mois en cours n'est pas compris
class AccountStatLastMonth(AccountStatLast):
end_date = this_monday_morning()
id_prefix = ID_PREFIX_ACC_LAST_MONTHS
def get_dates(self, **kwargs):
return lastmonths(7)
def get_labels(self, **kwargs):
months = lastmonths(7)
return monthnames(months)
# ------------------------
# Article Satistiques Last
# ------------------------
ID_PREFIX_ART_LAST = "last_art"
ID_PREFIX_ART_LAST_DAYS = "last_days_art"
ID_PREFIX_ART_LAST_WEEKS = "last_weeks_art"
ID_PREFIX_ART_LAST_MONTHS = "last_months_art"
# Un résumé de toutes les vues ArticleStatLast
# NE REND PAS DE JSON
class ArticleStatLastAll(ObjectResumeStat):
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']
@method_decorator(login_required)
def dispatch(self, *args, **kwargs):
return super(ArticleStatLastAll, self).dispatch(*args, **kwargs)
class ArticleStatLast(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)
# 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])
)
# 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 } ]
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):
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)