From c53725b5025f640f27d913a1a0bce2776c54c20a Mon Sep 17 00:00:00 2001 From: Anna Date: Tue, 31 Oct 2017 16:37:02 +0300 Subject: [PATCH] init --- .gitignore | 4 + manage.py | 10 + mysite/__init__.py | 0 mysite/settings.py | 139 +++++++++ mysite/urls.py | 25 ++ mysite/wsgi.py | 17 ++ pfm/__init__.py | 0 pfm/admin.py | 7 + pfm/apps.py | 7 + pfm/forms.py | 115 ++++++++ pfm/migrations/0001_initial.py | 70 +++++ pfm/migrations/0002_auto_20161002_1309.py | 22 ++ pfm/migrations/0003_auto_20161118_1230.py | 26 ++ pfm/migrations/__init__.py | 0 pfm/models.py | 142 +++++++++ pfm/static/css/pfm.css | 13 + pfm/static/js/diagram.js | 45 +++ pfm/static/js/scripts.js | 89 ++++++ pfm/templates/pfm/accounts.html | 53 ++++ pfm/templates/pfm/base.html | 148 ++++++++++ pfm/templates/pfm/cat_edit.html | 35 +++ pfm/templates/pfm/categories.html | 56 ++++ pfm/templates/pfm/login.html | 12 + pfm/templates/pfm/logout.html | 10 + pfm/templates/pfm/reports.html | 142 +++++++++ pfm/templates/pfm/test.html | 12 + pfm/templates/pfm/tr_edit.html | 63 ++++ pfm/templates/pfm/tr_edit_form.html | 27 ++ pfm/templates/pfm/transactions.html | 79 +++++ pfm/templates/pfm/upload.html | 21 ++ pfm/tests.py | 64 +++++ pfm/urls.py | 21 ++ pfm/views.py | 333 ++++++++++++++++++++++ 33 files changed, 1807 insertions(+) create mode 100644 manage.py create mode 100644 mysite/__init__.py create mode 100644 mysite/settings.py create mode 100644 mysite/urls.py create mode 100644 mysite/wsgi.py create mode 100644 pfm/__init__.py create mode 100644 pfm/admin.py create mode 100644 pfm/apps.py create mode 100644 pfm/forms.py create mode 100644 pfm/migrations/0001_initial.py create mode 100644 pfm/migrations/0002_auto_20161002_1309.py create mode 100644 pfm/migrations/0003_auto_20161118_1230.py create mode 100644 pfm/migrations/__init__.py create mode 100644 pfm/models.py create mode 100644 pfm/static/css/pfm.css create mode 100644 pfm/static/js/diagram.js create mode 100644 pfm/static/js/scripts.js create mode 100644 pfm/templates/pfm/accounts.html create mode 100644 pfm/templates/pfm/base.html create mode 100644 pfm/templates/pfm/cat_edit.html create mode 100644 pfm/templates/pfm/categories.html create mode 100644 pfm/templates/pfm/login.html create mode 100644 pfm/templates/pfm/logout.html create mode 100644 pfm/templates/pfm/reports.html create mode 100644 pfm/templates/pfm/test.html create mode 100644 pfm/templates/pfm/tr_edit.html create mode 100644 pfm/templates/pfm/tr_edit_form.html create mode 100644 pfm/templates/pfm/transactions.html create mode 100644 pfm/templates/pfm/upload.html create mode 100644 pfm/tests.py create mode 100644 pfm/urls.py create mode 100644 pfm/views.py diff --git a/.gitignore b/.gitignore index 7f7cccc..dba3bf9 100644 --- a/.gitignore +++ b/.gitignore @@ -58,3 +58,7 @@ docs/_build/ # PyBuilder target/ +*.pyc +*.save +*.swp +db.sqlite3 diff --git a/manage.py b/manage.py new file mode 100644 index 0000000..8a50ec0 --- /dev/null +++ b/manage.py @@ -0,0 +1,10 @@ +#!/usr/bin/env python +import os +import sys + +if __name__ == "__main__": + os.environ.setdefault("DJANGO_SETTINGS_MODULE", "mysite.settings") + + from django.core.management import execute_from_command_line + + execute_from_command_line(sys.argv) diff --git a/mysite/__init__.py b/mysite/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/mysite/settings.py b/mysite/settings.py new file mode 100644 index 0000000..b2fe018 --- /dev/null +++ b/mysite/settings.py @@ -0,0 +1,139 @@ +# -*- coding: utf-8 -*- + +""" +Django settings for mysite project. + +Generated by 'django-admin startproject' using Django 1.9.7. + +For more information on this file, see +https://docs.djangoproject.com/en/1.9/topics/settings/ + +For the full list of settings and their values, see +https://docs.djangoproject.com/en/1.9/ref/settings/ +""" + +import os + +# Build paths inside the project like this: os.path.join(BASE_DIR, ...) +BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + + +# Quick-start development settings - unsuitable for production +# See https://docs.djangoproject.com/en/1.9/howto/deployment/checklist/ + +# SECURITY WARNING: keep the secret key used in production secret! +SECRET_KEY = '1a+3jmf#g!q_gj66$^v9_&8uoev=%k49=j(%lp%!kvv$6x3bn^' + +# SECURITY WARNING: don't run with debug turned on in production! +DEBUG = True + +ALLOWED_HOSTS = [] + + +# Application definition + +INSTALLED_APPS = [ + 'django.contrib.admin', + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'django.contrib.sessions', + 'django.contrib.messages', + 'django.contrib.staticfiles', + 'polls', + 'blog', + 'imagehost', + 'pfm', + 'taggit', +] + +MIDDLEWARE_CLASSES = [ + 'django.middleware.security.SecurityMiddleware', + 'django.contrib.sessions.middleware.SessionMiddleware', + 'django.middleware.common.CommonMiddleware', + 'django.middleware.csrf.CsrfViewMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware', + 'django.contrib.auth.middleware.SessionAuthenticationMiddleware', + 'django.contrib.messages.middleware.MessageMiddleware', + 'django.middleware.clickjacking.XFrameOptionsMiddleware', +] + +ROOT_URLCONF = 'mysite.urls' + +TEMPLATES = [ + { + 'BACKEND': 'django.template.backends.django.DjangoTemplates', + 'DIRS': [], + 'APP_DIRS': True, + 'OPTIONS': { + 'context_processors': [ + 'django.template.context_processors.debug', + 'django.template.context_processors.request', + 'django.contrib.auth.context_processors.auth', + 'django.contrib.messages.context_processors.messages', + ], + }, + }, +] + +WSGI_APPLICATION = 'mysite.wsgi.application' + + +# Database +# https://docs.djangoproject.com/en/1.9/ref/settings/#databases + +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.sqlite3', + 'NAME': os.path.join(BASE_DIR, '../db.sqlite3'), + } +} + + +# Password validation +# https://docs.djangoproject.com/en/1.9/ref/settings/#auth-password-validators + +AUTH_PASSWORD_VALIDATORS = [ + { + 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', + }, +] + + +# Internationalization +# https://docs.djangoproject.com/en/1.9/topics/i18n/ + +LANGUAGE_CODE = 'ru-RU' +USE_I18N = True + +TIME_ZONE = 'Europe/Moscow' + + +USE_I18N = True + +USE_L10N = True + +USE_TZ = True + + +# Static files (CSS, JavaScript, Images) +# https://docs.djangoproject.com/en/1.9/howto/static-files/ + +PROJECT_ROOT = os.path.dirname(os.path.abspath(__file__)) +STATIC_URL = '/static/' +STATIC_ROOT = os.path.join(PROJECT_ROOT, 'static') + +ADMIN_TOOLS_MENU = 'myproject.menu.CustomMenu' +ADMIN_TOOLS_INDEX_DASHBOARD = 'myproject.dashboard.CustomIndexDashboard' +ADMIN_TOOLS_APP_INDEX_DASHBOARD = 'myproject.dashboard.CustomAppIndexDashboard' + +MEDIA_ROOT = os.path.join(PROJECT_ROOT, 'media') +MEDIA_URL = '/media/' diff --git a/mysite/urls.py b/mysite/urls.py new file mode 100644 index 0000000..2522f9f --- /dev/null +++ b/mysite/urls.py @@ -0,0 +1,25 @@ +"""mysite URL Configuration + +The `urlpatterns` list routes URLs to views. For more information please see: + https://docs.djangoproject.com/en/1.9/topics/http/urls/ +Examples: +Function views + 1. Add an import: from my_app import views + 2. Add a URL to urlpatterns: url(r'^$', views.home, name='home') +Class-based views + 1. Add an import: from other_app.views import Home + 2. Add a URL to urlpatterns: url(r'^$', Home.as_view(), name='home') +Including another URLconf + 1. Import the include() function: from django.conf.urls import url, include + 2. Add a URL to urlpatterns: url(r'^blog/', include('blog.urls')) +""" +from django.conf.urls import url, include, patterns +from django.contrib import admin +from django.conf import settings +from django.conf.urls.static import static +from django.views.generic.base import TemplateView +urlpatterns = patterns('', + url(r'^admin/', admin.site.urls), + url(r'^$', TemplateView.as_view(template_name='main.html')), + url(r'^pfm/', include('pfm.urls')), +) + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) diff --git a/mysite/wsgi.py b/mysite/wsgi.py new file mode 100644 index 0000000..f47ba04 --- /dev/null +++ b/mysite/wsgi.py @@ -0,0 +1,17 @@ +# -*- coding: utf-8 -*- +""" +WSGI config for mysite project. + +It exposes the WSGI callable as a module-level variable named ``application``. + +For more information on this file, see +https://docs.djangoproject.com/en/1.9/howto/deployment/wsgi/ +""" + +import os + +from django.core.wsgi import get_wsgi_application + +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "mysite.settings") + +application = get_wsgi_application() diff --git a/pfm/__init__.py b/pfm/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/pfm/admin.py b/pfm/admin.py new file mode 100644 index 0000000..b111a78 --- /dev/null +++ b/pfm/admin.py @@ -0,0 +1,7 @@ +from django.contrib import admin +from .models import Account, Category, Transaction, Transfer +admin.site.register(Account) +admin.site.register(Category) +admin.site.register(Transaction) +admin.site.register(Transfer) + diff --git a/pfm/apps.py b/pfm/apps.py new file mode 100644 index 0000000..a3076f0 --- /dev/null +++ b/pfm/apps.py @@ -0,0 +1,7 @@ +from __future__ import unicode_literals + +from django.apps import AppConfig + + +class PfmConfig(AppConfig): + name = 'pfm' diff --git a/pfm/forms.py b/pfm/forms.py new file mode 100644 index 0000000..d654f18 --- /dev/null +++ b/pfm/forms.py @@ -0,0 +1,115 @@ +from django import forms +from .models import Transaction, Account, Category, Transfer +from django.forms.models import modelform_factory, modelformset_factory, BaseModelFormSet +from django.forms.formsets import formset_factory +from django.forms.extras.widgets import SelectDateWidget +from django.forms.widgets import SplitDateTimeWidget, DateInput, TextInput +from django.contrib.admin.widgets import AdminSplitDateTime + + +class NewTransactionExp (forms.ModelForm): + + + def __init__(self, *args, **kwargs): + user = kwargs.pop('user', None) + type = kwargs.pop('type', None) + super(NewTransactionExp, self).__init__(*args, **kwargs) + self.fields['tr_acc'].queryset=Account.objects.filter(user=user) + self.fields['tr_cat'].queryset=Category.objects.filter(user=user, cat_type='E') + + class Meta: + model = Transaction + fields = ('tr_type', 'tr_date', 'tr_acc', 'tr_cat', 'tr_amount', 'tr_note',) + widgets = { + 'tr_amount': TextInput() + } + + +class NewTransactionInc (forms.ModelForm): + + def __init__(self, *args, **kwargs): + user = kwargs.pop('user', None) + type = kwargs.pop('type', None) + super(NewTransactionInc, self).__init__(*args, **kwargs) + self.fields['tr_acc'].queryset=Account.objects.filter(user=user) + self.fields['tr_cat'].queryset=Category.objects.filter(user=user, cat_type='I') + + class Meta: + model = Transaction + fields = ('tr_type', 'tr_date', 'tr_acc', 'tr_cat', 'tr_amount', 'tr_note',) + widgets = { + 'tr_amount': TextInput() + } + +class NewTransaction (forms.ModelForm): + + def __init__(self, *args, **kwargs): + user = kwargs.pop('user', None) + #type = kwargs.pop('type', None) + super(NewTransaction, self).__init__(*args, **kwargs) + self.fields['tr_acc'].queryset=Account.objects.filter(user=user) + self.fields['tr_cat'].queryset=Category.objects.filter(user=user) + + class Meta: + model = Transaction + fields = ('tr_type', 'tr_date', 'tr_acc', 'tr_cat', 'tr_amount', 'tr_note',) + widgets = { + 'tr_amount': TextInput() + } + + +class NewTransaction1 (forms.ModelForm): + + tr_acc = forms.ModelChoiceField( queryset=Account.objects.filter(user=3)) + tr_cat = forms.ModelChoiceField( queryset=Category.objects.filter(user=3)) + class Meta: + model = Transaction + fields = ('tr_date', 'tr_acc', 'tr_type', 'tr_cat', 'tr_amount', 'tr_note',) + widgets = { + 'tr_amount': TextInput() + } + +class BaseTransactionFormSet(BaseModelFormSet): + + def __init__(self, *args, **kwargs): + user = kwargs.pop('user', None) + super(BaseTransactionFormSet, self).__init__(*args, **kwargs) + self.fields['tr_acc'].queryset=Account.objects.filter(user=user) + self.fields['tr_cat'].queryset=Category.objects.filter(user=user) + self.queryset = Transaction.objects.filter(user=user) + + +class NewAccount(forms.ModelForm): + class Meta: + model = Account + fields = ('acc_name', 'acc_balance', 'acc_currency',) + +class NewCategory(forms.ModelForm): + + class Meta: + model = Category + #forms = '__all__' + exclude = ('user',) + #fields = ('cat_name', 'budget_amount', 'budget_type') + +class NewTransfer(forms.ModelForm): + + def __init__(self, *args, **kwargs): + user = kwargs.pop('user', None) + super(NewTransfer, self).__init__(*args, **kwargs) + self.fields['from_acc'].queryset=Account.objects.filter(user=user) + self.fields['to_acc'].queryset=Account.objects.filter(user=user) + + class Meta: + model = Transfer + exclude = ('user',) + forms = '__all__' + +class Upload (forms.ModelForm): + + class Meta: + model = Transaction + fields = [] + +#Cat_formset = modelformset_factory(Category, form=NewCategory, extra=0) +#TestTrModel = modelformset_factory(Transaction, form=NewTransaction, can_delete=True, extra=0) diff --git a/pfm/migrations/0001_initial.py b/pfm/migrations/0001_initial.py new file mode 100644 index 0000000..6a00811 --- /dev/null +++ b/pfm/migrations/0001_initial.py @@ -0,0 +1,70 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.7 on 2016-10-02 10:08 +from __future__ import unicode_literals + +import datetime +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion +from django.utils.timezone import utc +import django.utils.timezone + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name='Account', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('acc_name', models.CharField(max_length=50, verbose_name='Account')), + ('acc_balance', models.DecimalField(decimal_places=2, max_digits=10, verbose_name='Balance')), + ('acc_currency', models.CharField(max_length=3, verbose_name='Currency')), + ('user', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ], + ), + migrations.CreateModel( + name='Category', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('cat_name', models.CharField(max_length=50, verbose_name='Category')), + ('budget_type', models.CharField(choices=[('W', 'Weekly'), ('M', 'Monthly'), ('Y', 'Yearly')], default='W', max_length=1, verbose_name='Type')), + ('budget_amount', models.DecimalField(decimal_places=2, default=0, max_digits=10, verbose_name='Amount')), + ('user', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ], + options={ + 'verbose_name_plural': 'Categories', + }, + ), + migrations.CreateModel( + name='Transaction', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('tr_type', models.CharField(choices=[('I', 'Income'), ('E', 'Expence')], default='E', max_length=1, verbose_name='Type')), + ('tr_amount', models.DecimalField(decimal_places=2, default='0,00', max_digits=10, verbose_name='Amount')), + ('tr_note', models.CharField(blank=True, max_length=100, null=True)), + ('tr_date', models.DateField(default=datetime.datetime(2016, 10, 2, 10, 8, 39, 110793, tzinfo=utc))), + ('pub_date', models.DateTimeField(blank=True, null=True)), + ('tr_acc', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='pfm.Account')), + ('tr_cat', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='pfm.Category')), + ('user', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ], + ), + migrations.CreateModel( + name='Transfer', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('tr_amount', models.DecimalField(decimal_places=2, max_digits=10, verbose_name='Amount')), + ('tr_date', models.DateTimeField(default=django.utils.timezone.now)), + ('from_acc', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='to_acc', to='pfm.Account')), + ('to_acc', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='from_acc', to='pfm.Account')), + ('user', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ], + ), + ] diff --git a/pfm/migrations/0002_auto_20161002_1309.py b/pfm/migrations/0002_auto_20161002_1309.py new file mode 100644 index 0000000..b4c6405 --- /dev/null +++ b/pfm/migrations/0002_auto_20161002_1309.py @@ -0,0 +1,22 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.7 on 2016-10-02 10:09 +from __future__ import unicode_literals + +import datetime +from django.db import migrations, models +from django.utils.timezone import utc + + +class Migration(migrations.Migration): + + dependencies = [ + ('pfm', '0001_initial'), + ] + + operations = [ + migrations.AlterField( + model_name='transaction', + name='tr_date', + field=models.DateField(default=datetime.datetime(2016, 10, 2, 10, 9, 6, 87972, tzinfo=utc)), + ), + ] diff --git a/pfm/migrations/0003_auto_20161118_1230.py b/pfm/migrations/0003_auto_20161118_1230.py new file mode 100644 index 0000000..4cda1c5 --- /dev/null +++ b/pfm/migrations/0003_auto_20161118_1230.py @@ -0,0 +1,26 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.7 on 2016-11-18 09:30 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.utils.timezone + + +class Migration(migrations.Migration): + + dependencies = [ + ('pfm', '0002_auto_20161002_1309'), + ] + + operations = [ + migrations.AddField( + model_name='category', + name='cat_type', + field=models.CharField(choices=[('I', 'Income'), ('E', 'Expence')], default='E', max_length=1, verbose_name='Type'), + ), + migrations.AlterField( + model_name='transaction', + name='tr_date', + field=models.DateField(default=django.utils.timezone.now), + ), + ] diff --git a/pfm/migrations/__init__.py b/pfm/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/pfm/models.py b/pfm/models.py new file mode 100644 index 0000000..b24ebee --- /dev/null +++ b/pfm/models.py @@ -0,0 +1,142 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals +from django.db import models, connection +from django.utils import timezone +from datetime import datetime, date, timedelta +from django.core.exceptions import ValidationError +from decimal import Decimal + +class Category(models.Model): + cat_name = models.CharField("Category", max_length=50) + cat_type = models.CharField("Type", max_length=1, choices=(('I', 'Income'), ('E', 'Expence'))) + user = models.ForeignKey('auth.user', null=True, blank=True) + budget_type = models.CharField("Type", max_length=1, choices = (('W', 'Weekly'), ('M', 'Monthly'), ('Y', 'Yearly')), default='M') + budget_amount = models.DecimalField("Amount", max_digits=10, decimal_places=2, default=0) + + def natural_key(self): + return self.cat_name + + def __unicode__(self): + return self.cat_name + + def cat_sum(self): + cur = connection.cursor() + month = date.today().month + year = date.today().year + cur.execute("""SELECT SUM(tr_amount) from pfm_transaction as t JOIN pfm_category as c + ON t.tr_cat_id = c.id WHERE c.id=%s AND tr_type='E' AND date_part('month', t.tr_date)=%s AND date_part('year', t.tr_date)=%s""", (self.id, month, year )) + try: + return float(cur.fetchone()[0]) + except: + return 0 + cur.close() + + def minus_sum(self): + return (self.cat_sum()*(-1)) + + '''def budget(self): + cur = connection.cursor() + cur.execute("""SELECT amount FROM pfm_budget WHERE cat_id=:ID """, {"ID": self.id}) + try: return float(cur.fetchone()[0]) + except: return 0 + cur.close()''' + + def balance(self): + return -self.budget() + + def ratio(self): + try: return int(Decimal(self.cat_sum())/self.budget_amount*100) + except: return 100 + if ratio(self)>100: + return 100 + + class Meta: + verbose_name_plural = "Categories" + +class Account(models.Model): + acc_name = models.CharField("Account", max_length=50) + acc_balance = models.DecimalField("Balance", max_digits=10, decimal_places=2) + acc_currency = models.CharField("Currency", max_length=3) + user = models.ForeignKey('auth.user', null=True, blank=True) + + def natural_key(self): + return self.acc_name + + def __unicode__(self): + return self.acc_name + + SQL_exp = """SELECT SUM(tr_amount) from pfm_transaction as t JOIN pfm_account as a + ON t.tr_acc_id = a.id WHERE tr_type='E' AND tr_acc_id=%s""" + + SQL_inc = """SELECT SUM(tr_amount) from pfm_transaction as t JOIN pfm_account as a + ON t.tr_acc_id = a.id WHERE tr_type='I' AND tr_acc_id=%s""" + + def count(self, SQL): + cur = connection.cursor() + cur.execute(SQL, (self.id,)) + try: return float(cur.fetchone()[0]) + except: return 0 + cur.close() + + def exp(self): return self.count(Account.SQL_exp) + + def inc(self): return self.count(Account.SQL_inc) + + def tr_in(self): + cur = connection.cursor() + cur.execute("""SELECT SUM(tr_amount) from pfm_transfer as t JOIN pfm_account as a + ON t.to_acc_id = a.id WHERE to_acc_id=%s""", (self.id,)) + try: + return float(cur.fetchone()[0]) + except: + return 0 + cur.close() + + def tr_out(self): + cur = connection.cursor() + cur.execute("""SELECT SUM(tr_amount) from pfm_transfer as t JOIN pfm_account as a + ON t.from_acc_id = a.id WHERE from_acc_id=%s""", (self.id,)) + try: + return float(cur.fetchone()[0]) + except: + return 0 + cur.close() + + def rest(self): + return (float(self.acc_balance) - self.count(Account.SQL_exp) - + self.tr_out() + self.count(Account.SQL_inc) + self.tr_in()) + + +class Transaction(models.Model): + tr_acc = models.ForeignKey(Account) + tr_cat = models.ForeignKey(Category) + tr_type = models.CharField("Type", max_length=1, choices=(('I', 'Income'), ('E', 'Expence')), default='E') + tr_amount = models.DecimalField("Amount", max_digits=10, decimal_places=2, default='0.00') + tr_note = models.CharField(max_length=100, null=True, blank=True) + tr_date = models.DateField(default=timezone.now) + pub_date = models.DateTimeField(blank=True, null=True) + user = models.ForeignKey('auth.user', blank=True, null=True) + + def publish(self): + self.pub_date = timezone.now() + self.save() + + def __unicode__(self): + return '%s,%s,%s,%s' % (self.tr_cat, self.tr_acc, self.tr_amount, self.tr_date.strftime("%d.%m.%Y")) + + def sum_tr(): + cur = connection.cursor() + cur.execute("SELECT SUM(tr_amount) from pfm_transaction WHERE tr_date > :D", {"D": date.today()-timedelta(days=30)}) + return cur.fetchone() + cur.close() + +class Transfer(models.Model): + from_acc = models.ForeignKey(Account, related_name="to_acc") + to_acc = models.ForeignKey(Account, related_name="from_acc") + tr_amount = models.DecimalField("Amount", max_digits=10, decimal_places=2) + tr_date = models.DateTimeField(default=timezone.now) + user = models.ForeignKey('auth.user', blank=True, null=True) + + def publish(self): self.save() + def __unicode__(self): + return '%s >>> %s, %s' % (self.from_acc, self.to_acc, self.tr_amount) diff --git a/pfm/static/css/pfm.css b/pfm/static/css/pfm.css new file mode 100644 index 0000000..867c082 --- /dev/null +++ b/pfm/static/css/pfm.css @@ -0,0 +1,13 @@ +.blue-text {color:#2196F3} +.button-collapse +.container {margin:0 auto;max-width:1280px;width:90%} +.container .row{margin-left:-0.75rem;margin-right:-0.75rem} +.row .col.s12{width:100%;margin-left:auto;left:auto;right:auto} +.nav-wrapper {position:relative;height:100%} + +.brand-logo {position:absolute;color:#fff;display:inline-block;font-size:2.1rem;padding:0;white-space:nowrap} +@media only screen and (max-width: 992px){.hide-on-med-and-down{display:none}} +@media only screen and (min-width: 993px){.hide-on-large-only{display:none}} + +.right {float:right !important} + diff --git a/pfm/static/js/diagram.js b/pfm/static/js/diagram.js new file mode 100644 index 0000000..551f8cb --- /dev/null +++ b/pfm/static/js/diagram.js @@ -0,0 +1,45 @@ +console.log("test"); +$(function() { + +//var data = [{% for cat in categories %} {{ cat.sum_cat|escapejs }}, {% endfor %}]; +//var data_cat = [{% for cat in categories %} "{{ cat.cat_name|escapejs }}", {% endfor %}]; +var sum = 0; +for (var i=0; i < data.length; i++){sum += data[i]} +var r = 3.6; +var centerX = 180; +var centerY = 180; +var radius = 180; +var far = 108; +var start = (Math.PI/180)*0-Math.PI/2; +var colors = ['#5E35B1', '#43A047', '#E53935', '#1E88E5', '#D81B60', '#C0CA33', '#4e342e', '#00acc1', '#f9a825', '#546e7a', '#7b1fa2', '#689f38', '#ff6f00', '#6d4c41', '#3f51b5', '#00acc1']; +var canvas = document.getElementById('canvas'); +var total = 0; + if (canvas.getContext){ + var ctx = canvas.getContext('2d'); + ctx.strokeStyle = '#fff'; + ctx.lineWidth = 2; + ctx.lineCap = 'round'; + ctx.lineJoin = 'bevel'; + for (var i=0; i < data.length;i++) { + ctx.beginPath(); + total += data[i]*100/sum; + var end = (Math.PI/180)*r*total-Math.PI/2; + ctx.arc(centerX,centerY,radius, start, end); + ctx.lineTo(centerX,centerY); + ctx.fillStyle = colors[i]; + ctx.fill(); + ctx.stroke(); + ctx.fillStyle = "white"; + ctx.font = "10pt calibri"; + ctx.fillText(data_cat[i], centerX-15 + far * Math.cos((start + end) / 2), centerY + far * Math.sin((start + end) / 2)); + start = end+0.01; + } + ctx.beginPath(); + ctx.shadowBlur = 7; + ctx.shadowColor = "rgba(0, 0, 0, 0.2)"; + ctx.arc(centerX,centerY,radius, 0, 2*Math.PI); + ctx.stroke(); + + } + +}) diff --git a/pfm/static/js/scripts.js b/pfm/static/js/scripts.js new file mode 100644 index 0000000..6ce1cd3 --- /dev/null +++ b/pfm/static/js/scripts.js @@ -0,0 +1,89 @@ +$(function() { + + + +//$('select').material_select(); //new transfer form doesn't work whithout it + +$('').attr('id', 'value'); +$( "#draggable" ).draggable(); +// Initialize collapse button +//$(".button-collapse").sideNav({ +// menuWidth: 300, // Default is 240 +// edge: 'left', // Choose the horizontal origin +// closeOnClick: true // Closes side-nav on clicks, useful for Angular/Meteor +// }); + +// Initialize collapsible (uncomment the line below if you use the dropdown variation) +// $('.collapsible').collapsible(); + +/* +$(".dropdown-button").dropdown(); + + $('#id_tr_date').pickadate({ + format: 'dd.mm.yyyy', + selectMonths: true, + selectYears: true, + closeOnSelect: false, + closeOnClear: true + }); +*/ + +//categories with negative balance are red +var rows = $("td.balance"); +var cats = $("tr.category"); +for (i=0; iSave')} + }) + } else { + $("input[name='id']").attr('value', id); + $.ajax({ + data: {id: id}, + success: function(form){console.log("edit"); $("#form").html(form + + '' + + '')} + }) + } +} +/* +function TrModalForm(id) { + $("input[name='id']").attr('value', id); + var type = $('#id_tr_type').value; + console.log(type); + console.log("test"); + $.ajax({ + data: {id: id, type: $('#id_tr_type').value}, + success: function(form){console.log(form); $("#form").html(form)} + }) +} +*/ + +function showMenu() { + if ($("#dropdown-content").css("display") == "none") { + $("#dropdown-content").css("display","block"); + } else { + $("#dropdown-content").css("display","none"); + } +} + +function showSideMenu() { + if ($("#sideMenu").css("display") == "none") { + $("#sideMenu").css("display","block"); + $("#underlayer").css("display","block"); + } else { + $("#sideMenu").css("display","none"); + $("#underlayer").css("display","none"); + } +} + diff --git a/pfm/templates/pfm/accounts.html b/pfm/templates/pfm/accounts.html new file mode 100644 index 0000000..a453f7e --- /dev/null +++ b/pfm/templates/pfm/accounts.html @@ -0,0 +1,53 @@ +{% extends 'pfm/base.html' %} +{% block content %} + +
+
+ Add new account

