gnunet-svn
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[GNUnet-SVN] [taler-bank] 01/05: The big refactor.


From: gnunet
Subject: [GNUnet-SVN] [taler-bank] 01/05: The big refactor.
Date: Fri, 13 Jan 2017 14:59:48 +0100

This is an automated email from the git hooks/post-receive script.

dold pushed a commit to branch master
in repository bank.

commit af104fce9bc3b95c44371a1b137d1a733de027b2
Author: Florian Dold <address@hidden>
AuthorDate: Tue Nov 29 01:20:06 2016 +0100

    The big refactor.
---
 bank.conf                                          |   2 +-
 setup.py                                           |   1 +
 talerbank/app/admin.py                             |   4 +-
 talerbank/app/amounts.py                           |  42 ++-
 talerbank/app/captcha.py                           | 116 ------
 talerbank/app/errors.py                            | 182 ---------
 talerbank/app/funds.py                             | 174 ---------
 talerbank/app/history.py                           |  72 ----
 talerbank/app/middleware.py                        |  26 --
 talerbank/app/migrations/0001_initial.py           |  11 +-
 talerbank/app/models.py                            |  13 +-
 talerbank/app/schemas.py                           |  71 ++--
 talerbank/app/static/profile-page.js               |  35 +-
 talerbank/app/templates/Makefile.am                |   2 +-
 talerbank/app/templates/account_disabled.html      |   1 -
 talerbank/app/templates/error_exchange.html        |  12 +-
 talerbank/app/templates/history.html               |  33 --
 .../app/templates/{home_page.html => login.html}   |  61 ++-
 talerbank/app/templates/pin_tan.html               |  39 +-
 talerbank/app/templates/profile_page.html          |  36 +-
 talerbank/app/templates/public_accounts.html       |  81 ++++
 .../app/templates/public_histories_reloaded.html   | 113 ------
 talerbank/app/templates/register.html              |   7 +-
 talerbank/app/templatetags/settings.py             |   8 +
 talerbank/app/urls.py                              |  23 +-
 talerbank/app/user.py                              |  91 -----
 talerbank/app/views.py                             | 414 +++++++++++++++++----
 talerbank/settings.py                              |  61 ++-
 28 files changed, 605 insertions(+), 1126 deletions(-)

diff --git a/bank.conf b/bank.conf
index 3d5be5d..67d4455 100644
--- a/bank.conf
+++ b/bank.conf
@@ -1,3 +1,3 @@
 [bank]
 uwsgi_serve = tcp
