Plot, also caching
parent
41e53c2178
commit
98f8dbf90f
|
@ -1,3 +1,4 @@
|
|||
import memcache
|
||||
import requests
|
||||
|
||||
from flask import Flask
|
||||
|
@ -9,6 +10,8 @@ app.config.from_object("config.CurrentConfig")
|
|||
db = SQLAlchemy(app)
|
||||
login_manager = LoginManager()
|
||||
login_manager.init_app(app)
|
||||
mc = memcache.Client(app.config['MEMCACHE_SERVERS'], debug=0)
|
||||
|
||||
|
||||
import webapp.models
|
||||
|
||||
|
|
|
@ -6,13 +6,14 @@ from sqlalchemy import and_
|
|||
|
||||
from flask import request, abort, Response
|
||||
|
||||
from webapp import models, app
|
||||
from webapp import models, app, mc
|
||||
|
||||
class APIError(Exception):
|
||||
def __init__(self, message, code=500):
|
||||
self.message = message
|
||||
self.code = code
|
||||
|
||||
|
||||
def _public_api_method(path):
|
||||
"""A decorator that adds a public, GET based method at /api/<path>.json.
|
||||
|
||||
|
@ -29,6 +30,7 @@ def _public_api_method(path):
|
|||
code = e.code
|
||||
status = "error"
|
||||
except Exception as e:
|
||||
raise
|
||||
content = "Internal server error."
|
||||
code = 500
|
||||
status = "error"
|
||||
|
@ -130,6 +132,11 @@ def api_member():
|
|||
return response
|
||||
|
||||
def _stats_for_month(year, month):
|
||||
cache_key = 'kasownik-stats_for_month-{}-{}'.format(year, month)
|
||||
cache_data = mc.get(cache_key)
|
||||
if cache_data:
|
||||
cache_data = json.loads(cache_data)
|
||||
return cache_data[0], cache_data[1]
|
||||
# TODO: export this to the config
|
||||
money_required = 4800
|
||||
money_paid = 0
|
||||
|
@ -139,7 +146,7 @@ def _stats_for_month(year, month):
|
|||
amount_all = mt.transfer.amount
|
||||
amount = amount_all / len(mt.transfer.member_transfers)
|
||||
money_paid += amount
|
||||
|
||||
mc.set(cache_key, json.dumps([money_required, money_paid/100]))
|
||||
return money_required, money_paid/100
|
||||
|
||||
@_public_api_method("month/<year>/<month>")
|
||||
|
@ -156,6 +163,10 @@ def api_manamana(year=None, month=None):
|
|||
|
||||
@_public_api_method("months_due/<membername>")
|
||||
def api_months_due(membername):
|
||||
cache_key = 'kasownik-months_due-{}'.format(membername)
|
||||
cache_data = mc.get(cache_key)
|
||||
if cache_data:
|
||||
return cache_data
|
||||
member = models.Member.query.filter_by(username=membername).first()
|
||||
if not member:
|
||||
raise APIError("No such member.", 404)
|
||||
|
@ -168,16 +179,23 @@ def api_months_due(membername):
|
|||
#now = datetime.datetime.now()
|
||||
#then_timestamp = year * 12 + (month-1)
|
||||
#now_timestamp = now.year * 12 + (now.month-1)
|
||||
mc.set(cache_key, due)
|
||||
return due
|
||||
|
||||
@_public_api_method("cashflow/<int:year>/<int:month>")
|
||||
def api_cashflow(year, month):
|
||||
start = datetime.date(year=year, month=month, day=1)
|
||||
month += 1
|
||||
if month > 12:
|
||||
month = 1
|
||||
year += 1
|
||||
end = datetime.date(year=year, month=month, day=1)
|
||||
transfers = models.Transfer.query.filter(and_(models.Transfer.date >= start, models.Transfer.date < end)).all()
|
||||
amount_in = sum(t.amount for t in transfers)
|
||||
cache_key = 'kasownik-cashflow-{}-{}'.format(year, month)
|
||||
cache_data = mc.get(cache_key)
|
||||
if cache_data:
|
||||
amount_in = cache_data
|
||||
else:
|
||||
start = datetime.date(year=year, month=month, day=1)
|
||||
month += 1
|
||||
if month > 12:
|
||||
month = 1
|
||||
year += 1
|
||||
end = datetime.date(year=year, month=month, day=1)
|
||||
transfers = models.Transfer.query.filter(and_(models.Transfer.date >= start, models.Transfer.date < end)).all()
|
||||
amount_in = sum(t.amount for t in transfers)
|
||||
mc.set(cache_key, amount_in)
|
||||
return {"in": amount_in/100, "out": -1}
|
||||
|
|
|
@ -7,7 +7,20 @@ body {
|
|||
}
|
||||
|
||||
.stats {
|
||||
|
||||
padding: 40px 15px;
|
||||
}
|
||||
|
||||
.stats h1 {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.stats h4 {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#plot {
|
||||
margin-left: 10%;
|
||||
width: 80%;
|
||||
}
|
||||
|
||||
#legend {
|
||||
|
|
|
@ -1,89 +1,92 @@
|
|||
$(function() {
|
||||
$(window).load(function() {
|
||||
var required = [], paid = [], influx = [];
|
||||
var today = new Date(),
|
||||
year = 1900 + today.getYear(),
|
||||
month = today.getMonth() + 1,
|
||||
urlBase = 'https://kasownik.hackerspace.pl/api/',
|
||||
urlBase = '/api/',
|
||||
modified;
|
||||
|
||||
for(var i = 0; i < 28; ++i) {
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open("GET", urlBase + 'month/'+ year + '/' + month + '.json', false);
|
||||
xhr.send();
|
||||
|
||||
var data = JSON.parse(xhr.response),
|
||||
res = data.content,
|
||||
date = new Date(year, month, 1);
|
||||
modified = modified || data.modified;
|
||||
|
||||
required.unshift({ x: date.getTime() / 1000, y: res.required });
|
||||
paid.unshift({ x: date.getTime() / 1000, y: res.paid });
|
||||
// This is a hack so that the page loads before we try to load the plot
|
||||
// (which seems to make chrome sloooow)
|
||||
// Also I'm not a web developer.
|
||||
setTimeout(function(){
|
||||
for(var i = 0; i < 28; ++i) {
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open("GET", urlBase + 'month/'+ year + '/' + month + '.json', false);
|
||||
xhr.send();
|
||||
|
||||
var data = JSON.parse(xhr.response),
|
||||
res = data.content,
|
||||
date = new Date(year, month, 1);
|
||||
modified = modified || data.modified;
|
||||
|
||||
required.unshift({ x: date.getTime() / 1000, y: res.required });
|
||||
paid.unshift({ x: date.getTime() / 1000, y: res.paid });
|
||||
|
||||
xhr = new XMLHttpRequest();
|
||||
xhr.open("GET", urlBase + 'cashflow/'+ year + '/' + month + '.json', false);
|
||||
xhr.send();
|
||||
|
||||
res = JSON.parse(xhr.response).content,
|
||||
influx.unshift({ x: date.getTime() / 1000, y: res.in });
|
||||
month -= 1;
|
||||
if(month == 0) {
|
||||
month = 12;
|
||||
year -= 1;
|
||||
xhr = new XMLHttpRequest();
|
||||
xhr.open("GET", urlBase + 'cashflow/'+ year + '/' + month + '.json', false);
|
||||
xhr.send();
|
||||
|
||||
res = JSON.parse(xhr.response).content,
|
||||
influx.unshift({ x: date.getTime() / 1000, y: res.in });
|
||||
month -= 1;
|
||||
if(month == 0) {
|
||||
month = 12;
|
||||
year -= 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log(required, paid);
|
||||
|
||||
var lastmod = document.getElementById("lastmod");
|
||||
lastmod.innerHTML = modified;
|
||||
|
||||
var lastmod = document.getElementById("lastmod");
|
||||
lastmod.innerHTML = "Last Modified " + modified;
|
||||
|
||||
var palette = new Rickshaw.Color.Palette( { scheme: 'munin' } );
|
||||
var graph = new Rickshaw.Graph({
|
||||
element: document.getElementById("plot"),
|
||||
width: 1300,
|
||||
height: 600,
|
||||
renderer: 'line',
|
||||
series: [
|
||||
{
|
||||
color: palette.color(),
|
||||
data: required,
|
||||
name: 'Required',
|
||||
},
|
||||
{
|
||||
color: palette.color(),
|
||||
data: paid,
|
||||
name: 'Paid',
|
||||
},
|
||||
{
|
||||
color: palette.color(),
|
||||
data: influx,
|
||||
name: 'Cash Influx',
|
||||
},
|
||||
]
|
||||
});
|
||||
graph.render();
|
||||
var palette = new Rickshaw.Color.Palette( { scheme: 'munin' } );
|
||||
var graph = new Rickshaw.Graph({
|
||||
element: document.getElementById("plot"),
|
||||
width: $("#plot").width(),
|
||||
height: $("#plot").width()*0.4,
|
||||
renderer: 'line',
|
||||
series: [
|
||||
{
|
||||
color: palette.color(),
|
||||
data: required,
|
||||
name: 'Required',
|
||||
},
|
||||
{
|
||||
color: palette.color(),
|
||||
data: paid,
|
||||
name: 'Paid',
|
||||
},
|
||||
{
|
||||
color: palette.color(),
|
||||
data: influx,
|
||||
name: 'Cash Influx',
|
||||
},
|
||||
]
|
||||
});
|
||||
graph.render();
|
||||
|
||||
var yAxis = new Rickshaw.Graph.Axis.Y({
|
||||
graph: graph,
|
||||
});
|
||||
yAxis.render();
|
||||
var yAxis = new Rickshaw.Graph.Axis.Y({
|
||||
graph: graph,
|
||||
});
|
||||
yAxis.render();
|
||||
|
||||
var xAxis = new Rickshaw.Graph.Axis.Time({
|
||||
graph: graph,
|
||||
});
|
||||
xAxis.render();
|
||||
var xAxis = new Rickshaw.Graph.Axis.Time({
|
||||
graph: graph,
|
||||
});
|
||||
xAxis.render();
|
||||
|
||||
var legend = new Rickshaw.Graph.Legend({
|
||||
element: document.getElementById("legend"),
|
||||
graph: graph,
|
||||
});
|
||||
var legend = new Rickshaw.Graph.Legend({
|
||||
element: document.getElementById("legend"),
|
||||
graph: graph,
|
||||
});
|
||||
|
||||
var hoverDetail = new Rickshaw.Graph.HoverDetail( {
|
||||
graph: graph,
|
||||
xFormatter: function(x) {
|
||||
var date = new Date(x * 1000);
|
||||
return (1900 + date.getYear()) + '/' + date.getMonth();
|
||||
}
|
||||
});
|
||||
var hoverDetail = new Rickshaw.Graph.HoverDetail( {
|
||||
graph: graph,
|
||||
xFormatter: function(x) {
|
||||
var date = new Date(x * 1000);
|
||||
return (1900 + date.getYear()) + '/' + date.getMonth();
|
||||
}
|
||||
});
|
||||
}, 200);
|
||||
});
|
||||
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
<!-- Custom styles for this template -->
|
||||
<link href="/static/css/main.css" rel="stylesheet">
|
||||
|
||||
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
|
||||
{% block extraheader %}
|
||||
{% endblock %}
|
||||
</head>
|
||||
|
@ -72,7 +73,6 @@
|
|||
<!-- Bootstrap core JavaScript
|
||||
================================================== -->
|
||||
<!-- Placed at the end of the document so the pages load faster -->
|
||||
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
|
||||
<script src="/static/js/bootstrap.min.js"></script>
|
||||
{% block extrajs %}
|
||||
{% endblock %}
|
||||
|
|
|
@ -3,17 +3,16 @@
|
|||
{% block title %}Stats{% endblock %}
|
||||
{% block extraheader %}
|
||||
<link rel="stylesheet" href="/static/css/rickshaw.css">
|
||||
{% endblock %}
|
||||
{% block extrajs %}
|
||||
<script src="/static/js/d3.min.js"></script>
|
||||
<script src="/static/js/d3.layout.min.js"></script>
|
||||
<script src="/static/js/rickshaw.min.js"></script>
|
||||
<script src="/static/js/plot.js"></script>
|
||||
{% endblock %}
|
||||
{% block content %}
|
||||
<div class="stats">
|
||||
<div id="legend"></div>
|
||||
<div id="plot"></div>
|
||||
Last modified: <span id="lastmod">-</span>
|
||||
</div>
|
||||
<div class="container-fluid stats">
|
||||
<h1>Payment Stats</h1>
|
||||
<h4 id="lastmod">Loding...</h4>
|
||||
<div id="legend"></div>
|
||||
<div id="plot"></div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
|
Loading…
Reference in New Issue