diff --git a/project/config/urls.py b/project/config/urls.py index b69923e4..5925233d 100644 --- a/project/config/urls.py +++ b/project/config/urls.py @@ -37,6 +37,7 @@ # Your stuff: custom urls includes go here url(r'^members/', include('members.urls')), url(r'^velkoja/', include('velkoja.urls')), + url(settings.ADMIN_URL, include('creditor.urls_admin')), url(r'^api/', include(router.urls)), url(r'^api-auth/get-token/', authtoken_views.obtain_auth_token), diff --git a/project/creditor/admin.py b/project/creditor/admin.py index 842fa486..9694a3aa 100644 --- a/project/creditor/admin.py +++ b/project/creditor/admin.py @@ -9,6 +9,9 @@ from .models import RecurringTransaction, Transaction, TransactionTag +from django.contrib.admin import AdminSite +from django.http import HttpResponse + class TransactionTagAdmin(VersionAdmin): pass @@ -148,6 +151,7 @@ def dates_formatted(self, obj): dates_formatted.admin_order_field = 'start' + admin.site.register(TransactionTag, TransactionTagAdmin) admin.site.register(Transaction, TransactionAdmin) admin.site.register(RecurringTransaction, RecurringTransactionAdmin) diff --git a/project/creditor/locale/fi/LC_MESSAGES/django.po b/project/creditor/locale/fi/LC_MESSAGES/django.po index 47bf984b..b4df0d94 100644 --- a/project/creditor/locale/fi/LC_MESSAGES/django.po +++ b/project/creditor/locale/fi/LC_MESSAGES/django.po @@ -4,7 +4,7 @@ msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2015-12-30 13:20+0200\n" +"POT-Creation-Date: 2017-04-23 09:21+0300\n" "PO-Revision-Date: 2015-11-29 03:01+0200\n" "Last-Translator: \n" "Language-Team: \n" @@ -15,139 +15,195 @@ msgstr "" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "X-Generator: Poedit 1.8.6\n" -#: admin.py:15 +#: admin.py:18 #, fuzzy #| msgid "Transaction Tags" msgid "Transaction tags" msgstr "Tapahtuman tägit" -#: admin.py:29 admin.py:68 admin.py:130 handlers.py:9 models.py:39 -#: models.py:75 +#: admin.py:32 admin.py:71 admin.py:133 handlers.py:13 models.py:47 +#: models.py:84 templates/admin/table.html:71 msgid "Amount" -msgstr "Määrä" +msgstr "Arvo" -#: admin.py:34 +#: admin.py:37 msgid "Negative amount" -msgstr "Negatiivinen summa" +msgstr "Negatiivinen arvo" -#: admin.py:35 +#: admin.py:38 msgid "Positive amount" -msgstr "Positiivinen summa" +msgstr "Positiivinen arvo" -#: admin.py:60 handlers.py:6 models.py:35 +#: admin.py:63 handlers.py:9 models.py:43 msgid "Datetime" msgstr "Aika" -#: admin.py:73 admin.py:80 +#: admin.py:76 admin.py:83 msgid "Active" -msgstr "" +msgstr "Aktiiviset" -#: admin.py:78 +#: admin.py:81 msgid "All" msgstr "Kaikki" -#: admin.py:79 +#: admin.py:82 msgid "Inactive" msgstr "Ei-aktiiviset" -#: admin.py:122 models.py:38 models.py:74 +#: admin.py:125 models.py:46 models.py:83 msgid "Member" msgstr "Jäsen" -#: admin.py:144 +#: admin.py:147 msgid "Start / End" msgstr "Alku / Loppu" -#: handlers.py:7 +#: handlers.py:10 handlers.py:11 msgid "Name" msgstr "Nimi" -#: handlers.py:8 models.py:37 +#: handlers.py:12 models.py:45 msgid "Reference" msgstr "Viite" -#: handlers.py:10 models.py:40 +#: handlers.py:14 models.py:48 msgid "Unique transaction id" msgstr "Transaktion yksilöllinen tunniste" -#: handlers.py:17 +#: handlers.py:21 #, python-format msgid "AbstractTransaction %s: %+.2f " msgstr "" -#: handlers.py:44 +#: handlers.py:49 msgid "Transaction handler baseclass, this does nothing" msgstr "Transaktioiden baseclass, ei tee mitään" -#: models.py:12 models.py:71 +#: models.py:18 models.py:80 msgid "Label" msgstr "Nimiö" -#: models.py:13 +#: models.py:19 #, fuzzy #| msgid "Transaction Tag" msgid "Transaction match" msgstr "Tapahtuman tägi" -#: models.py:19 +#: models.py:20 +msgid "Holvi category code" +msgstr "Holvi-kategorian koodi" + +#: models.py:26 msgid "Transaction Tag" msgstr "Tapahtuman tägi" -#: models.py:20 +#: models.py:27 msgid "Transaction Tags" msgstr "Tapahtuman tägit" -#: models.py:36 models.py:73 +#: models.py:44 models.py:82 msgid "Tag" msgstr "Tägi" -#: models.py:44 +#: models.py:52 #, python-format msgid "%+.2f for %s (%s)" msgstr "" -#: models.py:45 +#: models.py:53 #, python-format msgid "%+.2f for %s" msgstr "" -#: models.py:48 +#: models.py:56 msgid "Transaction" msgstr "Tapahtuma" -#: models.py:49 +#: models.py:57 templates/admin/table.html:84 msgid "Transactions" msgstr "Tapahtumat" -#: models.py:59 models.py:64 +#: models.py:68 models.py:73 msgid "Monthly" msgstr "Kuukausittain" -#: models.py:60 models.py:65 +#: models.py:69 models.py:74 msgid "Yearly" msgstr "Vuosittain" -#: models.py:68 +#: models.py:77 msgid "Since" msgstr "Alkaen" -#: models.py:69 +#: models.py:78 msgid "Until" msgstr "Asti" -#: models.py:72 +#: models.py:81 msgid "Recurrence type" msgstr "Toistuvuuden tyyppi" -#: models.py:80 +#: models.py:89 #, python-format msgid "%+.2f %s for %s (%s)" msgstr "" -#: models.py:141 +#: models.py:156 msgid "Recurring Transaction" msgstr "Toistuva tapahtuma" -#: models.py:142 +#: models.py:157 msgid "Recurring Transactions" msgstr "Toistuvat tapahtumat" + +#: templates/admin/table.html:19 templates/admin/table_year.html:19 +msgid "Django site admin" +msgstr "" + +#: templates/admin/table.html:26 templates/admin/table_year.html:27 +msgid "Filter by transaction types" +msgstr "Suodata tapahtumatyypin mukaan" + +#: templates/admin/table.html:27 templates/admin/table_year.html:28 +msgid "All types" +msgstr "Kaikki tyypit" + +#: templates/admin/table.html:36 templates/admin/table_year.html:38 +msgid "Month selection" +msgstr "Valitse kuukausi" + +#: templates/admin/table.html:60 templates/admin/table_year.html:57 +msgid "Summary" +msgstr "Yhteenveto" + +#: templates/admin/table.html:62 templates/admin/table_year.html:63 +msgid "Income" +msgstr "Tulot" + +#: templates/admin/table.html:63 templates/admin/table_year.html:64 +msgid "Receivables" +msgstr "Saatavat" + +#: templates/admin/table.html:64 +msgid "Balance" +msgstr "Balanssi" + +#: templates/admin/table.html:68 +msgid "Frequency table" +msgstr "Frekvenssit" + +#: templates/admin/table.html:72 +msgid "Freq" +msgstr "Frekv." + +#: templates/admin/table_year.html:58 +msgid "Year" +msgstr "Vuosi" + +#: templates/admin/table_year.html:62 +msgid "Month" +msgstr "Kuukausi" + +#: templates/admin/table_year.html:65 +msgid "Total" +msgstr "Yhteensä" diff --git a/project/creditor/templates/admin/table.html b/project/creditor/templates/admin/table.html new file mode 100755 index 00000000..3fc72c3f --- /dev/null +++ b/project/creditor/templates/admin/table.html @@ -0,0 +1,99 @@ +{% extends "admin/base.html" %} + +{% load i18n %} + + +{% block extrastyle %} + +{% endblock %} + + +{% block title %} {{ site_title|default:_('Summary')|add:' '|add:month|add:'-'|add:year }}{% endblock %} + + +{% block content %} + +
+ +