+ + + {% for account in list %} + + + + + + + {% endfor %} +
NameBalance
{{ account.acc_name }} {{ account.rest }} {{account.acc_currency}}
+ + + +
+
+ New transfer +

+ +{% for t in trs %} +{{ t }}
+{% endfor %} + +
+ +{% endblock %} diff --git a/pfm/templates/pfm/base.html b/pfm/templates/pfm/base.html new file mode 100644 index 0000000..28c2809 --- /dev/null +++ b/pfm/templates/pfm/base.html @@ -0,0 +1,148 @@ +{% load staticfiles %} + + +PFM + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+ + +
+ menu +
+
+ + +
+
+{% block content %} + + + +
+

+ +

Журнал операций

+ + + + + {% for transaction in transactions %} + + + +{% endfor %} +
ДатаСуммаКатегория
{{ transaction.tr_date|date:"d/m/y"}}{{ transaction.tr_amount}}{{ transaction.tr_cat}}
+Show all +

+
+ + + +
+

+ +

My accounts

+ +{% for account in accounts %} + +{% endfor %} +
NameBalance
{{ account.acc_name }} {{ account.rest }} {{account.acc_currency}}
+

+ +

+ +

Сумма операций за {{date_today|date:"F"}}

+

+{% for category in categories %} + {% if category.sum_cat %} + + + + {% endif %}{% endfor %} + +
КатегорияСумма
{{ category.cat_name}}{{ category.sum_cat}}
Total : {{ sum_total }}

