Compare commits
2 commits
3cf3cb649e
...
19490e62b2
Author | SHA1 | Date | |
---|---|---|---|
19490e62b2 | |||
e095d8d35f |
11 changed files with 121 additions and 1 deletions
|
@ -194,7 +194,7 @@ class Snapshot(models.Model):
|
||||||
@property
|
@property
|
||||||
def sum(self):
|
def sum(self):
|
||||||
if self.previous is None:
|
if self.previous is None:
|
||||||
return None
|
return 0
|
||||||
trans = self.transactions.aggregate(sum=models.Sum("value"))
|
trans = self.transactions.aggregate(sum=models.Sum("value"))
|
||||||
return trans["sum"] or 0
|
return trans["sum"] or 0
|
||||||
|
|
||||||
|
@ -206,6 +206,24 @@ class Snapshot(models.Model):
|
||||||
date__lte=self.date, date__gt=self.previous.date
|
date__lte=self.date, date__gt=self.previous.date
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def pos(self):
|
||||||
|
return (
|
||||||
|
self.transactions.filter(value__gt=0).aggregate(sum=models.Sum("value"))[
|
||||||
|
"sum"
|
||||||
|
]
|
||||||
|
or 0
|
||||||
|
)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def neg(self):
|
||||||
|
return (
|
||||||
|
self.transactions.filter(value__lt=0).aggregate(sum=models.Sum("value"))[
|
||||||
|
"sum"
|
||||||
|
]
|
||||||
|
or 0
|
||||||
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
ordering = ["-date"]
|
ordering = ["-date"]
|
||||||
verbose_name = _("Snapshot")
|
verbose_name = _("Snapshot")
|
||||||
|
|
|
@ -20,6 +20,7 @@ from django.views.generic.base import RedirectView
|
||||||
|
|
||||||
urlpatterns = i18n_patterns(
|
urlpatterns = i18n_patterns(
|
||||||
path("", include("main.urls")),
|
path("", include("main.urls")),
|
||||||
|
path("plot/", include("plot.urls")),
|
||||||
path("admin/", admin.site.urls),
|
path("admin/", admin.site.urls),
|
||||||
prefix_default_language=False,
|
prefix_default_language=False,
|
||||||
)
|
)
|
||||||
|
|
0
nummi/plot/__init__.py
Normal file
0
nummi/plot/__init__.py
Normal file
3
nummi/plot/admin.py
Normal file
3
nummi/plot/admin.py
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
from django.contrib import admin
|
||||||
|
|
||||||
|
# Register your models here.
|
6
nummi/plot/apps.py
Normal file
6
nummi/plot/apps.py
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
|
class PlotConfig(AppConfig):
|
||||||
|
default_auto_field = "django.db.models.BigAutoField"
|
||||||
|
name = "plot"
|
0
nummi/plot/migrations/__init__.py
Normal file
0
nummi/plot/migrations/__init__.py
Normal file
3
nummi/plot/models.py
Normal file
3
nummi/plot/models.py
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
from django.db import models
|
||||||
|
|
||||||
|
# Create your models here.
|
13
nummi/plot/nummi.mplstyle
Normal file
13
nummi/plot/nummi.mplstyle
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
font.family: Inter
|
||||||
|
|
||||||
|
lines.linewidth: 2
|
||||||
|
|
||||||
|
figure.autolayout: True
|
||||||
|
figure.figsize: 8, 4
|
||||||
|
figure.dpi: 300
|
||||||
|
|
||||||
|
axes.prop_cycle: cycler('color', ["66cc66", "#338033", "#99ff99", "#802653", "#cc6699"])
|
||||||
|
axes.axisbelow: True
|
||||||
|
axes.grid: True
|
||||||
|
|
||||||
|
svg.fonttype: none
|
3
nummi/plot/tests.py
Normal file
3
nummi/plot/tests.py
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
|
# Create your tests here.
|
8
nummi/plot/urls.py
Normal file
8
nummi/plot/urls.py
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
from django.urls import path
|
||||||
|
|
||||||
|
from . import views
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
path("timeline", views.timeline, name="timeline"),
|
||||||
|
path("categories", views.categories, name="categories"),
|
||||||
|
]
|
65
nummi/plot/views.py
Normal file
65
nummi/plot/views.py
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
import io
|
||||||
|
|
||||||
|
import matplotlib
|
||||||
|
import matplotlib.pyplot as plt
|
||||||
|
from matplotlib import dates as mdates
|
||||||
|
from django.db import models
|
||||||
|
from django.contrib.auth.decorators import login_required
|
||||||
|
from django.http import HttpResponse
|
||||||
|
from django.utils.translation import gettext as _
|
||||||
|
|
||||||
|
from main.models import (
|
||||||
|
Transaction,
|
||||||
|
Invoice,
|
||||||
|
Category,
|
||||||
|
Snapshot,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
matplotlib.use("Agg")
|
||||||
|
plt.style.use("./plot/nummi.mplstyle")
|
||||||
|
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
def timeline(request):
|
||||||
|
_snapshots = Snapshot.objects.all()
|
||||||
|
|
||||||
|
fig, ax = plt.subplots()
|
||||||
|
ax.step(
|
||||||
|
[s.date for s in _snapshots],
|
||||||
|
[s.value for s in _snapshots],
|
||||||
|
where="post",
|
||||||
|
)
|
||||||
|
ax.set(ylabel=_("Snapshots"), ylim=0)
|
||||||
|
ax.xaxis.set_major_formatter(
|
||||||
|
mdates.ConciseDateFormatter(ax.xaxis.get_major_locator())
|
||||||
|
)
|
||||||
|
ax.autoscale(True, "x", True)
|
||||||
|
|
||||||
|
_io = io.StringIO()
|
||||||
|
|
||||||
|
fig.savefig(_io, format="svg")
|
||||||
|
|
||||||
|
return HttpResponse(_io.getvalue(), headers={"Content-Type": "image/svg+xml"})
|
||||||
|
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
def categories(request):
|
||||||
|
_categories = Category.objects.all()
|
||||||
|
|
||||||
|
fig, ax = plt.subplots(figsize=(8, _categories.count() / 4))
|
||||||
|
ax.barh(
|
||||||
|
[str(c) for c in _categories][::-1],
|
||||||
|
[
|
||||||
|
Transaction.objects.filter(category=c).aggregate(sum=models.Sum("value"))[
|
||||||
|
"sum"
|
||||||
|
]
|
||||||
|
for c in _categories
|
||||||
|
][::-1],
|
||||||
|
)
|
||||||
|
|
||||||
|
_io = io.StringIO()
|
||||||
|
|
||||||
|
fig.savefig(_io, format="svg")
|
||||||
|
|
||||||
|
return HttpResponse(_io.getvalue(), headers={"Content-Type": "image/svg+xml"})
|
Loading…
Reference in a new issue