{% trans "Filter by transaction types" %}

+{% trans "All types" %} +{% for t in tags %} +{% if t.pk = tag_pk %} +{{ t.label }} +{% else %} +{{ t.label }} +{% endif %} +{% endfor %} + +

{% trans "Month selection" %}

+ +{% for y in years %} + +{% if tag %} + +{% else %} + +{% endif %} +{% for m in months %} +{% if y = year and m = month %} + +{% else %} + +{% endif %} +{% endfor %} + +{% endfor %} +
{{ y }}{{ y }}{{ m }}{{ m }}
+
+ + +

{{ month }}-{{ year }} {{ tag.label }}

+ +

{% trans "Summary" %}

+ + + + +
{% trans "Income" %}{{ income.sum }}
{% trans "Receivables" %}{{ receivables.sum }}
{% trans "Balance" %}{{ total.sum }}
+ + +

{% trans "Frequency table" %}

+ + + + + +{% for f in freq_table %} + + + + +{% endfor %} +
{% trans "Amount" %}{% trans "Freq" %}
{{ f.amount }}{{ f.freq }}
+ + + +

{% trans "Transactions" %}

+ + + {% for list_item in object_list %} + + + {% if list_item.amount < 0 %} + {% else %}{% endif %} + + + + {% endfor %} +
{{ list_item.owner }}{{ list_item.amount }}+{{ list_item.amount }}{{ list_item.tag }}{{ list_item.stamp }}
+ +{% endblock content %} + diff --git a/project/creditor/templates/admin/table_year.html b/project/creditor/templates/admin/table_year.html new file mode 100755 index 00000000..2b9fd901 --- /dev/null +++ b/project/creditor/templates/admin/table_year.html @@ -0,0 +1,80 @@ +{% extends "admin/base.html" %} + +{% load i18n %} +{% load aggregation_utils %} + +{% block extrastyle %} + +{% endblock %} + + +{% block title %} {{ site_title|default:_('Summary')|add:' '|add:year }}{% endblock %} + + +{% block content %} + + +
+ +