-database = talerbank
+database = postgres://talerbank
diff --git a/setup.py b/setup.py
index 6ccfbaa..fbb8af0 100755
--- a/setup.py
+++ b/setup.py
@@ -19,6 +19,7 @@ setup(name='talerbank',
       package_data={
         'talerbank.app': [
             'templates/*.html',
+            'templates/registration/*.html',
             'static/web-common/*.js.tar.gz',
             'static/web-common/*.css',
             'static/web-common/*.html',
diff --git a/talerbank/app/admin.py b/talerbank/app/admin.py
index 8502f83..79b0037 100644
--- a/talerbank/app/admin.py
+++ b/talerbank/app/admin.py
@@ -1,7 +1,7 @@
 # This file is in the public domain
 
 from django.contrib import admin
-from .models import BankAccount, History
+from .models import BankAccount, BankTransaction
 
 admin.site.register(BankAccount)
-admin.site.register(History)
+admin.site.register(BankTransaction)
diff --git a/talerbank/app/amounts.py b/talerbank/app/amounts.py
index a852fd6..93f65e6 100644
--- a/talerbank/app/amounts.py
+++ b/talerbank/app/amounts.py
@@ -13,36 +13,38 @@
 #  TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
 #
 #  @author Marcello Stanisci
+#  @author Florian Dold
+
 
-from django.conf import settings
-from .errors import BadAmount
 import re
 import math
 import logging
 
 logger = logging.getLogger(__name__)
 
+FRACTION = 100000000
+
 def floatify(amount_dict):
-    return amount_dict['value'] + (float(amount_dict['fraction']) / 
float(settings.FRACTION))
+    return amount_dict['value'] + (float(amount_dict['fraction']) / 
float(FRACTION))
 
-def stringify_amount(amount_float):
-    o = "".join(["%.", "%sf" % settings.NDIGITS])
+def stringify(amount_float, digits=2):
+    o = "".join(["%.", "%sf" % digits])
     return o % amount_float
 
-# Return amount object from string like "x.yz CURRENCY".
-# We don't take "x CURRENCY", if no fractional part is needed;
-# use "x.00 CURRENCY" instead.
-
 def parse_amount(amount_str):
-    parsed = re.search('([0-9]+\.[0-9]+) ([-_*A-Za-z0-9]+)', amount_str)
+    """
+    Parse amount of return None if not a
+    valid amount string
+    """
+    parsed = re.search("^\s*([0-9]+)(\.[0-9]+)? ([-_*A-Za-z0-9]+)\s*$", 
amount_str)
     if not parsed:
-        raise BadAmount(amount_str)
-    amount = round(float(parsed.group(1)), settings.NDIGITS)
-    amount_value = int(amount)
-
-    if parsed.group(2) != settings.TALER_CURRENCY:
-        raise BadAmount()
-
-    return {'value': amount_value,
-            'fraction': int((amount - amount_value) * settings.FRACTION),
-            'currency': parsed.group(2)}
+        return None
+    value = int(parsed.group(1))
+    fraction = 0
+    if parsed.group(2) is not None:
+        for i, digit in enumerate(parsed.group(2)[1:]):
+            fraction += int(int(digit) * (FRACTION / 10 ** (i+1)))
+
+    return {'value': value,
+            'fraction': fraction,
+            'currency': parsed.group(3)}
diff --git a/talerbank/app/captcha.py b/talerbank/app/captcha.py
deleted file mode 100644
index 8caafdc..0000000
--- a/talerbank/app/captcha.py
+++ /dev/null
@@ -1,116 +0,0 @@
-#  This file is part of TALER
-#  (C) 2014, 2015, 2016 INRIA
-#
-#  TALER is free software; you can redistribute it and/or modify it under the
-#  terms of the GNU Affero General Public License as published by the Free 
Software
-#  Foundation; either version 3, or (at your option) any later version.
-#
-#  TALER is distributed in the hope that it will be useful, but WITHOUT ANY
-#  WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 
FOR
-#  A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
-#
-#  You should have received a copy of the GNU General Public License along with
-#  TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
-#
-#  @author Marcello Stanisci
-
-import hashlib
-import json
-import logging
-from urllib.parse import urlunparse, unquote, urlparse, ParseResult
-from simplemathcaptcha.fields import MathCaptchaField, MathCaptchaWidget
-from django.http import HttpResponse
-from django.shortcuts import render
-from django.conf import settings
-from django import forms
-from django.contrib.auth.decorators import login_required
-from django.views.decorators.http import require_POST, require_GET
-from .funds import Reserve, create_reserve_at_exchange
-from . import schemas
-from . import amounts
-from . import errors
-from .user import get_bank_account_from_username
-
-logger = logging.getLogger(__name__)
-
-
-class Pin(forms.Form):
-    pin = MathCaptchaField(
-        widget=MathCaptchaWidget(
-            attrs=dict(autocomplete="off"),
-            question_tmpl="<div lang=\"en\">What is %(num1)i %(operator)s 
%(num2)i ?</div>"))
-
-
address@hidden
address@hidden
-def pin_tan_question(request):
-    for param in ["amount_value",
-                  "amount_fraction",
-                  "amount_currency",
-                  "exchange",
-                  "reserve_pub",
-                  "wire_details"]:
-        if param not in request.GET:
-            raise errors.BadGetParameter(param)
-    try:
-        amount = {"value": int(request.GET["amount_value"]),
-                  "fraction": int(request.GET["amount_fraction"]),
-                  "currency": request.GET["amount_currency"]}
-    except ValueError:
-        raise errors.BadGetParameter()
-    wiredetails = json.loads(unquote(request.GET["wire_details"]))
-    schemas.validate_wiredetails(wiredetails)
-    request.session["account_number"] = wiredetails["test"]["account_number"]
-    request.session["wire_details"] = wiredetails["test"]
-    schemas.validate_amount(amount)
-    request.session["amount"] = amount
-    request.session["exchange"] = request.GET["exchange"]
-    request.session["reserve_pub"] = request.GET["reserve_pub"]
-    previous_failed = False
-    if "captcha_failed" in request.session:
-        previous_failed = True
-        del request.session["captcha_failed"]
-    return render(request, "pin_tan.html", {"form": Pin(auto_id=False),
-                                            "failed": 
request.GET.get("failed"),
-                                            "amount": amounts.floatify(amount),
-                                            "currency": 
settings.TALER_CURRENCY,
-                                            "previous_failed": previous_failed,
-                                            "exchange": 
request.GET["exchange"]})
-
-
address@hidden
address@hidden
-def pin_tan_verify(request):
-    try:
-        given = request.POST["pin_0"]
-        hashed_result = request.POST["pin_1"]
-    except Exception:  # FIXME narrow the Exception type
-        raise errors.BadPostValue()
-    hasher = hashlib.new("sha1")
-    hasher.update(settings.SECRET_KEY.encode("utf-8"))
-    hasher.update(given.encode("utf-8"))
-    hashed_attempt = hasher.hexdigest()
-    if hashed_attempt != hashed_result:
-        request.session["captcha_failed"] = True
-        return redirect("pin-question")
-
-    for param in ["amount", "exchange", "reserve_pub"]:
-        if param not in request.session:
-            return HttpResponse("Not a withdraw session", status=400)
-    settings.TALER_WIREDETAILS_COUNTER += 1
-    sender_wiredetails = dict(
-        type="TEST",
-        bank_uri=urlunparse(ParseResult(scheme=request.scheme, 
netloc=request.META['HTTP_HOST'],
-            path="/", params="", query="", fragment="")),
-        account_number=request.session['account_no']
-    )
-    reserve = Reserve(request.session["amount"],
-                      request.session["exchange"],
-                      request.session["account_number"],
-                      request.session["reserve_pub"],
-                      sender_wiredetails,
-                      wiredetails_counter=settings.TALER_WIREDETAILS_COUNTER)
-    success_url = urlunparse([request.scheme,
-                             request.META["HTTP_HOST"],
-                             "/success.html", "", "", ""])
-    return create_reserve_at_exchange(request, success_url, reserve)
diff --git a/talerbank/app/errors.py b/talerbank/app/errors.py
deleted file mode 100644
index 4619a1b..0000000
--- a/talerbank/app/errors.py
+++ /dev/null
@@ -1,182 +0,0 @@
-#  This file is part of TALER
-#  (C) 2014, 2015, 2016 INRIA
-#
-#  TALER is free software; you can redistribute it and/or modify it under the
-#  terms of the GNU Affero General Public License as published by the Free 
Software
-#  Foundation; either version 3, or (at your option) any later version.
-#
-#  TALER is distributed in the hope that it will be useful, but WITHOUT ANY
-#  WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 
FOR
-#  A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
-#
-#  You should have received a copy of the GNU General Public License along with
-#  TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
-#
-#  @author Marcello Stanisci
-
-from django.shortcuts import render
-from django.http import JsonResponse
-import logging
-
-logger = logging.getLogger(__name__)
-
-class RenderedException(Exception):
-    """An exception that is rendered by us"""
-    def render(self, request):
-        raise Exception("RenderedException must override render()")
-
-
-class BadGetParameter(RenderedException):
-    def __init__(self, name=None):
-        self.name = name
-    def render(self, request):
-        # the bank has no REST service available on GET,
-        # so no JSON returned in this case
-        return bad_get_parameter_handler(request, name)
-
-# Raised either at withdrawal time (prettier rendering needed (?)),
-# or when the exchange does deposits (JSON object returned is fine)
-class BadAmount(RenderedException):
-    def __init__(self, name=None):
-        self.name = name
-    def render(self, request):
-        data = {"error": "bad amount"}
-        if self.name:
-            data["parameter"] = self.name
-        return JsonResponse(data, status=400)
-
-
-class BadWireDetails(RenderedException):
-    def __init__(self, name=None, hint=None):
-        self.name = name
-        self.hint = hint
-    def render(self, request):
-        data = {"error": "bad wire details"}
-        if self.name:
-            data["parameter"] = self.name
-        if self.hint:
-            data["hint"] = self.hint
-        return JsonResponse(data, status=400)
-
-
-class BadIncomingRequest(RenderedException):
-    """Thrown for an /admin/add/incoming call"""
-    def __init__(self, name=None, hint=None):
-        self.name = name
-        self.hint = hint
-    def render(self, request):
-        data = {"error": "malformed JSON"}
-        if self.hint:
-            data["hint"] = self.hint
-        return JsonResponse(data, status=400)
-
-
-class NonExistentAccount(RenderedException):
-    """Thrown whenever a non existent account is referenced"""
-    def __init__(self, name=None, hint=None):
-        self.name = name
-        self.hint = hint
-    def render(self, request):
-        data = {"error": "non existent account"}
-        if self.hint:
-            logger.error(self.hint)
-            data["hint"] = self.hint
-        return JsonResponse(data, status=400)
-
-class NoWireMethodMatch(RenderedException):
-    def __init__(self):
-        pass
-    def render(self, request):
-      return non_suppoerted_wire_method(request)
-
-
-class BadPostValue(RenderedException):
-    def __init__(self):
-        pass
-    def render(self, request):
-      return bad_post_value_handler(request)
-
-
-class WrongMethod(RenderedException):
-    def __init__(self, allowed_method=None):
-        self.allowed_method = allowed_method
-    def render(self, request):
-        data = {"error": "wrong method"}
-        if self.allowed_method:
-            data["parameter"] = self.allowed_method
-        return JsonResponse(data)
-
-
-class ExchangeUnknown(RenderedException):
-    def __init__(self):
-        pass
-    def render(self, request):
-      return exchange_unknown_handler(request)
-
-
-class CurrencyMismatch(RenderedException):
-    def __init__(self, allowed_method=None):
-        pass
-    def render(self, request):
-        data = {"error": "currency mismatch"}
-        return JsonResponse(data)
-
-
-def no_bank_account_handler(request):
-    return internal_error_handler(
-        request,
-        "(The bank itself has no account,"
-        " please run 'taler-bank-manage --preaccounts')")
-
-
-def non_supported_wire_method(request):
-    return render(request, 'error.html', {'type': 'non_supported_method'}, 
status=400)
-
-
-def non_existent_db_handler(request):
-    return internal_error_handler(request, "(db does not exist)")
-
-
-def internal_error_handler(request, hint=False):
-    return render(request, 'error.html', {'type': 'internal_error', 'hint': 
hint}, status=500)
-
-
-def user_not_logged_handler(request):
-    return render(request, 'error.html', {'type': 'not_logged'}, status=401)
-
-
-def exchange_unknown_handler(request):
-    return render(request, 'error.html', {'type': 'exchange_unknown'}, 
status=401)
-
-
-def wrong_method_handler(request, err):
-    return render(request,
-                  'error.html',
-                  {'type': 'custom',
-                   'custom_title': "Wrong method",
-                   'custom_message': "Only " + err.allowed_method + " 
allowed"},
-                  status=405)
-
-
-def bad_get_parameter_handler(request, name):
-    msg = "Invalid parameter {!s}in GET request"
-    if name:
-        msg = msg.format(name + " ")
-    else:
-        msg = msg.format("")
-
-    return render(request,
-                  'error.html',
-                  {'type': 'custom',
-                   'custom_title': "Bad request",
-                   'custom_message': msg},
-                  status=405)
-
-
-def bad_post_value_handler(request):
-    return render(request,
-                  'error.html',
-                  {'type': 'custom',
-                   'custom_title': "Bad request",
-                   'custom_message': "Bad value in POSTed data"},
-                  status=400)
diff --git a/talerbank/app/funds.py b/talerbank/app/funds.py
deleted file mode 100644
index ea232f4..0000000
--- a/talerbank/app/funds.py
+++ /dev/null
@@ -1,174 +0,0 @@
-#  This file is part of TALER
-#  (C) 2014, 2015, 2016 INRIA
-#
-#  TALER is free software; you can redistribute it and/or modify it under the
-#  terms of the GNU Affero General Public License as published by the Free 
Software
-#  Foundation; either version 3, or (at your option) any later version.
-#
-#  TALER is distributed in the hope that it will be useful, but WITHOUT ANY
-#  WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 
FOR
-#  A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
-#
-#  You should have received a copy of the GNU General Public License along with
-#  TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
-#
-#  @author Marcello Stanisci
-
-from . import schemas
-from .errors import WrongMethod, BadWireDetails, CurrencyMismatch, 
NonExistentAccount
-from . import amounts
-from .models import BankAccount, History
-from django.views.decorators.csrf import csrf_exempt
-from django.http import JsonResponse, HttpResponse
-from django.shortcuts import render, redirect
-from django.http import HttpResponseServerError
-from django.contrib.auth.decorators import login_required
-from urllib.parse import urlparse, urljoin
-from django.conf import settings
-from django.core.urlresolvers import reverse
-import requests
-import time
-import json
-import logging
-
-logger = logging.getLogger(__name__)
-
-
-
-def check_exchange_account_no(account_no):
-    try:
-        BankAccount.objects.get(account_no=account_no)
-    except BankAccount.DoesNotExist:
-        raise errors.ExchangeUnknown()
-
-
-class Reserve:
-    def __init__(self, amount, exchange, exchange_account, reserve_pub, 
sender_account_details, wiredetails_counter=0):
-        schemas.validate_amount(amount)
-        self.amount = amount
-        self.exchange = exchange
-        self.exchange_account = exchange_account
-        self.reserve_pub = reserve_pub
-        self.sender_account_details = sender_account_details
-        self.transfer_details = wiredetails_counter
-
-# The CSRF exempt is due to the fact Django looks for an anti-CSRF token
-# In any POST it gets. Since the following function is meant to serve 
exchanges,
-# And exchanges don't send any such token, it is necessary to disable it.
-# Those tokens are normally hidden fields in Django-generated HTML forms.
-
-
address@hidden
-def add_incoming(request):
-    logger.info("handling /admin/add/incoming")
-    if request.method != 'POST':
-        raise WrongMethod('GET')
-    data = json.loads(request.body.decode('utf-8'))
-    logger.info("valid uploaded data")
-    schemas.validate_incoming_request(data)
-    logger.info("add_incoming for debit account %s and credit account %s, WTID 
%s",
-                data['debit_account'],
-               data['credit_account'],
-               data['wtid'])
-
-    wire_transfer_in_out(data['amount'],
-                         data['debit_account'],
-                         data['credit_account'],
-                         data['wtid'])
-    return JsonResponse({'outcome': 'ok'}, status=200)
-
-
address@hidden
-def withdraw_nojs(request):
-    amount = amounts.parse_amount(request.POST['kudos_amount'])
-    response = HttpResponse(status=202)
-    response['X-Taler-CallBack-Url'] = reverse('pin-question')
-    response['X-Taler-Wt-Types'] = '["TEST"]'
-    response['X-Taler-Amount'] = json.dumps(amount)
-    return response
-
-
-def create_reserve_at_exchange(request, success_url, reserve_set):
-    if not isinstance(reserve_set, Reserve):
-        return HttpResponseServerError
-    o = urlparse(reserve_set.exchange)
-    if o.scheme == '' or o.netloc == '' or not o.path.endswith('/'):
-        return JsonResponse({'error': 'bad exchange base url',
-                             'given_url': reserve_set.exchange}, status=400)
-    amount = {'value': reserve_set.amount['value'],
-              'fraction': reserve_set.amount['fraction'],
-              'currency': reserve_set.amount['currency']}
-    check_exchange_account_no(reserve_set.exchange_account)
-    json_body = {'reserve_pub': reserve_set.reserve_pub,
-                 'execution_date': "/Date(" + str(int(time.time())) + ")/",
-                 'sender_account_details': reserve_set.sender_account_details,
-                 'transfer_details': {'uuid': reserve_set.transfer_details},
-                 'amount': amount}
-    res = requests.post(urljoin(reserve_set.exchange, '/admin/add/incoming'), 
json=json_body)
-    logger.info("making request to /admin/add/incoming with %s", json_body)
-    if res.status_code != 200:
-        return render(request, "error_exchange.html", {
-            "message": "Could not transfer funds to the exchange.  The 
exchange ({}) gave a bad response.".format(reserve_set.exchange),
-            "response_text": res.text,
-            "response_status": res.status_code
-        })
-
-    wire_transfer_in_out(amount,
-                         request.session['account_no'],
-                         reserve_set.exchange_account,
-                         reserve_set.reserve_pub)
-    request.session['withdrawal_successful'] = True
-    return redirect('profile')
-
-
-
-def wire_transfer_in_out(amount,
-                         debit,
-                         credit,
-                         wtid):
-    if debit == credit:
-        raise BadWireDetails(hint="debit and credit accounts are the same")
-    try:
-        debit_account = BankAccount.objects.get(account_no=debit)
-    except BankAccount.DoesNotExist: 
-        raise NonExistentAccount(hint="account " + str(debit) + " unknown")
-    try:
-        credit_account = BankAccount.objects.get(account_no=credit)
-    except BankAccount.DoesNotExist:
-        raise NonExistentAccount(hint="account " + str(credit) + " unknown")
-    wire_transfer(amount,
-                  debit_account,
-                  "OUT",
-                  credit_account,
-                  wtid)
-    wire_transfer(amount,
-                  credit_account,
-                  "IN",
-                  debit_account,
-                  wtid)
-
-
-# transfer funds from/to 'account' (used as a subroutine)
-def wire_transfer(amount,
-                  account,
-                  direction,
-                  counterpart,
-                  wtid="not given"):
-    if account.currency != amount['currency']:
-        logger.error("currency %s and currency %s mismatch",
-                     account.currency,
-                     amount['currency'])
-        raise CurrencyMismatch()
-    float_amount = amounts.floatify(amount)
-    if "IN" == direction:
-        account.balance += float_amount
-    else:
-        account.balance -= float_amount
-    account.save()
-    history_item = History(amount=float_amount,
-                           currency=amount['currency'],
-                           direction=direction,
-                           counterpart=counterpart,
-                           subject=wtid,
-                           account=account)
-    history_item.save()
diff --git a/talerbank/app/history.py b/talerbank/app/history.py
deleted file mode 100644
index db3d6da..0000000
--- a/talerbank/app/history.py
+++ /dev/null
@@ -1,72 +0,0 @@
-#  This file is part of TALER
-#  (C) 2014, 2015, 2016 INRIA
-#
-#  TALER is free software; you can redistribute it and/or modify it under the
-#  terms of the GNU Affero General Public License as published by the Free 
Software
-#  Foundation; either version 3, or (at your option) any later version.
-#
-#  TALER is distributed in the hope that it will be useful, but WITHOUT ANY
-#  WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 
FOR
-#  A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
-#
-#  You should have received a copy of the GNU General Public License along with
-#  TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
-#
-#  @author Marcello Stanisci
-
-from .models import BankAccount
-from .errors import internal_error_handler
-from django.contrib.auth.decorators import login_required
-from django.shortcuts import render, redirect
-from django.conf import settings
-from .user import get_bank_account_from_username
-from .amounts import stringify_amount
-import logging
-
-logger = logging.getLogger(__name__)
-
-def get_public_accounts():
-    try:
-        return BankAccount.objects.filter(is_public=True)
-    except BankAccount.DoesNotExist:
-        return []
-
-
-
-def extract_history(bank_account):
-    ret = []
-    for item in bank_account.history_set.all():
-        entry = {'float_amount': stringify_amount(item.amount),
-                 'float_currency': item.currency,
-                 'direction': 'FROM' if item.direction == 'IN' else 'TO',
-                 'counterpart': item.counterpart.account_no,
-                 'subject': item.subject,
-                 'date': item.date.strftime("%d/%m/%y %H:%M")}  # Yes, ugly.
-        logger.info(item.counterpart.user.username)
-        # we don't make visible regular users' usernames
-        if item.counterpart.user.username in 
settings.TALER_PREDEFINED_ACCOUNTS + ["Bank", "Exchange"]:
-            entry['counterpart_username'] = item.counterpart.user.username
-        ret.append(entry)
-    return ret
-
-
-def public_accounts(request, name):
-    accounts = []
-    for item in get_public_accounts():
-        accounts.append({'account_name': item.user.username})
-    sel_account_name = request.GET.get('account')
-    if not sel_account_name:
-        return redirect("public-accounts", account="Tor")
-    sel_account = get_bank_account_from_username(sel_account_name)
-    if sel_account is False:
-        return internal_error_handler(request, "User '%s' does not exist" % 
(sel_account_name,))
-    history = extract_history(sel_account)
-    return render(request,
-                  'public_histories_reloaded.html',
-                  {'public_accounts': accounts,
-                   'currency': settings.TALER_CURRENCY,
-                   'selected_account':
-                       {'account_name': sel_account_name,
-                        'account_no': sel_account.account_no,
-                        'history': history}
-                   })
diff --git a/talerbank/app/middleware.py b/talerbank/app/middleware.py
deleted file mode 100644
index b2b2292..0000000
--- a/talerbank/app/middleware.py
+++ /dev/null
@@ -1,26 +0,0 @@
-#  This file is part of TALER
-#  (C) 2016 INRIA
-#
-#  TALER is free software; you can redistribute it and/or modify it under the
-#  terms of the GNU Affero General Public License as published by the Free 
Software
-#  Foundation; either version 3, or (at your option) any later version.
-#
-#  TALER is distributed in the hope that it will be useful, but WITHOUT ANY
-#  WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 
FOR
-#  A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
-#
-#  You should have received a copy of the GNU General Public License along with
-#  TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
-#
-#  @author Marcello Stanisci
-#  @author Florian Dold
-
-from . import errors
-
-
-class ExpectedExceptionsMiddleware:
-    def process_exception(self, request, exception):
-        if not isinstance(exception, errors.RenderedException):
-            # Leave handling up to django
-            return None
-        return exception.render(request)
diff --git a/talerbank/app/migrations/0001_initial.py 
b/talerbank/app/migrations/0001_initial.py
index f4a5d0c..310fc0c 100644
--- a/talerbank/app/migrations/0001_initial.py
+++ b/talerbank/app/migrations/0001_initial.py
@@ -1,5 +1,5 @@
 # -*- coding: utf-8 -*-
-# Generated by Django 1.10.1 on 2016-09-30 20:07
+# Generated by Django 1.10.3 on 2016-11-29 14:35
 from __future__ import unicode_literals
 
 from django.conf import settings
@@ -27,16 +27,15 @@ class Migration(migrations.Migration):
             ],
         ),
         migrations.CreateModel(
-            name='History',
+            name='BankTransaction',
             fields=[
                 ('id', models.AutoField(auto_created=True, primary_key=True, 
serialize=False, verbose_name='ID')),
                 ('amount', models.FloatField(default=0)),
                 ('currency', models.CharField(max_length=12)),
-                ('direction', models.CharField(max_length=4)),
-                ('subject', models.CharField(default='not given', 
max_length=200)),
+                ('subject', models.CharField(default='(no subject given)', 
max_length=200)),
                 ('date', models.DateTimeField(auto_now=True)),
-                ('account', 
models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, 
to='app.BankAccount')),
-                ('counterpart', 
models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, 
related_name='counterpart_account', to='app.BankAccount')),
+                ('credit_account', 
models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, 
related_name='credit_account', to='app.BankAccount')),
+                ('debit_account', 
models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, 
related_name='debit_account', to='app.BankAccount')),
             ],
         ),
     ]
