diff --git a/README.md b/README.md index 9f6dbcb..dd0799b 100755 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ First, make sure you have Django and NetworkX installed. Change directory to `/buddyledger/src` (the one containing `manage.py`). -Create `buddyledger/settings.py` relative to current directory (example content): +Create `buddyledger/environment_settings.py` relative to current directory (example content): ``` ROOT_URLCONF="buddyledger.urls" INSTALLED_APPS=( @@ -30,7 +30,7 @@ INSTALLED_APPS=( 'buddyledger' ) -TEMPLATE_DIRS=("templates") +TEMPLATE_DIRS=("templates",) DEBUG = True DEFAULT_FROM_EMAIL = 'webmaster@example.com' @@ -46,8 +46,8 @@ DATABASES = { Now execute (example for Windows): - c:\Python33\python manage.py syncdb - c:\Python33\python manage.py getcurrency - c:\Python33\python manage.py runserver + c:\Python38\python manage.py migrate + c:\Python38\python manage.py getcurrency + c:\Python38\python manage.py runserver The web application is available at http://localhost:8000/ . diff --git a/requirements.txt b/requirements.txt index b66b5bf..ed829f8 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,3 @@ -networkx==1.10 -Django==1.8.2 -django-bootstrap3==5.4.0 +networkx==2.5 +Django==3.1.* +django-bootstrap4==1.1.* diff --git a/src/buddyledger/forms.py b/src/buddyledger/forms.py index b86f378..8a8a7e2 100644 --- a/src/buddyledger/forms.py +++ b/src/buddyledger/forms.py @@ -106,7 +106,7 @@ def get_expense_parts(self): fielddict[fieldname] = value expenseparts = dict() - for fieldname,value in fielddict.iteritems(): + for fieldname,value in fielddict.items(): ### get the userid from the expensepart field if fieldname.startswith('person-expensepart-') and value == True: userid = fieldname[19:] diff --git a/src/buddyledger/management/commands/getcurrency.py b/src/buddyledger/management/commands/getcurrency.py index a5a31ae..ac1a736 100755 --- a/src/buddyledger/management/commands/getcurrency.py +++ b/src/buddyledger/management/commands/getcurrency.py @@ -1,38 +1,56 @@ -from django.core.management.base import BaseCommand, CommandError -from buddyledger.models import Currency -from decimal import * import urllib, json import xml.etree.ElementTree as etree +from decimal import * try: from urllib.request import urlopen except ImportError: from urllib import urlopen +def fetch(): + f = urlopen('https://www.ecb.europa.eu/stats/eurofxref/eurofxref-daily.xml') + xml = f.read() + f.close() + tree = etree.fromstring(xml) + cube = tree[2][0] + eur_rates = {} + for child in cube: + a = child.attrib + rate = float(a['rate']) + code = a['currency'] + eur_rates[code] = rate + + dkk_per_eur = 1 / eur_rates['DKK'] + + for code, rate in eur_rates.items(): + if code == "DKK": continue + yield code, rate * dkk_per_eur + yield "EUR", dkk_per_eur + +if __name__ == "__main__": + import pprint, sys + gen = fetch() + pprint.pprint(list(gen)) + sys.exit(0) + +from django.core.management.base import BaseCommand, CommandError +from buddyledger.models import Currency + class Command(BaseCommand): help = 'Gets currency exchange rates from nationalbanken' def handle(self, *args, **options): - f = urlopen('http://www.nationalbanken.dk/_vti_bin/DN/DataService.svc/CurrencyRatesXML?lang=da') - xml = f.read() - f.close() - tree = etree.fromstring(xml) - for child in tree[0]: - if child.attrib['rate'] != '-': - rate = float(child.attrib['rate'].replace(".", "").replace(",", "."))/100 - - try: - currency = Currency.objects.get(iso4217_code=child.attrib['code']) - currency.dkk_price=rate - temp = "" - except Currency.DoesNotExist: - currency = Currency(iso4217_code=child.attrib['code'],dkk_price=rate) - temp = " new" - - currency.save() - self.stdout.write('Saved%s rate: 1 %s costs %s DKK' % (temp, child.attrib['code'],rate)) - else: - self.stdout.write('Skipping currency %s - no price found' % child.attrib['code']) + for code, rate in fetch(): + try: + currency = Currency.objects.get(iso4217_code=code) + currency.dkk_price = rate + temp = "" + except Currency.DoesNotExist: + currency = Currency(iso4217_code=code, dkk_price=rate) + temp = " new" + + self.stdout.write('Saved%s rate: 1 %s costs %s DKK' % (temp, code, rate)) + currency.save() ########################################################################################### @@ -49,4 +67,3 @@ def handle(self, *args, **options): ########################################################################################### self.stdout.write('Done getting currencies.') - diff --git a/src/buddyledger/migrations/0001_initial.py b/src/buddyledger/migrations/0001_initial.py index 695a8aa..8b058c0 100644 --- a/src/buddyledger/migrations/0001_initial.py +++ b/src/buddyledger/migrations/0001_initial.py @@ -1,11 +1,13 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals +# Generated by Django 3.0.5 on 2020-04-16 21:40 -from django.db import models, migrations +from django.db import migrations, models +import django.db.models.deletion class Migration(migrations.Migration): + initial = True + dependencies = [ ] @@ -13,9 +15,9 @@ class Migration(migrations.Migration): migrations.CreateModel( name='Currency', fields=[ - ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('iso4217_code', models.CharField(max_length=3)), - ('dkk_price', models.DecimalField(max_digits=20, decimal_places=2)), + ('dkk_price', models.DecimalField(decimal_places=2, max_digits=20)), ], options={ 'ordering': ('iso4217_code',), @@ -24,62 +26,62 @@ class Migration(migrations.Migration): migrations.CreateModel( name='Expense', fields=[ - ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('name', models.CharField(max_length=100)), - ('amount', models.DecimalField(max_digits=20, decimal_places=2)), - ('amount_native', models.DecimalField(editable=False, max_digits=20, decimal_places=2)), + ('amount', models.DecimalField(decimal_places=2, max_digits=20)), + ('amount_native', models.DecimalField(decimal_places=2, editable=False, max_digits=20)), ('date', models.DateField()), - ('currency', models.ForeignKey(to='buddyledger.Currency')), + ('currency', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='buddyledger.Currency')), ], options={ 'ordering': ('date', 'id'), }, ), - migrations.CreateModel( - name='ExpensePart', - fields=[ - ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), - ('haspaid', models.DecimalField(null=True, max_digits=20, decimal_places=2, blank=True)), - ('haspaid_native', models.DecimalField(null=True, max_digits=20, decimal_places=2, blank=True)), - ('shouldpay', models.DecimalField(null=True, max_digits=20, decimal_places=2, blank=True)), - ('shouldpay_native', models.DecimalField(null=True, max_digits=20, decimal_places=2, blank=True)), - ('expense', models.ForeignKey(to='buddyledger.Expense')), - ], - ), migrations.CreateModel( name='Ledger', fields=[ - ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('name', models.CharField(max_length=100)), ('closed', models.BooleanField(default=False, editable=False)), - ('calcmethod', models.CharField(max_length=20, editable=False)), - ('currency', models.ForeignKey(to='buddyledger.Currency')), + ('calcmethod', models.CharField(default='basic', editable=False, max_length=20)), + ('currency', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='buddyledger.Currency')), ], ), migrations.CreateModel( name='Person', fields=[ - ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('name', models.CharField(max_length=100)), - ('ledger', models.ForeignKey(editable=False, to='buddyledger.Ledger')), + ('ledger', models.ForeignKey(editable=False, on_delete=django.db.models.deletion.CASCADE, to='buddyledger.Ledger')), ], options={ 'ordering': ('name',), }, ), - migrations.AddField( - model_name='expensepart', - name='person', - field=models.ForeignKey(to='buddyledger.Person'), + migrations.CreateModel( + name='ExpensePart', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('haspaid', models.DecimalField(blank=True, decimal_places=2, max_digits=20, null=True)), + ('haspaid_native', models.DecimalField(blank=True, decimal_places=2, max_digits=20, null=True)), + ('shouldpay', models.DecimalField(blank=True, decimal_places=2, max_digits=20, null=True)), + ('shouldpay_native', models.DecimalField(blank=True, decimal_places=2, max_digits=20, null=True)), + ('autoamount', models.BooleanField(default=False)), + ('expense', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='expenseparts', to='buddyledger.Expense')), + ('person', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='buddyledger.Person')), + ], + options={ + 'unique_together': {('expense', 'person')}, + }, ), migrations.AddField( model_name='expense', name='ledger', - field=models.ForeignKey(editable=False, to='buddyledger.Ledger'), + field=models.ForeignKey(editable=False, on_delete=django.db.models.deletion.CASCADE, to='buddyledger.Ledger'), ), migrations.AddField( model_name='expense', name='people', - field=models.ManyToManyField(to='buddyledger.Person', through='buddyledger.ExpensePart'), + field=models.ManyToManyField(through='buddyledger.ExpensePart', to='buddyledger.Person'), ), ] diff --git a/src/buddyledger/migrations/0002_auto_20150815_1640.py b/src/buddyledger/migrations/0002_auto_20150815_1640.py deleted file mode 100644 index 0d9ec1f..0000000 --- a/src/buddyledger/migrations/0002_auto_20150815_1640.py +++ /dev/null @@ -1,39 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - -from django.db import models, migrations -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('buddyledger', '0001_initial'), - ] - - operations = [ - migrations.AddField( - model_name='expensepart', - name='autoamount', - field=models.BooleanField(default=False), - ), - migrations.AlterField( - model_name='expensepart', - name='expense', - field=models.ForeignKey(related_name='expenseparts', to='buddyledger.Expense'), - ), - migrations.AlterField( - model_name='expensepart', - name='person', - field=models.ForeignKey(to='buddyledger.Person', on_delete=django.db.models.deletion.PROTECT), - ), - migrations.AlterField( - model_name='ledger', - name='calcmethod', - field=models.CharField(default=b'basic', max_length=20, editable=False), - ), - migrations.AlterUniqueTogether( - name='expensepart', - unique_together=set([('expense', 'person')]), - ), - ] diff --git a/src/buddyledger/models.py b/src/buddyledger/models.py index 0512730..c0ae2ef 100644 --- a/src/buddyledger/models.py +++ b/src/buddyledger/models.py @@ -2,19 +2,19 @@ class Ledger(models.Model): name = models.CharField(max_length=100) - currency = models.ForeignKey('Currency') + currency = models.ForeignKey('Currency', on_delete=models.CASCADE) closed = models.BooleanField(default=False, editable=False) calcmethod = models.CharField(max_length=20, editable=False, default="basic") - def __unicode__(self): + def __str__(self): return self.name class Person(models.Model): name = models.CharField(max_length=100) - ledger = models.ForeignKey(Ledger,editable=False) + ledger = models.ForeignKey(Ledger, editable=False, on_delete=models.CASCADE) - def __unicode__(self): + def __str__(self): return self.name class Meta: @@ -24,13 +24,13 @@ class Meta: class Expense(models.Model): name = models.CharField(max_length=100) amount = models.DecimalField(max_digits=20, decimal_places=2) - currency = models.ForeignKey('Currency') + currency = models.ForeignKey('Currency', on_delete=models.CASCADE) amount_native = models.DecimalField(max_digits=20, decimal_places=2, editable=False) - ledger = models.ForeignKey('Ledger',editable=False) + ledger = models.ForeignKey('Ledger', editable=False, on_delete=models.CASCADE) people = models.ManyToManyField('Person', through='ExpensePart') date = models.DateField() - def __unicode__(self): + def __str__(self): return self.name class Meta: @@ -38,7 +38,7 @@ class Meta: class ExpensePart(models.Model): - expense = models.ForeignKey(Expense, related_name="expenseparts") + expense = models.ForeignKey(Expense, related_name="expenseparts", on_delete=models.CASCADE) person = models.ForeignKey(Person, on_delete=models.PROTECT) haspaid = models.DecimalField(max_digits=20, decimal_places=2,null=True,blank=True) haspaid_native = models.DecimalField(max_digits=20, decimal_places=2,null=True,blank=True) @@ -54,7 +54,7 @@ class Currency(models.Model): iso4217_code = models.CharField(max_length=3) dkk_price = models.DecimalField(max_digits=20, decimal_places=2) - def __unicode__(self): + def __str__(self): return self.iso4217_code class Meta: diff --git a/src/buddyledger/settings.py b/src/buddyledger/settings.py index 6a8b755..084e009 100644 --- a/src/buddyledger/settings.py +++ b/src/buddyledger/settings.py @@ -5,7 +5,7 @@ # Build paths inside the project like this: os.path.join(BASE_DIR, ...) import os from django.conf import global_settings -from environment_settings import * +from .environment_settings import * BASE_DIR = os.path.dirname(os.path.dirname(__file__)) # Application definition @@ -42,17 +42,6 @@ 'BACKEND': 'django.template.backends.django.DjangoTemplates', 'DIRS': [os.path.join(BASE_DIR, 'templates')], 'APP_DIRS': True, - 'OPTIONS': { - 'context_processors': [ - 'django.template.context_processors.debug', - 'django.template.context_processors.i18n', - 'django.template.context_processors.media', - 'django.template.context_processors.static', - 'django.template.context_processors.tz', - 'django.contrib.messages.context_processors.messages', - 'django.core.context_processors.request', - ], - }, }, ] diff --git a/src/buddyledger/templates/add_expense.html b/src/buddyledger/templates/add_expense.html index 02deba2..f94fb9a 100644 --- a/src/buddyledger/templates/add_expense.html +++ b/src/buddyledger/templates/add_expense.html @@ -74,7 +74,7 @@

People

- + {{ ledger.currency.iso4217_code }}
diff --git a/src/buddyledger/urls.py b/src/buddyledger/urls.py index 2d28e4e..cfcbe44 100644 --- a/src/buddyledger/urls.py +++ b/src/buddyledger/urls.py @@ -1,27 +1,28 @@ -from django.conf.urls import patterns, include, url +from django.conf.urls import include, re_path +from . import views -urlpatterns = patterns('', +urlpatterns = [ ### frontpage and other static pages - url(r'^$', 'buddyledger.views.Frontpage', name='frontpage'), - url(r'^usage/$', 'buddyledger.views.ShowUsage', name='show_usage'), + re_path(r'^$', views.Frontpage, name='frontpage'), + re_path(r'^usage/?$', views.ShowUsage, name='show_usage'), ### ledger - url(r'^ledger/create/$', 'buddyledger.views.CreateLedger', name='create_ledger'), - url(r'^ledger/(?P\d+)/$', 'buddyledger.views.ShowLedger', name='show_ledger'), - url(r'^ledger/(?P\d+)/edit/$', 'buddyledger.views.EditLedger', name='edit_ledger'), - url(r'^ledger/(?P\d+)/close/$', 'buddyledger.views.CloseLedger', name='close_ledger'), - url(r'^ledger/(?P\d+)/reopen/$', 'buddyledger.views.ReopenLedger', name='reopen_ledger'), - url(r'^ledger/(?P\d+)/changemethod/$', 'buddyledger.views.ChangeMethod', name='change_ledger_method'), + re_path(r'^ledger/create/?$', views.CreateLedger, name='create_ledger'), + re_path(r'^ledger/(?P\d+)/?$', views.ShowLedger, name='show_ledger'), + re_path(r'^ledger/(?P\d+)/edit/?$', views.EditLedger, name='edit_ledger'), + re_path(r'^ledger/(?P\d+)/close/?$', views.CloseLedger, name='close_ledger'), + re_path(r'^ledger/(?P\d+)/reopen/?$', views.ReopenLedger, name='reopen_ledger'), + re_path(r'^ledger/(?P\d+)/changemethod/?$', views.ChangeMethod, name='change_ledger_method'), ### person - url(r'^ledger/(?P\d+)/addperson/$', 'buddyledger.views.AddPerson', name='add_person'), - url(r'^person/(?P\d+)/edit/$', 'buddyledger.views.EditPerson', name='edit_person'), - url(r'^person/(?P\d+)/remove/$', 'buddyledger.views.RemovePerson', name='remove_person'), + re_path(r'^ledger/(?P\d+)/addperson/?$', views.AddPerson, name='add_person'), + re_path(r'^person/(?P\d+)/edit/?$', views.EditPerson, name='edit_person'), + re_path(r'^person/(?P\d+)/remove/?$', views.RemovePerson, name='remove_person'), ### expense - url(r'^ledger/(?P\d+)/addexpense/$', 'buddyledger.views.AddExpense', name='add_expense'), - url(r'^expense/(?P\d+)/edit/$', 'buddyledger.views.EditExpense', name='edit_expense'), - url(r'^expense/(?P\d+)/remove/$', 'buddyledger.views.RemoveExpense', name='remove_expense'), - url(r'^expense/(?P\d+)/addperson/(?P\d+)/$', 'buddyledger.views.ExpenseAddPerson', name='expense_add_person'), - url(r'^expense/(?P\d+)/removeperson/(?P\d+)/$', 'buddyledger.views.ExpenseRemovePerson', name='expense_remove_person'), -) \ No newline at end of file + re_path(r'^ledger/(?P\d+)/addexpense/?$', views.AddExpense, name='add_expense'), + re_path(r'^expense/(?P\d+)/edit/?$', views.EditExpense, name='edit_expense'), + re_path(r'^expense/(?P\d+)/remove/?$', views.RemoveExpense, name='remove_expense'), + re_path(r'^expense/(?P\d+)/addperson/(?P\d+)/?$', views.ExpenseAddPerson, name='expense_add_person'), + re_path(r'^expense/(?P\d+)/removeperson/(?P\d+)/?$', views.ExpenseRemovePerson, name='expense_remove_person'), +] \ No newline at end of file diff --git a/src/buddyledger/views/__init__.py b/src/buddyledger/views/__init__.py index e132031..6961bd0 100644 --- a/src/buddyledger/views/__init__.py +++ b/src/buddyledger/views/__init__.py @@ -1,4 +1,4 @@ -from expense import * -from ledger import * -from person import * -from staticpage import * +from .expense import * +from .ledger import * +from .person import * +from .staticpage import * diff --git a/src/buddyledger/views/basiccalc.py b/src/buddyledger/views/basiccalc.py index e24237f..bbd126b 100644 --- a/src/buddyledger/views/basiccalc.py +++ b/src/buddyledger/views/basiccalc.py @@ -1,3 +1,5 @@ +from fractions import Fraction + def BasicCalc(expenses, peoplelist): print debtdict = dict() @@ -23,7 +25,7 @@ def BasicCalc(expenses, peoplelist): ### optimize payments to the same people dont have to pay to eachother, for payerid in list(debtdict): receiverdict = debtdict[payerid] - for receiverid,amount in receiverdict.iteritems(): + for receiverid,amount in receiverdict.items(): ### make certain everything is initialized if not payerid in debtdict: debtdict[payerid] = dict() @@ -39,7 +41,7 @@ def BasicCalc(expenses, peoplelist): continue ### if either one is 0 skip it - if debtdict[payerid][receiverid] == 0 or debtdict[receiverid][payerid] == 0: + if debtdict[payerid][receiverid] <= Fraction(1,100) or debtdict[receiverid][payerid] <= Fraction(1,100): continue ### check if receiver is to receive more than he is paying to this payer @@ -56,6 +58,11 @@ def BasicCalc(expenses, peoplelist): if debtdict[payerid][receiverid] != 0: debtdict[payerid][receiverid] = 0 debtdict[receiverid][payerid] = 0 - + + if debtdict[payerid][receiverid] <= Fraction(1,100): + debtdict[payerid][receiverid] = 0 + if debtdict[receiverid][payerid] <= Fraction(1,100): + debtdict[receiverid][payerid] = 0 + ### return the result return debtdict diff --git a/src/buddyledger/views/expense.py b/src/buddyledger/views/expense.py index a323d8e..675fdd5 100644 --- a/src/buddyledger/views/expense.py +++ b/src/buddyledger/views/expense.py @@ -1,14 +1,14 @@ from decimal import * import datetime from django.http import HttpResponseRedirect -from django.shortcuts import render, render_to_response +from django.shortcuts import render from django.forms.models import inlineformset_factory from django.views.generic.edit import DeleteView from buddyledger.models import Ledger, Person, Expense, Currency, ExpensePart from buddyledger.forms import LedgerForm, PersonForm, ExpenseForm, DeleteExpenseForm -from buddyledger.views.misc import ConvertCurrency +from buddyledger.views.misc import ConvertCurrency, render_to_response def ValidateExpense(customtotal, splitpart, autocount, remainder, expense, paymenttotal): ### check if customtotal + remaining = expense amount @@ -28,7 +28,7 @@ def GetTotals(expenseparts): customtotal = 0 autocount = 0 paymenttotal = 0 - for uid,temp in expenseparts.iteritems(): + for uid,temp in expenseparts.items(): if temp['shouldpay'] != "auto": customtotal += Decimal(temp['shouldpay']) else: @@ -53,7 +53,7 @@ def GetSplitParts(expense, customtotal, autocount): def CreateExpenseParts(expenseparts, expense, ledger, splitpart, remainder): - for uid,temp in expenseparts.iteritems(): + for uid,temp in expenseparts.items(): if temp['shouldpay'] != "auto": expensepart = ExpensePart.objects.create( person_id=uid, @@ -83,12 +83,12 @@ def AddExpense(request, ledgerid=0): try: ledger = Ledger.objects.get(pk = ledgerid) except Ledger.DoesNotExist: - response = render_to_response('ledgerdoesnotexist.html') + response = render_to_response(request, 'ledgerdoesnotexist.html') return response ### check if the ledger is open if ledger.closed: - response = render_to_response('ledger_is_closed.html') + response = render_to_response(request, 'ledger_is_closed.html') return response ### get all people associated with this ledger @@ -114,7 +114,7 @@ def AddExpense(request, ledgerid=0): ### get totals totals = GetTotals(expenseparts) if not totals: - return render_to_response('invalid_expense.html') + return render_to_response(request, 'invalid_expense.html') customtotal, autocount, paymenttotal = totals ### get splitpart (and remainder if any) @@ -122,7 +122,7 @@ def AddExpense(request, ledgerid=0): ### validate ledger if not ValidateExpense(customtotal, splitpart, autocount, remainder, expense, paymenttotal): - return render_to_response('invalid_expense.html') + return render_to_response(request, 'invalid_expense.html') ### OK, save the expense expense.save() # save the new expense @@ -148,18 +148,17 @@ def AddExpense(request, ledgerid=0): 'ledger': ledger }) - def EditExpense(request, expenseid=0): ### Check if the expense exists - bail out if not try: expense = Expense.objects.get(pk = expenseid) except Expense.DoesNotExist: - response = render_to_response('expense_does_not_exist.html') + response = render_to_response(request, 'expense_does_not_exist.html') return response ### check if the ledger is open if expense.ledger.closed: - response = render_to_response('ledger_is_closed.html') + response = render_to_response(request, 'ledger_is_closed.html') return response ### get all people associated with this ledger @@ -186,7 +185,7 @@ def EditExpense(request, expenseid=0): ### get totals totals = GetTotals(expenseparts) if not totals: - return render_to_response('invalid_expense.html') + return render_to_response(request, 'invalid_expense.html') customtotal, autocount, paymenttotal = totals ### get splitpart (and remainder if any) @@ -194,7 +193,7 @@ def EditExpense(request, expenseid=0): ### validate expense if not ValidateExpense(customtotal, splitpart, autocount, remainder, expense, paymenttotal): - return render_to_response('invalid_expense.html') + return render_to_response(request, 'invalid_expense.html') ### OK, save the expense expense.save() # save the expense @@ -221,12 +220,12 @@ def RemoveExpense(request, expenseid=0): try: expense = Expense.objects.get(pk = expenseid) except Expense.DoesNotExist: - response = render_to_response('expense_does_not_exist.html') + response = render_to_response(request, 'expense_does_not_exist.html') return response ### check if the ledger is open if expense.ledger.closed: - response = render_to_response('ledger_is_closed.html') + response = render_to_response(request, 'ledger_is_closed.html') return response if request.method == 'POST': diff --git a/src/buddyledger/views/graphbuilder.py b/src/buddyledger/views/graphbuilder.py index c51b426..2ea71f8 100644 --- a/src/buddyledger/views/graphbuilder.py +++ b/src/buddyledger/views/graphbuilder.py @@ -48,6 +48,13 @@ def solve_mincost_problem_for_expenses(expenses, people, debug=False): return flowDict if __name__ == "__main__": + print(solve_mincost_problem_for_expenses([{'whopaid': [ + {'amount': Fraction(33, 100), 'personId': 60}, + {'amount': Fraction(33, 100), 'personId': 61}, + {'amount': Fraction(34, 100), 'personId': 62}], 'whoshouldpay': { + 60: Fraction(33, 100), + 61: Fraction(33, 100), + 62: Fraction(34, 100)}}], [62, 61, 60], 1)) print(solve_mincost_problem_for_expenses([{"whopaid": [{"personId": 10, "amount": Fraction(1,2)}, {"personId": 11, "amount": Fraction(1,2)}], "whoshouldpay": {10: None, 11: None, 12: Fraction(1,2)}}] , [10,11,12], 1)) # invalid problems (invalid constraint because all people contribute an amount which is unequal the paid amount: # from buddyledger.baconsvin.org/ledger/17 diff --git a/src/buddyledger/views/ledger.py b/src/buddyledger/views/ledger.py index 203b5be..9382912 100644 --- a/src/buddyledger/views/ledger.py +++ b/src/buddyledger/views/ledger.py @@ -3,16 +3,16 @@ from fractions import Fraction ### django functions -from django.shortcuts import render, render_to_response +from django.shortcuts import render from django.http import HttpResponseRedirect from django.contrib import messages ### django models and forms from buddyledger.models import Ledger, Person, Expense, ExpensePart, Currency -from buddyledger.forms import LedgerForm, PersonForm, ChangeMethodForm +from buddyledger.forms import LedgerForm, PersonForm, ChangeMethodForm, ConfirmCloseLedgerForm ### misc convenience functions -from buddyledger.views.misc import ConvertCurrency, resultdict_to_decimal +from buddyledger.views.misc import ConvertCurrency, resultdict_to_decimal, render_to_response ### calculation methods from buddyledger.views.graphbuilder import solve_mincost_problem_for_expenses diff --git a/src/buddyledger/views/misc.py b/src/buddyledger/views/misc.py index 2cf2383..210ab25 100644 --- a/src/buddyledger/views/misc.py +++ b/src/buddyledger/views/misc.py @@ -1,5 +1,6 @@ from decimal import * from buddyledger.models import Ledger, Person, Expense, Currency +from django.shortcuts import render def ConvertCurrency(amount,fromcurrencyid,tocurrencyid): if fromcurrencyid != tocurrencyid: @@ -25,8 +26,11 @@ def conv_frac_to_decimal(f, precision): def resultdict_to_decimal(resultdict): returndict = dict() - for payerid, receiverdict in resultdict.iteritems(): - for receiverid, amount in receiverdict.iteritems(): + for payerid, receiverdict in resultdict.items(): + for receiverid, amount in receiverdict.items(): receiverdict[receiverid] = conv_frac_to_decimal(amount,2) returndict[payerid] = receiverdict return returndict + +def render_to_response(request, templatename): + return render(request, templatename, {}, content_type='text/html') diff --git a/src/buddyledger/views/person.py b/src/buddyledger/views/person.py index 5709602..63d6a0a 100644 --- a/src/buddyledger/views/person.py +++ b/src/buddyledger/views/person.py @@ -1,21 +1,22 @@ -from django.shortcuts import render, render_to_response, get_object_or_404 +from django.shortcuts import render, get_object_or_404 from django.http import HttpResponseRedirect from django.contrib import messages from buddyledger.forms import PersonForm, DeletePersonForm from buddyledger.models import Ledger, Person, Expense, Currency +from .misc import render_to_response def AddPerson(request,ledgerid=0): ### check if the ledger exists, bail out if not try: ledger = Ledger.objects.get(pk = ledgerid) except Ledger.DoesNotExist: - response = render_to_response('ledgerdoesnotexist.html') + response = render_to_response(request, 'ledgerdoesnotexist.html') return response ### check if the ledger is open if ledger.closed: - response = render_to_response('ledger_is_closed.html') + response = render_to_response(request, 'ledger_is_closed.html') return response if request.method == 'POST': @@ -39,14 +40,14 @@ def EditPerson(request, personid=0): try: person = Person.objects.get(pk = personid) except Person.DoesNotExist: - response = render_to_response('person_does_not_exist.html') + response = render_to_response(request, 'person_does_not_exist.html') return response ### check if the ledger is open ledger = Ledger.objects.get(pk=person.ledger.id) if ledger.closed: - response = render_to_response('ledger_is_closed.html') + response = render_to_response(request, 'ledger_is_closed.html') return response if request.method == 'POST': @@ -72,16 +73,16 @@ def RemovePerson(request, personid=0): ### check if the ledger is open if person.ledger.closed: - response = render_to_response('ledger_is_closed.html') + response = render_to_response(request, 'ledger_is_closed.html') return response expenses = person.expense_set.all() if expenses: - return render_to_response('expense_references_this_person.html', { + return render(request, 'expense_references_this_person.html', { 'expenses': expenses, 'ledger_id': person.ledger.id, 'person': person - }) + }, content_type="text/html") ### confirm delete form = DeletePersonForm(request.POST or None, instance=person) diff --git a/src/buddyledger/views/resultmatrix.py b/src/buddyledger/views/resultmatrix.py index 5c6f668..db1a80e 100644 --- a/src/buddyledger/views/resultmatrix.py +++ b/src/buddyledger/views/resultmatrix.py @@ -22,19 +22,19 @@ def GetEmptyMatrix(userdict): temp = OrderedDict() temp[0] = "n/a" # the 0,0 field is the upper left position - for userid,username in userdict.iteritems(): + for userid,username in userdict.items(): temp[userid] = "%s pay" % username resultdict[0] = temp ### now create a row per user - for receiverid,receivername in userdict.iteritems(): + for receiverid,receivername in userdict.items(): ### create new empty table row temp = OrderedDict() ### add the rows leftmost column with the name temp[0] = "%s receive" % receivername ### loop through users, add Decimal(0) or "n/a" - for payerid,payername in userdict.iteritems(): + for payerid,payername in userdict.items(): if receivername == payername: temp[payerid] = "n/a" else: @@ -50,12 +50,12 @@ def PopulateMatrix(result,resultdict,userdict): ### and also calculate the totals while we are here payertotal = OrderedDict() receivertotal = OrderedDict() - for userid,username in userdict.iteritems(): + for userid,username in userdict.items(): payertotal[userid] = 0 receivertotal[userid] = 0 - for payerid, receiverdict in result.iteritems(): - for receiverid, amount in receiverdict.iteritems(): + for payerid, receiverdict in result.items(): + for receiverid, amount in receiverdict.items(): ### add to totals for this payer payertotal[payerid] += amount @@ -67,7 +67,7 @@ def PopulateMatrix(result,resultdict,userdict): ### add totals columns and row to the matrix (bottom row and rightmost column) - for receiverid,row in resultdict.iteritems(): + for receiverid,row in resultdict.items(): ### add the rightmost column for this row if receiverid == 0: @@ -80,7 +80,7 @@ def PopulateMatrix(result,resultdict,userdict): ### create the new bottom row for the "total pay" amounts and add it to resultdict temp = OrderedDict() temp[0] = "Total Pay" - for userid,username in userdict.iteritems(): + for userid,username in userdict.items(): amount = payertotal[userid] temp[userid] = amount temp['total'] = 'n/a' diff --git a/src/buddyledger/views/staticpage.py b/src/buddyledger/views/staticpage.py index cc847d6..e8dc94e 100644 --- a/src/buddyledger/views/staticpage.py +++ b/src/buddyledger/views/staticpage.py @@ -1,11 +1,11 @@ -from django.shortcuts import render, render_to_response +from .misc import render_to_response def Frontpage(request): - response = render_to_response('frontpage.html') + response = render_to_response(request, 'frontpage.html') return response def ShowUsage(request): - response = render_to_response('usage.html') + response = render_to_response(request, 'usage.html') return response