+
+ + +{% endblock%} +
+
+ + diff --git a/pfm/templates/pfm/cat_edit.html b/pfm/templates/pfm/cat_edit.html new file mode 100644 index 0000000..cb4cc14 --- /dev/null +++ b/pfm/templates/pfm/cat_edit.html @@ -0,0 +1,35 @@ +{% extends 'pfm/base.html' %} +{% block content %} + +
+
{% csrf_token %} +{{ form.management_form }} + + + + + + + +{% for f in form %} + + + + + + + + + +{% endfor %} + +
КатегорияtypebudgetБюджетУдалить
{{ f.cat_name }}{{ f.cat_type }}{{ f.budget_type }}{{ f.budget_amount }}{{ f.DELETE }} {{ f.id }}
+ +
+
+ +{% endblock content %} diff --git a/pfm/templates/pfm/categories.html b/pfm/templates/pfm/categories.html new file mode 100644 index 0000000..fe89058 --- /dev/null +++ b/pfm/templates/pfm/categories.html @@ -0,0 +1,56 @@ +{% extends 'pfm/base.html' %} +{% block content %} +
+
+ Add new + Edit + + + +
+ + + + + + + + + {% for row in categories %} + + + + + + + + {% endfor %} + + + + + +
КатегорияПланФактРазница
{{ row.cat_name}} +
+
+
+
+
{{ row.budget_amount }} {{ row.cat_sum }} {{ row.budget_amount|add:row.minus_sum }}
Расходы{{ budget_total }}{{ sum_total }}{{ total_balance }}
+ +
+{% endblock content %} diff --git a/pfm/templates/pfm/login.html b/pfm/templates/pfm/login.html new file mode 100644 index 0000000..87ffd0f --- /dev/null +++ b/pfm/templates/pfm/login.html @@ -0,0 +1,12 @@ +{% extends 'pfm/base.html' %} + +{% block content %} + +
+ {% csrf_token %} + {{form}}
+ + +
+ +{% endblock %} diff --git a/pfm/templates/pfm/logout.html b/pfm/templates/pfm/logout.html new file mode 100644 index 0000000..e84aa58 --- /dev/null +++ b/pfm/templates/pfm/logout.html @@ -0,0 +1,10 @@ +{% extends 'pfm/base.html' %} +{% block content %} + + +

