gnunet-svn
[Top][All Lists]
Advanced

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

[GNUnet-SVN] [taler-bank] branch master updated (f325bdb -> 632f196)


From: gnunet
Subject: [GNUnet-SVN] [taler-bank] branch master updated (f325bdb -> 632f196)
Date: Fri, 13 Jan 2017 14:59:47 +0100

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

dold pushed a change to branch master
in repository bank.

    from f325bdb  README
     new af104fc  The big refactor.
     new 95e806c  fix reserve creation
     new 87ed2cf  submodules
     new 67d8193  fix method call
     new 632f196  logging

The 5 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 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/static/web-common                    |   2 +-
 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                             | 415 +++++++++++++++++----
 talerbank/settings.py                              |  61 ++-
 29 files changed, 607 insertions(+), 1127 deletions(-)
 delete mode 100644 talerbank/app/captcha.py
 delete mode 100644 talerbank/app/errors.py
 delete mode 100644 talerbank/app/funds.py
 delete mode 100644 talerbank/app/history.py
 delete mode 100644 talerbank/app/middleware.py
 delete mode 100644 talerbank/app/templates/history.html
 rename talerbank/app/templates/{home_page.html => login.html} (51%)
 create mode 100644 talerbank/app/templates/public_accounts.html
 delete mode 100644 talerbank/app/templates/public_histories_reloaded.html
 create mode 100644 talerbank/app/templatetags/settings.py
 delete mode 100644 talerbank/app/user.py

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/static/web-common b/talerbank/app/static/web-common
index b3d6222..dc9d5ab 160000
--- a/talerbank/app/static/web-common
+++ b/talerbank/app/static/web-common
@@ -1 +1 @@
-Subproject commit b3d6222e1b33553c4ccf576ae3ac2f4823f3dc20
+Subproject commit dc9d5ab2308fef7cdd1e8c95fbf4fdd51bed7bfb
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..50e3771 100644
--- a/talerbank/app/views.py
+++ b/talerbank/app/views.py
@@ -15,98 +15,359 @@
 #  @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))
+    logging.info("asking exchange {} to create reserve 
{}".format(exchange_url, reserve_pub))
+    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(account_no=exchange_account_no)
+    wire_transfer(amount, user_account, exchange_account, 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]