diff --git a/talerbank/app/models.py b/talerbank/app/models.py
index 2c4d44d..1b84fe9 100644
--- a/talerbank/app/models.py
+++ b/talerbank/app/models.py
@@ -13,6 +13,7 @@
 #  TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
 #
 #  @author Marcello Stanisci
+#  @author Florian Dold
 
 from __future__ import unicode_literals
 from django.contrib.auth.models import User
@@ -27,14 +28,10 @@ class BankAccount(models.Model):
     user = models.OneToOneField(User, on_delete=models.CASCADE)
 
 
-class History(models.Model):
+class BankTransaction(models.Model):
     amount = models.FloatField(default=0)
     currency = models.CharField(max_length=12)
-    direction = models.CharField(max_length=4)
-    counterpart = models.ForeignKey(BankAccount,
-                                    on_delete=models.CASCADE,
-                                   related_name="counterpart_account")
-    subject = models.CharField(default="not given", max_length=200)
+    debit_account = models.ForeignKey(BankAccount, on_delete=models.CASCADE, 
related_name="debit_account")
+    credit_account = models.ForeignKey(BankAccount, on_delete=models.CASCADE, 
related_name="credit_account")
+    subject = models.CharField(default="(no subject given)", max_length=200)
     date = models.DateTimeField(auto_now=True)