+ You are logged out. To log in again, click here. +

+ + +{% endblock %} diff --git a/pfm/templates/pfm/reports.html b/pfm/templates/pfm/reports.html new file mode 100644 index 0000000..94da506 --- /dev/null +++ b/pfm/templates/pfm/reports.html @@ -0,0 +1,142 @@ + +{% extends 'pfm/base.html' %} +{% load staticfiles %} + + +{% block content %} +
+
+

Сумма операций за +

{% csrf_token %} + + + + + {{ datedate|date:"F Y"|lower}}
+
{% csrf_token %} + + + +
+ +
{% csrf_token %} + +

+ + + + {{ mon_calendar|safe }} +

+
+ + + + + + {% for category in categories %} + {% if category.sum_cat %} + + + + {% endif %} + {% endfor %} + {% if sum_total %} + + + + +{% endif %} +
КатегорияСумма
{{ category.cat_name}}{{ category.sum_cat}}
Total: {{ sum_total }}
+ + + + + +{% endblock content %} diff --git a/pfm/templates/pfm/test.html b/pfm/templates/pfm/test.html new file mode 100644 index 0000000..2b03e15 --- /dev/null +++ b/pfm/templates/pfm/test.html @@ -0,0 +1,12 @@ +{% extends 'pfm/base.html' %} + +{% block content %} + +{% for item in status %} + + + +{% endfor %} +
{{ item.Name|cut:"/RUB" }} {{ item.Bid }} +
+{% endblock content %} diff --git a/pfm/templates/pfm/tr_edit.html b/pfm/templates/pfm/tr_edit.html new file mode 100644 index 0000000..540325a --- /dev/null +++ b/pfm/templates/pfm/tr_edit.html @@ -0,0 +1,63 @@ +{% extends 'pfm/base.html' %} +{% load pagination_tags %} +{% block content %} + + + +
+
{% csrf_token %} +{{form.management_form }} + + + + + + + + + + +{% for f in form %} + + + + + + + + + + +{% endfor %} +
ДатаТипСуммаСчетКатегорияКомментарийУдалить
{{ f.tr_date }}{{ f.tr_type }}{{ f.tr_amount }}{{ f.tr_acc }}{{ f.tr_cat }}{{ f.tr_note }}{{ f.DELETE }} {{ f.id }}
+ + + +
+
+{% endblock content %} diff --git a/pfm/templates/pfm/tr_edit_form.html b/pfm/templates/pfm/tr_edit_form.html new file mode 100644 index 0000000..75710a8 --- /dev/null +++ b/pfm/templates/pfm/tr_edit_form.html @@ -0,0 +1,27 @@ +
+
{{ form.tr_type }}
+
{{ form.tr_cat }}
+
{{ form.tr_date }}
+
{{ form.tr_acc }}
+
{{ form.tr_amount }}
+
{{ form.tr_note }}
+ +
+ diff --git a/pfm/templates/pfm/transactions.html b/pfm/templates/pfm/transactions.html new file mode 100644 index 0000000..c8b8fb4 --- /dev/null +++ b/pfm/templates/pfm/transactions.html @@ -0,0 +1,79 @@ +{% extends 'pfm/base.html' %} +{% load pagination_tags %} +{% block content %} +
+ New transaction + Massive update +

