This commit is contained in:
Anna Sudnitsina 2017-10-31 16:37:02 +03:00
parent 94f69b7038
commit c53725b502
33 changed files with 1807 additions and 0 deletions

4
.gitignore vendored
View File

@ -58,3 +58,7 @@ docs/_build/
# PyBuilder # PyBuilder
target/ target/
*.pyc
*.save
*.swp
db.sqlite3

10
manage.py Normal file
View 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
View File

139
mysite/settings.py Normal file
View 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
View 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
View 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
View File

7
pfm/admin.py Normal file
View 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
View 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
View 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)

View 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)),
],
),
]

View 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)),
),
]

View 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),
),
]

View File

142
pfm/models.py Normal file
View 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
View 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
View 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
View 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");
}
}

View 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
View 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>

View 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 %}

View 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 %}

View 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 %}

View 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 %}

View 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 %}

View 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 %}

View 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 %}

View 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>

View 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 %}

View 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
View 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
View 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
View 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})