init
This commit is contained in:
parent
94f69b7038
commit
c53725b502
4
.gitignore
vendored
4
.gitignore
vendored
@ -58,3 +58,7 @@ docs/_build/
|
||||
# PyBuilder
|
||||
target/
|
||||
|
||||
*.pyc
|
||||
*.save
|
||||
*.swp
|
||||
db.sqlite3
|
||||
|
10
manage.py
Normal file
10
manage.py
Normal file
@ -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)
|
0
mysite/__init__.py
Normal file
0
mysite/__init__.py
Normal file
139
mysite/settings.py
Normal file
139
mysite/settings.py
Normal file
@ -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/'
|
25
mysite/urls.py
Normal file
25
mysite/urls.py
Normal file
@ -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)
|
17
mysite/wsgi.py
Normal file
17
mysite/wsgi.py
Normal file
@ -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()
|
0
pfm/__init__.py
Normal file
0
pfm/__init__.py
Normal file
7
pfm/admin.py
Normal file
7
pfm/admin.py
Normal file
@ -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)
|
||||
|
7
pfm/apps.py
Normal file
7
pfm/apps.py
Normal file
@ -0,0 +1,7 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class PfmConfig(AppConfig):
|
||||
name = 'pfm'
|
115
pfm/forms.py
Normal file
115
pfm/forms.py
Normal file
@ -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)
|
70
pfm/migrations/0001_initial.py
Normal file
70
pfm/migrations/0001_initial.py
Normal file
@ -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)),
|
||||
],
|
||||
),
|
||||
]
|
22
pfm/migrations/0002_auto_20161002_1309.py
Normal file
22
pfm/migrations/0002_auto_20161002_1309.py
Normal file
@ -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)),
|
||||
),
|
||||
]
|
26
pfm/migrations/0003_auto_20161118_1230.py
Normal file
26
pfm/migrations/0003_auto_20161118_1230.py
Normal file
@ -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),
|
||||
),
|
||||
]
|
0
pfm/migrations/__init__.py
Normal file
0
pfm/migrations/__init__.py
Normal file
142
pfm/models.py
Normal file
142
pfm/models.py
Normal file
@ -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)
|
13
pfm/static/css/pfm.css
Normal file
13
pfm/static/css/pfm.css
Normal file
@ -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}
|
||||
|
45
pfm/static/js/diagram.js
Normal file
45
pfm/static/js/diagram.js
Normal file
@ -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();
|
||||
|
||||
}
|
||||
|
||||
})
|
89
pfm/static/js/scripts.js
Normal file
89
pfm/static/js/scripts.js
Normal file
@ -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 <a> 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; i<rows.length; i++) {
|
||||
if (rows[i].innerHTML < 0) {
|
||||
cats[i].style.color = "red";
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
function ModalForm(id) {
|
||||
if (id == '0') {
|
||||
$("input[name='tr_date']").attr('value', Date());
|
||||
$.ajax({
|
||||
data: {id: id},
|
||||
success: function(form){console.log("new"); $("#form").html(form +
|
||||
'<button type="submit" name="action" value="create" class="waves-effect waves-light btn-large blue">Save</button>')}
|
||||
})
|
||||
} else {
|
||||
$("input[name='id']").attr('value', id);
|
||||
$.ajax({
|
||||
data: {id: id},
|
||||
success: function(form){console.log("edit"); $("#form").html(form +
|
||||
'<button type="submit" name="action" value="edit" class="waves-effect waves-light btn-large blue">Save</button>' +
|
||||
'<button type="submit" name="action" value="delete" class="waves-effect waves-light btn-large blue">Delete</button>')}
|
||||
})
|
||||
}
|
||||
}
|
||||
/*
|
||||
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");
|
||||
}
|
||||
}
|
||||
|
53
pfm/templates/pfm/accounts.html
Normal file
53
pfm/templates/pfm/accounts.html
Normal file
@ -0,0 +1,53 @@
|
||||
{% extends 'pfm/base.html' %}
|
||||
{% block content %}
|
||||
|
||||
<div class="row">
|
||||
<div class="col s12 m6">
|
||||
<a class="btn blue" href="#new">Add new account </a> <br> <br>
|
||||
<table class="striped">
|
||||
<tr><th>Name</th><th>Balance</th></tr>
|
||||
{% for account in list %}
|
||||
<tr onclick='ModalForm({{account.pk}}); location.href="#modal"'>
|
||||
<td>{{ account.acc_name }}</td>
|
||||
<td align="right"> {{ account.rest }} {{account.acc_currency}}</td>
|
||||
<!--td>{{account.rest_RUB}}</td-->
|
||||
<!--/div-->
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
|
||||
<div id="modal" class="dialog1 modal">
|
||||
<div class="modal-content" id="modal-content">
|
||||
<a href="#!" class=" right modal-action modal-close waves-effect waves-green btn-flat">Close</a>
|
||||
<form method="POST">{% csrf_token %} <input type=hidden name="id">
|
||||
<div id="form"></div>
|
||||
<!--button type="submit" name="action" value="edit" class="waves-effect waves-light btn-large blue" onclick='location.href="#!"'>Save</button>
|
||||
<button type="submit" name="action" value="delete" class="waves-effect waves-light btn-large blue" onclick='location.href="#!"'>Delete</button-->
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div id="new" class="dialog1 modal">
|
||||
<div class="modal-content">
|
||||
<a href="#!;" class=" right modal-action modal-close waves-effect waves-green btn-flat">Close</a>
|
||||
<form method="POST"> {% csrf_token %} {{ form.as_p }}
|
||||
<button type="submit" name="action" value="create" class="waves-effect waves-light btn-large blue">Save</button>
|
||||
</form></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col s12 m6">
|
||||
<a class="btn blue" href="#new_tr">New transfer</a>
|
||||
<div id="new_tr" class="dialog1 modal">
|
||||
<div class="modal-content"> <a href="#!" class=" right modal-action modal-close waves-effect waves-green btn-flat">Close</a>
|
||||
<form method="POST">{% csrf_token %} {{ tr_form.as_p }}
|
||||
<button type="submit" name="action" value="transfer" class="waves-effect waves-light btn-large blue">Save</button>
|
||||
</form>
|
||||
</div>
|
||||
</div><br><br>
|
||||
|
||||
{% for t in trs %}
|
||||
{{ t }} <br>
|
||||
{% endfor %}
|
||||
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
148
pfm/templates/pfm/base.html
Normal file
148
pfm/templates/pfm/base.html
Normal file
@ -0,0 +1,148 @@
|
||||
{% load staticfiles %}
|
||||
<html>
|
||||
<head>
|
||||
<title>PFM</title>
|
||||
<meta charset="utf-8">
|
||||
<meta name=viewport content="width=device-width, initial-scale=1">
|
||||
<link rel="stylesheet" href="{% static 'css/blog.css' %}">
|
||||
<link rel="stylesheet" href="{% static 'css/pfm.css' %}">
|
||||
<link rel="stylesheet" href="//code.jquery.com/ui/1.12.0/themes/base/jquery-ui.css">
|
||||
<script type="text/javascript" src="https://code.jquery.com/jquery-2.1.1.min.js"></script>
|
||||
<!--script src="https://code.jquery.com/jquery-1.12.4.js"></script-->
|
||||
<script src="https://code.jquery.com/ui/1.12.0/jquery-ui.js"></script>
|
||||
<!--script type="text/javascript" src="/js/materialize.min.js"></script-->
|
||||
<script type="text/javascript" src="{% static 'js/scripts.js' %}"></script>
|
||||
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
|
||||
<link type="text/css" rel="stylesheet" href="/css/materialize.min.css" media="screen,projection"/>
|
||||
<style>select{
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
border-bottom: 1px solid #9e9e9e;
|
||||
outline: none;
|
||||
height: 3rem;
|
||||
line-height: 3rem;
|
||||
width: 100%;
|
||||
font-size: 1rem;
|
||||
margin: 0 0 20px 0;
|
||||
padding: 0;
|
||||
display: block;
|
||||
}
|
||||
#dropdown-content {display: none; position: absolute; top: 64px; right: 0; background-color:#fff;min-width:100px;max-height:650px;overflow-y:auto;}
|
||||
#dropdown-content>li {
|
||||
border:1px solid #eeeeec;
|
||||
display: block;
|
||||
padding: 10px 30px;
|
||||
}
|
||||
#underlayer {position: fixed; width: 100%; height: 100%; background: #000; opacity: 0.7; top: 0; display: none; z-index: 3}
|
||||
#sideMenu {position: fixed; display: none; z-index: 4}
|
||||
//#sidemenu>li>a {padding: 10px 30px;}
|
||||
#sidemenu>li>a {font-size: 20px; display: inline-block; height: 50px; padding: 10px 50px}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<!-- Dropdown Structure -->
|
||||
<ul id="dropdown-content">
|
||||
<li><a class="blue-text" href="/admin">Admin</a></li>
|
||||
<li><a class="blue-text" href="/pfm/logout">Exit</a></li>
|
||||
</ul>
|
||||
|
||||
<!-- Navigation -->
|
||||
<!-- for large -->
|
||||
<nav>
|
||||
<div class="nav-wrapper blue darken-3">
|
||||
<a class="brand-logo" href="/pfm">Django Finance Manager</a>
|
||||
<ul id="nav-mobile" class="right hide-on-med-and-down">
|
||||
<li> <a href="{% url 'get_csv' %}">extract-to-csv</a></li>
|
||||
<li> <a href="{% url 'upload_tr' %}">upload</a></li>
|
||||
<li> <a href="{% url 'acc_list' %}">accounts</a></li>
|
||||
<li> <a href="{% url 'cat_list' %}">categories</a></li>
|
||||
<li> <a href="{% url 'reports' %}">reports</a></li>
|
||||
<li> <a href="{% url 'tr_list' %}"> transactions </a></li>
|
||||
<li> <a href="{% url 'test' %}"> test page </a></li>
|
||||
<li> <a href="#!" class="dropdown-button" data-activates="dropdown1" onclick="showMenu()">
|
||||
{% if user.is_authenticated %} {{ user.username }} {% endif %}
|
||||
<i class="material-icons right">arrow_drop_down</i></a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- for small and medium -->
|
||||
<div class="center">
|
||||
<a href="#" class="button-collapse hide-on-large-only btn blue" onclick = "showSideMenu()"><i class="material-icons">menu</i></a>
|
||||
</div>
|
||||
<div id="underlayer" onclick = "showSideMenu()"></div>
|
||||
|
||||
<ul id="sideMenu">
|
||||
<li> <a class="btn red darken-1" href="{% url 'get_csv' %}">extract-to-csv</a></li>
|
||||
<li> <a class="btn green darken-1" href="{% url 'upload_tr' %}">upload</a></li>
|
||||
<li> <a class="btn deep-purple darken-1" href="{% url 'acc_list' %}">accounts</a></li>
|
||||
<li> <a class="btn cyan darken-1" href="{% url 'cat_list' %}">categories</a></li>
|
||||
<li> <a class="btn pink darken-1" href="{% url 'reports' %}">reports</a></li>
|
||||
<li> <a class="btn lime darken-1" href="{% url 'tr_list'%}"> transactions </a></li>
|
||||
<li> <a class="btn blue darken-1" href="{% url 'test' %}"> test page </a></li>
|
||||
|
||||
</ul>
|
||||
<div class="container">
|
||||
<div class = "row">
|
||||
{% block content %}
|
||||
|
||||
<!-- Journal -->
|
||||
|
||||
<div class="col s12 m6" style="padding: 10px;">
|
||||
<p class="z-depth-1">
|
||||
<table class="striped bordered">
|
||||
<p class="flow-text" style="padding: 10px;">Журнал операций</p>
|
||||
<tr>
|
||||
<th width=30%>Дата</th>
|
||||
<th width=30%>Сумма</th>
|
||||
<th width=30%>Категория</th> </tr>
|
||||
{% for transaction in transactions %}
|
||||
<tr><td align=center>{{ transaction.tr_date|date:"d/m/y"}}</td>
|
||||
<td align=right>{{ transaction.tr_amount}}</td>
|
||||
<td>{{ transaction.tr_cat}} </td>
|
||||
{% endfor %}
|
||||
</table>
|
||||
<a href="{% url 'tr_list'%}"><span style="display: inline-block; width: 100%; text-align: center; padding: 20px; ">Show all </span> </a>
|
||||
</p>
|
||||
</div>
|
||||
<!-- End Journal -->
|
||||
|
||||
<!-- 2nd block -->
|
||||
<div class="col s12 m6" style="padding: 10px;">
|
||||
<p class="z-depth-1">
|
||||
<table class="striped">
|
||||
<p class="flow-text" style="padding: 10px;">My accounts </p>
|
||||
<tr><th>Name</th><th>Balance</th></tr>
|
||||
{% for account in accounts %}
|
||||
<tr> <td>{{ account.acc_name }}</td><td> {{ account.rest }} {{account.acc_currency}}</td><!--td> {{ account.inc }} / {{ account.exp }} </td--></tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
</p>
|
||||
|
||||
<p class="z-depth-1">
|
||||
<table class="striped">
|
||||
<p class="flow-text" style="padding:10px;">Сумма операций за {{date_today|date:"F"}}<p/>
|
||||
<tr> <th>Категория</th><th>Сумма</th></tr>
|
||||
{% for category in categories %}
|
||||
{% if category.sum_cat %}
|
||||
<tr> <td>{{ category.cat_name}}</td>
|
||||
<td>{{ category.sum_cat}}</td>
|
||||
<!--td>{{ category.avg }} </td--> </tr>
|
||||
{% endif %}{% endfor %}
|
||||
<tr><td>Total :</td> <td>{{ sum_total }}</td></tr>
|
||||
</table> </p>
|
||||
</div>
|
||||
<!-- End 2nd Block -->
|
||||
|
||||
{% endblock%}
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
35
pfm/templates/pfm/cat_edit.html
Normal file
35
pfm/templates/pfm/cat_edit.html
Normal file
@ -0,0 +1,35 @@
|
||||
{% extends 'pfm/base.html' %}
|
||||
{% block content %}
|
||||
<style type="text/css">
|
||||
td {padding: 0 10px 0 }
|
||||
input[type=text] {border:none;}
|
||||
select { border: 0px solid #f2f2f2;}
|
||||
</style>
|
||||
<div class="input-field col s12 m12">
|
||||
<form method="POST" class="post-form ">{% csrf_token %}
|
||||
{{ form.management_form }}
|
||||
<table class="bordered">
|
||||
<tr>
|
||||
<th>Категория</th>
|
||||
<th>type</th>
|
||||
<th>budget</th>
|
||||
<th>Бюджет</th>
|
||||
<th>Удалить</th> </tr>
|
||||
{% for f in form %}
|
||||
<tr>
|
||||
<td>{{ f.cat_name }}</td>
|
||||
<td>{{ f.cat_type }}</td>
|
||||
<td>{{ f.budget_type }}</td>
|
||||
<td>{{ f.budget_amount }}</td>
|
||||
<td>{{ f.DELETE }} <label for="id_form-{{ forloop.counter0 }}-DELETE"><i class="material-icons">delete</i></label></td>
|
||||
<td>{{ f.id }}</td>
|
||||
</tr>
|
||||
|
||||
{% endfor %}
|
||||
|
||||
</table>
|
||||
<button type="submit" class="save btn blue" >Save</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
{% endblock content %}
|
56
pfm/templates/pfm/categories.html
Normal file
56
pfm/templates/pfm/categories.html
Normal file
@ -0,0 +1,56 @@
|
||||
{% extends 'pfm/base.html' %}
|
||||
{% block content %}
|
||||
<div class="row">
|
||||
<div class="col s12 m9 offset-m2">
|
||||
<a class="btn blue" href="#new">Add new </a>
|
||||
<a class="btn blue" href="/pfm/cat_edit">Edit</a>
|
||||
|
||||
<div id="new" class="dialog1 modal">
|
||||
<div class="modal-content">
|
||||
<a href="#!" class=" right modal-action modal-close waves-effect waves-green btn-flat">Close</a>
|
||||
<form method="POST">{% csrf_token %} {{ form.as_p }}
|
||||
<button type="submit" name="action" value="create" class="waves-effect waves-light btn-large blue">Save</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="overflow: auto; padding: 0">
|
||||
<table class="bordered">
|
||||
<tr>
|
||||
<th>Категория</th>
|
||||
<th></th>
|
||||
<th>План</th>
|
||||
<th>Факт</th>
|
||||
<th>Разница</th>
|
||||
</tr>
|
||||
{% for row in categories %}
|
||||
<tr class="category" onclick='ModalForm({{row.pk}}); location.href="#modal"'>
|
||||
<td>{{ row.cat_name}}</td>
|
||||
<td>
|
||||
<div style="display: block; width: 5em; height: 15px; border: 1px solid black; overflow: hidden;">
|
||||
<div style="display: block; width: {{row.ratio}}%; height: 100%; background: red;">
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td> {{ row.budget_amount }} </td>
|
||||
<td> {{ row.cat_sum }} </td>
|
||||
<td class="balance"> {{ row.budget_amount|add:row.minus_sum }} </td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</div>
|
||||
<tr class="category"><td>Расходы</td>
|
||||
<td></td>
|
||||
<td>{{ budget_total }}</td><td>{{ sum_total }}</td><td class="balance">{{ total_balance }}</td></tr>
|
||||
|
||||
</table>
|
||||
<div id="modal" class="dialog1 modal">
|
||||
<div class="modal-content" id="modal-content">
|
||||
<a href="#!" class=" right modal-action modal-close waves-effect waves-green btn-flat">Close</a>
|
||||
<form method="POST">{% csrf_token %} <input name="id" type="hidden">
|
||||
<div id="form"></div>
|
||||
<!--button type="submit" name="action" value="edit" class="waves-effect waves-light btn-large blue" onclick='location.href="#!"'>Save</button>
|
||||
<button type="submit" name="action" value="delete" class="waves-effect waves-light btn-large blue" onclick='location.href="#!"'>Delete</button-->
|
||||
</form>
|
||||
</div> </div>
|
||||
</div></div></div>
|
||||
{% endblock content %}
|
12
pfm/templates/pfm/login.html
Normal file
12
pfm/templates/pfm/login.html
Normal file
@ -0,0 +1,12 @@
|
||||
{% extends 'pfm/base.html' %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<form method="post" action="{% url 'login' %}">
|
||||
{% csrf_token %}
|
||||
{{form}}<br/>
|
||||
<input type="submit" value="login" />
|
||||
<input type="hidden" name="next" value="{{ next }}" />
|
||||
</form>
|
||||
|
||||
{% endblock %}
|
10
pfm/templates/pfm/logout.html
Normal file
10
pfm/templates/pfm/logout.html
Normal file
@ -0,0 +1,10 @@
|
||||
{% extends 'pfm/base.html' %}
|
||||
{% block content %}
|
||||
|
||||
<html>
|
||||
<p>
|
||||
You are logged out. To log in again, click <a href="/pfm/">here</a>.
|
||||
</p>
|
||||
</html>
|
||||
|
||||
{% endblock %}
|
142
pfm/templates/pfm/reports.html
Normal file
142
pfm/templates/pfm/reports.html
Normal file
@ -0,0 +1,142 @@
|
||||
|
||||
{% extends 'pfm/base.html' %}
|
||||
{% load staticfiles %}
|
||||
|
||||
|
||||
{% block content %}
|
||||
<div class="row">
|
||||
<div id="draggable" class="col m4 s12">
|
||||
<p class="flow-text">Сумма операций за
|
||||
<form method="POST" style="display: inline-block; float:left;"> {% csrf_token %}
|
||||
<input type="hidden" name="selected_month" value= "{{ month|add:-1 }}">
|
||||
<input type="hidden" name="selected_year" value="{{ datedate.year }}"><nobr>
|
||||
<button type="submit" name="action" value="send" class="blue" style="margin: 6px 1px; color: white; width: 40px; height: 40px; float: left; ">
|
||||
<i class="material-icons">skip_previous</i>
|
||||
</button>
|
||||
</form>
|
||||
<span class="flow-text" style="display: inline-block; padding: 10px 5px;"> {{ datedate|date:"F Y"|lower}} </span><nobr>
|
||||
<form method="POST" style="display: inline-block;"> {% csrf_token %}
|
||||
<input type="hidden" name="selected_month" value= "{{ month|add:1 }}">
|
||||
<input type="hidden" name="selected_year" value="{{ datedate.year }}">
|
||||
<button type="submit" name="action" value="send" class="blue" style="display: block; float: left; margin: 6px 1px; color: white; width: 40px; height: 40px;">
|
||||
<i class="material-icons" style="display: block; float: rigth; padding: 1px;">skip_next</i>
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<form method="POST"> {% csrf_token %}
|
||||
<input type="text" id="search" style="display: none">
|
||||
<p><select name="selected_month"><nobr>
|
||||
<option selected value="{{ datedate|date:"m" }}">{{ datedate|date:"F" }}</option>
|
||||
<option value=""> </option>
|
||||
<option value = "1">Январь</option>
|
||||
<option value = "2">Февраль</option>
|
||||
<option value = "3">Март</option>
|
||||
<option value = "4">Апрель</option>
|
||||
<option value = "5">Май</option>
|
||||
<option value = "6">Июнь</option>
|
||||
<option value = "7">Июль</option>
|
||||
<option value = "8">Август</option>
|
||||
<option value = "9">Сентябрь</option>
|
||||
<option value = "10">Октябрь</option>
|
||||
<option value = "11">Ноябрь</option>
|
||||
<option value = "12">Декабрь</option>
|
||||
</select> <nobr>
|
||||
<script>
|
||||
//$("select").on('click', function(){$("#search").css("display", "block")})
|
||||
</script>
|
||||
<select name="selected_year">
|
||||
<option selected value="{{ datedate|date:"Y" }}">{{ datedate|date:"Y" }}</option>
|
||||
<option value=""> </option>
|
||||
<option value="2017">2017</option>
|
||||
<option value="2016">2016</option>
|
||||
<option value="2015">2015</option>
|
||||
</select><nobr>
|
||||
</form>
|
||||
{{ mon_calendar|safe }}
|
||||
</div>
|
||||
<div class="col m4 s12 offset-m2">
|
||||
<table cellpadding=5 width=300 class="bordered">
|
||||
<tr>
|
||||
<th>Категория</th>
|
||||
<th>Сумма</th>
|
||||
</tr>
|
||||
{% for category in categories %}
|
||||
{% if category.sum_cat %}
|
||||
<tr> <td>{{ category.cat_name}}</td>
|
||||
<td>{{ category.sum_cat}}</td>
|
||||
<!--td>{{ category.avg }} </td--> </tr>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% if sum_total %}
|
||||
<tr>
|
||||
<td align="right">Total:</td>
|
||||
<td> {{ sum_total }} </td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
</table> <canvas id="canvas" width="360" height="360" style ="padding: 20px 0px;"></canvas> </div></div>
|
||||
|
||||
<!--script>
|
||||
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();
|
||||
|
||||
}
|
||||
|
||||
</script-->
|
||||
<script>
|
||||
var data = [{% for cat in categories %} {{ cat.sum_cat|escapejs }}, {% endfor %}];
|
||||
var data_cat = [{% for cat in categories %} "{{ cat.cat_name|escapejs }}", {% endfor %}];
|
||||
</script>
|
||||
<script type="text/javascript" src="{% static 'js/diagram.js' %}"></script>
|
||||
<script> console.log("t");
|
||||
$(document).ready( function() {
|
||||
$('form').on('change', function() {
|
||||
var $form = $(this);
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
data: $form.serialize(),
|
||||
success: function(response){$('html').html(response)}
|
||||
|
||||
}).done(function() {
|
||||
console.log('success');
|
||||
}).fail(function() {
|
||||
console.log('fail');
|
||||
});
|
||||
});
|
||||
}); </script>
|
||||
{% endblock content %}
|
12
pfm/templates/pfm/test.html
Normal file
12
pfm/templates/pfm/test.html
Normal file
@ -0,0 +1,12 @@
|
||||
{% extends 'pfm/base.html' %}
|
||||
|
||||
{% block content %}
|
||||
<table class="striped bordered">
|
||||
{% for item in status %}
|
||||
<tr>
|
||||
<td>{{ item.Name|cut:"/RUB" }}</td><td> {{ item.Bid }}<td>
|
||||
</tr>
|
||||
|
||||
{% endfor %}
|
||||
</table>
|
||||
{% endblock content %}
|
63
pfm/templates/pfm/tr_edit.html
Normal file
63
pfm/templates/pfm/tr_edit.html
Normal file
@ -0,0 +1,63 @@
|
||||
{% extends 'pfm/base.html' %}
|
||||
{% load pagination_tags %}
|
||||
{% block content %}
|
||||
|
||||
<style type="text/css">
|
||||
td {padding: 0 10px 0 }
|
||||
input[type=text] {border:none;}
|
||||
select { border: 0px solid #f2f2f2; }
|
||||
</style>
|
||||
|
||||
<div class="col s12 m12">
|
||||
<form method="POST" class="post-form ">{% csrf_token %}
|
||||
{{form.management_form }}
|
||||
<table class="bordered">
|
||||
<tr>
|
||||
<th>Дата</th>
|
||||
<th>Тип</th>
|
||||
<th>Сумма</th>
|
||||
<th>Счет</th>
|
||||
<th>Категория</th>
|
||||
<th>Комментарий</th>
|
||||
<th>Удалить</th>
|
||||
</tr>
|
||||
{% for f in form %}
|
||||
<tr>
|
||||
<td>{{ f.tr_date }}</td>
|
||||
<td>{{ f.tr_type }}</td>
|
||||
<td>{{ f.tr_amount }}</td>
|
||||
<td>{{ f.tr_acc }}</td>
|
||||
<td>{{ f.tr_cat }}</td>
|
||||
<td>{{ f.tr_note }}</td>
|
||||
<td>{{ f.DELETE }} <label for="id_form-{{ forloop.counter0 }}-DELETE"><i class="material-icons">delete</i></label></td>
|
||||
<td>{{ f.id }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
<button type="submit" style="margin-top: 20px" class="save btn blue" style="float: left">Save</button>
|
||||
|
||||
<ul class="menu_top paginator" style="float:right">
|
||||
{% if trs.has_previous %}
|
||||
<li> <a href="?page=1"><<</a> </li>
|
||||
<li> <a href="?page={{ trs.previous_page_number }}">{{ trs.previous_page_number }}</a> </li>
|
||||
{% endif %}
|
||||
<li> <a style="color: #000000" href="">{{ trs.number }}<a></li>
|
||||
{% if trs.has_next %}
|
||||
<li> <a href="?page={{ trs.next_page_number }}">
|
||||
{{ trs.next_page_number }}</a> </li>
|
||||
{% endif %}
|
||||
{% comment %}
|
||||
{% if trs.has_next %}
|
||||
<li> <a href="?page={{ trs.next_page_number|add:1 }}">{{ trs.next_page_number|add:1 }}</a></li>
|
||||
{% endif %}
|
||||
{% endcomment %}
|
||||
{% if trs.has_next %}
|
||||
<li><a href="?page={{ trs.paginator.num_pages }}"> >> </a></li>
|
||||
{% endif %}
|
||||
{% comment %} <!--li>
|
||||
{% for i in trs.paginator.page_range %} </li-->
|
||||
{% endcomment %}
|
||||
</ul>
|
||||
</form>
|
||||
</div>
|
||||
{% endblock content %}
|
27
pfm/templates/pfm/tr_edit_form.html
Normal file
27
pfm/templates/pfm/tr_edit_form.html
Normal file
@ -0,0 +1,27 @@
|
||||
<div>
|
||||
<div><label>Type</label>{{ form.tr_type }} </div>
|
||||
<div id='cat' height = 44px> <label>Category</label>{{ form.tr_cat }}</div>
|
||||
<div><label>Date</label>{{ form.tr_date }}</div>
|
||||
<div><label>Account</label>{{ form.tr_acc }}</div>
|
||||
<div><label>Amount</label>{{ form.tr_amount }}</div>
|
||||
<div><label>Notes</label>{{ form.tr_note }} </div></div>
|
||||
<!--button type="submit" name="action" value="create" class="waves-effect waves-light btn-large blue">Save</button>
|
||||
<button type="submit" name="action" value="edit" class="waves-effect waves-light btn-large blue">Save</button>
|
||||
<button type="submit" name="action" value="delete" class="waves-effect waves-light btn-large blue">Delete</button-->
|
||||
</div>
|
||||
<script>
|
||||
$(document).ready( function() {
|
||||
if ($("input[name='id']")){
|
||||
console.log($("input[name='id']").val());}
|
||||
else {console.log("no id");}
|
||||
|
||||
$('#id_tr_type').on('change', function() {
|
||||
$('#cat').html("<label>Category</label> loading...");
|
||||
$.ajax({
|
||||
type: 'GET',
|
||||
data: {type: $(this).val()},
|
||||
success: function(form){$('#cat').html(form)}
|
||||
})
|
||||
});
|
||||
});
|
||||
</script>
|
79
pfm/templates/pfm/transactions.html
Normal file
79
pfm/templates/pfm/transactions.html
Normal file
@ -0,0 +1,79 @@
|
||||
{% extends 'pfm/base.html' %}
|
||||
{% load pagination_tags %}
|
||||
{% block content %}
|
||||
<div class="grid-example col s12">
|
||||
<a class="waves-effect waves-light btn-large blue" style="display: block; float: right; margin: 1px" onclick = 'ModalForm(0); location.href = "#new"'>New transaction</a>
|
||||
<a class="waves-effect waves-light btn-large blue" style="display: block; float: right; margin: 1px" href="/pfm/tr_edit">Massive update</a>
|
||||
<p class="flow-text">Журнал операций</p> </div>
|
||||
|
||||
<table class="striped bordered">
|
||||
<tr>
|
||||
<th>Дата</th>
|
||||
<th>Сумма</th>
|
||||
<th>Счет</th>
|
||||
<th>Категория</th>
|
||||
<th class="hide-on-med-and-down">Комментарий
|
||||
</tr>
|
||||
{% for transaction in trs %}
|
||||
<tr onclick='ModalForm({{transaction.pk}}); location.href="#new"';"><td align=center>{{ transaction.tr_date|date:"d/m/y"}}</td>
|
||||
<td>{{ transaction.tr_amount}}</td>
|
||||
<td>{{ transaction.tr_acc}}</td>
|
||||
<td>{{ transaction.tr_cat}}</td>
|
||||
<td class="hide-on-med-and-down">{{ transaction.tr_note }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
|
||||
<div id="new" class="dialog1 modal">
|
||||
<div class="modal-content">
|
||||
<a href="#!" class=" right modal-action modal-close waves-effect waves-green btn-flat">Close</a>
|
||||
<!--h4 class="flow-text">New transaction</h4-->
|
||||
<form method="POST"> {% csrf_token %}
|
||||
<input name="id" type="hidden">
|
||||
<!--div><label>Date</label>{{ form.tr_date }}</div-->
|
||||
<div id='form'> </div>
|
||||
</form></div></div>
|
||||
|
||||
</div>
|
||||
|
||||
<ul class="menu_top paginator">
|
||||
{% if trs.has_previous %}
|
||||
<li> <a href="?page=1"><<</a> </li>
|
||||
<li> <a href="?page={{ trs.previous_page_number }}">{{ trs.previous_page_number }}</a> </li>
|
||||
{% endif %}
|
||||
<li>
|
||||
<a style="color: #000000" href="">{{ trs.number }}<a>
|
||||
</li>
|
||||
{% if trs.has_next %}
|
||||
<li>
|
||||
<a href="?page={{ trs.next_page_number }}"> {{ trs.next_page_number }} </a>
|
||||
</li>
|
||||
{% endif %}
|
||||
|
||||
{% comment %}
|
||||
{% if trs.has_next %}
|
||||
<li> <a href="?page={{ trs.next_page_number|add:1 }}">{{ trs.next_page_number|add:1 }}</a></li>{% endif %}
|
||||
{% endcomment %}
|
||||
|
||||
{% if trs.has_next %}
|
||||
<li><a href="?page={{ trs.paginator.num_pages }}"> >> </a></li>
|
||||
{% endif %}
|
||||
|
||||
{% comment %}
|
||||
<!--li> {% for i in trs.paginator.page_range %} </li-->
|
||||
{% endcomment %}
|
||||
|
||||
</ul>
|
||||
|
||||
<script>
|
||||
$(document).ready( function() {
|
||||
$('#id_tr_type').on('change', function() {
|
||||
$.ajax({
|
||||
type: 'GET',
|
||||
data: {type: $(this).val()},
|
||||
success: function(form){ $('#cat').html(form)}
|
||||
})
|
||||
});
|
||||
}); </script>
|
||||
|
||||
{% endblock content %}
|
21
pfm/templates/pfm/upload.html
Normal file
21
pfm/templates/pfm/upload.html
Normal file
@ -0,0 +1,21 @@
|
||||
{% extends 'pfm/base.html' %}
|
||||
|
||||
{% block content %}
|
||||
<div id="draggable" >
|
||||
<form method="POST" class="post-form" enctype="multipart/form-data" action="" >{% csrf_token %} {{ form.as_p }}
|
||||
<div class="file-field input-field">
|
||||
<div class="btn blue">
|
||||
<span>File</span>
|
||||
<input type="file" name="uploaded" "accept=text/csv" >
|
||||
</div>
|
||||
<div class="file-path-wrapper">
|
||||
<input class="file-path validate" type="text">
|
||||
</div>
|
||||
<input type="submit" value="upload file" class="save btn blue">
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
|
64
pfm/tests.py
Normal file
64
pfm/tests.py
Normal file
@ -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")
|
||||
|
21
pfm/urls.py
Normal file
21
pfm/urls.py
Normal file
@ -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'),
|
||||
]
|
333
pfm/views.py
Normal file
333
pfm/views.py
Normal file
@ -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})
|
||||
|
Loading…
Reference in New Issue
Block a user