From ab60de04a7a0d0162610b7621d6b8ca05febfa17 Mon Sep 17 00:00:00 2001 From: Janus <ysangkok@gmail.com> Date: Thu, 16 Apr 2020 17:29:43 -0500 Subject: [PATCH 1/2] Port to Python 3 and Django 3 --- README.md | 10 +-- requirements.txt | 6 +- src/buddyledger/forms.py | 2 +- .../management/commands/getcurrency.py | 65 ++++++++++++------- src/buddyledger/migrations/0001_initial.py | 64 +++++++++--------- .../migrations/0002_auto_20150815_1640.py | 39 ----------- src/buddyledger/models.py | 18 ++--- src/buddyledger/settings.py | 13 +--- src/buddyledger/templates/add_expense.html | 2 +- src/buddyledger/urls.py | 39 +++++------ src/buddyledger/views/__init__.py | 8 +-- src/buddyledger/views/basiccalc.py | 2 +- src/buddyledger/views/expense.py | 29 ++++----- src/buddyledger/views/graphbuilder.py | 7 ++ src/buddyledger/views/ledger.py | 6 +- src/buddyledger/views/misc.py | 8 ++- src/buddyledger/views/person.py | 17 ++--- src/buddyledger/views/resultmatrix.py | 16 ++--- src/buddyledger/views/staticpage.py | 6 +- 19 files changed, 169 insertions(+), 188 deletions(-) delete mode 100644 src/buddyledger/migrations/0002_auto_20150815_1640.py 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 `<repository_root>/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 @@ <h4>People</h4> <div id="controlgroup-paymentamount-{{person.id}}" class="control-group"> <div class="controls"> <div class="input-append"> - <input id="paymentamount-{{person.id}}" class="input-small" name="person-paymentamount-{{person.id}}" type="number" placeholder="0"> + <input id="paymentamount-{{person.id}}" class="input-small" name="person-paymentamount-{{person.id}}" type="number" step="0.01" placeholder="0"> <span class="add-on currencylabel">{{ ledger.currency.iso4217_code }}</span> </div> </div> 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<ledgerid>\d+)/$', 'buddyledger.views.ShowLedger', name='show_ledger'), - url(r'^ledger/(?P<ledgerid>\d+)/edit/$', 'buddyledger.views.EditLedger', name='edit_ledger'), - url(r'^ledger/(?P<ledgerid>\d+)/close/$', 'buddyledger.views.CloseLedger', name='close_ledger'), - url(r'^ledger/(?P<ledgerid>\d+)/reopen/$', 'buddyledger.views.ReopenLedger', name='reopen_ledger'), - url(r'^ledger/(?P<ledgerid>\d+)/changemethod/$', 'buddyledger.views.ChangeMethod', name='change_ledger_method'), + re_path(r'^ledger/create/?$', views.CreateLedger, name='create_ledger'), + re_path(r'^ledger/(?P<ledgerid>\d+)/?$', views.ShowLedger, name='show_ledger'), + re_path(r'^ledger/(?P<ledgerid>\d+)/edit/?$', views.EditLedger, name='edit_ledger'), + re_path(r'^ledger/(?P<ledgerid>\d+)/close/?$', views.CloseLedger, name='close_ledger'), + re_path(r'^ledger/(?P<ledgerid>\d+)/reopen/?$', views.ReopenLedger, name='reopen_ledger'), + re_path(r'^ledger/(?P<ledgerid>\d+)/changemethod/?$', views.ChangeMethod, name='change_ledger_method'), ### person - url(r'^ledger/(?P<ledgerid>\d+)/addperson/$', 'buddyledger.views.AddPerson', name='add_person'), - url(r'^person/(?P<personid>\d+)/edit/$', 'buddyledger.views.EditPerson', name='edit_person'), - url(r'^person/(?P<personid>\d+)/remove/$', 'buddyledger.views.RemovePerson', name='remove_person'), + re_path(r'^ledger/(?P<ledgerid>\d+)/addperson/?$', views.AddPerson, name='add_person'), + re_path(r'^person/(?P<personid>\d+)/edit/?$', views.EditPerson, name='edit_person'), + re_path(r'^person/(?P<personid>\d+)/remove/?$', views.RemovePerson, name='remove_person'), ### expense - url(r'^ledger/(?P<ledgerid>\d+)/addexpense/$', 'buddyledger.views.AddExpense', name='add_expense'), - url(r'^expense/(?P<expenseid>\d+)/edit/$', 'buddyledger.views.EditExpense', name='edit_expense'), - url(r'^expense/(?P<expenseid>\d+)/remove/$', 'buddyledger.views.RemoveExpense', name='remove_expense'), - url(r'^expense/(?P<expenseid>\d+)/addperson/(?P<personid>\d+)/$', 'buddyledger.views.ExpenseAddPerson', name='expense_add_person'), - url(r'^expense/(?P<expenseid>\d+)/removeperson/(?P<personid>\d+)/$', 'buddyledger.views.ExpenseRemovePerson', name='expense_remove_person'), -) \ No newline at end of file + re_path(r'^ledger/(?P<ledgerid>\d+)/addexpense/?$', views.AddExpense, name='add_expense'), + re_path(r'^expense/(?P<expenseid>\d+)/edit/?$', views.EditExpense, name='edit_expense'), + re_path(r'^expense/(?P<expenseid>\d+)/remove/?$', views.RemoveExpense, name='remove_expense'), + re_path(r'^expense/(?P<expenseid>\d+)/addperson/(?P<personid>\d+)/?$', views.ExpenseAddPerson, name='expense_add_person'), + re_path(r'^expense/(?P<expenseid>\d+)/removeperson/(?P<personid>\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..6ace5ee 100644 --- a/src/buddyledger/views/basiccalc.py +++ b/src/buddyledger/views/basiccalc.py @@ -23,7 +23,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() 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 From 58353ac011c275c72b183ab55cedfac2083ba1b2 Mon Sep 17 00:00:00 2001 From: Janus <ysangkok@gmail.com> Date: Fri, 11 Sep 2020 19:57:50 -0500 Subject: [PATCH 2/2] Address rounding issue With three people sharing an expense that costs 1, person one paying 0.34, and the others paying 0.33, this commit makes it such that the result matrix is empty with the basic calc method. --- src/buddyledger/views/basiccalc.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/buddyledger/views/basiccalc.py b/src/buddyledger/views/basiccalc.py index 6ace5ee..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() @@ -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