gnunet-svn
[Top][All Lists]
Advanced

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

[GNUnet-SVN] [taler-bank] branch stable updated (f368a5d -> e224c23)


From: gnunet
Subject: [GNUnet-SVN] [taler-bank] branch stable updated (f368a5d -> e224c23)
Date: Fri, 10 Mar 2017 16:18:24 +0100

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

dold pushed a change to branch stable
in repository bank.

    omit f368a5d  submodules
     add e60578f  fix forgotten files in web-common
     add 9f30678  Fix other forgotten files from web-common
     add c7447e0  submodule
     add 8d8d2ac  Forgotten files make dist
     add f991f5c  submodules
     add 1e27fcc  app-relative navbar
     add b8b9983  use static helper
     add 3125c9e  more relative URLs
     add 9899a39  consistent naming / URLs
     add 5224154  urls
     add d3529fd  allow asterisk, lowercase and numbers in currency
     add 48bd226  logging
     add c2f28af  logging
     add bc6bcc3  amounts regex
     add 5bbd665  login url
     add 66d6eaa  fix URL validation, mint->exchange
     add 31ea77e  better error handling
     add e33e8a4  fix template
     add 2d81756  fix template again
     add a58fb0c  fix template again
     add ec49777  dce
     add e4a3065  remove unused public account details
     add 7fb3b39  formattting
     add c03657d  rename
     add b95f1ec  small fixes
     add e6a7812  code style
     add 5dd3f52  style / captcha error
     add 9df0e73  syntax errors
     add 6da87a8  Timezone comment
     add 46c4ebb  40% install/config instructions
     add 4a1d287  Updating README
     add f325bdb  README
     add af104fc  The big refactor.
     add 95e806c  fix reserve creation
     add 87ed2cf  submodules
     add 67d8193  fix method call
     add 632f196  logging
     add 0a1bdb2  adding make check
     add e58758d  Make testcases match refactoring
     add dcff654  testcase for /add/incoming
     add 1e2db9c  Setting up the db in order to test /add/incoming. Data 
persistency needs a fix..
     add 4e0efc8  Fix testcase preparation
     add 471ebf4  /add/admin testcase fixed but ROOT_URLCONF doesn't switch 
between normal and admin url-views mapping.
     add 678bc32  Explicitly importing urlconf from reverse()
     add 21c0afe  Splitting settings for normal and admin instances.
     add a471b6f  Splitting testscases according to normal and admin instance.
     add b5ad78b  Run tests' README
     add 3ae843c  #4822
     add cee663e  gitignore
     add 73d0193  suggest exchange to wallet
     add 6de5dc5  add missing var declaration in JS
     add 7b7e2ae  README
     add cd2031d  Adapting dump_talerdb to new models.
     add 79a9987  Double-checking DJANGO_SETTINGS_MODULE env var.
     add b3ac8a6  Indentation, logging.
     add 66e32bb  Wire transfer subject is JSON containing exchange URL and 
reserve public key.
     add 5cef379  Fix wire transfer subject.
     add 556e5a7  fix schema
     add e224c23  Remove old code.

This update added new revisions after undoing existing revisions.
That is to say, some revisions that were in the old version of the
branch are not in the new version.  This situation occurs
when a user --force pushes a change and generates a repository
containing something like this:

 * -- * -- B -- O -- O -- O   (f368a5d)
            \
             N -- N -- N   refs/heads/stable (e224c23)

You should already have received notification emails for all of the O
revisions, and so the following emails describe only the N revisions
from the common base, B.

Any revisions marked "omit" are not gone; other references still
refer to them.  Any revisions marked "discard" are gone forever.

No new revisions were added by this update.

Summary of changes:
 .gitignore                                         |   1 +
 Makefile.am                                        |   3 +
 README                                             | 202 ++++------
 bank-admin.wsgi.in                                 |   5 +-
 bank.conf                                          |   2 +-
 bank.wsgi.in                                       |   3 -
 setup.py                                           |   1 +
 taler-bank-manage.in                               |   8 +-
 talerbank/app/admin.py                             |   4 +-
 talerbank/app/amounts.py                           |  42 +-
 talerbank/app/captcha.py                           | 108 -----
 talerbank/app/errors.py                            | 182 ---------
 talerbank/app/funds.py                             | 170 --------
 talerbank/app/history.py                           |  72 ----
 talerbank/app/management/commands/dump_talerdb.py  |  14 +-
 talerbank/app/middleware.py                        |  26 --
 talerbank/app/migrations/0001_initial.py           |  11 +-
 talerbank/app/models.py                            |  12 +-
 talerbank/app/schemas.py                           |  70 ++--
 talerbank/app/static/Makefile.am                   |   4 +-
 talerbank/app/static/profile-page.js               |  39 +-
 talerbank/app/static/web-common                    |   2 +-
 talerbank/app/templates/Makefile.am                |   3 +-
 talerbank/app/templates/account_disabled.html      |   1 -
 talerbank/app/templates/base.html                  |  14 +-
 talerbank/app/templates/error.html                 |  72 ----
 ...ic_account_details.html => error_exchange.html} |  24 +-
 talerbank/app/templates/history.html               |  33 --
 talerbank/app/templates/home_page.html             |  83 ----
 talerbank/app/templates/login.html                 |  76 ++++
 talerbank/app/templates/pin_tan.html               |  46 +--
 talerbank/app/templates/profile_page.html          |  46 +--
 talerbank/app/templates/public_accounts.html       |  81 ++++
 .../app/templates/public_histories_reloaded.html   | 113 ------
 talerbank/app/templates/register.html              |   7 +-
 .../app/{management => templatetags}/__init__.py   |   0
 talerbank/app/templatetags/mystatic.py             |  30 ++
 talerbank/app/templatetags/settings.py             |   8 +
 talerbank/app/tests.py                             | 147 ++-----
 talerbank/app/tests_admin.py                       |  62 +++
 talerbank/app/urls.py                              |  24 +-
 talerbank/app/urlsadmin.py                         |   4 +-
 talerbank/app/user.py                              | 110 -----
 talerbank/app/views.py                             | 442 +++++++++++++++++----
 talerbank/settings.py                              | 182 +--------
 talerbank/settings_admin.py                        |   2 +
 talerbank/{settings.py => settings_base.py}        |  73 ++--
 47 files changed, 958 insertions(+), 1706 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/error.html
 rename talerbank/app/templates/{public_account_details.html => 
error_exchange.html} (78%)
 delete mode 100644 talerbank/app/templates/history.html
 delete mode 100644 talerbank/app/templates/home_page.html
 create mode 100644 talerbank/app/templates/login.html
 create mode 100644 talerbank/app/templates/public_accounts.html
 delete mode 100644 talerbank/app/templates/public_histories_reloaded.html
 copy talerbank/app/{management => templatetags}/__init__.py (100%)
 create mode 100644 talerbank/app/templatetags/mystatic.py
 create mode 100644 talerbank/app/templatetags/settings.py
 create mode 100644 talerbank/app/tests_admin.py
 delete mode 100644 talerbank/app/user.py
 create mode 100644 talerbank/settings_admin.py
 copy talerbank/{settings.py => settings_base.py} (75%)