-    account = models.ForeignKey(BankAccount, on_delete=models.CASCADE)
-    # NOTE: by default, UTC timezone is used.
diff --git a/talerbank/app/schemas.py b/talerbank/app/schemas.py
index fd71b14..5dfcbce 100644
--- a/talerbank/app/schemas.py
+++ b/talerbank/app/schemas.py
@@ -20,46 +20,49 @@ definitions of JSON schemas for validating data
 """
 
 import validictory
-from .errors import BadIncomingRequest, BadWireDetails, BadAmount
+from django.core.exceptions import ValidationError
 
-wiredetails_schema = {"type": "object",
-                      "properties": {
-                          "test": {"type": "object",
-                                   "properties": {
-                                       "type": {"type": "string"},
-                                       "account_number": {"type": "integer"},
-                                       "bank_uri": {"type": "string"},
-                                       "name": {"type": "string"},
-                                       "salt": {"type": "string"},
-                                       "sig": {"type": "string"}}}}}
+wiredetails_schema = {
+    "type": "object",
+    "properties": {
+        "test": {
+            "type": "object",
+            "properties": {
+                "type": {"type": "string"},
+                "account_number": {"type": "integer"},
+                "bank_uri": {"type": "string"},
+                "name": {"type": "string"},
+                "salt": {"type": "string"},
+                "sig": {"type": "string"}
+            }
+        }
+    }
+}
 
-amount_schema = {"type": "object",
-                 "properties": {
-                     "value": {"type": "integer"},
-                     "fraction": {"type": "integer"},
-                     "currency": {"type": "string"}}}
+amount_schema = {
+    "type": "object",
+    "properties": {
+        "value": {"type": "integer"},
+        "fraction": {"type": "integer"},
+        "currency": {"type": "string"}
+    }
+}
 
-incoming_request_schema = {"type": "object",
-                           "properties": {
-                               "amount": {"type": amount_schema},
-                               "wtid": {"type": "string"},
-                               "credit_account": {"type": "integer"},
-                               "debit_account": {"type": "integer"}}}
+incoming_request_schema = {
+    "type": "object",
+    "properties": {
+        "amount": {"type": amount_schema},
+        "wtid": {"type": "string"},
+        "credit_account": {"type": "integer"},
+        "debit_account": {"type": "integer"}
+    }
+}
 
 def validate_amount(amount):
-    try:
-        validictory.validate(amount, amount_schema)
-    except (ValueError, TypeError):
-        raise BadAmount()
+    validictory.validate(amount, amount_schema)
 
 def validate_wiredetails(wiredetails):
-    try:
-        validictory.validate(wiredetails, wiredetails_schema)
-    except (ValueError, TypeError) as e:
-        raise BadWireDetails(str(e))
+    validictory.validate(wiredetails, wiredetails_schema)
 
 def validate_incoming_request(incoming_request):
-    try:
-        validictory.validate(incoming_request, incoming_request_schema)
-    except (ValueError, TypeError):
-        raise BadIncomingRequest()
+    validictory.validate(incoming_request, incoming_request_schema)
diff --git a/talerbank/app/static/profile-page.js 
b/talerbank/app/static/profile-page.js
index 62f8eb3..f0791e3 100644
--- a/talerbank/app/static/profile-page.js
+++ b/talerbank/app/static/profile-page.js
@@ -43,23 +43,6 @@ function getConst(name) {
 }
 
 
-function addOption(value, currency) {
-  var s = document.getElementById("reserve-amount");
-  var e = document.createElement("option");
-  e.textContent = "".concat(value.toFixed(precision), " ", currency);
-  e.value = value;
-  s.appendChild(e);
-}
-
-
-function addOptions() {
-  addOption(1, bank_currency);
-  addOption(5, bank_currency);
-  addOption(10, bank_currency);
-  addOption(20, bank_currency);
-}
-
-
 /**
  * Parse fractional format (e.g. 42.12 EUR) into
  * Taler amount.
@@ -77,9 +60,11 @@ function parseAmount(amount_str) {
       amount_fraction = Number("0." + amount[2]) * 1000000;
   if (amount[1] + amount_fraction == 0)
     return null;
-  return {value: Number(amount[1]),
-          fraction: amount_fraction,
-             currency: amount[amount.length - 1]};
+  return {
+    value: Number(amount[1]),
+    fraction: amount_fraction,
+    currency: amount[amount.length - 1]
+  };
 };
 
 
@@ -92,13 +77,12 @@ function init() {
     console.log("confirming reserve", reserve_pub);
     taler.confirmReserve(reserve_pub); 
   }
-  addOptions();
-  document.getElementById("select-exchange").onclick = function() {
+  document.getElementById("select-exchange").onclick = function () {
     var form = document.getElementById("reserve-form");
     var amount = {
-         value: parseInt(form.elements["reserve-amount"].value),
-         fraction: 0,
-         currency: bank_currency
+      value: parseInt(form.elements["reserve-amount"].value),
+      fraction: 0,
+      currency: bank_currency
     };
     console.log("callback_url", callback_url);
     taler.createReserve(callback_url, amount, ["TEST"]);
@@ -111,4 +95,3 @@ if (document.readyState == "loading") {
 } else {
   init();
 }
-
diff --git a/talerbank/app/templates/Makefile.am 
b/talerbank/app/templates/Makefile.am
index 7be4a12..34751ff 100644
--- a/talerbank/app/templates/Makefile.am
+++ b/talerbank/app/templates/Makefile.am
@@ -5,7 +5,7 @@ EXTRA_DIST = \
   account_disabled.html \
   history.html \
   profile_page.html \
-  public_histories_reloaded.html \
+  public_accounts.html \
   error.html \
   home_page.html \
   pin_tan.html \
diff --git a/talerbank/app/templates/account_disabled.html 
b/talerbank/app/templates/account_disabled.html
index 776d24c..d2ebfb4 100644
--- a/talerbank/app/templates/account_disabled.html
+++ b/talerbank/app/templates/account_disabled.html
@@ -17,7 +17,6 @@
   @author Marcello Stanisci
 -->
 {% extends "base.html" %}
-{% load staticfiles %}
 
 {% block headermsg %}
   <h1 class="nav">Account disabled<h1>
diff --git a/talerbank/app/templates/error_exchange.html 
b/talerbank/app/templates/error_exchange.html
index 1bb1c4a..e3319a6 100644
--- a/talerbank/app/templates/error_exchange.html
+++ b/talerbank/app/templates/error_exchange.html
@@ -15,9 +15,9 @@
   TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
 
   @author Marcello Stanisci
+  @author Florian Dold
 -->
 {% extends "base.html" %}
-{% load staticfiles %}
 
 {% block headermsg %}
   <h1>Error</h1>
@@ -30,13 +30,13 @@
       <p>
       {{ message }}
       </p>
-
+      <p>
       Status: {{ response_status }}
-
+      </p>
+      <p>
       Response body:
-      <pre>
-        {{ response_text }}
-      </pre>
+      <pre>{{ response_text }}</pre>
+      </p>
     </article>
   </section>
 {% endblock content %}
diff --git a/talerbank/app/templates/history.html 
b/talerbank/app/templates/history.html
deleted file mode 100644
index 9af14ec..0000000
--- a/talerbank/app/templates/history.html
+++ /dev/null
@@ -1,33 +0,0 @@
-<!DOCTYPE html>
-<!-- 
-  This file is part of GNU TALER.
-  Copyright (C) 2014, 2015, 2016 INRIA
-
-  TALER is free software; you can redistribute it and/or modify it under the
-  terms of the GNU Lesser General Public License as published by the Free 
Software
-  Foundation; either version 2.1, or (at your option) any later version.
-
-  TALER is distributed in the hope that it will be useful, but WITHOUT ANY
-  WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
-  A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more 
details.
-
-  You should have received a copy of the GNU Lesser General Public License 
along with
-  TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
-
-  @author Marcello Stanisci
--->
-
-{% extends "base.html" %}
-
-{% block content %}
-  {% if history %}
-    <ul>
-      {% for item in history %}
-        <li> {{ item.float_amount }} {{ item.float_currency }} {{ 
item.direction }} {{ item.counterpart }}</li>
-      {% endfor %}
-    </ul>
-    {% else %}
-    No transactions made to/from this account
-  {% endif %}
-{% endblock content %}
-
diff --git a/talerbank/app/templates/home_page.html 
b/talerbank/app/templates/login.html
similarity index 51%
rename from talerbank/app/templates/home_page.html
rename to talerbank/app/templates/login.html
index 8fbfa49..b9d6020 100644
--- a/talerbank/app/templates/home_page.html
+++ b/talerbank/app/templates/login.html
@@ -15,69 +15,62 @@
   TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
 
   @author Marcello Stanisci
+  @author Florian Dold
 -->
 
 {% extends "base.html" %}
-{% load staticfiles %}
+{% load settings_value from settings %}
 
 {% block headermsg %}
-  <h1 lang="en" class="nav">Welcome to the {{ currency }} Bank!</h1>
-  <h1 lang="it" class="nav">Banca {{ currency }} vi da il benvenuto!</h1>
+  <h1 lang="en" class="nav">Welcome to the {% settings_value "TALER_CURRENCY" 
%} Bank!</h1>
 {% endblock headermsg %}
 
 {% block content %}
   <aside class="sidebar" id="left">
   </aside>
   <section id="main">
-    {% if js == 'use_js' %}
-      You are using the JavaScript version, try out the
-      <a href="{% url 'index' js='no_js' %}">JavaScript-less version!</a>
-    {% else %}
-      You are using the JavaScript-less version, try out the
-      <a href="{% url 'index' js='use_js' %}">JavaScript version!</a>
-    {% endif %}
-    <br>
     <article>
       <div class="login-form">
-        <h1 lang="en">Please login!</h1>
-        <h1 lang="it">Accedi!</h1>
-        {% if wrong %}
-       <p class="informational informational-fail">
-          Some fields were either not filled or filled incorrectly.
-          Please try again.
+        <h1>Please login!</h1>
+        {% if form.errors %}
+        <p class="informational informational-fail">
+          Your username and password didn't match. Please try again.
         </p>
         {% endif %}
-        {% if logged_out %}
-       <p class="informational informational-ok">
-          Successfully logged out!
+
+        {% if just_logged_out %}
+        <p class="informational informational-ok">
+          You were logged out successfully.
         </p>
         {% endif %}
+
+        {% if next %}
+            {% if user.is_authenticated %}
+            <p class="informational informational-fail">Your account doesn't 
have access to this page. To proceed,
+            please login with an account that has access.</p>
+            {% else %}
+            <p>Please login to see this page.</p>
+            {% endif %}
+        {% endif %}
        <table>
           <form method="post" action="{% url 'login' %}">
             {% csrf_token %}
-            <input type="text" name="username" placeholder="username" 
autofocus></input>
+            {{ form.username }}
             <input type="password" name="password" 
placeholder="password"></input>
-            <input type="submit" value="Ok"></input>
+            <input type="submit" value="login" />
+            <input type="hidden" name="next" value="{{ next }}" />
           </form>
        </table>
       </div>
-      <p lang="en">
+      <p>
       If you are a new customer, please <a href="{% url 'register' 
%}">register</a>.
       Registration is fast and gratis, and it gives you a registration bonus
-      of 100 {{ currency }}!
+      of 100 {% settings_value "TALER_CURRENCY" %}!
       </p>
-      <p lang="it">
-      Se sei un nuovo cliente, puoi <a href="{% url 'register' 
%}">registrarti</a>.
-      La registrazione &egrave; semplice e veloce, e ti regala un bonus di 100 
{{ currency }}!
-      </p>
-      <p lang="en">
-      To view account histories for public accounts,
+      <p>
+      To view transactions of public accounts,
       please <a href="{% url "public-accounts" %}">click here</a>.
       </p>
-      <p lang="it">
-      Per vedere l'attivit&agrave; dei conti pubblici,
-      <a href="{% url "public-accounts" %}">clicca qui</a>.
-      </p>
     </article>
   </section>
 {% endblock content %}
diff --git a/talerbank/app/templates/pin_tan.html 
b/talerbank/app/templates/pin_tan.html
index 25de2d0..244cf9c 100644
--- a/talerbank/app/templates/pin_tan.html
+++ b/talerbank/app/templates/pin_tan.html
@@ -18,11 +18,13 @@
 -->
 
 {% extends "base.html" %}
-{% load staticfiles %}
+
+{% load settings_value from settings %}
 
 {% block headermsg %}
   <h1 class="nav">PIN/TAN:  Confirm transaction</h1>
 {% endblock %}
+
 {% block content %}
   <aside class="sidebar" id="left">
   </aside>
@@ -34,8 +36,8 @@
       </p>
       {% endif %}
       <p>
-        {{ currency }} Bank needs to verify that you
-        intend to withdraw <b>{{ amount }} {{ currency }}</b> from
+        {% settings_value "TALER_CURRENCY" %} Bank needs to verify that you
+        intend to withdraw <b>{{ amount }} {% settings_value "TALER_CURRENCY" 
%}</b> from
         <b>{{ exchange }}</b>.
         To prove that you are the account owner, please answer the
         following &quot;security question&quot; (*):
@@ -43,6 +45,7 @@
       <form method="post" action="{% url "pin-verify" %}">
         {% csrf_token %}
         {{ form.pin }}
+        <input type="hidden" name="question_url" value="{{ 
request.get_full_path }}"></input>
         <input type="submit" value="Ok"></input>
       </form>
       <small style="margin: 40px 0px">(*) A real bank should ask for
@@ -52,34 +55,4 @@
       <small>
     </article>
   </section>
-  <script>
-    /*
-      @licstart  The following is the entire license notice for the
-      JavaScript code in this page.
-
-      Copyright (C) 2014, 2015, 2016 Inria and GNUnet e.V.
-
-      The JavaScript code in this page is free software: you can
-      redistribute it and/or modify it under the terms of the GNU
-      General Public License (GNU GPL) as published by the Free Software
-      Foundation, either version 3 of the License, or (at your option)
-      any later version.  The code is distributed WITHOUT ANY WARRANTY;
-      without even the implied warranty of MERCHANTABILITY or FITNESS
-      FOR A PARTICULAR PURPOSE.  See the GNU GPL for more details.
-
-      As additional permission under GNU GPL version 3 section 7, you
-      may distribute non-source (e.g., minimized or compacted) forms of
-      that code without the copy of the GNU GPL normally required by
-      section 4, provided you include this license notice and a URL
-      through which recipients can access the Corresponding Source.
-
-      @licend  The above is the entire license notice
-      for the JavaScript code in this page.
-    */
-  </script>
-  <script type="application/javascript">
-    var i = document.getElementsByName("pin_0")[0];
-    i.setAttribute("autofocus", true);
-    i.focus();
-  </script>
 {% endblock content %}
diff --git a/talerbank/app/templates/profile_page.html 
b/talerbank/app/templates/profile_page.html
index 752432f..c3e6eda 100644
--- a/talerbank/app/templates/profile_page.html
+++ b/talerbank/app/templates/profile_page.html
@@ -17,7 +17,7 @@
   @author Marcello Stanisci
 -->
 {% extends "base.html" %}
-{% load staticfiles %}
+{% load static from mystatic %}
 
 {% block head %}
   <meta name="currency" value="{{ currency }}">
@@ -28,7 +28,7 @@
   {% endif %}
   <link rel="stylesheet" type="text/css" href="{% static "disabled-button.css" 
%}">
   <script src="{% static "chrome-store-link.js" %}" 
type="application/javascript"></script>
-  {% if js == 'use_js' %}
+  {% if use_js %}
     <script src="{% static "profile-page.js" %}" 
type="application/javascript"></script>
   {% endif %}
 {% endblock head %}
@@ -53,24 +53,16 @@
   <section id="main">
     <article>
       <div class="notification">
-        {% if withdraw %}
-        {% if withdraw == "success" %}
+        {% if just_withdrawn %}
         <p class="informational informational-ok">
           Withdrawal approved!
         </p>
-        {% else %}
-        <p class="informational informational-fail">
-          Withdrawal failed!
-        </p>
         {% endif %}
-        {% endif %}
-        {% if registration %}
-        {% if registration == "success" %}
+        {% if just_registered %}
         <p class="informational informational-ok">
           Registration successful!
         </p>
         {% endif %}
-        {% endif %}
         </div>
     </article>
     <article>
@@ -104,20 +96,14 @@
          {% csrf_token %}
           Amount to withdraw:
           <select id="reserve-amount" name="kudos_amount" autofocus>
-            {% if js == 'use_js' %}
-              <!-- programmatically filled -->
-            {% else %}
-              <!-- NOTE: the fractional part has to match settings.NDIGITS
-                   FIXME: provide values programmatically -->
               <option value="1.00 {{ currency }}">1.00 {{ currency }}</option>
               <option value="10.00 {{ currency }}">10.00 {{ currency 
}}</option>
               <option value="15.00 {{ currency }}">15.00 {{ currency 
}}</option>
               <option value="20.00 {{ currency }}">20.00 {{ currency 
}}</option>
-            {% endif %}
           </select>
           <input id="select-exchange"
                  class="taler-installed-show"
-                 {% if js == 'use_js' %}
+                 {% if use_js %}
                    type="button"
                  {% else %}
                    type="submit"
@@ -130,6 +116,13 @@
           </div>
         </form>
       </div>
+      <p>
+      {% if use_js %}
+      You're using the JavaScript version of the bank.  You can <a href="{% 
url 'profile' %}?use_js=false">switch</a> to the JS-free version.
+      {% else %}
+      You're using the JavaScript-free version of the bank.  You can <a 
href="{% url 'profile' %}?use_js=true">switch</a> to the JS version.
+      {% endif %}
+      </p>
     </article>
     <article>
       <h2>Transaction history</h2>
@@ -147,8 +140,7 @@
           <tr>
             <td style="text-align:right">{{ item.date }}</td>
            <td style="text-align:right">
-              {% if item.direction == "FROM" %}+{% else %}-{% endif %}{{ 
item.float_amount }}
-             {{ item.float_currency }}
+              {{ item.float_amount }} {{ item.float_currency }}
             </td>
            <td class="text-align:left">{% if item.counterpart_username %} {{ 
item.counterpart_username }} {% endif %} (account #{{ item.counterpart }})</td>
            <td class="text-align:left">{{ item.subject }}</td>
@@ -167,6 +159,6 @@
   </section>
   <div class="copyright">
     <p>Copyright &copy; 2014&mdash;2016 INRIA</p>
-    <a href="/javascript" data-jslicense="1" class="jslicenseinfo">JavaScript 
license information</a>
+    <a href="{% url "javascript" %}" data-jslicense="1" 
class="jslicenseinfo">JavaScript license information</a>
   </div>
 {% endblock %}
diff --git a/talerbank/app/templates/public_accounts.html 
b/talerbank/app/templates/public_accounts.html
new file mode 100644
index 0000000..d6c2519
--- /dev/null
+++ b/talerbank/app/templates/public_accounts.html
@@ -0,0 +1,81 @@
+<!DOCTYPE html>
+<!-- 
+  This file is part of GNU TALER.
+  Copyright (C) 2014, 2015, 2016 INRIA
+
+  TALER is free software; you can redistribute it and/or modify it under the
+  terms of the GNU Lesser General Public License as published by the Free 
Software
+  Foundation; either version 2.1, or (at your option) any later version.
+
+  TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+  WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+  A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more 
details.
+
+  You should have received a copy of the GNU Lesser General Public License 
along with
+  TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
+
+  @author Marcello Stanisci
+-->
+{% extends "base.html" %}
+
+{% load static from mystatic %}
+
+{% block headermsg %}
+  <h1 class="nav">History of public accounts</h1>
+{% endblock headermsg %}
+
+{% block content %}
+  <aside class="sidebar" id="left">
+  </aside>
+  <section id="main">
+    <article>
+      <table bgcolor="#E0E0E0" width="100%" width="100%" border="0" 
cellpadding="2" cellspacing="1">
+        <tr>
+          {% for account in public_accounts %}
+            <td width="12%" align="center">
+              <a id="{{ account.user.username }}"
+                 href="{% url "public-accounts" name=account.user.username %}"
+                 {% if account.account_no == selected_account.number %}
+                 style="font-weight: bold"
+                 {% endif %}
+                 >
+            {{ account.user.username }}
+              </a>
+           </td>
+          {% endfor %}
+        </tr>
+      </table>
+      <div id="transactions-history">
+        {% if selected_account.history %}
+         <table class="history">
+            <tr>
+              <th style="text-align:center">Date</th>
+              <th style="text-align:center">Amount</th>
+              <th style="text-align:center">Counterpart</th>
+              <th style="text-align:center">Subject</th>
+           </tr>
+            {% for entry in selected_account.history %}
+           <tr>
+              <td style="text-align:right">{{entry.date}}</td>
+              <td style="text-align:right">
+               {{ entry.float_amount }} {{ entry.float_currency }}
+             </td>
+              <td style="text-align:left">{% if entry.counterpart_username %} 
{{ entry.counterpart_username }} {% endif %} (account #{{ entry.counterpart 
}})</td>
+              <td style="text-align:left">
+                {% if entry.counterpart_username %}
+               <a name="{{ entry.subject }}"></a>
+               <a href="public-accounts?account={{ entry.counterpart_username 
}}#{{ entry.subject }}">{{ entry.subject }}</a>
+                {% else %}
+                  {{ entry.subject }}
+               {% endif %}
+             </td>
+           </tr>
+            {% endfor %}
+         {% else %}
+            <p>No history for account #{{ selected_account.number }} ({{ 
selected_account.name}}) yet</p>
+       {% endif %}
+      </div>
+    </table>
+    </article>
+  </section>
+{% endblock content %}
diff --git a/talerbank/app/templates/public_histories_reloaded.html 
b/talerbank/app/templates/public_histories_reloaded.html
deleted file mode 100644
index 2d6d5ff..0000000
--- a/talerbank/app/templates/public_histories_reloaded.html
+++ /dev/null
@@ -1,113 +0,0 @@
-<!DOCTYPE html>
-<!-- 
-  This file is part of GNU TALER.
-  Copyright (C) 2014, 2015, 2016 INRIA
-
-  TALER is free software; you can redistribute it and/or modify it under the
-  terms of the GNU Lesser General Public License as published by the Free 
Software
-  Foundation; either version 2.1, or (at your option) any later version.
-
-  TALER is distributed in the hope that it will be useful, but WITHOUT ANY
-  WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
-  A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more 
details.
-
-  You should have received a copy of the GNU Lesser General Public License 
along with
-  TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
-
-  @author Marcello Stanisci
--->
-{% extends "base.html" %}
-{% load staticfiles %}
-{% block headermsg %}
-  <h1 lang="en" class="nav">History of public accounts</h1>
-  <h1 lang="it" class="nav">Storico dei conti pubblici</h1>
-{% endblock headermsg %}
-{% block content %}
-  <aside class="sidebar" id="left">
-  </aside>
-  <section id="main">
-    <article>
-      <table bgcolor="#E0E0E0" width="100%" width="100%" border="0" 
cellpadding="2" cellspacing="1">
-        <tr>
-          {% for item in public_accounts %}
-            <td width="12%" align="center">
-              <a id="{{ item.account_name }}"
-                 href="/public-accounts?account={{ item.account_name }}">
-            {{ item.account_name }}
-              </a>
-           </td>
-          {% endfor %}
-        </tr>
-      </table>
-      <div id="transactions-history">
-        {% if selected_account.history %}
-         <table class="history">
-            <tr>
-              <th lang="en" style="text-align:center">Date</th>
-              <th lang="en" style="text-align:center">Amount</th>
-              <th lang="en" style="text-align:center">Counterpart</th>
-              <th lang="en" style="text-align:center">Subject</th>
-              <th lang="it" style="text-align:center">Data</th>
-              <th lang="it" style="text-align:center">Ammontare</th>
-              <th lang="it" style="text-align:center">Controparte</th>
-              <th lang="it" style="text-align:center">Causale</th>
-           </tr>
-            {% for entry in selected_account.history %}
-           <tr>
-              <td style="text-align:right">{{entry.date}}</td>
-              <td style="text-align:right">
-               {% if entry.direction == "FROM" %}+{% else %}-{% endif %}{{ 
entry.float_amount }} {{ entry.float_currency }}
-             </td>
-              <td style="text-align:left">{% if entry.counterpart_username %} 
{{ entry.counterpart_username }} {% endif %} (account #{{ entry.counterpart 
}})</td>
-              <td style="text-align:left">
-                {% if entry.counterpart_username %}
-               <a name="{{ entry.subject }}"></a>
-               <a href="public-accounts?account={{ entry.counterpart_username 
}}#{{ entry.subject }}">{{ entry.subject }}</a>
-                {% else %}
-                  {{ entry.subject }}
-               {% endif %}
-             </td>
-           </tr>
-            {% endfor %}
-         {% else %}
-           <p>No history for this account yet</p>
-       {% endif %}
-      </div>
-    </table>
-    </article>
-  </section>
-  <script>
-  /*
-  @licstart  The following is the entire license notice for the
-  JavaScript code in this page.
-
-  Copyright (C) 2014, 2015, 2016 Inria and GNUnet e.V.
-
-  The JavaScript code in this page is free software: you can
-  redistribute it and/or modify it under the terms of the GNU
-  General Public License (GNU GPL) as published by the Free Software
-  Foundation, either version 3 of the License, or (at your option)
-  any later version.  The code is distributed WITHOUT ANY WARRANTY;
-  without even the implied warranty of MERCHANTABILITY or FITNESS
-  FOR A PARTICULAR PURPOSE.  See the GNU GPL for more details.
-
-  As additional permission under GNU GPL version 3 section 7, you
-  may distribute non-source (e.g., minimized or compacted) forms of
-  that code without the copy of the GNU GPL normally required by
-  section 4, provided you include this license notice and a URL
-  through which recipients can access the Corresponding Source.
-
-  @licend  The above is the entire license notice
-  for the JavaScript code in this page.
-  */
-  </script>
-  <script type="application/javascript">
-    function bold_selected(){
-      var target = document.getElementById("{{ selected_account.account_name 
}}");
-      target.style.fontWeight = "bold";
-      console.log ("selecting", target.parentNode);
-      target.parentNode.setAttribute("class", "selected-item");
-    };
-    document.addEventListener('DOMContentLoaded', bold_selected);
-  </script>
-{% endblock content %}
diff --git a/talerbank/app/templates/register.html 
b/talerbank/app/templates/register.html
index a1ca54e..20aa8af 100644
--- a/talerbank/app/templates/register.html
+++ b/talerbank/app/templates/register.html
@@ -18,11 +18,10 @@
 -->
 
 {% extends "base.html" %}
-{% load staticfiles %}
+{% load settings_value from settings %}
 
 {% block headermsg %}
-  <h1 lang="en" class="nav">Register to the {{ currency }} bank!</h1>
-  <h1 lang="it" class="nav">Registrati in banca {{ currency }}!</h1>
+  <h1 class="nav">Register to the {% settings_value "TALER_CURRENCY" %} 
bank!</h1>
 {% endblock headermsg %}
 
 {% block content %}
@@ -52,7 +51,7 @@
         <h1 lang="it">Form di registrazione</h1>
         <form method="post" action="{% url 'register' %}">
           {% csrf_token %}
-          <input type="text" name="username" placeholder="username"></input>
+          <input type="text" name="username" placeholder="username" 
autofocus></input>
           <input type="password" name="password" 
placeholder="password"></input>
           <input type="submit" value="Ok"></input>
         </form>
diff --git a/talerbank/app/templatetags/settings.py 
b/talerbank/app/templatetags/settings.py
new file mode 100644
index 0000000..08fb084
--- /dev/null
+++ b/talerbank/app/templatetags/settings.py
@@ -0,0 +1,8 @@
+import django.template
+from django.conf import settings
+
+register = django.template.Library()
+
address@hidden
+def settings_value(name):
+    return getattr(settings, name, "")
diff --git a/talerbank/app/urls.py b/talerbank/app/urls.py
index 3b5e74c..752dd6a 100644
--- a/talerbank/app/urls.py
+++ b/talerbank/app/urls.py
@@ -16,24 +16,21 @@
 
 from django.conf.urls import include, url
 from django.views.generic.base import RedirectView
-from . import user
-from . import funds
 from . import views
-from . import history
-from . import captcha
 
 urlpatterns = [
     url(r'^', include('talerbank.urls')),
-    url(r'^(?P<js>(use_js|no_js))?$', views.home_page, name='index'),
+    url(r'^$', RedirectView.as_view(pattern_name="profile"), name="index"),
     url(r'^favicon\.ico$', views.ignore),
-    url(r'^accounts/register/$', user.register, name="register"),
-    url(r'^accounts/login/$', views.home_page, name="login"),
-    url(r'^accounts/logout/$', user.logout, name="logout"),
+    url(r'^javascript(.html)?/$', views.javascript_licensing, 
name="javascript"),
+    url(r'^login/$', views.login_view, name="login"),
+    url(r'^logout/$', views.logout_view, name="logout"),
+    url(r'^accounts/register/$', views.register, name="register"),
     url(r'^profile$', views.profile_page, name="profile"),
-    url(r'^withdraw$', funds.withdraw_nojs, name="withdraw-nojs"),
-    url(r'^public-accounts$', history.public_accounts, name="public-accounts"),
-    url(r'^public-accounts?account=(P?<name>[a-zA-Z0-9 ]+)$', 
history.public_accounts, name="public-accounts"),
-    url(r'^pin/question$', captcha.pin_tan_question, name="pin-question"),
-    url(r'^pin/verify$', captcha.pin_tan_verify, name="pin-verify"),
+    url(r'^withdraw$', views.withdraw_nojs, name="withdraw-nojs"),
+    url(r'^public-accounts$', views.public_accounts, name="public-accounts"),
+    url(r'^public-accounts/(?P<name>[a-zA-Z0-9 ]+)$', views.public_accounts, 
name="public-accounts"),
+    url(r'^pin/question$', views.pin_tan_question, name="pin-question"),
+    url(r'^pin/verify$', views.pin_tan_verify, name="pin-verify"),
     url(r'^javascript$', views.javascript_licensing)
     ]
diff --git a/talerbank/app/user.py b/talerbank/app/user.py
deleted file mode 100644
index d71a77b..0000000
--- a/talerbank/app/user.py
+++ /dev/null
@@ -1,91 +0,0 @@
-#  This file is part of TALER
-#  (C) 2014, 2015, 2016 INRIA
-#
-#  TALER is free software; you can redistribute it and/or modify it under the
-#  terms of the GNU Affero General Public License as published by the Free 
Software
-#  Foundation; either version 3, or (at your option) any later version.
-#
-#  TALER is distributed in the hope that it will be useful, but WITHOUT ANY
-#  WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 
FOR
-#  A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
-#
-#  You should have received a copy of the GNU General Public License along with
-#  TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
-#
-#  @author Marcello Stanisci
-
-import logging
-import django.db
-from django.contrib.auth import authenticate, login, logout
-from django.contrib.auth.models import User
-from django.shortcuts import render, redirect
-from django import forms
-from django.conf import settings
-from .models import BankAccount
-from .errors import bad_get_parameter_handler
-from .funds import wire_transfer_in_out
-
-logger = logging.getLogger(__name__)
-
-class UserReg(forms.Form):
-    username = forms.CharField()
-    password = forms.CharField(widget=forms.PasswordInput())
-
-
-def register(request):
-    """
-    register a new user giving 100 KUDOS bonus
-    """
-    wrong_field = False
-    not_available = False
-    if request.method == 'POST':
-        form = UserReg(request.POST)
-        if not form.is_valid():
-            try:
-                username = form.cleaned_data['username']
-                password = form.cleaned_data['password']
-                user = User.objects.create_user(username=username,
-                                                password=password)
-                account = BankAccount(user=user,
-                                      currency=settings.TALER_CURRENCY)
-                account.save()
-                wire_transfer_in_out({'value': 100,
-                                      'fraction': 0,
-                                      'currency': settings.TALER_CURRENCY},
-                                      1,
-                                      account.account_no,
-                                      "Joining bonus")
-                request.session['account_no'] = account.account_no
-                request.session['registration_successful'] = True
-                user = authenticate(username=username, password=password)
-                login(request, user)
-                return redirect("profile")
-            except django.db.IntegrityError:
-                not_available = True
-        else:
-            wrong_field = True
-    return render(request,
-                  'register.html',
-                  {'wrong': wrong_field,
-                   'currency': settings.TALER_CURRENCY,
-                   'not_available': not_available})
-
-
-def get_bank_account_from_username(username):
-    try:
-        user_account = User.objects.get(username=username)
-        return user_account.bankaccount
-    except User.DoesNotExist:
-        logger.warn("user '%s' does not exist", username)
-        return None
-
-
-def logout(request):
-    """
-    Log out the user and redirect to index page.
-    """
-    del request.session["account_no"]
-    logout(request)
-    request.session['logged_out'] = True
-    return redirect("index")
-
diff --git a/talerbank/app/views.py b/talerbank/app/views.py
index 4901ff7..9a0e06d 100644
--- a/talerbank/app/views.py
+++ b/talerbank/app/views.py
@@ -15,98 +15,358 @@
 #  @author Marcello Stanisci
 #  @author Florian Dold
 
-from django.contrib.auth.decorators import login_required
-from django.http import HttpResponse
+import django.contrib.auth
+import django.contrib.auth.views
+import django.contrib.auth.forms
+from django import forms
 from django.conf import settings
+from django.contrib.auth.decorators import login_required
+from django.http import HttpResponse, HttpResponseBadRequest, 
HttpResponseServerError
 from django.shortcuts import render, redirect
-from django.contrib.auth import authenticate, login, logout
-from .models import BankAccount
-from .history import extract_history
-from .amounts import stringify_amount
-from . import errors
+from django.views.decorators.csrf import csrf_exempt
+from django.views.decorators.http import require_POST, require_GET
+from simplemathcaptcha.fields import MathCaptchaField, MathCaptchaWidget
+from django.core.urlresolvers import reverse
+from django.contrib.auth.models import User
+from django.db.models import Q
+import json
 import logging
+import time
+import hashlib
+import requests
+from urllib.parse import urljoin
+from . import amounts
+from . import schemas
+from .models import BankAccount, BankTransaction
 
 logger = logging.getLogger(__name__)
 
+
+class MyAuthenticationForm(django.contrib.auth.forms.AuthenticationForm):
+    def __init__(self, *args, **kwargs):           
+        super().__init__(*args, **kwargs)
+        self.fields["username"].widget.attrs["autofocus"] = True
+        self.fields["username"].widget.attrs["placeholder"] = "Username"
+        self.fields["password"].widget.attrs["placeholder"] = "Password"
+
+
 def ignore(request):
     return HttpResponse()
 
 def javascript_licensing(request):
-    return render(request, 'javascript.html')
-
-def home_page(request, js=None):
-    logger.info("js: %s" % js)
-
-    if js: 
-        request.session['js'] = js
-    else:
-        js = request.session.get('js', 'no_js')
-
-    if request.method == 'POST':
-        username = request.POST['username']
-        password = request.POST['password']
-        user = authenticate(username=username, password=password)
-        if user is None:
-            request.session['wrong_login'] = True
-            return redirect("index")
-        if not user.is_active:
-            return render(request, 'account_disabled.html', {'name': 
user.username,
-                                                             'currency': 
settings.TALER_CURRENCY})
-        login(request, user)
-        request.session["account_no"] = user.bankaccount.account_no
-        logger.info("Redirecting to /profile, js: %s" % js)
-        return redirect("profile")
-    wrong = False
-    if "wrong_login" in request.session:
-        wrong = request.session['wrong_login']
-        del request.session['wrong_login']
-    if "logged_out" in request.session:
-        del request.session['logged_out']
-        return render(request,
-                      'home_page.html',
-                      {'currency': settings.TALER_CURRENCY,
-                       'logged_out': True,
-                       'js': js})
-    if request.user.is_authenticated():
-        return redirect("profile")
-    return render(request,
-                  'home_page.html',
-                  {'currency': settings.TALER_CURRENCY,
-                   'wrong': wrong,
-                   'js': js})
+    return render(request, "javascript.html")
+
+def login_view(request):
+    just_logged_out = get_session_flag(request, "just_logged_out")
+    response = django.contrib.auth.views.login(
+            request, authentication_form=MyAuthenticationForm, 
template_name="login.html")
+    # sometimes the response is a redirect and not a template response
+    if hasattr(response, "context_data"):
+        response.context_data["just_logged_out"] = just_logged_out
+    return response
+
+def get_session_flag(request, name):
+    """
+    Get a flag from the session and clear it.
+    """
+    if name in request.session:
+        del request.session[name]
+        return True
+    return False
 
 
 @login_required
 def profile_page(request):
-    withdraw = False
-    registration = False
-    if 'registration_successful' in request.session:
-        del request.session['registration_successful']
-        registration = "success"
-    if 'withdrawal_successful' in request.session:
-        del request.session['withdrawal_successful']
-        withdraw = "success"
-    user_account = 
BankAccount.objects.get(account_no=request.session['account_no'])
+    just_withdrawn = get_session_flag(request, "just_withdrawn")
+    just_registered = get_session_flag(request, "just_registered")
+    user_account = BankAccount.objects.get(user=request.user)
     history = extract_history(user_account)
-    logger.info(str(history))
-    reserve_pub = request.session.get('reserve_pub')
-
-    # Should never hit an empty session['js']
-    js = request.session.get('js', 'no_js')
-    logger.info("js: %s" % js)
-    response = render(request,
-                      'profile_page.html',
-                      {'name': user_account.user.username,
-                       'balance': stringify_amount(user_account.balance),
-                       'currency': user_account.currency,
-                       'precision': settings.NDIGITS,
-                       'account_no': user_account.account_no,
-                       'history': history,
-                       'withdraw': withdraw,
-                       'registration': registration,
-                       'js': js})
-    if js and withdraw == "success":
-       response['X-Taler-Reserve-Pub'] = reserve_pub
+    reserve_pub = request.session.get("reserve_pub")
+    if "use_js" in request.GET:
+        print("use_js is in GET as '{}'".format(request.GET["use_js"]))
+        if request.GET["use_js"].lower() == "true":
+            request.session["use_js"] = True
+        else:
+            request.session["use_js"] = False
+    use_js = request.session.get("use_js", False)
+
+    context = dict(
+        name=user_account.user.username,
+        balance=amounts.stringify(user_account.balance),
+        currency=user_account.currency,
+        precision=settings.TALER_DIGITS,
+        account_no=user_account.account_no,
+        history=history,
+        just_withdrawn=just_withdrawn,
+        just_registered=just_registered,
+        use_js=use_js,
+    )
+
+    response = render(request, "profile_page.html", context)
+    if just_withdrawn and not use_js:
+       response["X-Taler-Operation"] = "confirm-reserve"
+       response["X-Taler-Reserve-Pub"] = reserve_pub
        response.status_code = 202
+    return response
+
+
+class Pin(forms.Form):
+    pin = MathCaptchaField(
+        widget=MathCaptchaWidget(
+            attrs=dict(autocomplete="off", autofocus=True),
+            question_tmpl="<div lang=\"en\">What is %(num1)i %(operator)s 
%(num2)i ?</div>"))
+
+
address@hidden
address@hidden
+def pin_tan_question(request):
+    for param in ("amount_value",
+                  "amount_fraction",
+                  "amount_currency",
+                  "exchange",
+                  "reserve_pub",
+                  "wire_details"):
+        if param not in request.GET:
+            return HttpResponseBadRequest("parameter {} missing".format(param))
+    try:
+        amount = {"value": int(request.GET["amount_value"]),
+                  "fraction": int(request.GET["amount_fraction"]),
+                  "currency": request.GET["amount_currency"]}
+    except ValueError:
+        return HttpResponseBadRequest("invalid parameters")
+    user_account = BankAccount.objects.get(user=request.user)
+    wiredetails = json.loads(request.GET["wire_details"])
+    if not isinstance(wiredetails, dict) or "test" not in wiredetails:
+        return HttpResponseBadRequest(
+                "This bank only supports the test wire transfer method. "
+                "The exchange does not seem to support it.")
+    try:
+        schemas.validate_wiredetails(wiredetails)
+        schemas.validate_amount(amount)
+    except ValueError:
+        return HttpResponseBadRequest("invalid parameters")
+    # parameters we store in the session are (more or less) validated
+    request.session["exchange_account_number"] = 
wiredetails["test"]["account_number"]
+    request.session["amount"] = amount
+    request.session["exchange_url"] = request.GET["exchange"]
+    request.session["reserve_pub"] = request.GET["reserve_pub"]
+    request.session["sender_wiredetails"] = dict(
+        type="TEST",
+        bank_uri=request.build_absolute_uri(reverse("index")),
+        account_number=user_account.account_no
+    )
+    previous_failed = get_session_flag(request, "captcha_failed")
+    context = dict(
+        form=Pin(auto_id=False),
+        amount=amounts.floatify(amount),
+        previous_failed=previous_failed,
+        exchange=request.GET["exchange"],
+    )
+    return render(request, "pin_tan.html", context)
+
+
address@hidden
address@hidden
+def pin_tan_verify(request):
+    try:
+        given = request.POST["pin_0"]
+        hashed_result = request.POST["pin_1"]
+        question_url = request.POST["question_url"]
+    except Exception:  # FIXME narrow the Exception type
+        return redirect("profile")
+    hasher = hashlib.new("sha1")
+    hasher.update(settings.SECRET_KEY.encode("utf-8"))
+    hasher.update(given.encode("utf-8"))
+    hashed_attempt = hasher.hexdigest()
+    if hashed_attempt != hashed_result:
+        request.session["captcha_failed"] = True
+        return redirect(question_url)
+    # We recover the info about reserve creation from the session (and
+    # not from POST parameters), since we don't what the user to
+    # change it after we've verified it.
+    try:
+        amount = request.session["amount"]
+        exchange_url = request.session["exchange_url"]
+        reserve_pub = request.session["reserve_pub"]
+        exchange_account_number = request.session["exchange_account_number"]
+        sender_wiredetails = request.session["sender_wiredetails"]
+    except KeyError:
+        # This is not a withdraw session, we redirect the user to the
+        # profile page.
+        return redirect("profile")
+    return create_reserve_at_exchange(request, amount, exchange_url, 
exchange_account_number, reserve_pub, sender_wiredetails)
+
+
+class UserReg(forms.Form):
+    username = forms.CharField()
+    password = forms.CharField(widget=forms.PasswordInput())
+
+
+def register(request):
+    """
+    register a new user giving 100 KUDOS bonus
+    """
+    if request.method != "POST":
+        return render(request, "register.html")
+    form = UserReg(request.POST)
+    if not form.is_valid():
+        return render(request, "register.html", dict(wrong_field=True))
+    username = form.cleaned_data["username"]
+    password = form.cleaned_data["password"]
+    if User.objects.filter(username=username).exists():
+        return render(request, "register.html", dict(not_available=True))
+    user = User.objects.create_user(username=username, password=password)
+    user_account = BankAccount(user=user, currency=settings.TALER_CURRENCY)
+    user_account.save()
+    bank_internal_account = BankAccount.objects.get(account_no=1)
+    amount = dict(value=100, fraction=0, currency=settings.TALER_CURRENCY)
+    wire_transfer(amount, bank_internal_account, user_account, "Joining bonus")
+    request.session["just_registered"] = True
+    user = django.contrib.auth.authenticate(username=username, 
password=password)
+    django.contrib.auth.login(request, user)
+    return redirect("profile")
+
+
+def logout_view(request):
+    """
+    Log out the user and redirect to index page.
+    """
+    django.contrib.auth.logout(request)
+    request.session["just_logged_out"] = True
+    return redirect("index")
+
+
+def extract_history(account):
+    history = []
+    related_transactions = BankTransaction.objects.filter(
+            Q(debit_account=account) | Q(credit_account=account))
+    for item in related_transactions:
+        if item.credit_account == account:
+            counterpart = item.debit_account
+            sign = 1
+        else:
+            counterpart = item.credit_account
+            sign = -1
+        entry = dict(
+            float_amount=amounts.stringify(item.amount * sign),
+            float_currency=item.currency,
+            counterpart=counterpart.account_no,
+            counterpart_username=counterpart.user.username,
+            subject=item.subject,
+            date=item.date.strftime("%d/%m/%y %H:%M"),
+        )
+        history.append(entry)
+    return history
+
 
+def public_accounts(request, name=None):
+    if not name:
+        name = settings.TALER_PREDEFINED_ACCOUNTS[0]
+    try:
+        user = User.objects.get(username=name)
+        account = BankAccount.objects.get(user=user, is_public=True)
+    except User.DoesNotExist:
+        return HttpResponse("account '{}' not found".format(name), status=404)
+    except BankAccount.DoesNotExist:
+        return HttpResponse("account '{}' not found".format(name), status=404)
+    public_accounts = BankAccount.objects.filter(is_public=True)
+    history = extract_history(account)
+    context = dict(
+        public_accounts=public_accounts,
+        selected_account=dict(
+            name=name,
+            number=account.account_no,
+            history=history,
+        )
+    )
+    return render(request, "public_accounts.html", context)
+
+
address@hidden
address@hidden
+def add_incoming(request):
+    """
+    Internal API used by exchanges to notify the bank
+    of incoming payments.
+
+    This view is CSRF exempt, since it is not used from
+    within the browser, and only over the private admin interface.
+    """
+    logger.info("handling /admin/add/incoming")
+    data = json.loads(request.body.decode("utf-8"))
+    try:
+        schemas.validate_incoming_request(data)
+    except ValueError:
+        return HttpResponseBadRequest()
+    logger.info("add_incoming for debit account %s and credit account %s, WTID 
%s",
+                data["debit_account"],
+               data["credit_account"],
+               data["wtid"])
+    try:
+        debit_account = user_account = 
BankAccount.objects.get(user=data["debit_account"])
+        credit_account = user_account = 
BankAccount.objects.get(user=data["credit_account"])
+    except BankAccount.DoesNotExist:
+        return HttpResponse(status=404)
+    wire_transfer(data["amount"], debit_account, credit_account, data["wtid"])
+    return JsonResponse({"outcome": "ok"}, status=200)
+
+
address@hidden
address@hidden
+def withdraw_nojs(request):
+    amount = amounts.parse_amount(request.POST.get("kudos_amount", ""))
+    if amount is None:
+        return HttpResponseBadRequest()
+    response = HttpResponse(status=202)
+    response["X-Taler-Operation"] = "create-reserve"
+    response["X-Taler-Callback-Url"] = reverse("pin-question")
+    response["X-Taler-Wt-Types"] = '["TEST"]'
+    response["X-Taler-Amount"] = json.dumps(amount)
     return response
+
+
+def create_reserve_at_exchange(request, amount, exchange_url, 
exchange_account_no, reserve_pub, sender_account_details):
+    try:
+        BankAccount.objects.get(account_no=exchange_account_no)
+    except BankAccount.DoesNotExist:
+        raise HttpResponseBadRequest("The bank account #{} of exchange {} does 
not exist".format(exchange_account_no, exchange_url))
+    json_body = dict(
+            reserve_pub=reserve_pub,
+            execution_date="/Date(" + str(int(time.time())) + ")/",
+            sender_account_details=sender_account_details,
+             # just something unique
+            transfer_details=dict(timestamp=int(time.time() * 1000)),
+            amount=amount,
+    )
+    request_url = urljoin(exchange_url, "admin/add/incoming")
+    res = requests.post(request_url, json=json_body)
+    if res.status_code != 200:
+        return render(request, "error_exchange.html", dict(
+            message="Could not transfer funds to the exchange.  The exchange 
({}) gave a bad response.".format(exchange_url),
+            response_text=res.text,
+            response_status=res.status_code,
+        ))
+    user_account = BankAccount.objects.get(user=request.user)
+    exchange_account = BankAccount.objects.get(user=exchange_account_no)
+    wire_transfer(amount, user_account, exchange_account_no, reserve_pub)
+    request.session["just_withdrawn"] = True
+    return redirect("profile")
+
+
+def wire_transfer(amount,
+                  debit_account,
+                  credit_account,
+                  subject):
+    if debit_account.pk == credit_account.pk:
+        return
+    float_amount = amounts.floatify(amount)
+    transaction_item = BankTransaction(amount=float_amount,
+                                       currency=amount["currency"],
+                                       credit_account=credit_account,
+                                       debit_account=debit_account,
+                                       subject=subject)
+    debit_account.balance -= float_amount
+    credit_account.balance += float_amount
+    debit_account.save()
+    credit_account.save()
+    transaction_item.save()
diff --git a/talerbank/settings.py b/talerbank/settings.py
index 6f6a857..4625fe7 100644
--- a/talerbank/settings.py
+++ b/talerbank/settings.py
@@ -39,6 +39,8 @@ ALLOWED_HOSTS = ["*"]
 
 LOGIN_URL = "login"
 
+LOGIN_REDIRECT_URL = "index"
+
 
 # Application definition
 
@@ -61,7 +63,6 @@ MIDDLEWARE_CLASSES = [
     'django.contrib.auth.middleware.SessionAuthenticationMiddleware',
     'django.contrib.messages.middleware.MessageMiddleware',
     'django.middleware.clickjacking.XFrameOptionsMiddleware',
-    'talerbank.app.middleware.ExpectedExceptionsMiddleware',
 ]
 
 # To be dynamically set at launch time (by *.wsgi scripts)
@@ -89,33 +90,39 @@ WSGI_APPLICATION = 'talerbank.wsgi.application'
 # Database
 # https://docs.djangoproject.com/en/1.9/ref/settings/#databases
 
+DATABASES = {}
+
 # parse a database URL, django can't natively do this!
-dbname = tc.value_string("bank", "database", required=True)
+dbname = tc.value_string("bank", "database", required=False)
 dbconfig = {}
-db_url = urllib.parse.urlparse(dbname)
-if db_url.scheme != "postgres":
-    raise Exception("only postgres db is supported ('{}' not 
understood)".format(dbname))
-dbconfig['ENGINE'] = 'django.db.backends.postgresql_psycopg2'
-dbconfig['NAME'] = db_url.path.lstrip("/")
-
-if not db_url.netloc:
-    p = urllib.parse.parse_qs(db_url.query)
-    if ("host" not in p) or len(p["host"]) == 0:
-        host = None
+if dbname:
+    db_url = urllib.parse.urlparse(dbname)
+    if db_url.scheme != "postgres":
+        raise Exception("only postgres db is supported ('{}' not 
understood)".format(dbname))
+    dbconfig['ENGINE'] = 'django.db.backends.postgresql_psycopg2'
+    dbconfig['NAME'] = db_url.path.lstrip("/")
+
+    if not db_url.netloc:
+        p = urllib.parse.parse_qs(db_url.query)
+        if ("host" not in p) or len(p["host"]) == 0:
+            host = None
+        else:
+            host = p["host"][0]
     else:
-        host = p["host"][0]
-else:
-    host = db_url.netloc
+        host = db_url.netloc
 
-if host:
-    dbconfig["HOST"] = host
+    if host:
+        dbconfig["HOST"] = host
 
-logger.info("db string '%s'", dbname)
-logger.info("db info '%s'", dbconfig)
+    logger.info("db string '%s'", dbname)
+    logger.info("db info '%s'", dbconfig)
 
-DATABASES = {
-    'default': dbconfig
-}
+    DATABASES["default"] = dbconfig
+else:
+    DATABASES["default"] = {
+            'ENGINE': 'django.db.backends.sqlite3',
+            'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
+    }
 
 
 # Password validation
@@ -168,16 +175,8 @@ STATIC_ROOT = '/tmp/talerbankstatic/'
 
 
 
-TALER_WIREDETAILS_COUNTER = 0
 TALER_CURRENCY = tc.value_string("taler", "currency", required=True)
-# How many digits we want to be shown for amounts fractional part
-NDIGITS = tc.value_int("bank", "ndigits")
-if NDIGITS is None:
-    NDIGITS = 2
-# Taler-compliant fractional part in amount objects, currently 1e8
-FRACTION = tc.value_int("bank", "fraction")
-if FRACTION is None:
-    FRACTION = 100000000
+TALER_DIGITS = 2
 TALER_PREDEFINED_ACCOUNTS = ['Tor', 'GNUnet', 'Taler', 'FSF', 'Tutorial']
 TALER_EXPECTS_DONATIONS = ['Tor', 'GNUnet', 'Taler', 'FSF']
 

-- 
To stop receiving notification emails like this one, please contact
address@hidden



reply via email to

[Prev in Thread] Current Thread [Next in Thread]