{% trans "Filter by transaction types" %}

+{% trans "All types" %} +{% for t in tags %} +{% if t.pk = tag_pk %} +{{ t.label }} +{% else %} +{{ t.label }} +{% endif %} +{% endfor %} + + +

{% trans "Month selection" %}

+ +{% for y in years %} + + +{% for m in months %} + +{% endfor %} + +{% endfor %} +
{{ y }}{{ m }}
+
+ +
+ + + +

{{ year }} {{ tag.label }}

+ +

{% trans "Summary" %}

+

{% trans "Year" %} {{ year }}

+ + + + + + + + +{% for m in months %} + + + + + + +{% endfor %} +
{% trans "Month" %}{% trans "Income" %}{% trans "Receivables" %}{% trans "Total" %}
{{ m }}{{income|month_sum_value:m}}{{receivables|month_sum_value:m}}{% if total|month_sum_value:m < 0 %}{% else %}{% endif %}{{total|month_sum_value:m}}
+ +
+ +{% endblock content %} + diff --git a/project/creditor/templatetags/__init__.py b/project/creditor/templatetags/__init__.py new file mode 100644 index 00000000..40a96afc --- /dev/null +++ b/project/creditor/templatetags/__init__.py @@ -0,0 +1 @@ +# -*- coding: utf-8 -*- diff --git a/project/creditor/templatetags/aggregation_utils.py b/project/creditor/templatetags/aggregation_utils.py new file mode 100644 index 00000000..5febc897 --- /dev/null +++ b/project/creditor/templatetags/aggregation_utils.py @@ -0,0 +1,11 @@ +from django import template + +register = template.Library() + +@register.filter +def month_sum_value(month_array, month): + for d in month_array: + if str(month) == d['month']: + return d['sum'] + return '.' + diff --git a/project/creditor/tests/test_transaction_table.py b/project/creditor/tests/test_transaction_table.py new file mode 100644 index 00000000..2fd44918 --- /dev/null +++ b/project/creditor/tests/test_transaction_table.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +import pytest + +# Nonadmins should be redirected +@pytest.mark.django_db +def test_views_nonadmin2(client): + assert client.get('/admin/transaction_table/').status_code == 302 + assert client.get('/admin/transaction_table/2000/').status_code == 302 + assert client.get('/admin/transaction_table/2000/01/').status_code == 302 + assert client.get('/admin/transaction_table/2000/tag=0').status_code == 302 + assert client.get('/admin/transaction_table/2000/01/tag=0').status_code == 302 + + +# Only admins should be able to see the table +def test_views_admin(admin_client): + assert admin_client.get('/admin/transaction_table/').status_code == 200 + assert admin_client.get('/admin/transaction_table/2000/').status_code == 200 + assert admin_client.get('/admin/transaction_table/2000/01/').status_code == 200 + assert admin_client.get('/admin/transaction_table/2000/tag=0').status_code == 200 + assert admin_client.get('/admin/transaction_table/2000/01/tag=0').status_code == 200 + diff --git a/project/creditor/urls_admin.py b/project/creditor/urls_admin.py new file mode 100644 index 00000000..8b94243c --- /dev/null +++ b/project/creditor/urls_admin.py @@ -0,0 +1,17 @@ +# -*- coding: utf-8 -*- +from django.conf.urls import url +from django.contrib.auth.decorators import permission_required + +#from creditor.admin import transaction_table_admin + + +from . import views + +urlpatterns = [ + url(r'^transaction_table/$', permission_required('is_staff',login_url='/admin/login/')(views.TransactionYearView.as_view()), name="table_y"), + url(r'^transaction_table/(?P[0-9]{4})/$', permission_required('is_staff',login_url='/admin/login/')(views.TransactionYearView.as_view()), name="table_y"), + url(r'^transaction_table/(?P[0-9]{4})/tag=(?P[0-9]+)$', permission_required('is_staff',login_url='/admin/login/')(views.TransactionYearView.as_view()), name="table_y"), + + url(r'^transaction_table/(?P[0-9]{4})/(?P(1[0-2]|0[1-9]))/$', permission_required('is_staff',login_url='/admin/login/')(views.TransactionMonthView.as_view()), name="table_m"), + url(r'^transaction_table/(?P[0-9]{4})/(?P(1[0-2]|0[1-9]))/tag=(?P[0-9]+)$', permission_required('is_staff',login_url='/admin/login/')(views.TransactionMonthView.as_view()), name="table_m"), +] diff --git a/project/creditor/views.py b/project/creditor/views.py index d3ff201c..71704ab0 100644 --- a/project/creditor/views.py +++ b/project/creditor/views.py @@ -1,4 +1,73 @@ # -*- coding: utf-8 -*- from django.shortcuts import render +from django.views.generic import ListView +from creditor.models import Transaction, TransactionTag +from django.db.models import Sum, Count +from django.utils import timezone -# Create your views here. + +class TransactionMonthView(ListView): + model = Transaction + template_name = "admin/table.html" + + param_tag = None + def get_queryset(self): + + self.param_year = self.kwargs['year'] + self.param_month = self.kwargs['month'] + + if('tag' in self.kwargs and int(self.kwargs['tag']) in TransactionTag.objects.values_list('pk', flat=True)): + self.param_tag = TransactionTag.objects.filter(pk=int(self.kwargs['tag']))[0] + return Transaction.objects.filter(stamp__year=self.param_year).filter(stamp__month=self.param_month).filter(tag__pk=self.param_tag.pk).order_by('owner__lname','owner__fname') + + return Transaction.objects.filter(stamp__year=self.param_year).filter(stamp__month=self.param_month).order_by('owner__lname','owner__fname') + + + def get_context_data(self, **kwargs): + context = super(TransactionMonthView, self).get_context_data(**kwargs) + context['income'] = self.get_queryset().filter(amount__gte=0).aggregate(sum=Sum('amount')) + context['receivables'] = self.get_queryset().filter(amount__lt=0).aggregate(sum=Sum('amount')) + context['freq_table'] = self.get_queryset().values('amount').annotate(freq=Count('amount')).order_by('-amount') + context['total'] = self.get_queryset().aggregate(sum=Sum('amount')) + context['year'] = self.param_year + context['tag'] = self.param_tag + context['tag_pk'] = 0 if self.param_tag == None else self.param_tag.pk + context['month'] = self.param_month.rjust(2, "0") + context['months'] = ['01','02','03','04','05','06','07','08','09','10','11','12'] + context['years'] = [str(d.year) for d in Transaction.objects.datetimes('stamp', 'year')] + context['tags'] = TransactionTag.objects.values('pk','label').order_by('pk') + + return context + +class TransactionYearView(ListView): + model = Transaction + template_name = "admin/table_year.html" + + param_year = timezone.now().year + param_tag = None + def get_queryset(self): + + if('year' in self.kwargs): + self.param_year = self.kwargs['year'] + + # tag number must refer to existing TransactionTag + if('tag' in self.kwargs and int(self.kwargs['tag']) in TransactionTag.objects.values_list('pk', flat=True)): + self.param_tag = TransactionTag.objects.filter(pk=int(self.kwargs['tag']))[0] + return Transaction.objects.filter(stamp__year=self.param_year).filter(tag__pk=self.param_tag.pk) + + return Transaction.objects.filter(stamp__year=self.param_year) + + + def get_context_data(self, **kwargs): + context = super(TransactionYearView, self).get_context_data(**kwargs) + context['receivables'] = self.get_queryset().filter(amount__lt=0).extra(select={'month': "to_char(stamp, 'MM' )"}).values('month').annotate(sum=Sum('amount')).order_by() + context['income'] = self.get_queryset().filter(amount__gte=0).extra(select={'month': "to_char(stamp, 'MM' )"}).values('month').annotate(sum=Sum('amount')).order_by() + context['total'] = self.get_queryset().extra(select={'month': "to_char(stamp, 'MM' )"}).values('month').annotate(sum=Sum('amount')).order_by() + context['tag'] = self.param_tag + context['tag_pk'] = 0 if self.param_tag == None else self.param_tag.pk + context['year'] = self.param_year + context['months'] = ['01','02','03','04','05','06','07','08','09','10','11','12'] + context['years'] = [d.year for d in Transaction.objects.datetimes('stamp', 'year')] + context['tags'] = TransactionTag.objects.values('pk','label').order_by('pk') + + return context \ No newline at end of file