diff --git a/.gitignore b/.gitignore
index 2239c33..4c14e6b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -13,5 +13,6 @@ config.log
 aclocal.m4
 configure
 bank.wsgi
+bank-admin.wsgi
 talerbank.egg-info
 talerbank/vassals-*/*.ini
diff --git a/Makefile.am b/Makefile.am
index 3ee448a..21d25d0 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -22,6 +22,9 @@ pkgdata_DATA = \
 install-dev:
        @pip3 install -e . --install-option="address@hidden@"
 
+check:
+       @export DJANGO_SETTINGS_MODULE="talerbank.settings" 
TALER_PREFIX="@prefix@" && python3 -m django test talerbank.app.tests
+       @export DJANGO_SETTINGS_MODULE="talerbank.settings_admin" 
TALER_PREFIX="@prefix@" && python3 -m django test talerbank.app.tests_admin
 
 # install into prefix
 install-exec-hook:
diff --git a/README b/README
index 287b283..7148927 100644
--- a/README
+++ b/README
@@ -2,163 +2,133 @@ This code implements a bank Web portal that tightly 
integrates with
 the Taler payment system.  The bank it primarily meant be used as part
 of a demonstrator for the Taler system.
 
-================== HOW INSTALL THE PYTHON BANK =================
+================== HOW TO INSTALL THE BANK =================
 
-----------------------------0 Preface --------------------------
+From the repository's top directory, run
 
-In order to get the complete bank's codebase, a submodule needs to be
-pulled. To do this, run:
+$ ./bootstrap
 
-$ git submodule update --init
+this operation will fetch additional libraries needed by the
+bank (mostly JavaScript includes), and create the configure script.
 
-(This submodule carries some HTML/JS code which is reused among all
-Taler's Web components.)
+The next step is to specify the install prefix, run
 
-As for the database backend, the bank uses PostgreSQL and needs a
-database called 'talertest' in it, owned by the user which launches
-the bank. To grant access to Postgres 9.x, run:
+$ ./configure --prefix=$HOME/local # Adapt to your needs.
 
-$ su # give root password
-# su - postgres
-$ createuser -d $TALER_BANK_USER
+Then the usual GNU-compatible commands, that are
 
-where $TALER_BANK_USER is the username of the user who will run the
-bank.
-
-For Postgres 8.x, the last step needs to be changed to just:
-
-$ createuser
-
-then enter the username interactively. When asked, do not set it to
-superuser, but allow the creation of databases, and do not allow the
-creation of new roles.
-
-
-After this, you can follow two styles of installation, the Pythonic
-way or the GNU way.  The Pythonic way is recommended for development,
-while the GNU way is recommended for actual deployment.
-
-
----------------------1 The Pythonic way ------------------------
-
-Make sure your system has 'virtualenv' and 'pip' installed.
-For example, on Debian systems, run:
-
-# apt-get install virtualenv python3-pip python3.4-dev
-
-Next, create an environment which will "host" the bank's dependencies:
-
-$ virtualenv bank_env
-
-This should create a directory named 'bank_env/' in your current
-working directory.
-That directory will contain all the Pythonic dependencies of the bank.
-
-To activate your environment in the current shell, use:
-
-$ source bank_env/bin/activate
-
-(To later deactivate it, you can simply close the shell.)
-Inside the "activated" shell, install the dependencies:
-
-$ pip install -r requirements.txt
-
-The file 'requirements.txt' is contained in this folder.
-
-Once the environment is set up, the bank can be launched. For that
-purpose, you need to first
-
-$ cd django/
+$ make
 
-Before actually launching the bank, the bank's database and static files'
-serving must be initialized. Give the following commands (respecting the
-sequence):
+and
 
-$ python manage.py makemigrations
-$ python manage.py migrate $ python manage.py collectstatic --noinput
+$ make install
 
-The following command is also needed, in order to create some predefined
-accounts:
+================== HOW TO CONFIGURE THE BANK =================
 
-$ python manage.py pre_accounts
+The bank obeys to the INI syntax for configuration files. When launched, the 
bank
+will by default look for a configuration file located at ~/.config/taler.conf.
+To ovveride this behaviour, give the -c option when launching the bank.
 
-In order to add sample donations to predefined account:
+In order to properly run, the bank needs the following parts to be configured
 
-$ python manage.py sample_donations
+* Database name: connection string for the database to be used, currently 
Postgresql.
+* Serving: whether we want the bank accessible via TCP or unix domain sockets.
+* Fractional precision: precision of the fractional part of Taler objects.
+* Fractional length: how many digits after the floating point we want to be 
shown
+  in HTML pages.
 
-If everything has worked fine, launch the bank's web server with:
+Furthermore, the bank is "split" in two processes: the "normal" and the 
"admin" one.
+The admin one implements the part of REST API used to transfer money from 
account
+x to account y.  The normal one implements all the rest.
+The reason is that the sysadmin may want to enforce special rules to make the 
admin
+process reachable from the external world.
 
-$ python manage.py runserver 127.0.0.1:8080
+See the configuration file in the example below.
 
-The bank's homepage is now available at http://127.0.0.1:8080
+# Mandatory section name
+[bank]
 
+# We accept requests via unix domain sockets
+UWSGI_SERVE = unix
 
---------------------- 2 The GNU way ----------------------------
+# If we want serve the bank via TCP, just replace the statement
+# above with the following one:
+# UWSGI_SERVE = tcp
+# And also give the port to listen to
+# UWSGI_PORT = 8585
 
-The installation is done by the usual
+# The path below indicates where to create the unix domain socket
+UWSGI_UNIXPATH = /deployment/sockets/bank.uwsgi
 
-$ ./bootstrap.sh
-$ ./configure
-$ make
-$ make install
+# The mode to assign to the unix domain socket when
+# creating it (automatically done by the bank)
+UWSGI_UNIXPATH_MODE = 660
 
-sequence. Note that you cannot skip 'make'.  For './configure',
-you can pass a few options:
+# We want at most two fractional digits shown for amounts
+# throughout the page pages
+NDIGITS = 2
 
-(*) The '--prefix' option will point a directory which hosts both the
-virtualenv data and the bank's website itself.
+# The fractional part of Taler amount objects is 100000000.
+# This value needs to be changed ONLY IF there is a change
+# protocol-wise.  In other words, its value it's not up to the
+# bank
+FRACTION = 100000000
 
-(*) The '--exec-prefix' option will point to a directory which will
-host the executables (i.e. a shell scripts which launch the Web
-services), so it should probably be somewhere within $PATH.
+# The bank will try to connect to a database called 'talerlocal'
+# running under Postgresql.  The sysadmin will have to make sure
+# that the bank has all the rights to work on that database.
+DATABASE = postgres:///talerlocal
 
+# The following sections are to configure the "admin" part of
+# the bank
 
-To complete the setup, you need to initialize the database:
+# Mandatory section name
+[bank-admin]
 
-0) a DB called 'talertest' must exist, with the user running the bank
-owning it; try:
+# See above
+UWSGI_SERVE = unix
 
-$ taler-bank-manage --createdb
+# See above
+UWSGI_UNIXPATH = /deployment/sockets/bank-admin.uwsgi
 
-It should either report that the database 'talertest' exists, or
-create it.  If neither works, please re-read the preface on top
-of this file.
+# See above
+UWSGI_UNIXPATH_MODE = 660
 
-1) After the database has been created, some tables must be created:
 
-$ taler-bank-manage --definetables
+================== HOW TO LAUNCH THE BANK =================
 
-2) Optionally, if the bank is to be used in the demo setup, we
-   predefine certain public accounts:
+The bank can accept connections via the UWSGI protocol and HTTP.
 
-$ taler-bank-manage --preaccounts
+To launch it for the UWSGI protocol, issue
 
-3) Optionally, to populate the DB with sample transaction data (i.e.
-   to check how some pages are rendered once data is present), issue:
+$ taler-bank-manage serve-uwsgi
+# The -a option launches the admin part of the bank
+$ taler-bank-manage -a serve-uwsgi
 
-$ taler-bank-manage --sampledata
+To launch it for HTTP, issue
 
-4a) To quickly run the bank as an HTTP server without nginx and
-without the SSI on port 8080, run:
+$ taler-bank-manage serve-http
+# The -a option launches the admin part of the bank
+$ taler-bank-manage -a serve-http
 
-$ taler-bank-manage --bareserver 8080
+the port the bank will listen to, can be either specified on the
+command line (option -p), or in the configuration file with the
+HTTP_PORT option. Note, launching the bank with 'serve-http',
+will make the UWSGI_SERVE option to be ignored.
 
-4b) In production, the bank should be run with:
 
-$ taler-bank-wsgi
+================== HOW TO RUN YOUR TESTS =================
 
-Please note that the bank works properly only via nginx since it uses
-SSI and the above runs the WSGI protocol, not HTTP!  This is described
-in the next section.
+From the repository's top directory, just issue
 
+$ make check
 
------------------ 3 How to bind Django and nginx ---------------
+in order to have test cases run.  A previous 'make install' is
+NOT required.
 
-The basic statements needed to run the bank via nginx are shown in
-'django_nginx.conf'. Once those statements have been applied, assuming
-your CWD is still 'django/', the bank is run with
+Test cases' code is hosted in talerbank/app/tests.py and
+talerbank/app/tests_admin.py
 
-$ ./django_wsgi.sh /path/to/bank_env
+================= HOW TO FORCE MIGRATIONS =================
 
-The nginx configuration is based on the guide in [1]
-[1] http://uwsgi-docs.readthedocs.org/en/latest/tutorials/Django_and_nginx.html
+https://simpleisbetterthancomplex.com/tutorial/2016/07/26/how-to-reset-migrations.html
diff --git a/bank-admin.wsgi.in b/bank-admin.wsgi.in
index 93f035f..94dc5ed 100644
--- a/bank-admin.wsgi.in
+++ b/bank-admin.wsgi.in
@@ -6,16 +6,13 @@ if sys.version_info.major < 3:
     print("The taler bank needs to run with Python>=3.4")
     sys.exit(1)
 
-os.environ.setdefault("DJANGO_SETTINGS_MODULE", "talerbank.settings")
+os.environ.setdefault("DJANGO_SETTINGS_MODULE", "talerbank.settings_admin")
 os.environ.setdefault("TALER_PREFIX", "@prefix@")
 site.addsitedir("%s/lib/python%d.%d/site-packages" % (
     "@prefix@", 
     sys.version_info.major,
     sys.version_info.minor))
 
-from django.conf import settings
-settings.ROOT_URLCONF = 'talerbank.app.urlsadmin'
-
 import django
 django.setup()
 
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/bank.wsgi.in b/bank.wsgi.in
index ba1695a..f276b99 100644
--- a/bank.wsgi.in
+++ b/bank.wsgi.in
@@ -13,9 +13,6 @@ site.addsitedir("%s/lib/python%d.%d/site-packages" % (
     sys.version_info.major,
     sys.version_info.minor))
 
-from django.conf import settings
-settings.ROOT_URLCONF = 'talerbank.app.urls'
-
 import django
 django.setup()
 
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/taler-bank-manage.in b/taler-bank-manage.in
index 69b6e6f..911782f 100644
--- a/taler-bank-manage.in
+++ b/taler-bank-manage.in
@@ -10,7 +10,6 @@ import sys
 import os
 import site
 
-os.environ.setdefault("DJANGO_SETTINGS_MODULE", "talerbank.settings")
 os.environ.setdefault("TALER_PREFIX", "@prefix@")
 site.addsitedir("%s/lib/python%d.%d/site-packages" % (
     "@prefix@",
@@ -121,6 +120,13 @@ p.set_defaults(func=handle_config)
 args = parser.parse_args()
 
 token = "bank%s" % ("-admin" if args.admin else "")
+
+settings_module = "talerbank.settings"
+if token == "bank-admin":
+    settings_module = "talerbank.settings_admin"
+
+os.environ.setdefault("DJANGO_SETTINGS_MODULE", settings_module)
+
 logger.info("Setting token to %s" % token)
 
 if args.altdb:
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 a322995..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-Z]+)', 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 CurrencyMismatchError()
-
-    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 0bca072..0000000
--- a/talerbank/app/captcha.py
+++ /dev/null
@@ -1,108 +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={'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']
-    return render(request, 'pin_tan.html', {'form': Pin(auto_id=False),
-                                            'amount': amounts.floatify(amount),
-                                            'currency': 
settings.TALER_CURRENCY,
-                                            '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:
-        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 = {'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)
-
-    else:
-        return render(request, 'error.html', {'type': "wrong_pin"}, status=400)
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 926c077..0000000
--- a/talerbank/app/funds.py
+++ /dev/null
@@ -1,170 +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 mints,
-# And mints 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_tan')
-    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 o.path != '/':
-        return JsonResponse({'error': 'bad exchange base url',
-                             'given_url': reserve_set.mint}, 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 JsonResponse({'error': "internal error",
-                             'hint': "wire transfer failed (bad response)",
-                             'status': res.status_code,
-                             'body': res.text}, status=500)
-    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 fb97be4..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_process(request):
-    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/management/commands/dump_talerdb.py 
b/talerbank/app/management/commands/dump_talerdb.py
index 8f7b099..89c4933 100644
--- a/talerbank/app/management/commands/dump_talerdb.py
+++ b/talerbank/app/management/commands/dump_talerdb.py
@@ -15,11 +15,13 @@
 #  @author Marcello Stanisci
 
 from django.core.management.base import BaseCommand
-from ...models import BankAccount, History
+from ...models import BankAccount, BankTransaction
 from django.db.utils import OperationalError, ProgrammingError
 import logging
 import sys
 
+# Rewrite to match new BankTransaction layout.
+
 logger = logging.getLogger(__name__)
 
 def dump_accounts():
@@ -40,15 +42,15 @@ def dump_accounts():
 
 def dump_history():
     try:
-        history = History.objects.all()
+        history = BankTransaction.objects.all()
         for item in history:
             msg = []
             # concatenating via 'append' because the + operator put
             # as the first/last character on a line makes flake8 complain
-            msg.append("Account ")
-            msg.append(item.account.user.username + ": ")
-            msg.append(item.direction + " " + str(item.amount) + " ")
-            msg.append(item.currency + " on " + str(item.date))
+            msg.append("+%s, " % item.credit_account.account_no)
+            msg.append("-%s, " % item.debit_account.account_no)
+            msg.append("%.2f, " % item.amount)
+            msg.append(item.subject)
             print(''.join(msg))
     except (OperationalError, ProgrammingError):
         logger.error("likely causes: non existent DB or unmigrated project\n"
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 7a8ce6c..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,13 +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)
diff --git a/talerbank/app/schemas.py b/talerbank/app/schemas.py
index fd71b14..d4ba21b 100644
--- a/talerbank/app/schemas.py
+++ b/talerbank/app/schemas.py
@@ -20,46 +20,48 @@ 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"},
+            }
+        }
+    }
+}
 
-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"},
+        "exchange_url": {"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/Makefile.am b/talerbank/app/static/Makefile.am
index 5b2484f..e45c105 100644
--- a/talerbank/app/static/Makefile.am
+++ b/talerbank/app/static/Makefile.am
@@ -2,4 +2,6 @@ SUBDIRS = . web-common
 
 EXTRA_DIST = \
   favicon.ico \
-  style.css
+  style.css \
+  disabled-button.css \
+  chrome-store-link.js
diff --git a/talerbank/app/static/profile-page.js 
b/talerbank/app/static/profile-page.js
index 62f8eb3..9839e03 100644
--- a/talerbank/app/static/profile-page.js
+++ b/talerbank/app/static/profile-page.js
@@ -28,6 +28,7 @@ var bank_currency;
 var precision;
 var callback_url;
 var reserve_pub;
+var suggested_exchange;
 
 
 /**
@@ -43,23 +44,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 +61,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]
+  };
 };
 
 
@@ -88,20 +74,20 @@ function init() {
   precision = getConst("precision");
   callback_url = getConst("callback-url");
   reserve_pub = getConst("reserve-pub");
+  suggested_exchange = getConst("suggested-exchange");
   if (reserve_pub) {
     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"]);
+    taler.createReserve(callback_url, amount, ["TEST"], suggested_exchange);
   };
 }
 
@@ -111,4 +97,3 @@ if (document.readyState == "loading") {
 } else {
   init();
 }
-
diff --git a/talerbank/app/static/web-common b/talerbank/app/static/web-common
index bf0e04e..dc9d5ab 160000
--- a/talerbank/app/static/web-common
+++ b/talerbank/app/static/web-common
@@ -1 +1 @@
-Subproject commit bf0e04ec0d8d87c1c9c888219c3722d07a1bb9bb
+Subproject commit dc9d5ab2308fef7cdd1e8c95fbf4fdd51bed7bfb
diff --git a/talerbank/app/templates/Makefile.am 
b/talerbank/app/templates/Makefile.am
index 43ca4a2..34751ff 100644
--- a/talerbank/app/templates/Makefile.am
+++ b/talerbank/app/templates/Makefile.am
@@ -5,9 +5,8 @@ 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 \
-  public_account_details.html \
   register.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/base.html 
b/talerbank/app/templates/base.html
index 04649dc..a532ae2 100644
--- a/talerbank/app/templates/base.html
+++ b/talerbank/app/templates/base.html
@@ -16,23 +16,23 @@
   @author Marcello Stanisci
 -->
 
-{% load staticfiles %}
+{% load static from mystatic %}
 
 <html data-taler-nojs="true">
   <head>
     <title>{{ currency }} Bank - Taler Demo</title>
-    <link rel="stylesheet" type="text/css" href="/static/web-common/style.css">
-    <link rel="stylesheet" type="text/css" href="/static/style.css">
-    <link rel="stylesheet" type="text/css" 
href="/static/web-common/taler-fallback.css" id="taler-presence-stylesheet" />
+    <link rel="stylesheet" type="text/css" href="{% static 
"web-common/style.css" %}">
+    <link rel="stylesheet" type="text/css" href="{% static "style.css" %}">
+    <link rel="stylesheet" type="text/css" href="{% static 
"web-common/taler-fallback.css" %}" id="taler-presence-stylesheet" />
     <link rel="stylesheet" type="text/css" href="{% static 
"web-common/lang.css" %}">
-    <!--#include virtual="{% static "web-common/lang.html" %}" -->
+    {% include "lang.html" %} 
     <script src="{% static "web-common/lang.js" %}" 
type="application/javascript"></script>
     <script src="{% static "web-common/taler-wallet-lib.js" %}" 
type="application/javascript"></script>
     {% block head %} {% endblock %}
   </head>
   <body class="en">
     <header>
-      <a href="/">
+      <a href="{% url "index" %}">
         <div id="logo">
           <svg height="100" width="100">
             <circle cx="50" cy="50" r="40" stroke="darkmagenta" 
stroke-width="6" fill="white" />
@@ -42,7 +42,7 @@
           </svg>
         </div>
       </a>
-      <!--#include virtual="/static/web-common/dropdown-navbar.html" -->
+      {% include "dropdown-navbar.html" %} 
       {% block headermsg %} {% endblock %}
     </header>
     {% block content %} {% endblock %}
diff --git a/talerbank/app/templates/error.html 
b/talerbank/app/templates/error.html
deleted file mode 100644
index 7ec46e1..0000000
--- a/talerbank/app/templates/error.html
+++ /dev/null
@@ -1,72 +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>Error</h1>
-{% endblock headermsg %}
-{% block content %}
-  <aside class="sidebar" id="left">
-  </aside>
-  <section id="main">
-    <article>
-      <div>
-        <h3>
-         {% if type == "wrong_pin" %}
-           Wrong PIN
-         {% elif type == "non_supported_method" %}
-           Non supported wire method
-         {% elif type == "internal_error" %}
-           Internal error
-         {% elif type == "exchange_unknown" %}
-           Exchange unknown
-         {% elif type == "not_logged" %}
-             User not logged
-         {% elif type == "custom" %}
-           {% if not custom_title %}
-             Error
-           {% else %}
-             {{ custom_title }}
-           {% endif %}
-         {% endif %}
-       </h3>
-       <p>
-         {% if type == "wrong_pin" %}
-           Return to your <a href="/profile">profile page</a>
-         {% elif type == "internal_error" %}
-           Internal error: {{ hint }}
-         {% elif type == "non_supported_method" %}
-           This bank supports TEST wire method only
-         {% elif type == "exchange_unknown" %}
-           Given exchange does not exist (try 'http' instead of 'https'?) 
<!---ok, not user-friendly but informative for developers -->
-         {% elif type == "not_logged" %}
-           Operation not possible, log in at <a href="/">home page</a>
-         {% elif type == "custom" %}
-           {% if not custom_message %}
-             error details not given
-           {% else %}
-             {{ custom_message }}
-           {% endif %}
-         {% endif %}
-       </p>
-      </div>
-    </article>
-  </section>
-{% endblock content %}
diff --git a/talerbank/app/templates/public_account_details.html 
b/talerbank/app/templates/error_exchange.html
similarity index 78%
rename from talerbank/app/templates/public_account_details.html
rename to talerbank/app/templates/error_exchange.html
index 38dfb5f..e3319a6 100644
--- a/talerbank/app/templates/public_account_details.html
+++ b/talerbank/app/templates/error_exchange.html
@@ -1,5 +1,5 @@
 <!DOCTYPE html>
-<!-- 
+<!--
   This file is part of GNU TALER.
   Copyright (C) 2014, 2015, 2016 INRIA
 
@@ -15,26 +15,28 @@
   TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
 
   @author Marcello Stanisci
-  @brief Page showing just the account number from one public account.
+  @author Florian Dold
 -->
 {% extends "base.html" %}
-{% load staticfiles %}
 
 {% block headermsg %}
-  <h1>Public account '{{ account_name }}'</h1>
+  <h1>Error</h1>
 {% endblock headermsg %}
-
 {% block content %}
   <aside class="sidebar" id="left">
   </aside>
   <section id="main">
     <article>
-      <div>
-        <h3>
-        Account number of {{ account_name }}
-       </h3>
-       <p>{{ account_no }}</p>
-      </div>
+      <p>
+      {{ message }}
+      </p>
+      <p>
+      Status: {{ response_status }}
+      </p>
+      <p>
+      Response body:
+      <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/home_page.html
deleted file mode 100644
index f53bc48..0000000
--- a/talerbank/app/templates/home_page.html
+++ /dev/null
@@ -1,83 +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">Welcome to the {{ currency }} Bank!</h1>
-  <h1 lang="it" class="nav">Banca {{ currency }} vi da il benvenuto!</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.
-        </p>
-        {% endif %}
-        {% if logged_out %}
-       <p class="informational informational-ok">
-          Successfully logged out!
-        </p>
-        {% endif %}
-       <table>
-          <form method="post" action="{% url 'login' %}">
-            {% csrf_token %}
-            <input type="text" name="username" placeholder="username" 
autofocus></input>
-            <input type="password" name="password" 
placeholder="password"></input>
-            <input type="submit" value="Ok"></input>
-          </form>
-       </table>
-      </div>
-      <p lang="en">
-      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 }}!
-      </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,
-      please <a href="/public-accounts">click here</a>.
-      </p>
-      <p lang="it">
-      Per vedere l'attivit&agrave; dei conti pubblici,
-      <a href="/public-accounts">clicca qui</a>.
-      </p>
-    </article>
-  </section>
-{% endblock content %}
diff --git a/talerbank/app/templates/login.html 
b/talerbank/app/templates/login.html
new file mode 100644
index 0000000..b9d6020
--- /dev/null
+++ b/talerbank/app/templates/login.html
@@ -0,0 +1,76 @@
+<!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
+  @author Florian Dold
+-->
+
+{% extends "base.html" %}
+{% load settings_value from settings %}
+
+{% block headermsg %}
+  <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">
+    <article>
+      <div class="login-form">
+        <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 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 %}
+            {{ form.username }}
+            <input type="password" name="password" 
placeholder="password"></input>
+            <input type="submit" value="login" />
+            <input type="hidden" name="next" value="{{ next }}" />
+          </form>
+       </table>
+      </div>
+      <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 {% settings_value "TALER_CURRENCY" %}!
+      </p>
+      <p>
+      To view transactions of public accounts,
+      please <a href="{% url "public-accounts" %}">click here</a>.
+      </p>
+    </article>
+  </section>
+{% endblock content %}
diff --git a/talerbank/app/templates/pin_tan.html 
b/talerbank/app/templates/pin_tan.html
index 93b433d..244cf9c 100644
--- a/talerbank/app/templates/pin_tan.html
+++ b/talerbank/app/templates/pin_tan.html
@@ -18,26 +18,34 @@
 -->
 
 {% 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>
   <section id="main">
     <article>
+      {% if previous_failed %}
+      <p class="informational informational-fail">
+        The captcha wasn't solved correctly.  Please try again.
+      </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; (*):
       </p>
-      <form method="post" action="/pin/verify">
+      <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
@@ -47,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 c0ce9d4..dd2ca67 100644
--- a/talerbank/app/templates/profile_page.html
+++ b/talerbank/app/templates/profile_page.html
@@ -17,18 +17,21 @@
   @author Marcello Stanisci
 -->
 {% extends "base.html" %}
-{% load staticfiles %}
+{% load static from mystatic %}
 
 {% block head %}
   <meta name="currency" value="{{ currency }}">
   <meta name="precision" value="{{ precision }}">
-  <meta name="callback-url" value="{% url 'pin_tan' %}">
+  <meta name="callback-url" value="{% url 'pin-question' %}">
   {% if withdraw and withdraw == "success" %}
     <meta name="reserve-pub" value="{{ reserve_pub }}">
   {% endif %}
+  {% if suggested_exchange %}
+    <meta name="suggested-exchange" value="{{ suggested_exchange }}">
+  {% 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 %}
@@ -38,6 +41,7 @@
 {% block content %}
   <section id="menu">
     <a href="{% url 'logout' %}">[Logout]</a><br>
+    <a href="{% url 'public-accounts' %}">[Public Accounts]</a><br>
     <p>
     Account:<br>
     # {{ account_no }}
@@ -52,24 +56,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>
@@ -101,26 +97,16 @@
               {% endif %}
               name="tform">
          {% csrf_token %}
-          <!-- FIXME: This 'input' field seems not used, make sure it's not
-               then remove it -->
-          <input type="text" id="reserve-pk-input" name="reserve_pk" 
hidden></input>
           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 type="text" name="mint_rcv" id="kudos-mint" hidden></input>
           <input id="select-exchange"
                  class="taler-installed-show"
-                 {% if js == 'use_js' %}
+                 {% if use_js %}
                    type="button"
                  {% else %}
                    type="submit"
@@ -133,6 +119,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>
@@ -150,8 +143,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>
@@ -170,6 +162,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/management/__init__.py 
b/talerbank/app/templatetags/__init__.py
similarity index 100%
copy from talerbank/app/management/__init__.py
copy to talerbank/app/templatetags/__init__.py
diff --git a/talerbank/app/templatetags/mystatic.py 
b/talerbank/app/templatetags/mystatic.py
new file mode 100644
index 0000000..2c57841
--- /dev/null
+++ b/talerbank/app/templatetags/mystatic.py
@@ -0,0 +1,30 @@
+from django import template
+from django.conf import settings
+from urllib.parse import urlparse
+from django.core.urlresolvers import get_script_prefix
+
+def is_absolute(url):
+    return bool(urlparse(url).netloc)
+
+def join_urlparts(*parts):
+    s = ""
+    i = 0
+    while i < len(parts):
+        n = parts[i]
+        i += 1
+        if s.endswith("/"):
+            n = n.lstrip("/")
+        elif s and  not n.startswith("/"):
+            n = "/" + n
+        s += n
+    return s
+
+
+register = template.Library()
+
address@hidden(takes_context=True)
+def static(context, url):
+    if is_absolute(url):
+        return url
+    request = context["request"]
+    return join_urlparts(get_script_prefix(), settings.STATIC_URL, url)
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/tests.py b/talerbank/app/tests.py
index 30ffb1e..7b437f1 100644
--- a/talerbank/app/tests.py
+++ b/talerbank/app/tests.py
@@ -14,145 +14,64 @@
 #
 #  @author Marcello Stanisci
 
-from django.test import TestCase, TransactionTestCase, Client
+from django.test import TestCase, Client
 from django.core.urlresolvers import reverse
+from django.conf import settings
 from django.contrib.auth.models import User
-from .user import register_process
 from .models import BankAccount
-from django.conf import settings
-from .funds import wire_transfer_in_out
-from .errors import BadWireDetails
-from django.db import connection
-from .management.commands import provide_accounts
-from . import history
-from .amounts import floatify
+from . import urlsadmin, urls
+
 import logging
-import json
 
 logger = logging.getLogger(__name__)
 
-
-def create_bank_and_user():
-    bank = User.objects.create_user(username='Bank')
-    ba = BankAccount(user=bank, currency=settings.TALER_CURRENCY)
-    ba.save()    
-    user = User.objects.create_user(username='test_login', 
password='test_login')
-    ua = BankAccount(user=user, currency=settings.TALER_CURRENCY)
-    ua.save()
+def clearDb():
+    User.objects.all().delete()
+    BankAccount.objects.all().delete()
 
 
-def reset_db():
-    users = User.objects.all()
-    for u in users:
-        u.delete()
-    cursor = connection.cursor()
-    # Zero the 'account_no' autoincrement counter, because
-    # tables' deletion occurring after each test* function
-    # does NOT reset that counter, and this causes problem when
-    # creating 'Bank' user, since it gets account_no != 1, and
-    # this breaks wiretransfers involving the bank
-    cursor.execute("ALTER SEQUENCE app_bankaccount_account_no_seq RESTART")
-
-class UserTestCase(TestCase):
-    """Test user registration/login/logout"""
+class RegisterTestCase(TestCase):
+    """User registration"""
 
     def setUp(self):
-        create_bank_and_user()
+        bank = User.objects.create_user(username='Bank')
+        ba = BankAccount(user=bank, currency=settings.TALER_CURRENCY)
+        ba.account_no = 1
+        ba.save() 
 
     def tearDown(self):
-        reset_db()
+        clearDb()
 
     def test_register(self):
         c = Client()
-        response = c.post(reverse('register'),
-                          {'username': 'test_register',
-                           'password': 'test_register'},
+        response = c.post(reverse("register", urlconf=urls),
+                          {"username": "test_register",
+                           "password": "test_register"},
                            follow=True)
         self.assertIn(("/profile", 302), response.redirect_chain)
-        # this assertion tests "/profile"'s view
+        # this assertion tests "/profile""s view
         self.assertEqual(200, response.status_code)
-    
 
-    def test_login(self):
-        c = Client()
-        response = c.post(reverse('login'),
-                          {'username': 'test_login',
-                           'password': 'test_login'},
-                          follow=True)
-        self.assertEqual(200, response.status_code)
-        response = c.get(reverse('logout'))
-        self.assertEqual(302, response.status_code)
 
+class LoginTestCase(TestCase):
+    """User login"""
 
-class FundsTestCase(TestCase):
     def setUp(self):
-        logging.disable(logging.CRITICAL)
-        create_bank_and_user()
-
-    def tearDown(self):
-        logging.disable(logging.NOTSET)
-        reset_db()
-
-    def test_wiretransfer(self):
-        wire_transfer_in_out({'value': 100,
-                              'fraction': 0,
-                              'currency': settings.TALER_CURRENCY},
-                             1,
-                             2,
-                             "Joining bonus")
-    def test_addincoming(self):
-        c = Client()
-        data = {'amount': {'value': 1, 'fraction': 0, 'currency': 
settings.TALER_CURRENCY},
-                'debit_account': 1,
-                'credit_account': 2,
-                'wtid': 'TEST'}
-        response = c.post(reverse('add-incoming'), json.dumps(data), 
content_type="application/json")
-        self.assertEqual(200, response.status_code)
+        user = User.objects.create_user(username="test_user",
+                                        password="test_password")
+        user_account = BankAccount(user=user,
+                                   currency=settings.TALER_CURRENCY)
+        user_account.save()
 
-class HistoryTestCase(TestCase):
-    def setUp(self):
-        logging.disable(logging.CRITICAL)
-        provide_accounts.basic_accounts()
-        provide_accounts.demo_accounts()
 
     def tearDown(self):
-        logging.disable(logging.NOTSET)
-
-    # Extract history from public accounts
-    def test_public_history(self):
-        c = Client() 
-        response = c.get(reverse('public-history'), follow=True)
-        self.assertEqual(200, response.status_code)
-        response = c.get(reverse('public-history'), {'account': 'GNUnet'})
-        self.assertEqual(200, response.status_code)
-
+        clearDb()
+    
 
-class CaptchaTestCase(TestCase):
-    def test_pin_tan_question(self): 
+    def test_login(self):
         c = Client()
-        User.objects.create_user(username='test_pintan', 
password='test_pintan')
-        c.login(username='test_pintan', password='test_pintan')
-        wiredetails = {'test':
-                          {'type': 'test',
-                           'account_number': 0,
-                           'bank_uri': 'http://test',
-                           'name': 'test',
-                           'salt': 'test',
-                           'sig': 'test',
-                           'uri': 'test'}}   
-        data = {'amount_value': 1,
-                'amount_fraction': 0,
-                'amount_currency': settings.TALER_CURRENCY,
-                'exchange': 'http://test',
-                'reserve_pub': 'TEST',
-                'wire_details': json.dumps(wiredetails)}
-        response = c.get(reverse('pin_tan'), data)
-        self.assertEqual(200, response.status_code)
-
-
-class FloatifyTestCase(TestCase):
-    def test_floatify(self):
-        data = {'value': 3, 'fraction': 700, 'currency': 
settings.TALER_CURRENCY}
-        result = 3 + (float(700) / float(1000000))
-        ret = floatify(data)
-        self.assertEqual(result, ret)
+        response = c.post(reverse("login", urlconf=urls),
+                          {"username": "test_user",
+                           "password": "test_password"},
+                           follow=True)
+        self.assertIn(("/profile", 302), response.redirect_chain)
diff --git a/talerbank/app/tests_admin.py b/talerbank/app/tests_admin.py
new file mode 100644
index 0000000..8adbfe1
--- /dev/null
+++ b/talerbank/app/tests_admin.py
@@ -0,0 +1,62 @@
+#  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.test import TestCase, Client
+from django.core.urlresolvers import reverse
+from django.conf import settings
+from django.contrib.auth.models import User
+from .models import BankAccount
+from . import urlsadmin, urls
+
+import logging
+
+logger = logging.getLogger(__name__)
+
+
+class AddIncomingTestCase(TestCase):
+    """Test money transfer's API"""
+
+    def setUp(self):
+        bank = User.objects.create_user(username="bank_user",
+                                        password="bank_password")
+        bank_account = BankAccount(user=bank,
+                                   currency=settings.TALER_CURRENCY)
+        user = User.objects.create_user(username="user_user",
+                                        password="user_password")
+        user_account = BankAccount(user=user,
+                                   currency=settings.TALER_CURRENCY)
+        bank_account.save()
+        user_account.save()
+
+    def tearDown(self):
+        User.objects.all().delete()
+        BankAccount.objects.all().delete()
+
+    def test_add_incoming(self):
+        c = Client()
+        data = '{"debit_account":1, \
+                 "credit_account":2, \
+                 "wtid":"TESTWTID", \
+                 "exchange_url":"https://exchange.test";, \
+                 "amount": \
+                   {"value":1, \
+                    "fraction":0, \
+                    "currency":"%s"}}' \
+               % settings.TALER_CURRENCY
+        response = c.post(reverse("add-incoming", urlconf=urlsadmin),
+                          data=data,
+                          content_type="application/json",
+                          follow=True)
diff --git a/talerbank/app/urls.py b/talerbank/app/urls.py
index 9520827..752dd6a 100644
--- a/talerbank/app/urls.py
+++ b/talerbank/app/urls.py
@@ -15,24 +15,22 @@
 #  @author Marcello Stanisci
 
 from django.conf.urls import include, url
-from . import user
-from . import funds
+from django.views.generic.base import RedirectView
 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_process, name="register"),
-    url(r'^accounts/login/$', views.home_page, name="login"),
-    url(r'^accounts/logout/$', user.logout_process, 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/details$', user.view_public_accno_process),
-    url(r'^public-accounts$', history.public_accounts_process, 
name="public-history"),
-    url(r'^pin/question$', captcha.pin_tan_question, name="pin_tan"),
-    url(r'^pin/verify$', captcha.pin_tan_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/urlsadmin.py b/talerbank/app/urlsadmin.py
index 9f2f78b..e2e51f8 100644
--- a/talerbank/app/urlsadmin.py
+++ b/talerbank/app/urlsadmin.py
@@ -15,9 +15,9 @@
 #  @author Marcello Stanisci
 
 from django.conf.urls import include, url
-from . import funds
+from . import views
 
 urlpatterns = [
     url(r'^', include('talerbank.urls')),
-    url(r'^admin/add/incoming$', funds.add_incoming, name="add-incoming"),
+    url(r'^admin/add/incoming$', views.add_incoming, name="add-incoming"),
     ]
diff --git a/talerbank/app/user.py b/talerbank/app/user.py
deleted file mode 100644
index cc73415..0000000
--- a/talerbank/app/user.py
+++ /dev/null
@@ -1,110 +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_process(request):
-    """
-    register a new user giving 100 KUDOS bonus
-    """
-    wrong_field = False
-    not_available = False
-    if request.method == 'POST':
-        form = UserReg(request.POST)
-        if 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_process(request):
-    del request.session["account_no"]
-    logout(request)
-    request.session['logged_out'] = True
-    return redirect("/")
-
-
-def view_public_accno_process(request):
-    """
-    Return a HTML page showing which account number has the
-    pubblic account given in parameter 'account'.
-    """
-    account_name = request.GET.get('account')
-    if not account_name:
-        return bad_get_parameter_handler(request, 'account')
-    # FIXME the following function must be exception-driven
-    bank_account = get_bank_account_from_username(account_name)
-    if not bank_account or not bank_account.is_public:
-        return render(request,
-                      'error.html',
-                      {'type': 'custom',
-                       'custom_title': "Unknown public account",
-                       'custom_message': "Wrong account name given"},
-                      status=405)
-    return render(request,
-                  'public_account_details.html',
-                  {'account_no': bank_account.account_no,
-                   'account_name': account_name})
diff --git a/talerbank/app/views.py b/talerbank/app/views.py
index df19b95..d3b8428 100644
--- a/talerbank/app/views.py
+++ b/talerbank/app/views.py
@@ -15,97 +15,383 @@
 #  @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.db import transaction
+from django import forms
 from django.conf import settings
+from django.contrib.auth.decorators import login_required
+from django.http import JsonResponse, 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("/")
-        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
+
+
+# Check if user's logged in.  Check if he/she has withdrawn or
+# registered; render profile page.
 
 @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'])
+def profile_page(request):
+    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,
+    )
+    if settings.TALER_SUGGESTED_EXCHANGE:
+        context["suggested_exchange"] = settings.TALER_SUGGESTED_EXCHANGE
+
+    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))
+    with transaction.atomic():
+        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"))
+    subject = "%s %s" % (data["wtid"], data["exchange_url"])
+    logger.info("Submitting wire transfer: '%s'", subject)
+    try:
+        schemas.validate_incoming_request(data)
+    except ValueError:
+        return HttpResponseBadRequest()
+    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,
+                  subject)
+    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)
+    if settings.TALER_SUGGESTED_EXCHANGE:
+        response["X-Taler-Suggested-Exchange"] = 
settings.TALER_SUGGESTED_EXCHANGE
+    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)
+    # Build subject including Exchange base URL here.
+    logger.info("Reserve data: '%s'" % json.dumps(res.json()))
+    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
 
-    return response    
+    with transaction.atomic():
+        debit_account.save()
+        credit_account.save()
+        transaction_item.save()
diff --git a/talerbank/settings.py b/talerbank/settings.py
index f7864dd..6542acf 100644
--- a/talerbank/settings.py
+++ b/talerbank/settings.py
@@ -1,180 +1,2 @@
-"""
-Django settings for talerbank.
-
-For more information on this file, see
-https://docs.djangoproject.com/en/1.9/topics/settings/
-
-For the full list of settings and their values, see
-https://docs.djangoproject.com/en/1.9/ref/settings/
-"""
-
-import os
-import logging
-import base64
-from .talerconfig import TalerConfig
-import sys
-import urllib.parse
-
-logger = logging.getLogger(__name__)
-
-tc = TalerConfig.from_file(os.environ.get("TALER_CONFIG_FILE"))
-
-# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
-BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
-
-# Quick-start development settings - unsuitable for production
-# See https://docs.djangoproject.com/en/1.9/howto/deployment/checklist/
-
-
-SECRET_KEY = os.environ.get("TALER_BANK_SECRET_KEY", None)
-
-if not SECRET_KEY:
-    logging.info("secret key not configured in TALER_BANK_SECRET_KEY env 
variable, generating random secret")
-    SECRET_KEY = base64.b64encode(os.urandom(32)).decode('utf-8')
-
-# SECURITY WARNING: don't run with debug turned on in production!
-DEBUG = True
-
-ALLOWED_HOSTS = ["*"]
-
-
-# Application definition
-
-INSTALLED_APPS = [
-    'django.contrib.admin',
-    'django.contrib.auth',
-    'django.contrib.contenttypes',
-    'django.contrib.sessions',
-    'django.contrib.messages',
-    'django.contrib.staticfiles',
-    'talerbank.app'
-]
-
-MIDDLEWARE_CLASSES = [
-    'django.middleware.security.SecurityMiddleware',
-    'django.contrib.sessions.middleware.SessionMiddleware',
-    'django.middleware.common.CommonMiddleware',
-    'django.middleware.csrf.CsrfViewMiddleware',
-    'django.contrib.auth.middleware.AuthenticationMiddleware',
-    '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)
-ROOT_URLCONF = ''
-
-TEMPLATES = [
-    {
-        'BACKEND': 'django.template.backends.django.DjangoTemplates',
-        'DIRS': [],
-        'APP_DIRS': True,
-        'OPTIONS': {
-            'context_processors': [
-                'django.template.context_processors.debug',
-                'django.template.context_processors.request',
-                'django.contrib.auth.context_processors.auth',
-                'django.contrib.messages.context_processors.messages',
-            ],
-        },
-    },
-]
-
-WSGI_APPLICATION = 'talerbank.wsgi.application'
-
-
-# Database
-# https://docs.djangoproject.com/en/1.9/ref/settings/#databases
-
-# parse a database URL, django can't natively do this!
-dbname = tc.value_string("bank", "database", required=True)
-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
-    else:
-        host = p["host"][0]
-else:
-    host = db_url.netloc
-
-if host:
-    dbconfig["HOST"] = host
-
-logger.info("db string '%s'", dbname)
-logger.info("db info '%s'", dbconfig)
-
-DATABASES = {
-    'default': dbconfig
-}
-
-
-# Password validation
-# https://docs.djangoproject.com/en/1.9/ref/settings/#auth-password-validators
-
-AUTH_PASSWORD_VALIDATORS = [
-    {
-        'NAME': 
'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
-    },
-    {
-        'NAME': 
'django.contrib.auth.password_validation.MinimumLengthValidator',
-    },
-    {
-        'NAME': 
'django.contrib.auth.password_validation.CommonPasswordValidator',
-    },
-    {
-        'NAME': 
'django.contrib.auth.password_validation.NumericPasswordValidator',
-    },
-]
-
-
-# Internationalization
-# https://docs.djangoproject.com/en/1.9/topics/i18n/
-
-LANGUAGE_CODE = 'en-us'
-
-TIME_ZONE = 'UTC'
-
-USE_I18N = True
-
-USE_L10N = True
-
-USE_TZ = True
-
-
-# Static files (CSS, JavaScript, Images)
-# https://docs.djangoproject.com/en/1.9/howto/static-files/
-
-STATIC_URL = '/static/'
-
-STATICFILES_DIRS = [
-    os.path.join(BASE_DIR, "talerbank/app/static"),
-]
-
-# Currently we don't use "collectstatic", so this value isn't used.
-# Instead, we serve static files directly from the installed python package
-# via the "django.contrib.staticfiles" app.
-# We must set it to something valid though, # or django will give us warnings.
-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_PREDEFINED_ACCOUNTS = ['Tor', 'GNUnet', 'Taler', 'FSF', 'Tutorial']
-TALER_EXPECTS_DONATIONS = ['Tor', 'GNUnet', 'Taler', 'FSF']
+from talerbank.settings_base import *
+ROOT_URLCONF = "talerbank.app.urls"
diff --git a/talerbank/settings_admin.py b/talerbank/settings_admin.py
new file mode 100644
index 0000000..b2ce6a3
--- /dev/null
+++ b/talerbank/settings_admin.py
@@ -0,0 +1,2 @@
+from talerbank.settings_base import *
+ROOT_URLCONF = "talerbank.app.urlsadmin"
diff --git a/talerbank/settings.py b/talerbank/settings_base.py
similarity index 75%
copy from talerbank/settings.py
copy to talerbank/settings_base.py
index f7864dd..f92d9f3 100644
--- a/talerbank/settings.py
+++ b/talerbank/settings_base.py
@@ -17,6 +17,8 @@ import urllib.parse
 
 logger = logging.getLogger(__name__)
 
+logger.info("DJANGO_SETTINGS_MODULE: %s" % 
os.environ.get("DJANGO_SETTINGS_MODULE"))
+
 tc = TalerConfig.from_file(os.environ.get("TALER_CONFIG_FILE"))
 
 # Build paths inside the project like this: os.path.join(BASE_DIR, ...)
@@ -37,6 +39,10 @@ DEBUG = True
 
 ALLOWED_HOSTS = ["*"]
 
+LOGIN_URL = "login"
+
+LOGIN_REDIRECT_URL = "index"
+
 
 # Application definition
 
@@ -59,16 +65,12 @@ 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)
-ROOT_URLCONF = ''
-
 TEMPLATES = [
     {
         'BACKEND': 'django.template.backends.django.DjangoTemplates',
-        'DIRS': [],
+        'DIRS': [os.path.join(BASE_DIR, "talerbank/app/static/web-common/")],
         'APP_DIRS': True,
         'OPTIONS': {
             'context_processors': [
@@ -87,33 +89,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
@@ -166,15 +174,10 @@ 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']
+TALER_SUGGESTED_EXCHANGE = tc.value_string("bank", "suggested_exchange")
+
+logging.info("currency: '%s'", TALER_CURRENCY)

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



reply via email to

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