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