Журнал операций

+ + + + + + + + + {% for transaction in trs %} + + + + + + + {% endfor %} +
ДатаСуммаСчетКатегорияКомментарий +
{{ transaction.tr_date|date:"d/m/y"}}{{ transaction.tr_amount}}{{ transaction.tr_acc}}{{ transaction.tr_cat}}{{ transaction.tr_note }}
+ + + + + + + + + +{% endblock content %} diff --git a/pfm/templates/pfm/upload.html b/pfm/templates/pfm/upload.html new file mode 100644 index 0000000..b304709 --- /dev/null +++ b/pfm/templates/pfm/upload.html @@ -0,0 +1,21 @@ +{% extends 'pfm/base.html' %} + +{% block content %} +
+
{% csrf_token %} {{ form.as_p }} +
+
+ File + +
+
+ +
+ +
+
+ + +{% endblock %} + + diff --git a/pfm/tests.py b/pfm/tests.py new file mode 100644 index 0000000..10762f3 --- /dev/null +++ b/pfm/tests.py @@ -0,0 +1,64 @@ +import datetime + +from django.utils import timezone +from django.test import TestCase +from django.core.urlresolvers import reverse +from pfm.models import Account, Transaction +from django.contrib.auth import authenticate +from django.contrib.auth.models import User +from django.contrib.staticfiles.testing import StaticLiveServerTestCase +from selenium.webdriver.phantomjs.webdriver import WebDriver + +class MySeleniumTests(StaticLiveServerTestCase): + + UserModel = User + user_credentials = {'username': 'test', 'password': 'test'} + + @classmethod + def setUpClass(cls): + super(MySeleniumTests, cls).setUpClass() + cls.selenium = WebDriver() + cls.selenium.implicitly_wait(10) + + @classmethod + def tearDownClass(cls): + cls.selenium.quit() + super(MySeleniumTests, cls).tearDownClass() + + def test_login(self): + + User.objects.create_user(**self.user_credentials) + self.selenium.get('%s%s' % (self.live_server_url, '/pfm/login/?next=/pfm/')) + username_input = self.selenium.find_element_by_name("username") + username_input.send_keys('test') + password_input = self.selenium.find_element_by_name("password") + password_input.send_keys('test1') + self.selenium.find_element_by_xpath('//input[@value="login"]').click() + + password_input = self.selenium.find_element_by_name("password") + password_input.send_keys('test') + self.selenium.find_element_by_xpath('//input[@value="login"]').click() + self.selenium.save_screenshot('mysite/media/images/login_passed.png') + +class Auth(TestCase): + def test_view(self): + response = self.client.get(reverse('login')) + self.assertEqual(response.status_code, 200) + +class AccountTestCase(TestCase): + UserModel = User + user_credentials = {'username': 'test', 'password': 'test'} + def setUp(self): + self.user = User.objects.create_user(**self.user_credentials) + Account.objects.create(acc_name='test1', acc_balance=0, acc_currency='RUB', user=self.user) + #Account.objects.create(acc_name='test2', acc_balance=100, acc_currency='RUB', user='test') + #Account.objects.create(acc_name='test3', acc_balance=-100, acc_currency='RUB', user='test') + def test_acc_view(self): + #authenticate(**self.user_credentials) + ''' default behavior ''' + response = self.client.get('/pfm/') + self.assertRedirects(response, '/pfm/login/?next=/pfm/') + self.assertEqual(authenticate(**self.user_credentials), self.user) + #self.assertEqual(response.status_code, 200) + self.assertContains(response, "test1") + diff --git a/pfm/urls.py b/pfm/urls.py new file mode 100644 index 0000000..8f76a79 --- /dev/null +++ b/pfm/urls.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- # + +from django.conf.urls import url +from django.contrib.auth.views import login, logout +from . import views + +urlpatterns = [ + url(r'^$', views.main, name='main'), + url(r'^test/$', views.test, name = 'test'), + url(r'^transactions/$', views.tr_list, name = 'tr_list'), + url(r'^tr_edit/$', views.massive_tr_edit, name='massive_tr_edit'), + url(r'^csv/$', views.get_csv, name='get_csv'), + url(r'^upload/$', views.upload_tr, name='upload_tr'), + url(r'^account/$', views.acc_list, name='acc_list'), + url(r'^category/$', views.cat_list, name='cat_list'), + url(r'^reports/$', views.reports, name='reports'), + url(r'^cat_edit/$', views.massive_cat_edit, name='massive_cat_edit'), + url(r'^json/$', views.json, name='json'), + url(r'^login/$', login, {'template_name':'pfm/login.html'}, name='login'), + url(r'^logout/$', logout,{'template_name':'pfm/logout.html'}, name='logout'), +] diff --git a/pfm/views.py b/pfm/views.py new file mode 100644 index 0000000..fb1cdde --- /dev/null +++ b/pfm/views.py @@ -0,0 +1,333 @@ +from django.shortcuts import render, redirect, HttpResponseRedirect, get_object_or_404, render_to_response +from django.http import HttpResponse, JsonResponse +from .models import Account, Category, Transaction, Transfer +from .forms import NewTransactionExp, NewTransactionInc, NewTransaction, NewTransaction1, NewAccount, NewCategory, NewTransfer, Upload, BaseTransactionFormSet +from django.forms import ModelChoiceField +from django.forms.models import modelformset_factory, formset_factory +from django.template.context_processors import csrf +from django.utils import timezone +from datetime import datetime, date +from django.db.models import Sum, Avg +from django.db import connection +from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger +from django.contrib.auth import authenticate, login, logout +from django.contrib.auth.decorators import login_required +from django.core import serializers +import calendar +import requests +from json import loads + +def test(request): + response = requests.get('https://query.yahooapis.com/v1/public/yql?q=select+*+from+yahoo.finance.xchange+where+pair+=+%22USDRUB,%20EURRUB,%20BYNRUB%22&format=json&env=store%3A%2F%2Fdatatables.org%2Falltableswithkeys&callback=') + status = loads(response.content) + status = status["query"]["results"]["rate"] + #return HttpResponse(status) + return render(request, 'pfm/test.html', {'status': status}) + #return render(request, 'pfm/test.html', {'form': form, }) + +def tr_list(request): + tr_list = Transaction.objects.filter(user=request.user).order_by('-tr_date') + paginator = Paginator(tr_list, 10) + page = request.GET.get('page') + try: trs = paginator.page(page) + except PageNotAnInteger: trs = paginator.page(1) + except EmptyPage: trs = paginator.page(paginator.num_pages) + form = NewTransaction(user=request.user) + if request.method == 'GET' and 'type' in request.GET: + if request.GET['type'] == 'E': + form = NewTransactionExp(request.POST, user=request.user) + return HttpResponse (form['tr_cat']) + #return render(request, 'pfm/tr_cat_selector.html', {'form': form,}) + elif request.GET['type'] == 'I': + form = NewTransactionInc(request.POST, user=request.user) + return HttpResponse (form['tr_cat']) + #return render(request, 'pfm/tr_cat_selector.html', {'form': form,}) + if request.method == 'GET' and 'id' in request.GET: + if request.GET['id'] == '0': + form = NewTransaction(user=request.user) + return render(request, 'pfm/tr_edit_form.html', {'form': form,}) + tr = Transaction.objects.get(id=request.GET['id']) + type = tr.tr_type + if type == 'E': + form = NewTransactionExp(instance=Transaction.objects.get(id=request.GET['id']), user=request.user) + return render(request, 'pfm/tr_edit_form.html', {'form': form,}) + if type == 'I': + form = NewTransactionInc(instance=Transaction.objects.get(id=request.GET['id']), user=request.user) + return render(request, 'pfm/tr_edit_form.html', {'form': form,}) + else: + if request.method == "POST" and request.POST.get('action') == 'create': + form = NewTransaction(request.POST, user=request.user) + if form.is_valid(): + tr = form.save(commit=False) + tr.published_date = timezone.now() + tr.user = request.user + tr.save() + return redirect('/pfm/transactions/#!') + else: + print('Not valid!', form.errors) + elif request.POST.get('action') == 'edit': + transaction = Transaction.objects.get(id=request.POST['id']) + form = NewTransaction(request.POST, instance = transaction, user=request.user ) + if form.is_valid(): + transaction = form.save(commit=False) + transaction.user = request.user + transaction.save() + return redirect('/pfm/transactions/#!') + elif request.method == 'POST' and request.POST.get('action') == 'delete': + form = NewTransaction(request.POST, user=request.user) + transaction = Transaction.objects.get(id=request.POST.get('id')) + transaction.delete() + return redirect('/pfm/transactions/#!') + else: + form=NewTransaction(user=request.user) + return render(request, 'pfm/transactions.html', {'form': form, 'trs': trs}) + +def acc_list(request): + list = Account.objects.filter(user=request.user) + tr_form = NewTransfer(request.POST, user=request.user) + trs = Transfer.objects.filter(user=request.user) + #response = requests.get('https://query.yahooapis.com/v1/public/yql?q=select+*+from+yahoo.finance.xchange+where+pair+=+%22USDRUB,%20EURRUB,%20BYNRUB%22&format=json&env=store%3A%2F%2Fdatatables.org%2Falltableswithkeys&callback=') + #status = loads(response.content) + #status = status["query"]["results"]["rate"] + + if request.POST.get('action') == 'delete': + account = Account.objects.get(id=request.POST.get('id')) + account.delete() + return redirect('/pfm/account/#!') + #return redirect('pfm.views.acc_list') + elif request.POST.get('action') == 'create': + form = NewAccount(request.POST) + if form.is_valid(): + account = form.save(commit=False) + account.user = request.user + account.save() + return redirect('/pfm/account/#!') + elif request.POST.get('action') == 'edit': + acc = Account.objects.get(id=request.POST['id']) + form = NewAccount(request.POST, instance = acc ) + if form.is_valid(): + account = form.save(commit=False) + account.user = request.user + account.save() + #return redirect('pfm.views.acc_list') + return redirect('/pfm/account/#!') + + elif request.POST.get('action') == 'transfer': + if tr_form.is_valid(): + transfer = tr_form.save(commit=False) + transfer.user = request.user + transfer.save() + return redirect('/pfm/account/#!') + else: + try: + form = NewAccount(instance=Account.objects.get(id=request.GET['id'])) + return HttpResponse(form) + except: form = NewAccount() + tr_form = NewTransfer(user=request.user) + #connection. queries + return render(request, 'pfm/accounts.html', {'list': list, 'form': form, 'tr_form': tr_form, 'trs': trs}) + +def json(request): + transactions = Transaction.objects.all() + data = serializers.serialize("json", transactions, use_natural_foreign_keys=True) + data_dict = loads(data) + return JsonResponse(data_dict, safe=False) + +def massive_cat_edit(request): + Cat_formset = modelformset_factory(Category, form=NewCategory, can_delete=True, extra=0) + if request.method == "POST": + form = Cat_formset(request.POST) + if form.is_valid(): + categories = form.save(commit=False) + for category in categories: + category.user = request.user + category.save() + form.save() + return redirect('pfm.views.cat_list') + else: + form = Cat_formset(queryset=Category.objects.filter(user=request.user).order_by('id')) + context = {'form': form, } + return render(request, 'pfm/cat_edit.html', context) + +def massive_tr_edit(request): + TestTrModel1 = modelformset_factory(Transaction, form=NewTransaction, can_delete=True, extra=0) + if request.method == "POST": + form = TestTrModel1(request.POST, form_kwargs={'user': request.user}) + paginator = Paginator(form, 2) + page = request.GET.get('page') + try: + trs = paginator.page(page) + except PageNotAnInteger: + trs = paginator.page(1) + except EmptyPage: + trs = paginator.page(paginator.num_pages) + if form.is_valid(): + transactions = form.save(commit=False) + for transaction in transactions: + transaction.user = request.user + transaction.save() + form.save() + return redirect('pfm.views.massive_tr_edit') + else: + #show only revalent values to select for the user + query=Transaction.objects.filter(user=request.user).order_by('-tr_date') + paginator = Paginator(query, 8) + page = request.GET.get('page') + try: + trs = paginator.page(page) + except PageNotAnInteger: + trs = paginator.page(1) + except EmptyPage: + trs = paginator.page(paginator.num_pages) + page_query = query.filter(id__in=[tr.id for tr in trs]) + form = TestTrModel1(queryset=page_query, form_kwargs={'user': request.user}) + context = {'form': form, 'trs': trs} + return render(request, 'pfm/tr_edit.html', context) + + +@login_required(login_url="login/?next=/pfm/") +def main(request): + accounts = Account.objects.filter(user=request.user).annotate(spent=(Sum('transaction__tr_amount'))) + transactions = Transaction.objects.filter(user=request.user).order_by('-tr_date')[:11] + categories = Category.objects.filter(transaction__user=request.user, transaction__tr_date__month=date.today().month, + transaction__tr_date__year=date.today().year, + transaction__tr_type='E').annotate(sum_cat=(Sum('transaction__tr_amount')), + avg_ca=Avg('transaction__tr_amount')) + sum_total = Transaction.objects.filter(user=request.user, tr_date__month =date.today().month, tr_type='E').aggregate(Sum('tr_amount'))['tr_amount__sum'] + date_today = datetime.now() + return render(request, 'pfm/base.html', {'accounts': accounts, 'transactions': transactions, 'categories': categories, 'sum_total': sum_total, 'date_today': date_today}) + +def get_csv(request): + transactions = Transaction.objects.defer('tr_date').filter(user=request.user) + response = HttpResponse(content_type='text/csv') + for item in transactions: + date = item.tr_date.strftime('%d.%m.%Y') + i = '%s,%s,%s,%s,%s,%s\n' % (date, item.tr_type, item.tr_cat, item.tr_acc, item.tr_amount, item.tr_note) #format is equal to uplolad format + #response.write(str(i) + '\n') + response.write(i) + + return response + +def cat_list(request): + categories = Category.objects.filter(user=request.user, cat_type='E') + sum_total = Transaction.objects.filter(user=request.user, tr_date__month =date.today().month, tr_date__year=date.today().year, tr_type='E').aggregate(Sum('tr_amount'))['tr_amount__sum'] + if sum_total == None: + sum_total = 0 + budget_total = Category.objects.filter(user=request.user, cat_type='E').aggregate(Sum('budget_amount'))['budget_amount__sum'] + try: total_balance = budget_total - sum_total + except: total_balance = 0 + try: progress = sum_total/budget_total*100 + except: progress = 100 + #form_new = modelformset_factory(Category, exclude=('user',)) + #form = NewCategory(request.POST) + form = NewCategory(request.POST) + if request.POST.get('action') == 'delete': + category = Category.objects.get(id=request.POST.get('id')) + category.delete() + return redirect('/pfm/category/#!') + elif request.POST.get('action') == 'create': + if form.is_valid(): + category = form.save(commit=False) + category.user = request.user + category.save() + return redirect('/pfm/category/#!') + elif request.POST.get('action') == 'edit': + print request.POST + cat = Category.objects.get(id=request.POST['id']) + form = NewCategory(request.POST, instance = cat ) + if form.is_valid(): + category = form.save(commit=False) + category.user = request.user + category.save() + return redirect('/pfm/category/#!') +# return redirect('pfm.views.cat_list') + else: + print('Not valid!', form.errors) + else: + try: + form = NewCategory(instance=Category.objects.get(id=request.GET['id'])) + return HttpResponse (form) +# return render(request, 'pfm/cat_tmpl.html', {'form': form,}) + except: + form = NewCategory() + return render(request, 'pfm/categories.html', {'form': form, 'categories': categories, 'sum_total': sum_total, 'budget_total': budget_total, 'total_balance': total_balance, + 'progress': progress}) + +def reports(request): + categories = Category.objects.filter(transaction__user=request.user, + transaction__tr_date__month=date.today().month, + transaction__tr_date__year=date.today().year, + transaction__tr_type='E').order_by('-sum_cat').annotate( + sum_cat=Sum('transaction__tr_amount')).order_by('-sum_cat') + sum_total = Transaction.objects.filter(user=request.user, tr_date__month =date.today().month, tr_date__year=date.today().year, tr_type='E').aggregate(Sum('tr_amount'))['tr_amount__sum'] + date_today = datetime.now() + selected_month = datetime.now().month + year_list = [2015, 2016, 2017] + #months = Transaction.objects.datetimes('tr_date', 'month').aggregate(Sum('tr_amount')) + month = date.today().month + year = date.today().year + testing = '%d %d' % (selected_month, year) + datedate = date_today + mon_calendar = calendar.LocaleHTMLCalendar(0, "ru_RU.utf-8").formatmonth(year, month) + context = {'categories': categories, 'datedate': datedate, 'date_today': date_today, 'month': month, 'testing': testing, 'sum_total': sum_total, 'mon_calendar': mon_calendar, 'years': year_list} + + if request.method == 'POST': + print request.__dict__ + print '------month-------: %s' % (request.POST.get('selected_month')) + selected_month=request.POST.get('selected_month') + selected_year=request.POST.get('selected_year') + if selected_month == '13': + selected_month = 1 + selected_year = int(selected_year)+1 + if selected_month == '0': + selected_month = 12 + selected_year = int(selected_year)-1 + print selected_month, selected_year + + #months = Transaction.objects.datetimes('tr_date', 'month') + + categories = Category.objects.filter(transaction__user=request.user, + transaction__tr_date__month=selected_month, + transaction__tr_date__year=selected_year, + transaction__tr_type='E').annotate( + sum_cat=(Sum('transaction__tr_amount')), + avg_cat=Avg('transaction__tr_amount')).order_by('-sum_cat') + datedate = date(year=int(selected_year), month=int(selected_month), day=1) + sum_total = Transaction.objects.filter(user=request.user, tr_date__month=selected_month, tr_date__year=selected_year, tr_type='E').aggregate(Sum('tr_amount'))['tr_amount__sum'] + mon_calendar = calendar.LocaleHTMLCalendar(0, "ru_RU.utf-8").formatmonth(int(selected_year), int(selected_month)) + context = {'categories': categories, 'month': selected_month, 'datedate': datedate, 'selected_month': selected_month, 'sum_total': sum_total, 'mon_calendar': mon_calendar} + return render(request, 'pfm/reports.html', context) + return render(request, 'pfm/reports.html', context) + +def upload_tr(request): + if request.method == 'POST': + form = Upload(request.POST, request.FILES) + if form.is_valid: + try: + for row in request.FILES['uploaded']: + row = row.split(',') + print len(row) + transaction = Transaction() + transaction.published_date = timezone.now() + transaction.user = request.user + transaction.tr_date = datetime.strptime(row[0], '%d.%m.%Y') + transaction.tr_type = row[1] + transaction.tr_cat_id = Category.objects.get(cat_name=row[2], user = request.user).id + transaction.tr_acc_id = Account.objects.get(acc_name=row[3], user = request.user).id + if len(row) == 5: + transaction.tr_amount = row[4].strip() + transaction.tr_note = '' + if len(row) == 6: + transaction.tr_amount = row[4] + transaction.tr_note = row[5].strip() + transaction.save() + return redirect('pfm.views.main') + + except Account.DoesNotExist: + return HttpResponse ('no such acc: %s' % row[1]) + except Category.DoesNotExist: + return HttpResponse ('no such cat: %s' % row[3]) + else: + form = Upload() + return render(request, 'pfm/upload.html', {'form': form}) +