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 (1bae352 -> 1238da4)


From: gnunet
Subject: [GNUnet-SVN] [taler-bank] branch stable updated (1bae352 -> 1238da4)
Date: Wed, 31 May 2017 17:15:57 +0200

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

marcello pushed a change to branch stable
in repository bank.

    from 1bae352  kill navbar until we have a better one that works
     add 60224f6  navbar on the side
     add 921106c  jinja conversion
     add 0c773d6  list instead of table for accounts
     add 39d772e  eh, oops
     add ef8ed34  table style / heading fix
     add 5c035cf  Fix public accounts table
     add f6dd7ee  public accounts markup
     add a5293d8  public accounts markup
     add 5b05c4d  markup
     add 41bee23  markup
     add e1388d3  stylesheet from web-common
     add 4b3027a  copyright / js license
     add 5ad6c1b  various fixes
     add 304b245  take URLs from env
     add b1d91a7  fix variable name clash
     add 4bf2350  submodules
     add ef9a605  submodules
     add c581f2c  sidebar links
     add 68e731e  fix pin/tan form
     add b2adea3   #4824, not finished yet.
     add a37e84d  fix #4824's related issues
     add 635ea32  Getting rid of the sign in front of amount's "value" field, 
and implementing a flag-based way of accounting debts.
     add 0f99308  Managing uncovered cases related to debits.
     add 976763e  Check if the user respects his debt threshold.
     add 11a7e1e  Managing 3/4 of debt limit exceeded exceptions, and getting 
rid of helper function used to create reserves.
     add 6f7b1f5  Better creation of initial accounts, logging statements.
     add 95810a8  Finish fixing #4949's minor issues.
     add 573b2cc  Minor fixes on debit mgmt.
     add 5c2bdea  Give defaults for debt limit
     add 78334a4  typo
     add 2abf4bb  fix db dump
     add c743f49  making /admin call authenticated
     add 2789025  defining json schema for /history requests
     add 22945cf  authenticating /history request
     add 0838e22  extract and json-format /history outcome
     add 5d52ca2  fix db string in the default conf file
     add 6c9203e  Reintroducing fixes from #4603, as regressions appeared.
     add bd80ef1  debug print
     add 86285ee  Getting again rid of --system as a pip3's option, as it fails 
on pip3 9.0.1 ..
     add b51725d  readme and remove useless default config
     add 288726e  still on removing useless code
     add 85168e6  including 'authtoken' in INSTALLED_APPS
     add 02a6594  only use nojs-version
     add b72f707  remove rest_framework from settings
     add 1016679  remove ancient jsmin autocheck
     add d62b569  get rid of multichoice regarding db backend: only postgres 
for now.
     add f6c3a64  restoring default config
     add ee81eba  additional fixes to provide default config
     add 382ffb2  implementing auth type basic
     add b3ad8ff  moving "direction" parameter inside POSTed data to /history
     add 27db538  implementing 'direction' in /history
     add 9cb82c4  fix JsonResponse invocation
     add 50b30d7  fixing error objects
     add 70452e7  still polishing error objects
     add 867bf7e  moving auth credentials in the HTTP headers
     add ae74b2d  towards /history refactoring
     add c1b5e96  getting http headers the working way
     add 9d304a5  fix query string composition + related tests
     add 9cdc7cd  add dummy transactions in test
     add 4c48839  deploy FIXME
     add 5168b65  sign & delta dealt with.  testing needed.
     add 5b0ff06  fix query of non-existent future /history element
     add 773c15c  test latest /history record returned
     add 938f20a  test non existent future /history record
     add c511745  additional fixes as of the 'start' /history's argument. new 
crashes when trying to build the history response returning one element having 
row id in the middle of the id samples.
     add c20b274  fix ascending order in query set
     add 6225b37  testing empty responses from /history
     add 139edb5  querying non existent / non owned accounts
     add 4e687bb  simplifying errors handling when authenticating
     add 3645d94  add --enable-debian-system option to install bank to proper 
prefix even on Debian
     add 2ecab8d  include error message for invalid /admin/add/incoming POSTed 
data.
     add f97a7eb  fix #5005
     add fe1b84e  fix default passwords for exchange's testing purposes
     add 2591b26  make "+" sign in 'delta' /history's parameter optional.
     add 9e25a68  mentioning bad json field in error message of 
/admin/add/incoming
     add 10ec225  fix 'sign' regex
     add 6b2e201  remove comments leftovers
     add 8e7202e  dedicated class for currency mismatch errors
     add c23a650  towards removing the 'admin' interface
     add 5e18da7  still on removing admin interface
     add 33360c4  remove dedicated testcase for admin interface
     add 669fff8  fix +n for 'delta'
     add 58127cb  fix /history test, as moving admin test into the other tests 
file altered the way row_ids are defined during the test run.
     add fb9f8bb  rename field to match exchange-lib
     add b49d9ab  remove string formatting where not needed
     add 8c63853  fix fields in history to pacify the exchange's bank-lib
     add 863de3a  fix install-dev for latest pip version
     add 32cb2ab  add debian-specific option to install-dev
     add 6013574  adapt amounts' format to the standard Taler format.
     add f22f3bd  fix logic for /history view selection to properly handle misc 
cornercases
     add 984401d  adding --with-db option, commenting out obsolete command to 
create sample data
     add bbeba15  run 'migrate' from 'django' subparser handler, as virgin dbs 
need that.
     add 068bf61  checking if db exists in 'django' subparser handler (still 
not swallowing the stacktrace)
     add bcbe8f5  remove useless command
     add 368f146  typo
     add 7d862ca  make check uses dedicated config file now (partly fixes #5017)
     add a5f3123  improve installation instructions a bit
     add 979ba26  update README
     add 6f7391b  add option to run tests by wrapper script: 'make 
check_with_wrapper'
     add d730ad2  fix 'make dist'
     add 65dc8a1  adapt currency format in withdraw form to new format
     add fb37e89  invert fields of prevoius fix
     add 9430850  more diagnostics for #5033
     add 85fea9b  addressing #5013
     add 32bde8f  first tests under erroneous circumstances
     add 40dbd51  Add manual authentication before logging a user in. Just 
calling login() without authenticating the user before, returns 200 even for 
wrong login attempts.
     add a3aa430  testing operations with wrong currencies
     add e96c9fb  removing unneeded comments
     add 05d86a1  commenting out faults-injected tests
     add b3486f1  making "name" field in /pin/question URL optional
     add 1238da4  fix typo

No new revisions were added by this update.

Summary of changes:
 .gitignore                                         |    1 +
 Makefile.am                                        |   18 +-
 README                                             |   32 +-
 bank-admin.wsgi.in                                 |   21 -
 bank-check-alt.conf                                |   17 +
 bank-check.conf                                    |   15 +
 bank.conf                                          |   11 +-
 bank.wsgi.in                                       |    1 -
 configure.ac                                       |   27 +-
 install-dev.py.in                                  |   31 +
 run_tests.py                                       |   13 +
 taler-bank-manage.in                               |   40 +-
 talerbank/app/Makefile.am                          |    8 +-
 talerbank/app/amounts.py                           |   65 +-
 talerbank/app/checks.py                            |    4 +-
 talerbank/app/management/commands/dump_talerdb.py  |    3 +-
 .../app/management/commands/provide_accounts.py    |   16 +-
 talerbank/app/migrations/0001_initial.py           |    8 +-
 talerbank/app/models.py                            |   33 +-
 talerbank/app/schemas.py                           |   15 +-
 talerbank/app/static/Makefile.am                   |    2 -
 talerbank/app/static/pure.css                      | 1508 ++++++++++++++++++++
 talerbank/app/static/style.css                     |  158 --
 talerbank/app/static/web-common                    |    2 +-
 talerbank/app/templates/Makefile.am                |    8 +-
 talerbank/app/templates/account_disabled.html      |   20 +-
 talerbank/app/templates/base.html                  |   54 +-
 talerbank/app/templates/login.html                 |   39 +-
 talerbank/app/templates/pin_tan.html               |   54 +-
 talerbank/app/templates/profile_page.html          |   82 +-
 talerbank/app/templates/public_accounts.html       |   99 +-
 talerbank/app/templates/register.html              |   14 +-
 talerbank/app/templatetags/__init__.py             |    0
 talerbank/app/templatetags/mystatic.py             |   30 -
 talerbank/app/templatetags/settings.py             |    8 -
 talerbank/app/tests.py                             |  199 ++-
 talerbank/app/tests_admin.py                       |   62 -
 talerbank/app/tests_err.py                         |  301 ++++
 talerbank/app/urls.py                              |    4 +-
 talerbank/app/urlsadmin.py                         |   23 -
 talerbank/app/views.py                             |  365 +++--
 talerbank/jinja2.py                                |   74 +
 talerbank/settings.py                              |  196 ++-
 talerbank/settings_admin.py                        |    2 -
 talerbank/settings_base.py                         |  183 ---
 45 files changed, 2990 insertions(+), 876 deletions(-)
 delete mode 100644 bank-admin.wsgi.in
 create mode 100644 bank-check-alt.conf
 create mode 100644 bank-check.conf
 create mode 100644 install-dev.py.in
 create mode 100755 run_tests.py
 create mode 100644 talerbank/app/static/pure.css
 delete mode 100644 talerbank/app/static/style.css
 delete mode 100644 talerbank/app/templatetags/__init__.py
 delete mode 100644 talerbank/app/templatetags/mystatic.py
 delete mode 100644 talerbank/app/templatetags/settings.py
 delete mode 100644 talerbank/app/tests_admin.py
 create mode 100644 talerbank/app/tests_err.py
 delete mode 100644 talerbank/app/urlsadmin.py
 create mode 100644 talerbank/jinja2.py
 delete mode 100644 talerbank/settings_admin.py
 delete mode 100644 talerbank/settings_base.py

diff --git a/.gitignore b/.gitignore
index 4c14e6b..e217b27 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,6 +2,7 @@ compile
 missing
 install-sh
 Makefile.in
+install-dev.py
 *.pyc
 *.pyo
 *.swp
diff --git a/Makefile.am b/Makefile.am
index 21d25d0..70b02ce 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -5,7 +5,6 @@ EXTRA_DIST = \
  requirements.txt \
  taler-bank-manage.in \
  bank.wsgi.in \
- bank.conf \
  setup.py \
  contrib/nginx/django_nginx.conf
 
@@ -15,22 +14,25 @@ pkgcfg_DATA = \
   bank.conf
 
 pkgdata_DATA = \
-  bank.wsgi \
-  bank-admin.wsgi
+  bank.wsgi
 
 # link package under prefix to source tree
+.PHONY: install-dev
 install-dev:
-       @pip3 install -e . --install-option="address@hidden@"
+       @$(PYTHON) ./install-dev.py
 
 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
+       @export DJANGO_SETTINGS_MODULE="talerbank.settings" 
TALER_PREFIX="@prefix@" TALER_CONFIG_FILE="bank-check.conf" && python3 -m 
django test talerbank.app.tests
+#      @export DJANGO_SETTINGS_MODULE="talerbank.settings" 
TALER_PREFIX="@prefix@" TALER_CONFIG_FILE="bank-check-alt.conf" && python3 -m 
django test talerbank.app.tests_err
+
+check_with_wrapper:
+       @export DJANGO_SETTINGS_MODULE="talerbank.settings" 
TALER_PREFIX="@prefix@" TALER_CONFIG_FILE="bank-check.conf" && python3 
run_tests.py
 
 # install into prefix
 install-exec-hook:
-       @pip3 install . --install-option="address@hidden@"
+       @pip3 install . --install-option="address@hidden@" @DEBIAN_PIP3_SYSTEM@
        @# force update when sources changed
-       @pip3 install . --install-option="address@hidden@" --upgrade --no-deps
+       @pip3 install . --install-option="address@hidden@" @DEBIAN_PIP3_SYSTEM@ 
--upgrade --no-deps
 
 app:
        @tar czf taler-bank-$(PACKAGE_VERSION)-app.tgz `cat INCLUDE.APP`
diff --git a/README b/README
index 7148927..4ccf8b4 100644
--- a/README
+++ b/README
@@ -1,6 +1,20 @@
 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.
+the GNU Taler payment system.  The bank it primarily meant be used as
+part of a demonstrator for the Taler system.
+
+==================== Dependencies ==========================
+
+-----------
+For Debian:
+-----------
+
+First, you need to:
+
+# apt-get install -t unstable git python3-django python3-psycopg2
+
+Note that "make install" will re-download additional dependencies
+needed for "make check".  For the above, at the time of writing, you
+need Debian unstable, with older versions I get obscure errors.
 
 ================== HOW TO INSTALL THE BANK =================
 
@@ -13,21 +27,23 @@ bank (mostly JavaScript includes), and create the configure 
script.
 
 The next step is to specify the install prefix, run
 
-$ ./configure --prefix=$HOME/local # Adapt to your needs.
+$ export PREFIX=$HOME/local # Adapt to your needs.
+$ ./configure --prefix=$PREFIX
 
 Then the usual GNU-compatible commands, that are
 
-$ make
+$ make install
 
-and
+and optionally
 
-$ make install
+$ export PYTHONPATH=$PREFIX/lib/python3.5/site-packages/
+$ make check # run the tests
 
 ================== HOW TO CONFIGURE THE BANK =================
 
 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.
+To overide this behaviour, give the -c option when launching the bank.
 
 In order to properly run, the bank needs the following parts to be configured
 
@@ -77,6 +93,8 @@ FRACTION = 100000000
 # 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.
+# NOTE, this value is optional, and the bank will fallback to sqlite3
+# if not given.
 DATABASE = postgres:///talerlocal
 
 # The following sections are to configure the "admin" part of
diff --git a/bank-admin.wsgi.in b/bank-admin.wsgi.in
deleted file mode 100644
index 94dc5ed..0000000
--- a/bank-admin.wsgi.in
+++ /dev/null
@@ -1,21 +0,0 @@
-import os
-import sys
-import site
-
-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_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))
-
-import django
-django.setup()
-
-from django.core.wsgi import get_wsgi_application
-
-application = get_wsgi_application()
diff --git a/bank-check-alt.conf b/bank-check-alt.conf
new file mode 100644
index 0000000..9c77a2a
--- /dev/null
+++ b/bank-check-alt.conf
@@ -0,0 +1,17 @@
+# Config file containing intentional errors, used
+# to test how the bank reacts.
+
+[taler]
+
+CURRENCY = KUDOS
+
+[bank]
+
+# Which database should we use?
+DATABASE = postgres:///talerbank
+
+# FIXME
+MAX_DEBT = KUDOS:50
+
+# FIXME
+MAX_DEBT_BANK = KUDOS:0
diff --git a/bank-check.conf b/bank-check.conf
new file mode 100644
index 0000000..b38ccd0
--- /dev/null
+++ b/bank-check.conf
@@ -0,0 +1,15 @@
+
+[taler]
+
+CURRENCY = KUDOS
+
+[bank]
+
+# Which database should we use?
+DATABASE = postgres:///talerbank
+
+# FIXME
+MAX_DEBT = KUDOS:50
+
+# FIXME
+MAX_DEBT_BANK = KUDOS:0
diff --git a/bank.conf b/bank.conf
index 67d4455..c99c2f9 100644
--- a/bank.conf
+++ b/bank.conf
@@ -1,3 +1,10 @@
 [bank]
-uwsgi_serve = tcp
-database = postgres://talerbank
+
+# Which database should we use?
+DATABASE = postgres:///talerbank
+
+# FIXME
+MAX_DEBT = KUDOS:50
+
+# FIXME
+MAX_DEBT_BANK = KUDOS:0
\ No newline at end of file
diff --git a/bank.wsgi.in b/bank.wsgi.in
index f276b99..c15e645 100644
--- a/bank.wsgi.in
+++ b/bank.wsgi.in
@@ -6,7 +6,6 @@ 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("TALER_PREFIX", "@prefix@")
 site.addsitedir("%s/lib/python%d.%d/site-packages" % (
     "@prefix@", 
diff --git a/configure.ac b/configure.ac
index 94f1ba2..72c8047 100644
--- a/configure.ac
+++ b/configure.ac
@@ -33,6 +33,17 @@ AC_MSG_RESULT([$PIP_VERSION])
 
 AX_COMPARE_VERSION([$PIP_VERSION],[lt],[6.0], [AC_MSG_ERROR([Please install 
pip3>=6.0])])
 
+# On Debian systems, we may need to pass "--system" to pip3 to get
+# to the desired installation target directory
+AC_ARG_ENABLE(debian-system,
+  AS_HELP_STRING(--enable-debian-system, pass --system option to pip3 to make 
Debian pip obey installation prefix),
+[if test x$enableval = xyes; then
+   DEBIAN_PIP3_SYSTEM="--system"
+else
+   DEBIAN_PIP3_SYSTEM=""
+fi])
+AC_SUBST(DEBIAN_PIP3_SYSTEM)
+
 #
 # Check for PostgreSQL
 #
@@ -55,20 +66,6 @@ fi
 AC_CHECK_PROG([tsc],[tsc],[yes],[no])
 AM_CONDITIONAL([HAVE_TSC], [test "x$tsc" = xyes])
 
-#
-# Check for minifier
-#
-AC_MSG_CHECKING([Checking for jsmin])
-python3 -m jsmin &> /dev/null
-if test $? -ne 0;
-  then
-  AC_MSG_ERROR([Please install Python3 module 'jsmin'])
-fi
-
-#
-# Report
-#
-
 if test x$pyheaders != x1; then
   AC_MSG_WARN([Python headers not installed, might be required to build uwsgi])
 fi
@@ -78,8 +75,8 @@ fi
 #
 
 AC_CONFIG_FILES([Makefile
+ install-dev.py
  bank.wsgi
- bank-admin.wsgi
  taler-bank-manage
  talerbank/Makefile
  talerbank/app/Makefile
diff --git a/install-dev.py.in b/install-dev.py.in
new file mode 100644
index 0000000..b4cfc6e
--- /dev/null
+++ b/install-dev.py.in
@@ -0,0 +1,31 @@
+#!/usr/bin/env python
+
+"""
+This file is in the public domain.
+
+Execute pip3 in the right environment and the right parameters to install this
+package in the correct path.
+
+This is not a common use-case for pip, and thus it needs some hand-holding.
+"""
+
+import sys
+import os
+
+prefix_path = "%s/lib/python%d.%d/site-packages" % (
+    "@prefix@",
+    sys.version_info.major,
+    sys.version_info.minor)
+
+current_paths = os.environ.get("PYTHONPATH", "").split(":")
+current_paths.append(prefix_path)
+current_paths.remove("")
+os.environ["PYTHONPATH"] = ":".join(current_paths)
+
+args = ["pip3", "install", '--install-option=--prefix=%s' % "@prefix@", "-e", 
"."]
+if "@DEBIAN_PIP3_SYSTEM@":
+    args.push("@DEBIAN_PIP3_SYSTEM@")
+
+os.execvp("pip3", args)
+
+
diff --git a/run_tests.py b/run_tests.py
new file mode 100755
index 0000000..1fd4485
--- /dev/null
+++ b/run_tests.py
@@ -0,0 +1,13 @@
+#!/usr/bin/env python3
+
+from django.core.management import call_command
+from django.db.utils import OperationalError
+import django
+import sys
+
+django.setup()
+try:
+    call_command("test", "talerbank.app.tests")
+except OperationalError:
+    print("Catching DB hard error, skipping the test")
+    sys.exit(0)
diff --git a/taler-bank-manage.in b/taler-bank-manage.in
index 911782f..75ba391 100644
--- a/taler-bank-manage.in
+++ b/taler-bank-manage.in
@@ -27,6 +27,9 @@ uwsgi_logfmt = "%(ltime) %(proto) %(method) %(uri) %(proto) 
=> %(status)"
 def handle_django(args):
     import django
     django.setup()
+    from django.core.management import call_command
+    # always run 'migrate' first, in case a virgin db is being used.
+    call_command('migrate')
     from django.core.management import execute_from_command_line
     execute_from_command_line([sys.argv[0] + " django"] + args.command)
 
@@ -41,7 +44,7 @@ def handle_serve_http(args):
     tc = TalerConfig.from_file(os.environ.get("TALER_CONFIG_FILE"))
     port = args.port
     if port is None:
-        port = tc[token]["http_port"].value_int(required=True)
+        port = tc["bank"]["http_port"].value_int(required=True)
 
     httpspec = ":%d" % (port,)
     params = ["uwsgi", "uwsgi",
@@ -49,7 +52,7 @@ def handle_serve_http(args):
               "--die-on-term",
               "--http", httpspec,
               "--log-format", uwsgi_logfmt,
-              "--wsgi-file", "@prefix@/share/taler-bank/%s.wsgi" % token]
+              "--wsgi-file", "@prefix@/share/taler-bank/bank.wsgi"]
     os.execlp(*params)
 
 
@@ -66,14 +69,14 @@ def handle_serve_uwsgi(args):
               "--master",
               "--die-on-term",
               "--log-format", uwsgi_logfmt,
-              "--wsgi-file", "@prefix@/share/taler-bank/%s.wsgi" % token]
+              "--wsgi-file", "@prefix@/share/taler-bank/bank.wsgi"]
     if "tcp" == serve_uwsgi:
-        port = tc[token]["uwsgi_port"].value_int(required=True)
+        port = tc["bank"]["uwsgi_port"].value_int(required=True)
         spec = ":%d" % (port,)
         params.extend(["--socket", spec])
     else:
-        spec = tc[token]["uwsgi_unixpath"].value_filename(required=True)
-        mode = tc[token]["uwsgi_unixpath_mode"].value_filename(required=True)
+        spec = tc["bank"]["uwsgi_unixpath"].value_filename(required=True)
+        mode = tc["bank"]["uwsgi_unixpath_mode"].value_filename(required=True)
         params.extend(["--socket", spec])
         params.extend(["--chmod-socket="+mode])
         os.makedirs(os.path.dirname(spec), exist_ok=True)
@@ -94,17 +97,19 @@ def handle_config(args):
 
 parser = argparse.ArgumentParser()
 parser.set_defaults(func=None)
-parser.add_argument('--config', '-c', help="configuration file to use", 
metavar="CONFIG", type=str, dest="config", default=None)
-parser.add_argument('--with-db', help="use ALTERNATE_DB", type=str, 
metavar="ALTERNATE_DB", dest="altdb")
-parser.add_argument("--admin", "-a", dest="admin", action="store_true", 
help="Only run the \"admin\" interface")
+parser.add_argument('--config', '-c', help="configuration file to use",
+                    metavar="CONFIG", type=str, dest="config", default=None)
+parser.add_argument('--with-db', help="use 'dbname' (currently only 
'dbtype'=='postgres' is supported)",
+                    type=str, metavar="dbtype:///dbname", dest="altdb")
 sub = parser.add_subparsers()
 
 p = sub.add_parser('django', help="Run django-admin command")
 p.add_argument("command", nargs=argparse.REMAINDER)
 p.set_defaults(func=handle_django)
 
-p = sub.add_parser('sampledata', help="Put sample data into the db")
-p.set_defaults(func=handle_sampledata)
+# FIXME: adapt to newest wire_transfer()
+# p = sub.add_parser('sampledata', help="Put sample data into the db")
+# p.set_defaults(func=handle_sampledata)
 
 p = sub.add_parser('serve-http', help="Serve bank over HTTP")
 p.add_argument("--port", "-p", dest="port", type=int, default=None, 
metavar="PORT")
@@ -119,18 +124,11 @@ 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)
+os.environ.setdefault("DJANGO_SETTINGS_MODULE", "talerbank.settings")
 
 if args.altdb:
-    os.environ.setdefault("ALTDB", args.altdb)
+    logger.info("Setting alternate db: %s" % args.altdb)
+    os.environ.setdefault("TALER_BANK_ALTDB", args.altdb)
 
 if getattr(args, 'func', None) is None:
     parser.print_help()
diff --git a/talerbank/app/Makefile.am b/talerbank/app/Makefile.am
index cf8e06b..01543fd 100644
--- a/talerbank/app/Makefile.am
+++ b/talerbank/app/Makefile.am
@@ -2,17 +2,11 @@ SUBDIRS = . management migrations templates static
 
 EXTRA_DIST = \
   admin.py \
-  captcha.py \
-  errors.py \
-  history.py \
-  middleware.py \
   schemas.py \
   urls.py \
   views.py \
   amounts.py \
   checks.py  \
-  funds.py \
   __init__.py \
   models.py \
-  tests.py \
-  user.py
+  tests.py
diff --git a/talerbank/app/amounts.py b/talerbank/app/amounts.py
index 93f65e6..f9bdd02 100644
--- a/talerbank/app/amounts.py
+++ b/talerbank/app/amounts.py
@@ -19,11 +19,63 @@
 import re
 import math
 import logging
+from django.conf import settings
 
 logger = logging.getLogger(__name__)
 
 FRACTION = 100000000
 
+class CurrencyMismatchException(Exception):
+    def __init__(self, msg=None, status_code=0):
+        self.msg = msg
+        # HTTP status code to be returned as response for
+        # this exception
+        self.status_code = status_code
+
+class BadFormatAmount(Exception):
+    def __init__(self, msg=None, status_code=0):
+        self.msg = msg
+        # HTTP status code to be returned as response for
+        # this exception
+        self.status_code = status_code
+
+
+def check_currency(a1, a2):
+    if a1["currency"] != a2["currency"]:
+        logger.error("Different currencies given: %s vs %s" % (a1["currency"], 
a2["currency"]))
+        raise CurrencyMismatchException
+
+def get_zero():
+    return dict(value=0, fraction=0, currency=settings.TALER_CURRENCY)
+
+def amount_add(a1, a2):
+    check_currency(a1, a2)
+    a1_float = floatify(a1)
+    a2_float = floatify(a2)
+    return parse_amount("%s:%s" % (a2["currency"], str(a1_float + a2_float)))
+
+def amount_sub(a1, a2):
+    check_currency(a1, a2)
+    a1_float = floatify(a1)
+    a2_float = floatify(a2)
+    sub = a1_float - a2_float
+    fmt = "%s:%s" % (a2["currency"], str(sub))
+    return parse_amount(fmt)
+
+# Return -1 if a1 < a2, 0 if a1 == a2, 1 if a1 > a2
+def amount_cmp(a1, a2):
+    check_currency(a1, a2)
+    a1_float = floatify(a1)
+    a2_float = floatify(a2)
+
+    if a1_float < a2_float:
+        return -1
+    elif a1_float == a2_float:
+        return 0
+
+    return 1
+
+
 def floatify(amount_dict):
     return amount_dict['value'] + (float(amount_dict['fraction']) / 
float(FRACTION))
 
@@ -36,15 +88,14 @@ def parse_amount(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)
+    parsed = re.search("^\s*([-_*A-Za-z0-9]+):([0-9]+)(\.[0-9]+)?\s*$", 
amount_str)
     if not parsed:
-        return None
-    value = int(parsed.group(1))
+        raise BadFormatAmount
+    value = int(parsed.group(2))
     fraction = 0
-    if parsed.group(2) is not None:
-        for i, digit in enumerate(parsed.group(2)[1:]):
+    if parsed.group(3) is not None:
+        for i, digit in enumerate(parsed.group(3)[1:]):
             fraction += int(int(digit) * (FRACTION / 10 ** (i+1)))
-
     return {'value': value,
             'fraction': fraction,
-            'currency': parsed.group(3)}
+            'currency': parsed.group(1)}
diff --git a/talerbank/app/checks.py b/talerbank/app/checks.py
index 3654816..6734d3a 100644
--- a/talerbank/app/checks.py
+++ b/talerbank/app/checks.py
@@ -1,5 +1,5 @@
 from django.core.checks import register, Warning
-from django.db import OperationalError
+from django.db.utils import OperationalError
 
 
 @register()
@@ -17,7 +17,7 @@ def example_check(app_configs, **kwargs):
             ))
     except OperationalError:
             errors.append(Warning(
-                'The bank user does not exist',
+                'Presumably non existent database',
                 hint="create a database for the application",
                 id='talerbank.E002'
             ))
diff --git a/talerbank/app/management/commands/dump_talerdb.py 
b/talerbank/app/management/commands/dump_talerdb.py
index 89c4933..ab587d9 100644
--- a/talerbank/app/management/commands/dump_talerdb.py
+++ b/talerbank/app/management/commands/dump_talerdb.py
@@ -19,6 +19,7 @@ from ...models import BankAccount, BankTransaction
 from django.db.utils import OperationalError, ProgrammingError
 import logging
 import sys
+from ...amounts import floatify
 
 # Rewrite to match new BankTransaction layout.
 
@@ -49,7 +50,7 @@ def dump_history():
             # as the first/last character on a line makes flake8 complain
             msg.append("+%s, " % item.credit_account.account_no)
             msg.append("-%s, " % item.debit_account.account_no)
-            msg.append("%.2f, " % item.amount)
+            msg.append("%.2f, " % floatify(item.amount_obj))
             msg.append(item.subject)
             print(''.join(msg))
     except (OperationalError, ProgrammingError):
diff --git a/talerbank/app/management/commands/provide_accounts.py 
b/talerbank/app/management/commands/provide_accounts.py
index 421f896..cf3f0cd 100644
--- a/talerbank/app/management/commands/provide_accounts.py
+++ b/talerbank/app/management/commands/provide_accounts.py
@@ -31,7 +31,7 @@ def demo_accounts():
         try:
             User.objects.get(username=name)
         except User.DoesNotExist:
-            u = User.objects.create_user(username=name, password='')
+            u = User.objects.create_user(username=name, password='x')
             b = BankAccount(user=u,
                             currency=settings.TALER_CURRENCY,
                             is_public=True)
@@ -40,8 +40,10 @@ def demo_accounts():
 
 
 def ensure_account(name):
+    logger.info("ensuring account '{}'".format(name))
+    user = None
     try:
-        User.objects.get(username=name)
+        user = User.objects.get(username=name)
     except (OperationalError, ProgrammingError):
         logger.error("likely causes: non existent DB or unmigrated project\n"
                      "(try 'taler-bank-manage django migrate' in the latter 
case)",
@@ -49,12 +51,18 @@ def ensure_account(name):
                      exc_info=True)
         sys.exit(1)
     except User.DoesNotExist:
-        user = User.objects.create_user(username=name, password='')
+        logger.info("Creating *user* account '{}'".format(name))
+        user = User.objects.create_user(username=name, password='x')
+
+    try:
+        BankAccount.objects.get(user=user)
+
+    except BankAccount.DoesNotExist:
         acc = BankAccount(user=user,
                           currency=settings.TALER_CURRENCY,
                           is_public=True)
         acc.save()
-        logger.info("Creating account '%s', with number %s", name, 
acc.account_no)
+        logger.info("Creating *bank* account number '{}' for user 
'{}'".format(acc.account_no, name))
 
 
 def basic_accounts():
diff --git a/talerbank/app/migrations/0001_initial.py 
b/talerbank/app/migrations/0001_initial.py
index 310fc0c..ff05150 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.3 on 2016-11-29 14:35
+# Generated by Django 1.10.3 on 2017-03-22 20:22
 from __future__ import unicode_literals
 
 from django.conf import settings
@@ -20,6 +20,9 @@ class Migration(migrations.Migration):
             name='BankAccount',
             fields=[
                 ('is_public', models.BooleanField(default=False)),
+                ('debit', models.BooleanField(default=False)),
+                ('balance_value', models.IntegerField(default=0)),
+                ('balance_fraction', models.IntegerField(default=0)),
                 ('balance', models.FloatField(default=0)),
                 ('currency', models.CharField(default='', max_length=12)),
                 ('account_no', models.AutoField(primary_key=True, 
serialize=False)),
@@ -30,7 +33,8 @@ class Migration(migrations.Migration):
             name='BankTransaction',
             fields=[
                 ('id', models.AutoField(auto_created=True, primary_key=True, 
serialize=False, verbose_name='ID')),
-                ('amount', models.FloatField(default=0)),
+                ('amount_value', models.IntegerField(default=0)),
+                ('amount_fraction', models.IntegerField(default=0)),
                 ('currency', models.CharField(max_length=12)),
                 ('subject', models.CharField(default='(no subject given)', 
max_length=200)),
                 ('date', models.DateTimeField(auto_now=True)),
diff --git a/talerbank/app/models.py b/talerbank/app/models.py
index 1b84fe9..f1c3485 100644
--- a/talerbank/app/models.py
+++ b/talerbank/app/models.py
@@ -22,16 +22,41 @@ from django.db import models
 
 class BankAccount(models.Model):
     is_public = models.BooleanField(default=False)
+    debit = models.BooleanField(default=False)
+    balance_value = models.IntegerField(default=0)
+    balance_fraction = models.IntegerField(default=0)
     balance = models.FloatField(default=0)
     currency = models.CharField(max_length=12, default="")
     account_no = models.AutoField(primary_key=True)
     user = models.OneToOneField(User, on_delete=models.CASCADE)
-
+    def _get_balance(self):
+        return dict(value=self.balance_value,
+                    fraction=self.balance_fraction,
+                    currency=self.currency)
+    def _set_balance(self, amount):
+        self.balance_value = amount["value"]
+        self.balance_fraction = amount["fraction"]
+        self.currency = amount["currency"]
+    balance_obj = property(_get_balance, _set_balance)
 
 class BankTransaction(models.Model):
-    amount = models.FloatField(default=0)
+    amount_value = models.IntegerField(default=0)
+    amount_fraction = models.IntegerField(default=0)
     currency = models.CharField(max_length=12)
-    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")
+    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)
+    def _get_amount(self):
+        return dict(value=self.amount_value,
+                    fraction=self.amount_fraction,
+                    currency=self.currency)
+    def _set_amount(self, amount):
+        self.amount_value = amount["value"]
+        self.amount_fraction = amount["fraction"]
+        self.currency = amount["currency"]
+    amount_obj = property(_get_amount, _set_amount)
diff --git a/talerbank/app/schemas.py b/talerbank/app/schemas.py
index d4ba21b..91771d1 100644
--- a/talerbank/app/schemas.py
+++ b/talerbank/app/schemas.py
@@ -31,12 +31,20 @@ wiredetails_schema = {
                 "type": {"type": "string"},
                 "account_number": {"type": "integer"},
                 "bank_uri": {"type": "string"},
-                "name": {"type": "string"},
+                "name": {"type": "string", "required": False},
             }
         }
     }
 }
 
+auth_schema = {
+    "type": "object",
+    "properties": {
+        "type": {"type": "string"},
+        "data": {"type": "object", "required": False}
+    }
+}
+
 amount_schema = {
     "type": "object",
     "properties": {
@@ -53,7 +61,7 @@ incoming_request_schema = {
         "wtid": {"type": "string"},
         "exchange_url": {"type": "string"},
         "credit_account": {"type": "integer"},
-        "debit_account": {"type": "integer"}
+        "auth": auth_schema
     }
 }
 
@@ -65,3 +73,6 @@ def validate_wiredetails(wiredetails):
 
 def validate_incoming_request(incoming_request):
     validictory.validate(incoming_request, incoming_request_schema)
+
+def validate_auth_basic(auth_basic):
+    validictory.validate(auth_basic, auth_basic_schema)
diff --git a/talerbank/app/static/Makefile.am b/talerbank/app/static/Makefile.am
index e45c105..2742b65 100644
--- a/talerbank/app/static/Makefile.am
+++ b/talerbank/app/static/Makefile.am
@@ -2,6 +2,4 @@ SUBDIRS = . web-common
 
 EXTRA_DIST = \
   favicon.ico \
-  style.css \
-  disabled-button.css \
   chrome-store-link.js
diff --git a/talerbank/app/static/pure.css b/talerbank/app/static/pure.css
new file mode 100644
index 0000000..7391139
--- /dev/null
+++ b/talerbank/app/static/pure.css
@@ -0,0 +1,1508 @@
+/*!
+Pure v0.6.2
+Copyright 2013 Yahoo!
+Licensed under the BSD License.
+https://github.com/yahoo/pure/blob/master/LICENSE.md
+*/
+/*!
+normalize.css v^3.0 | MIT License | git.io/normalize
+Copyright (c) Nicolas Gallagher and Jonathan Neal
+*/
+/*! normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css */
+
+/**
+ * 1. Set default font family to sans-serif.
+ * 2. Prevent iOS and IE text size adjust after device orientation change,
+ *    without disabling user zoom.
+ */
+
+html {
+  font-family: sans-serif; /* 1 */
+  -ms-text-size-adjust: 100%; /* 2 */
+  -webkit-text-size-adjust: 100%; /* 2 */
+}
+
+/**
+ * Remove default margin.
+ */
+
+body {
+  margin: 0;
+}
+
+/* HTML5 display definitions
+   ========================================================================== 
*/
+
+/**
+ * Correct `block` display not defined for any HTML5 element in IE 8/9.
+ * Correct `block` display not defined for `details` or `summary` in IE 10/11
+ * and Firefox.
+ * Correct `block` display not defined for `main` in IE 11.
+ */
+
+article,
+aside,
+details,
+figcaption,
+figure,
+footer,
+header,
+hgroup,
+main,
+menu,
+nav,
+section,
+summary {
+  display: block;
+}
+
+/**
+ * 1. Correct `inline-block` display not defined in IE 8/9.
+ * 2. Normalize vertical alignment of `progress` in Chrome, Firefox, and Opera.
+ */
+
+audio,
+canvas,
+progress,
+video {
+  display: inline-block; /* 1 */
+  vertical-align: baseline; /* 2 */
+}
+
+/**
+ * Prevent modern browsers from displaying `audio` without controls.
+ * Remove excess height in iOS 5 devices.
+ */
+
+audio:not([controls]) {
+  display: none;
+  height: 0;
+}
+
+/**
+ * Address `[hidden]` styling not present in IE 8/9/10.
+ * Hide the `template` element in IE 8/9/10/11, Safari, and Firefox < 22.
+ */
+
+[hidden],
+template {
+  display: none;
+}
+
+/* Links
+   ========================================================================== 
*/
+
+/**
+ * Remove the gray background color from active links in IE 10.
+ */
+
+a {
+  background-color: transparent;
+}
+
+/**
+ * Improve readability of focused elements when they are also in an
+ * active/hover state.
+ */
+
+a:active,
+a:hover {
+  outline: 0;
+}
+
+/* Text-level semantics
+   ========================================================================== 
*/
+
+/**
+ * Address styling not present in IE 8/9/10/11, Safari, and Chrome.
+ */
+
+abbr[title] {
+  border-bottom: 1px dotted;
+}
+
+/**
+ * Address style set to `bolder` in Firefox 4+, Safari, and Chrome.
+ */
+
+b,
+strong {
+  font-weight: bold;
+}
+
+/**
+ * Address styling not present in Safari and Chrome.
+ */
+
+dfn {
+  font-style: italic;
+}
+
+/**
+ * Address variable `h1` font-size and margin within `section` and `article`
+ * contexts in Firefox 4+, Safari, and Chrome.
+ */
+
+h1 {
+  font-size: 2em;
+  margin: 0.67em 0;
+}
+
+/**
+ * Address styling not present in IE 8/9.
+ */
+
+mark {
+  background: #ff0;
+  color: #000;
+}
+
+/**
+ * Address inconsistent and variable font size in all browsers.
+ */
+
+small {
+  font-size: 80%;
+}
+
+/**
+ * Prevent `sub` and `sup` affecting `line-height` in all browsers.
+ */
+
+sub,
+sup {
+  font-size: 75%;
+  line-height: 0;
+  position: relative;
+  vertical-align: baseline;
+}
+
+sup {
+  top: -0.5em;
+}
+
+sub {
+  bottom: -0.25em;
+}
+
+/* Embedded content
+   ========================================================================== 
*/
+
+/**
+ * Remove border when inside `a` element in IE 8/9/10.
+ */
+
+img {
+  border: 0;
+}
+
+/**
+ * Correct overflow not hidden in IE 9/10/11.
+ */
+
+svg:not(:root) {
+  overflow: hidden;
+}
+
+/* Grouping content
+   ========================================================================== 
*/
+
+/**
+ * Address margin not present in IE 8/9 and Safari.
+ */
+
+figure {
+  margin: 1em 40px;
+}
+
+/**
+ * Address differences between Firefox and other browsers.
+ */
+
+hr {
+  box-sizing: content-box;
+  height: 0;
+}
+
+/**
+ * Contain overflow in all browsers.
+ */
+
+pre {
+  overflow: auto;
+}
+
+/**
+ * Address odd `em`-unit font size rendering in all browsers.
+ */
+
+code,
+kbd,
+pre,
+samp {
+  font-family: monospace, monospace;
+  font-size: 1em;
+}
+
+/* Forms
+   ========================================================================== 
*/
+
+/**
+ * Known limitation: by default, Chrome and Safari on OS X allow very limited
+ * styling of `select`, unless a `border` property is set.
+ */
+
+/**
+ * 1. Correct color not being inherited.
+ *    Known issue: affects color of disabled elements.
+ * 2. Correct font properties not being inherited.
+ * 3. Address margins set differently in Firefox 4+, Safari, and Chrome.
+ */
+
+button,
+input,
+optgroup,
+select,
+textarea {
+  color: inherit; /* 1 */
+  font: inherit; /* 2 */
+  margin: 0; /* 3 */
+}
+
+/**
+ * Address `overflow` set to `hidden` in IE 8/9/10/11.
+ */
+
+button {
+  overflow: visible;
+}
+
+/**
+ * Address inconsistent `text-transform` inheritance for `button` and `select`.
+ * All other form control elements do not inherit `text-transform` values.
+ * Correct `button` style inheritance in Firefox, IE 8/9/10/11, and Opera.
+ * Correct `select` style inheritance in Firefox.
+ */
+
+button,
+select {
+  text-transform: none;
+}
+
+/**
+ * 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio`
+ *    and `video` controls.
+ * 2. Correct inability to style clickable `input` types in iOS.
+ * 3. Improve usability and consistency of cursor style between image-type
+ *    `input` and others.
+ */
+
+button,
+html input[type="button"], /* 1 */
+input[type="reset"],
+input[type="submit"] {
+  -webkit-appearance: button; /* 2 */
+  cursor: pointer; /* 3 */
+}
+
+/**
+ * Re-set default cursor for disabled elements.
+ */
+
+button[disabled],
+html input[disabled] {
+  cursor: default;
+}
+
+/**
+ * Remove inner padding and border in Firefox 4+.
+ */
+
+button::-moz-focus-inner,
+input::-moz-focus-inner {
+  border: 0;
+  padding: 0;
+}
+
+/**
+ * Address Firefox 4+ setting `line-height` on `input` using `!important` in
+ * the UA stylesheet.
+ */
+
+input {
+  line-height: normal;
+}
+
+/**
+ * It's recommended that you don't attempt to style these elements.
+ * Firefox's implementation doesn't respect box-sizing, padding, or width.
+ *
+ * 1. Address box sizing set to `content-box` in IE 8/9/10.
+ * 2. Remove excess padding in IE 8/9/10.
+ */
+
+input[type="checkbox"],
+input[type="radio"] {
+  box-sizing: border-box; /* 1 */
+  padding: 0; /* 2 */
+}
+
+/**
+ * Fix the cursor style for Chrome's increment/decrement buttons. For certain
+ * `font-size` values of the `input`, it causes the cursor style of the
+ * decrement button to change from `default` to `text`.
+ */
+
+input[type="number"]::-webkit-inner-spin-button,
+input[type="number"]::-webkit-outer-spin-button {
+  height: auto;
+}
+
+/**
+ * 1. Address `appearance` set to `searchfield` in Safari and Chrome.
+ * 2. Address `box-sizing` set to `border-box` in Safari and Chrome.
+ */
+
+input[type="search"] {
+  -webkit-appearance: textfield; /* 1 */
+  box-sizing: content-box; /* 2 */
+}
+
+/**
+ * Remove inner padding and search cancel button in Safari and Chrome on OS X.
+ * Safari (but not Chrome) clips the cancel button when the search input has
+ * padding (and `textfield` appearance).
+ */
+
+input[type="search"]::-webkit-search-cancel-button,
+input[type="search"]::-webkit-search-decoration {
+  -webkit-appearance: none;
+}
+
+/**
+ * Define consistent border, margin, and padding.
+ */
+
+fieldset {
+  border: 1px solid #c0c0c0;
+  margin: 0 2px;
+  padding: 0.35em 0.625em 0.75em;
+}
+
+/**
+ * 1. Correct `color` not being inherited in IE 8/9/10/11.
+ * 2. Remove padding so people aren't caught out if they zero out fieldsets.
+ */
+
+legend {
+  border: 0; /* 1 */
+  padding: 0; /* 2 */
+}
+
+/**
+ * Remove default vertical scrollbar in IE 8/9/10/11.
+ */
+
+textarea {
+  overflow: auto;
+}
+
+/**
+ * Don't inherit the `font-weight` (applied by a rule above).
+ * NOTE: the default cannot safely be changed in Chrome and Safari on OS X.
+ */
+
+optgroup {
+  font-weight: bold;
+}
+
+/* Tables
+   ========================================================================== 
*/
+
+/**
+ * Remove most spacing between table cells.
+ */
+
+table {
+  border-collapse: collapse;
+  border-spacing: 0;
+}
+
+td,
+th {
+  padding: 0;
+}
+
+/*csslint important:false*/
+
+/* ==========================================================================
+   Pure Base Extras
+   ========================================================================== 
*/
+
+/**
+ * Extra rules that Pure adds on top of Normalize.css
+ */
+
+/**
+ * Always hide an element when it has the `hidden` HTML attribute.
+ */
+
+.hidden,
+[hidden] {
+    display: none !important;
+}
+
+/**
+ * Add this class to an image to make it fit within it's fluid parent wrapper 
while maintaining
+ * aspect ratio.
+ */
+.pure-img {
+    max-width: 100%;
+    height: auto;
+    display: block;
+}
+
+/*csslint regex-selectors:false, known-properties:false, 
duplicate-properties:false*/
+
+.pure-g {
+    letter-spacing: -0.31em; /* Webkit: collapse white-space between units */
+    *letter-spacing: normal; /* reset IE < 8 */
+    *word-spacing: -0.43em; /* IE < 8: collapse white-space between units */
+    text-rendering: optimizespeed; /* Webkit: fixes text-rendering: 
optimizeLegibility */
+
+    /*
+    Sets the font stack to fonts known to work properly with the above letter
+    and word spacings. See: https://github.com/yahoo/pure/issues/41/
+
+    The following font stack makes Pure Grids work on all known environments.
+
+    * FreeSans: Ships with many Linux distros, including Ubuntu
+
+    * Arimo: Ships with Chrome OS. Arimo has to be defined before Helvetica and
+      Arial to get picked up by the browser, even though neither is available
+      in Chrome OS.
+
+    * Droid Sans: Ships with all versions of Android.
+
+    * Helvetica, Arial, sans-serif: Common font stack on OS X and Windows.
+    */
+    font-family: FreeSans, Arimo, "Droid Sans", Helvetica, Arial, sans-serif;
+
+    /* Use flexbox when possible to avoid `letter-spacing` side-effects. */
+    display: -webkit-box;
+    display: -webkit-flex;
+    display: -ms-flexbox;
+    display: flex;
+    -webkit-flex-flow: row wrap;
+        -ms-flex-flow: row wrap;
+            flex-flow: row wrap;
+
+    /* Prevents distributing space between rows */
+    -webkit-align-content: flex-start;
+        -ms-flex-line-pack: start;
+            align-content: flex-start;
+}
+
+/* IE10 display: -ms-flexbox (and display: flex in IE 11) does not work inside 
a table; fall back to block and rely on font hack */
address@hidden all and (-ms-high-contrast: none), (-ms-high-contrast: active) {
+       table .pure-g {
+               display: block;
+       }
+}
+
+/* Opera as of 12 on Windows needs word-spacing.
+   The ".opera-only" selector is used to prevent actual prefocus styling
+   and is not required in markup.
+*/
+.opera-only :-o-prefocus,
+.pure-g {
+    word-spacing: -0.43em;
+}
+
+.pure-u {
+    display: inline-block;
+    *display: inline; /* IE < 8: fake inline-block */
+    zoom: 1;
+    letter-spacing: normal;
+    word-spacing: normal;
+    vertical-align: top;
+    text-rendering: auto;
+}
+
+/*
+Resets the font family back to the OS/browser's default sans-serif font,
+this the same font stack that Normalize.css sets for the `body`.
+*/
+.pure-g [class *= "pure-u"] {
+    font-family: sans-serif;
+}
+
+.pure-u-1,
+.pure-u-1-1,
+.pure-u-1-2,
+.pure-u-1-3,
+.pure-u-2-3,
+.pure-u-1-4,
+.pure-u-3-4,
+.pure-u-1-5,
+.pure-u-2-5,
+.pure-u-3-5,
+.pure-u-4-5,
+.pure-u-5-5,
+.pure-u-1-6,
+.pure-u-5-6,
+.pure-u-1-8,
+.pure-u-3-8,
+.pure-u-5-8,
+.pure-u-7-8,
+.pure-u-1-12,
+.pure-u-5-12,
+.pure-u-7-12,
+.pure-u-11-12,
+.pure-u-1-24,
+.pure-u-2-24,
+.pure-u-3-24,
+.pure-u-4-24,
+.pure-u-5-24,
+.pure-u-6-24,
+.pure-u-7-24,
+.pure-u-8-24,
+.pure-u-9-24,
+.pure-u-10-24,
+.pure-u-11-24,
+.pure-u-12-24,
+.pure-u-13-24,
+.pure-u-14-24,
+.pure-u-15-24,
+.pure-u-16-24,
+.pure-u-17-24,
+.pure-u-18-24,
+.pure-u-19-24,
+.pure-u-20-24,
+.pure-u-21-24,
+.pure-u-22-24,
+.pure-u-23-24,
+.pure-u-24-24 {
+    display: inline-block;
+    *display: inline;
+    zoom: 1;
+    letter-spacing: normal;
+    word-spacing: normal;
+    vertical-align: top;
+    text-rendering: auto;
+}
+
+.pure-u-1-24 {
+    width: 4.1667%;
+    *width: 4.1357%;
+}
+
+.pure-u-1-12,
+.pure-u-2-24 {
+    width: 8.3333%;
+    *width: 8.3023%;
+}
+
+.pure-u-1-8,
+.pure-u-3-24 {
+    width: 12.5000%;
+    *width: 12.4690%;
+}
+
+.pure-u-1-6,
+.pure-u-4-24 {
+    width: 16.6667%;
+    *width: 16.6357%;
+}
+
+.pure-u-1-5 {
+    width: 20%;
+    *width: 19.9690%;
+}
+
+.pure-u-5-24 {
+    width: 20.8333%;
+    *width: 20.8023%;
+}
+
+.pure-u-1-4,
+.pure-u-6-24 {
+    width: 25%;
+    *width: 24.9690%;
+}
+
+.pure-u-7-24 {
+    width: 29.1667%;
+    *width: 29.1357%;
+}
+
+.pure-u-1-3,
+.pure-u-8-24 {
+    width: 33.3333%;
+    *width: 33.3023%;
+}
+
+.pure-u-3-8,
+.pure-u-9-24 {
+    width: 37.5000%;
+    *width: 37.4690%;
+}
+
+.pure-u-2-5 {
+    width: 40%;
+    *width: 39.9690%;
+}
+
+.pure-u-5-12,
+.pure-u-10-24 {
+    width: 41.6667%;
+    *width: 41.6357%;
+}
+
+.pure-u-11-24 {
+    width: 45.8333%;
+    *width: 45.8023%;
+}
+
+.pure-u-1-2,
+.pure-u-12-24 {
+    width: 50%;
+    *width: 49.9690%;
+}
+
+.pure-u-13-24 {
+    width: 54.1667%;
+    *width: 54.1357%;
+}
+
+.pure-u-7-12,
+.pure-u-14-24 {
+    width: 58.3333%;
+    *width: 58.3023%;
+}
+
+.pure-u-3-5 {
+    width: 60%;
+    *width: 59.9690%;
+}
+
+.pure-u-5-8,
+.pure-u-15-24 {
+    width: 62.5000%;
+    *width: 62.4690%;
+}
+
+.pure-u-2-3,
+.pure-u-16-24 {
+    width: 66.6667%;
+    *width: 66.6357%;
+}
+
+.pure-u-17-24 {
+    width: 70.8333%;
+    *width: 70.8023%;
+}
+
+.pure-u-3-4,
+.pure-u-18-24 {
+    width: 75%;
+    *width: 74.9690%;
+}
+
+.pure-u-19-24 {
+    width: 79.1667%;
+    *width: 79.1357%;
+}
+
+.pure-u-4-5 {
+    width: 80%;
+    *width: 79.9690%;
+}
+
+.pure-u-5-6,
+.pure-u-20-24 {
+    width: 83.3333%;
+    *width: 83.3023%;
+}
+
+.pure-u-7-8,
+.pure-u-21-24 {
+    width: 87.5000%;
+    *width: 87.4690%;
+}
+
+.pure-u-11-12,
+.pure-u-22-24 {
+    width: 91.6667%;
+    *width: 91.6357%;
+}
+
+.pure-u-23-24 {
+    width: 95.8333%;
+    *width: 95.8023%;
+}
+
+.pure-u-1,
+.pure-u-1-1,
+.pure-u-5-5,
+.pure-u-24-24 {
+    width: 100%;
+}
+.pure-button {
+    /* Structure */
+    display: inline-block;
+    zoom: 1;
+    line-height: normal;
+    white-space: nowrap;
+    vertical-align: middle;
+    text-align: center;
+    cursor: pointer;
+    -webkit-user-drag: none;
+    -webkit-user-select: none;
+       -moz-user-select: none;
+        -ms-user-select: none;
+            user-select: none;
+    box-sizing: border-box;
+}
+
+/* Firefox: Get rid of the inner focus border */
+.pure-button::-moz-focus-inner {
+    padding: 0;
+    border: 0;
+}
+
+/* Inherit .pure-g styles */
+.pure-button-group {
+    letter-spacing: -0.31em; /* Webkit: collapse white-space between units */
+    *letter-spacing: normal; /* reset IE < 8 */
+    *word-spacing: -0.43em; /* IE < 8: collapse white-space between units */
+    text-rendering: optimizespeed; /* Webkit: fixes text-rendering: 
optimizeLegibility */
+}
+
+.opera-only :-o-prefocus,
+.pure-button-group {
+    word-spacing: -0.43em;
+}
+
+.pure-button-group .pure-button {
+    letter-spacing: normal;
+    word-spacing: normal;
+    vertical-align: top;
+    text-rendering: auto;
+}
+
+/*csslint outline-none:false*/
+
+.pure-button {
+    font-family: inherit;
+    font-size: 100%;
+    padding: 0.5em 1em;
+    color: #444; /* rgba not supported (IE 8) */
+    color: rgba(0, 0, 0, 0.80); /* rgba supported */
+    border: 1px solid #999;  /*IE 6/7/8*/
+    border: none rgba(0, 0, 0, 0);  /*IE9 + everything else*/
+    background-color: #E6E6E6;
+    text-decoration: none;
+    border-radius: 2px;
+}
+
+.pure-button-hover,
+.pure-button:hover,
+.pure-button:focus {
+    /* csslint ignore:start */
+    filter: alpha(opacity=90);
+    /* csslint ignore:end */
+    background-image: -webkit-linear-gradient(transparent, rgba(0,0,0, 0.05) 
40%, rgba(0,0,0, 0.10));
+    background-image: linear-gradient(transparent, rgba(0,0,0, 0.05) 40%, 
rgba(0,0,0, 0.10));
+}
+.pure-button:focus {
+    outline: 0;
+}
+.pure-button-active,
+.pure-button:active {
+    box-shadow: 0 0 0 1px rgba(0,0,0, 0.15) inset, 0 0 6px rgba(0,0,0, 0.20) 
inset;
+    border-color: #000\9;
+}
+
+.pure-button[disabled],
+.pure-button-disabled,
+.pure-button-disabled:hover,
+.pure-button-disabled:focus,
+.pure-button-disabled:active {
+    border: none;
+    background-image: none;
+    /* csslint ignore:start */
+    filter: alpha(opacity=40);
+    /* csslint ignore:end */
+    opacity: 0.40;
+    cursor: not-allowed;
+    box-shadow: none;
+    pointer-events: none;
+}
+
+.pure-button-hidden {
+    display: none;
+}
+
+.pure-button-primary,
+.pure-button-selected,
+a.pure-button-primary,
+a.pure-button-selected {
+    background-color: rgb(0, 120, 231);
+    color: #fff;
+}
+
+/* Button Groups */
+.pure-button-group .pure-button {
+    margin: 0;
+    border-radius: 0;
+    border-right: 1px solid #111;  /* fallback color for rgba() for IE7/8 */
+    border-right: 1px solid rgba(0, 0, 0, 0.2);
+
+}
+
+.pure-button-group .pure-button:first-child {
+    border-top-left-radius: 2px;
+    border-bottom-left-radius: 2px;
+}
+.pure-button-group .pure-button:last-child {
+    border-top-right-radius: 2px;
+    border-bottom-right-radius: 2px;
+    border-right: none;
+}
+
+/*csslint box-model:false*/
+/*
+Box-model set to false because we're setting a height on select elements, which
+also have border and padding. This is done because some browsers don't render
+the padding. We explicitly set the box-model for select elements to border-box,
+so we can ignore the csslint warning.
+*/
+
+.pure-form input[type="text"],
+.pure-form input[type="password"],
+.pure-form input[type="email"],
+.pure-form input[type="url"],
+.pure-form input[type="date"],
+.pure-form input[type="month"],
+.pure-form input[type="time"],
+.pure-form input[type="datetime"],
+.pure-form input[type="datetime-local"],
+.pure-form input[type="week"],
+.pure-form input[type="number"],
+.pure-form input[type="search"],
+.pure-form input[type="tel"],
+.pure-form input[type="color"],
+.pure-form select,
+.pure-form textarea {
+    padding: 0.5em 0.6em;
+    display: inline-block;
+    border: 1px solid #ccc;
+    box-shadow: inset 0 1px 3px #ddd;
+    border-radius: 4px;
+    vertical-align: middle;
+    box-sizing: border-box;
+}
+
+/*
+Need to separate out the :not() selector from the rest of the CSS 2.1 selectors
+since IE8 won't execute CSS that contains a CSS3 selector.
+*/
+.pure-form input:not([type]) {
+    padding: 0.5em 0.6em;
+    display: inline-block;
+    border: 1px solid #ccc;
+    box-shadow: inset 0 1px 3px #ddd;
+    border-radius: 4px;
+    box-sizing: border-box;
+}
+
+
+/* Chrome (as of v.32/34 on OS X) needs additional room for color to display. 
*/
+/* May be able to remove this tweak as color inputs become more standardized 
across browsers. */
+.pure-form input[type="color"] {
+    padding: 0.2em 0.5em;
+}
+
+
+.pure-form input[type="text"]:focus,
+.pure-form input[type="password"]:focus,
+.pure-form input[type="email"]:focus,
+.pure-form input[type="url"]:focus,
+.pure-form input[type="date"]:focus,
+.pure-form input[type="month"]:focus,
+.pure-form input[type="time"]:focus,
+.pure-form input[type="datetime"]:focus,
+.pure-form input[type="datetime-local"]:focus,
+.pure-form input[type="week"]:focus,
+.pure-form input[type="number"]:focus,
+.pure-form input[type="search"]:focus,
+.pure-form input[type="tel"]:focus,
+.pure-form input[type="color"]:focus,
+.pure-form select:focus,
+.pure-form textarea:focus {
+    outline: 0;
+    border-color: #129FEA;
+}
+
+/*
+Need to separate out the :not() selector from the rest of the CSS 2.1 selectors
+since IE8 won't execute CSS that contains a CSS3 selector.
+*/
+.pure-form input:not([type]):focus {
+    outline: 0;
+    border-color: #129FEA;
+}
+
+.pure-form input[type="file"]:focus,
+.pure-form input[type="radio"]:focus,
+.pure-form input[type="checkbox"]:focus {
+    outline: thin solid #129FEA;
+    outline: 1px auto #129FEA;
+}
+.pure-form .pure-checkbox,
+.pure-form .pure-radio {
+    margin: 0.5em 0;
+    display: block;
+}
+
+.pure-form input[type="text"][disabled],
+.pure-form input[type="password"][disabled],
+.pure-form input[type="email"][disabled],
+.pure-form input[type="url"][disabled],
+.pure-form input[type="date"][disabled],
+.pure-form input[type="month"][disabled],
+.pure-form input[type="time"][disabled],
+.pure-form input[type="datetime"][disabled],
+.pure-form input[type="datetime-local"][disabled],
+.pure-form input[type="week"][disabled],
+.pure-form input[type="number"][disabled],
+.pure-form input[type="search"][disabled],
+.pure-form input[type="tel"][disabled],
+.pure-form input[type="color"][disabled],
+.pure-form select[disabled],
+.pure-form textarea[disabled] {
+    cursor: not-allowed;
+    background-color: #eaeded;
+    color: #cad2d3;
+}
+
+/*
+Need to separate out the :not() selector from the rest of the CSS 2.1 selectors
+since IE8 won't execute CSS that contains a CSS3 selector.
+*/
+.pure-form input:not([type])[disabled] {
+    cursor: not-allowed;
+    background-color: #eaeded;
+    color: #cad2d3;
+}
+.pure-form input[readonly],
+.pure-form select[readonly],
+.pure-form textarea[readonly] {
+    background-color: #eee; /* menu hover bg color */
+    color: #777; /* menu text color */
+    border-color: #ccc;
+}
+
+.pure-form input:focus:invalid,
+.pure-form textarea:focus:invalid,
+.pure-form select:focus:invalid {
+    color: #b94a48;
+    border-color: #e9322d;
+}
+.pure-form input[type="file"]:focus:invalid:focus,
+.pure-form input[type="radio"]:focus:invalid:focus,
+.pure-form input[type="checkbox"]:focus:invalid:focus {
+    outline-color: #e9322d;
+}
+.pure-form select {
+    /* Normalizes the height; padding is not sufficient. */
+    height: 2.25em;
+    border: 1px solid #ccc;
+    background-color: white;
+}
+.pure-form select[multiple] {
+    height: auto;
+}
+.pure-form label {
+    margin: 0.5em 0 0.2em;
+}
+.pure-form fieldset {
+    margin: 0;
+    padding: 0.35em 0 0.75em;
+    border: 0;
+}
+.pure-form legend {
+    display: block;
+    width: 100%;
+    padding: 0.3em 0;
+    margin-bottom: 0.3em;
+    color: #333;
+    border-bottom: 1px solid #e5e5e5;
+}
+
+.pure-form-stacked input[type="text"],
+.pure-form-stacked input[type="password"],
+.pure-form-stacked input[type="email"],
+.pure-form-stacked input[type="url"],
+.pure-form-stacked input[type="date"],
+.pure-form-stacked input[type="month"],
+.pure-form-stacked input[type="time"],
+.pure-form-stacked input[type="datetime"],
+.pure-form-stacked input[type="datetime-local"],
+.pure-form-stacked input[type="week"],
+.pure-form-stacked input[type="number"],
+.pure-form-stacked input[type="search"],
+.pure-form-stacked input[type="tel"],
+.pure-form-stacked input[type="color"],
+.pure-form-stacked input[type="file"],
+.pure-form-stacked select,
+.pure-form-stacked label,
+.pure-form-stacked textarea {
+    display: block;
+    margin: 0.25em 0;
+}
+
+/*
+Need to separate out the :not() selector from the rest of the CSS 2.1 selectors
+since IE8 won't execute CSS that contains a CSS3 selector.
+*/
+.pure-form-stacked input:not([type]) {
+    display: block;
+    margin: 0.25em 0;
+}
+.pure-form-aligned input,
+.pure-form-aligned textarea,
+.pure-form-aligned select,
+/* NOTE: pure-help-inline is deprecated. Use .pure-form-message-inline 
instead. */
+.pure-form-aligned .pure-help-inline,
+.pure-form-message-inline {
+    display: inline-block;
+    *display: inline;
+    *zoom: 1;
+    vertical-align: middle;
+}
+.pure-form-aligned textarea {
+    vertical-align: top;
+}
+
+/* Aligned Forms */
+.pure-form-aligned .pure-control-group {
+    margin-bottom: 0.5em;
+}
+.pure-form-aligned .pure-control-group label {
+    text-align: right;
+    display: inline-block;
+    vertical-align: middle;
+    width: 10em;
+    margin: 0 1em 0 0;
+}
+.pure-form-aligned .pure-controls {
+    margin: 1.5em 0 0 11em;
+}
+
+/* Rounded Inputs */
+.pure-form input.pure-input-rounded,
+.pure-form .pure-input-rounded {
+    border-radius: 2em;
+    padding: 0.5em 1em;
+}
+
+/* Grouped Inputs */
+.pure-form .pure-group fieldset {
+    margin-bottom: 10px;
+}
+.pure-form .pure-group input,
+.pure-form .pure-group textarea {
+    display: block;
+    padding: 10px;
+    margin: 0 0 -1px;
+    border-radius: 0;
+    position: relative;
+    top: -1px;
+}
+.pure-form .pure-group input:focus,
+.pure-form .pure-group textarea:focus {
+    z-index: 3;
+}
+.pure-form .pure-group input:first-child,
+.pure-form .pure-group textarea:first-child {
+    top: 1px;
+    border-radius: 4px 4px 0 0;
+    margin: 0;
+}
+.pure-form .pure-group input:first-child:last-child,
+.pure-form .pure-group textarea:first-child:last-child {
+    top: 1px;
+    border-radius: 4px;
+    margin: 0;
+}
+.pure-form .pure-group input:last-child,
+.pure-form .pure-group textarea:last-child {
+    top: -2px;
+    border-radius: 0 0 4px 4px;
+    margin: 0;
+}
+.pure-form .pure-group button {
+    margin: 0.35em 0;
+}
+
+.pure-form .pure-input-1 {
+    width: 100%;
+}
+.pure-form .pure-input-3-4 {
+    width: 75%;
+}
+.pure-form .pure-input-2-3 {
+    width: 66%;
+}
+.pure-form .pure-input-1-2 {
+    width: 50%;
+}
+.pure-form .pure-input-1-3 {
+    width: 33%;
+}
+.pure-form .pure-input-1-4 {
+    width: 25%;
+}
+
+/* Inline help for forms */
+/* NOTE: pure-help-inline is deprecated. Use .pure-form-message-inline 
instead. */
+.pure-form .pure-help-inline,
+.pure-form-message-inline {
+    display: inline-block;
+    padding-left: 0.3em;
+    color: #666;
+    vertical-align: middle;
+    font-size: 0.875em;
+}
+
+/* Block help for forms */
+.pure-form-message {
+    display: block;
+    color: #666;
+    font-size: 0.875em;
+}
+
address@hidden only screen and (max-width : 480px) {
+    .pure-form button[type="submit"] {
+        margin: 0.7em 0 0;
+    }
+
+    .pure-form input:not([type]),
+    .pure-form input[type="text"],
+    .pure-form input[type="password"],
+    .pure-form input[type="email"],
+    .pure-form input[type="url"],
+    .pure-form input[type="date"],
+    .pure-form input[type="month"],
+    .pure-form input[type="time"],
+    .pure-form input[type="datetime"],
+    .pure-form input[type="datetime-local"],
+    .pure-form input[type="week"],
+    .pure-form input[type="number"],
+    .pure-form input[type="search"],
+    .pure-form input[type="tel"],
+    .pure-form input[type="color"],
+    .pure-form label {
+        margin-bottom: 0.3em;
+        display: block;
+    }
+
+    .pure-group input:not([type]),
+    .pure-group input[type="text"],
+    .pure-group input[type="password"],
+    .pure-group input[type="email"],
+    .pure-group input[type="url"],
+    .pure-group input[type="date"],
+    .pure-group input[type="month"],
+    .pure-group input[type="time"],
+    .pure-group input[type="datetime"],
+    .pure-group input[type="datetime-local"],
+    .pure-group input[type="week"],
+    .pure-group input[type="number"],
+    .pure-group input[type="search"],
+    .pure-group input[type="tel"],
+    .pure-group input[type="color"] {
+        margin-bottom: 0;
+    }
+
+    .pure-form-aligned .pure-control-group label {
+        margin-bottom: 0.3em;
+        text-align: left;
+        display: block;
+        width: 100%;
+    }
+
+    .pure-form-aligned .pure-controls {
+        margin: 1.5em 0 0 0;
+    }
+
+    /* NOTE: pure-help-inline is deprecated. Use .pure-form-message-inline 
instead. */
+    .pure-form .pure-help-inline,
+    .pure-form-message-inline,
+    .pure-form-message {
+        display: block;
+        font-size: 0.75em;
+        /* Increased bottom padding to make it group with its related input 
element. */
+        padding: 0.2em 0 0.8em;
+    }
+}
+
+/*csslint adjoining-classes: false, box-model:false*/
+.pure-menu {
+    box-sizing: border-box;
+}
+
+.pure-menu-fixed {
+    position: fixed;
+    left: 0;
+    top: 0;
+    z-index: 3;
+}
+
+.pure-menu-list,
+.pure-menu-item {
+    position: relative;
+}
+
+.pure-menu-list {
+    list-style: none;
+    margin: 0;
+    padding: 0;
+}
+
+.pure-menu-item {
+    padding: 0;
+    margin: 0;
+    height: 100%;
+}
+
+.pure-menu-link,
+.pure-menu-heading {
+    display: block;
+    text-decoration: none;
+    white-space: nowrap;
+}
+
+/* HORIZONTAL MENU */
+.pure-menu-horizontal {
+    width: 100%;
+    white-space: nowrap;
+}
+
+.pure-menu-horizontal .pure-menu-list {
+    display: inline-block;
+}
+
+/* Initial menus should be inline-block so that they are horizontal */
+.pure-menu-horizontal .pure-menu-item,
+.pure-menu-horizontal .pure-menu-heading,
+.pure-menu-horizontal .pure-menu-separator {
+    display: inline-block;
+    *display: inline;
+    zoom: 1;
+    vertical-align: middle;
+}
+
+/* Submenus should still be display: block; */
+.pure-menu-item .pure-menu-item {
+    display: block;
+}
+
+.pure-menu-children {
+    display: none;
+    position: absolute;
+    left: 100%;
+    top: 0;
+    margin: 0;
+    padding: 0;
+    z-index: 3;
+}
+
+.pure-menu-horizontal .pure-menu-children {
+    left: 0;
+    top: auto;
+    width: inherit;
+}
+
+.pure-menu-allow-hover:hover > .pure-menu-children,
+.pure-menu-active > .pure-menu-children {
+    display: block;
+    position: absolute;
+}
+
+/* Vertical Menus - show the dropdown arrow */
+.pure-menu-has-children > .pure-menu-link:after {
+    padding-left: 0.5em;
+    content: "\25B8";
+    font-size: small;
+}
+
+/* Horizontal Menus - show the dropdown arrow */
+.pure-menu-horizontal .pure-menu-has-children > .pure-menu-link:after {
+    content: "\25BE";
+}
+
+/* scrollable menus */
+.pure-menu-scrollable {
+    overflow-y: scroll;
+    overflow-x: hidden;
+}
+
+.pure-menu-scrollable .pure-menu-list {
+    display: block;
+}
+
+.pure-menu-horizontal.pure-menu-scrollable .pure-menu-list {
+    display: inline-block;
+}
+
+.pure-menu-horizontal.pure-menu-scrollable {
+    white-space: nowrap;
+    overflow-y: hidden;
+    overflow-x: auto;
+    -ms-overflow-style: none;
+    -webkit-overflow-scrolling: touch;
+    /* a little extra padding for this style to allow for scrollbars */
+    padding: .5em 0;
+}
+
+.pure-menu-horizontal.pure-menu-scrollable::-webkit-scrollbar {
+    display: none;
+}
+
+/* misc default styling */
+
+.pure-menu-separator,
+.pure-menu-horizontal .pure-menu-children .pure-menu-separator {
+    background-color: #ccc;
+    height: 1px;
+    margin: .3em 0;
+}
+
+.pure-menu-horizontal .pure-menu-separator {
+    width: 1px;
+    height: 1.3em;
+    margin: 0 .3em ;
+}
+
+/* Need to reset the separator since submenu is vertical */
+.pure-menu-horizontal .pure-menu-children .pure-menu-separator {
+    display: block;
+    width: auto;
+}
+
+.pure-menu-heading {
+    text-transform: uppercase;
+    color: #565d64;
+}
+
+.pure-menu-link {
+    color: #777;
+}
+
+.pure-menu-children {
+    background-color: #fff;
+}
+
+.pure-menu-link,
+.pure-menu-disabled,
+.pure-menu-heading {
+    padding: .5em 1em;
+}
+
+.pure-menu-disabled {
+    opacity: .5;
+}
+
+.pure-menu-disabled .pure-menu-link:hover {
+    background-color: transparent;
+}
+
+.pure-menu-active > .pure-menu-link,
+.pure-menu-link:hover,
+.pure-menu-link:focus {
+    background-color: #eee;
+}
+
+.pure-menu-selected .pure-menu-link,
+.pure-menu-selected .pure-menu-link:visited {
+    color: #000;
+}
+
+.pure-table {
+    /* Remove spacing between table cells (from Normalize.css) */
+    border-collapse: collapse;
+    border-spacing: 0;
+    empty-cells: show;
+    border: 1px solid #cbcbcb;
+}
+
+.pure-table caption {
+    color: #000;
+    font: italic 85%/1 arial, sans-serif;
+    padding: 1em 0;
+    text-align: center;
+}
+
+.pure-table td,
+.pure-table th {
+    border-left: 1px solid #cbcbcb;/*  inner column border */
+    border-width: 0 0 0 1px;
+    font-size: inherit;
+    margin: 0;
+    overflow: visible; /*to make ths where the title is really long work*/
+    padding: 0.5em 1em; /* cell padding */
+}
+
+/* Consider removing this next declaration block, as it causes problems when
+there's a rowspan on the first cell. Case added to the tests. issue#432 */
+.pure-table td:first-child,
+.pure-table th:first-child {
+    border-left-width: 0;
+}
+
+.pure-table thead {
+    background-color: #e0e0e0;
+    color: #000;
+    text-align: left;
+    vertical-align: bottom;
+}
+
+/*
+striping:
+   even - #fff (white)
+   odd  - #f2f2f2 (light gray)
+*/
+.pure-table td {
+    background-color: transparent;
+}
+.pure-table-odd td {
+    background-color: #f2f2f2;
+}
+
+/* nth-child selector for modern browsers */
+.pure-table-striped tr:nth-child(2n-1) td {
+    background-color: #f2f2f2;
+}
+
+/* BORDERED TABLES */
+.pure-table-bordered td {
+    border-bottom: 1px solid #cbcbcb;
+}
+.pure-table-bordered tbody > tr:last-child > td {
+    border-bottom-width: 0;
+}
+
+
+/* HORIZONTAL BORDERED TABLES */
+
+.pure-table-horizontal td,
+.pure-table-horizontal th {
+    border-width: 0 0 1px 0;
+    border-bottom: 1px solid #cbcbcb;
+}
+.pure-table-horizontal tbody > tr:last-child > td {
+    border-bottom-width: 0;
+}
diff --git a/talerbank/app/static/style.css b/talerbank/app/static/style.css
deleted file mode 100644
index c720186..0000000
--- a/talerbank/app/static/style.css
+++ /dev/null
@@ -1,158 +0,0 @@
-/*
-  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 Gabor Toth
-*/
-
-body {
-    background-color: white;
-    margin: 0;
-    padding: 0;
-    font-family: Verdana, sans;
-}
-
-header {
-    width: 100%;
-    height: 100px;
-    margin: 0;
-    padding: 0;
-    border-bottom: 1px solid black;
-}
-
-header h1 {
-    font-size: 200%;
-    margin: 15 0 0 120px;
-/*    position: relative;
-    top: 50%;
-    transform: translateY(-50%);*/
-}
-header #logo {
-    float: left;
-    width: 100px;
-    height: 100px;
-    padding: 0;
-    margin: 0;
-    text-align: center;
-    border-right: 1px solid black;
-}
-
-section#menu {
-    margin: 0 0 90 0;
-    padding: 5px;
-    border-right: 1px solid black;
-    height: 100%;
-    width: 90px;
-    float: left;
-}
-
-section#main {
-    margin: 0 0 0 100px;
-    padding: 20px;
-    border-left: 1px solid black;
-    height: 100%;
-    max-width: 40em;
-}
-
-section#main h1:first-child {
-    margin-top: 0;
-}
-
-div.login-form, div.register-form {
-    border-radius: 10px;
-    background-color: #f2f2f2;
-    padding: 11px 27px 44px 27px;
-    max-width: 200px;
-}
-
-div.login-form input[type=submit],
-div.register-form input[type=submit] {
-    width-max: 30%;
-    float: right;
-    padding: 6px;
-}
-
-.selected-item {
-    border-style: solid;
-    border-width: 1px;
-}
-
-.informational {
-    border-radius: 8px;
-    padding: 8px;
-}
-
-.informational-ok {
-    background: #ccffcc;
-}
-
-.informational-fail {
-    background: #ff8566;
-}
-
-div.login-form input[type=text],
-div.login-form input[type=password],
-div.register-form input[type=text],
-div.register-form input[type=password] {
-    width: 100%;
-    padding: 12px 20px;
-    margin: 8px 0;
-    display: inline-block;
-    border: 1px solid #ccc;
-    border-radius: 4px;
-    box-sizing: border-box;
-}
-
-h1 {
-    font-size: 160%;
-}
-
-h2 {
-    font-size: 140%;
-}
-
-h3 {
-    font-size: 120%;
-}
-
-h4, h5, h6 {
-    font-size: 100%;
-}
-
-table.history {
-       margin: 30px 0px;
-       border-width: 0px;
-       border-spacing: 3px;
-       border-style: groove;
-       border-color: gray;
-       border-collapse: separate;
-       background-color: white;
-}
-table.history th {
-       border-width: 1px;
-       padding: 5px;
-       border-style: outset;
-       border-color: gray;
-       background-color: white;
-       -moz-border-radius: ;
-}
-table.history td {
-       border-width: 1px;
-       padding: 5px;
-       border-style: outset;
-       border-color: gray;
-       background-color: white;
-       -moz-border-radius: ;
-}
diff --git a/talerbank/app/static/web-common b/talerbank/app/static/web-common
index caf5a98..d7e0135 160000
--- a/talerbank/app/static/web-common
+++ b/talerbank/app/static/web-common
@@ -1 +1 @@
-Subproject commit caf5a98114402d057ba08b14279eb8e46481a02c
+Subproject commit d7e013594d15388b1a7342a44a0e9c8d4ecca82d
diff --git a/talerbank/app/templates/Makefile.am 
b/talerbank/app/templates/Makefile.am
index 34751ff..15aa5a8 100644
--- a/talerbank/app/templates/Makefile.am
+++ b/talerbank/app/templates/Makefile.am
@@ -3,10 +3,10 @@ SUBDIRS = .
 EXTRA_DIST = \
   base.html \
   account_disabled.html \
-  history.html \
   profile_page.html \
   public_accounts.html \
-  error.html \
-  home_page.html \
   pin_tan.html \
-  register.html
+  register.html \
+  login.html \
+  javascript.html \
+  error_exchange.html
diff --git a/talerbank/app/templates/account_disabled.html 
b/talerbank/app/templates/account_disabled.html
index d2ebfb4..c84351c 100644
--- a/talerbank/app/templates/account_disabled.html
+++ b/talerbank/app/templates/account_disabled.html
@@ -1,21 +1,3 @@
-<!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 headermsg %}
@@ -27,7 +9,7 @@
   <section id="main">
     <article>
       <h1>Account disabled</h1>
-      <p>{{ name }}, your account has been disabled, <a href="{% url 
'register' %}">register</a>
+      <p>{{ name }}, your account has been disabled, <a href="{% 
url("register") %}">register</a>
           a new one!</p>
     </article>
   </section>
diff --git a/talerbank/app/templates/base.html 
b/talerbank/app/templates/base.html
index 3285dd6..c4d0efd 100644
--- a/talerbank/app/templates/base.html
+++ b/talerbank/app/templates/base.html
@@ -1,3 +1,4 @@
+<!doctype html>
 <!--
   This file is part of GNU TALER.
   Copyright (C) 2014, 2015, 2016 INRIA
@@ -14,36 +15,41 @@
   TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
 
   @author Marcello Stanisci
+  @author Florian Dold
 -->
 
-{% 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/lang.css" %}">
-    {% 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>
+    <title>{{ settings_value("TALER_CURRENCY") }} Bank - Taler Demo</title>
+    <link rel="stylesheet" type="text/css" href="{{ static('pure.css') }}" />
+    <link rel="stylesheet" type="text/css" href="{{ 
static('web-common/demo.css') }}" />
+    <link rel="stylesheet" type="text/css" href="{{ 
static('web-common/taler-fallback.css') }}" id="taler-presence-stylesheet" />
+    <script src="{{ static('web-common/taler-wallet-lib.js') }}" 
type="application/javascript"></script>
     {% block head %} {% endblock %}
   </head>
-  <body class="en">
-    <header>
-      <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" />
-            <text x="19" y="83" font-family="Verdana" font-size="90" 
fill="darkmagenta">
-           B
-           </text>
-          </svg>
-        </div>
-      </a>
+  <body>
+    <div class="demobar">
+      <h1><span class="tt adorn-brackets">Taler Demo</span></h1>
+      <h1><span class="it"><a href="{{ url('index') }}">Bank</a></span></h1>
+      <p>This part of the demo shows how a bank that supports Taler directly 
would work.  In addition to
+      using your own bank account, you can also see the transaction history of 
some <a href="{{ url('public-accounts') }}">Public Accounts</a>.</p>
+      <p>Other parts of the demo:</p>
+      <ul>
+        <li><a href="{{ env('TALER_ENV_URL_INTRO', '#') 
}}">Introduction</a></li>
+        <li><a href="{{ env('TALER_ENV_URL_BANK', '#') }}">Bank</a></li>
+        <li><a href="{{ env('TALER_ENV_URL_MERCHANT_BLOG', '#') }}">Essay 
Shop</a></li>
+        <li><a href="{{ env('TALER_ENV_URL_MERCHANT_DONATIONS', '#') 
}}">Donations</a></li>
+      </ul>
+      <p>You can learn more about Taler on our main <a 
href="https://taler.net";>website</a>.</p>
+    </div>
+    <div class="content">
       {% block headermsg %} {% endblock %}
-    </header>
-    {% block content %} {% endblock %}
+      {% block content %} {% endblock %}
+      <div class="copyright">
+        <hr />
+        <p>Copyright &copy; 2014&mdash;2017 INRIA</p>
+        <a href="{{ url('javascript') }}" data-jslicense="1" 
class="jslicenseinfo">JavaScript license information</a>
+      </div>
+    </div>
   </body>
 </html>
diff --git a/talerbank/app/templates/login.html 
b/talerbank/app/templates/login.html
index b9d6020..c86d4f0 100644
--- a/talerbank/app/templates/login.html
+++ b/talerbank/app/templates/login.html
@@ -1,5 +1,5 @@
-<!DOCTYPE html>
-<!-- 
+{% extends "base.html" %}
+{#
   This file is part of GNU TALER.
   Copyright (C) 2014, 2015, 2016 INRIA
 
@@ -16,22 +16,17 @@
 
   @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>
+  <h1 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>
+        <h2>Please login!</h2>
         {% if form.errors %}
         <p class="informational informational-fail">
           Your username and password didn't match. Please try again.
@@ -45,31 +40,29 @@
         {% endif %}
 
         {% if next %}
-            {% if user.is_authenticated %}
+            {% 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>
+        <form method="post" class="pure-form" action="{{ url('login') }}">
+          <input type="hidden" name="csrfmiddlewaretoken" value="{{ csrf_token 
}}">
+          {{ form.username }}
+          <input type="password" name="password" 
placeholder="password"></input>
+          <input type="submit" value="login" class="pure-button 
pure-button-primary" />
+          <input type="hidden" name="next" value="{{ next }}" />
+        </form>
       </div>
       <p>
-      If you are a new customer, please <a href="{% url 'register' 
%}">register</a>.
+      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" %}!
+      of 100 {{ settings_value("TALER_CURRENCY") }}!
       </p>
       <p>
       To view transactions of public accounts,
-      please <a href="{% url "public-accounts" %}">click here</a>.
+      please <a href="{{ url('public-accounts') }}">click here</a>.
       </p>
     </article>
   </section>
diff --git a/talerbank/app/templates/pin_tan.html 
b/talerbank/app/templates/pin_tan.html
index 244cf9c..166118f 100644
--- a/talerbank/app/templates/pin_tan.html
+++ b/talerbank/app/templates/pin_tan.html
@@ -19,40 +19,32 @@
 
 {% extends "base.html" %}
 
-{% 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>
-        {% 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="{% 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
-        a PIN/TAN instead of a simple calculation. For example by sending
-       a one time password to the customer's mobile or providing her a
-       random password generator.
-      <small>
-    </article>
-  </section>
+  {% if previous_failed %}
+  <p class="informational informational-fail">
+    The captcha wasn't solved correctly.  Please try again.
+  </p>
+  {% endif %}
+  <p>
+    {{ 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="{{ url('pin-verify') }}">
+    <input type="hidden" name="csrfmiddlewaretoken" value="{{ 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
+    a PIN/TAN instead of a simple calculation. For example by sending
+    a one time password to the customer's mobile or providing her a
+    random password generator.
+  <small>
 {% endblock content %}
diff --git a/talerbank/app/templates/profile_page.html 
b/talerbank/app/templates/profile_page.html
index dd2ca67..1a016f1 100644
--- a/talerbank/app/templates/profile_page.html
+++ b/talerbank/app/templates/profile_page.html
@@ -1,5 +1,5 @@
-<!DOCTYPE html>
-<!--
+{% extends "base.html" %}
+{#
   This file is part of GNU TALER.
   Copyright (C) 2014, 2015, 2016 INRIA
 
@@ -15,47 +15,38 @@
   TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
 
   @author Marcello Stanisci
--->
-{% extends "base.html" %}
-{% load static from mystatic %}
+#}
 
 {% block head %}
   <meta name="currency" value="{{ currency }}">
   <meta name="precision" value="{{ precision }}">
-  <meta name="callback-url" value="{% url 'pin-question' %}">
+  <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 use_js %}
-    <script src="{% static "profile-page.js" %}" 
type="application/javascript"></script>
-  {% endif %}
+  <link rel="stylesheet" type="text/css" href="{{ 
static('disabled-button.css') }}">
+  <script src="{{ static('chrome-store-link.js') }}" 
type="application/javascript"></script>
 {% endblock head %}
 {% block headermsg %}
   <h1 class="nav">Welcome <em>{{ name }}</em>!</h1>
 {% endblock headermsg %}
 {% 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 }}
-    </p>
-    <p>Current<br>
-      balance:<br>
-      <b>
-      {{ balance }}</b> <br>
-      {{ currency }}
-    </p>
+    <a href="{{ url('logout') }}" class="pure-button">[Logout]</a><br>
+    <p>Account: # {{ account_no }}</p>
+    <p>Current balance: {{ balance }} {{ currency }}</p>
   </section>
   <section id="main">
     <article>
       <div class="notification">
+        {% if no_initial_bonus %}
+        <p class="informational informational-fail">
+          No initial bonus given, poor bank!
+        </p>
+        {% endif %}
         {% if just_withdrawn %}
         <p class="informational informational-ok">
           Withdrawal approved!
@@ -89,56 +80,45 @@
         <h2>Withdraw digital coins using Taler</h2>
 
         <form id="reserve-form"
-              {% if js == 'use_js' %}
-                action=""
-              {% else %}
-                action="{% url 'withdraw-nojs' %}"
-                method="post"
-              {% endif %}
+              class="pure-form"
+              action="{{ url('withdraw-nojs') }}"
+              method="post"
               name="tform">
-         {% csrf_token %}
+          <input type="hidden" name="csrfmiddlewaretoken" value="{{ csrf_token 
}}">
           Amount to withdraw:
           <select id="reserve-amount" name="kudos_amount" autofocus>
-              <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>
+              <option value="{{ currency }}:1">1.00 {{ currency }}</option>
+              <option value="{{ currency }}:10">10.00 {{ currency }}</option>
+              <option value="{{ currency }}:15">15.00 {{ currency }}</option>
+              <option value="{{ currency }}:20">20.00 {{ currency }}</option>
           </select>
           <input id="select-exchange"
-                 class="taler-installed-show"
-                 {% if use_js %}
-                   type="button"
-                 {% else %}
-                   type="submit"
-                 {% endif %}
+                 class="taler-installed-show pure-button pure-button-primary"
+                 type="submit"
                  value="Select exchange provider"></input>
-          <input class="taler-installed-hide"
+          <input class="taler-installed-hide pure-button pure-button-primary"
                  type="button"
                  disabled
                  value="Select exchange provider"></input>
-          </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>
       <div id="transactions-history">
         {% if history %}
-        <table class="history">
-          <tbody>
+        <table class="pure-table">
+          <thead>
           <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>
+          </thead>
+          <tbody>
           {% for item in history %}
           <tr>
             <td style="text-align:right">{{ item.date }}</td>
@@ -160,8 +140,4 @@
 
     </article>
   </section>
-  <div class="copyright">
-    <p>Copyright &copy; 2014&mdash;2016 INRIA</p>
-    <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
index d6c2519..2f38489 100644
--- a/talerbank/app/templates/public_accounts.html
+++ b/talerbank/app/templates/public_accounts.html
@@ -1,5 +1,5 @@
-<!DOCTYPE html>
-<!-- 
+{% extends "base.html" %}
+{#
   This file is part of GNU TALER.
   Copyright (C) 2014, 2015, 2016 INRIA
 
@@ -15,67 +15,58 @@
   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>
+  <a href="{{ url('index') }}">Back</a>
   <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 name="accountMenu" class="pure-menu pure-menu-horizontal">
+        <ul class="pure-menu-list">
+        {% for account in public_accounts %}
+          {% if account.account_no == selected_account.number %}
+          <li class="pure-menu-item pure-menu-selected">
+          {% else %}
+          <li class="pure-menu-item pure-menu">
+          {% endif %}
+            <a href="{{ url("public-accounts", name=account.user.username) }}" 
class="pure-menu-link">
+              {{ account.user.username }}
+            </a>
+          </li>
+        {% endfor %}
+        </ul>
       </div>
-    </table>
+
+      {% if selected_account.history %}
+        <table class="pure-table pure-table-striped">
+          <thead>
+            <th>Date</th>
+            <th>Amount</th>
+            <th>Counterpart</th>
+            <th>Subject</th>
+          </thead>
+          <tbody>
+          {% for entry in selected_account.history %}
+          <tr>
+            <td>{{entry.date}}</td>
+            <td>
+              {{ entry.float_amount }} {{ entry.float_currency }}
+            </td>
+            <td>{% if entry.counterpart_username %} {{ 
entry.counterpart_username }} {% endif %} (account #{{ entry.counterpart 
}})</td>
+            <td>
+              {{ entry.subject }}
+            </td>
+          </tr>
+          {% endfor %}
+          </tbody>
+        </table>
+      {% else %}
+        <p>No history for account #{{ selected_account.number }} ({{ 
selected_account.name}}) yet</p>
+      {% endif %}
     </article>
   </section>
 {% endblock content %}
diff --git a/talerbank/app/templates/register.html 
b/talerbank/app/templates/register.html
index 20aa8af..509c689 100644
--- a/talerbank/app/templates/register.html
+++ b/talerbank/app/templates/register.html
@@ -1,4 +1,3 @@
-<!DOCTYPE html>
 <!-- 
   This file is part of GNU TALER.
   Copyright (C) 2014, 2015, 2016 INRIA
@@ -18,10 +17,9 @@
 -->
 
 {% extends "base.html" %}
-{% load settings_value from settings %}
 
 {% block headermsg %}
-  <h1 class="nav">Register to the {% settings_value "TALER_CURRENCY" %} 
bank!</h1>
+  <h1 class="nav">Register to the {{ settings_value('TALER_CURRENCY') }} 
bank!</h1>
 {% endblock headermsg %}
 
 {% block content %}
@@ -29,6 +27,7 @@
   </aside>
   <section id="main">
     <article>
+      <a href="{{ url('index') }}">Back</a>
       <div class="notification">
         {% if wrong %}
           <p class="informational informational-fail">
@@ -47,13 +46,12 @@
   <section id="main">
     <article>
       <div class="register-form">
-        <h1 lang="en">Registration form</h1>
-        <h1 lang="it">Form di registrazione</h1>
-        <form method="post" action="{% url 'register' %}">
-          {% csrf_token %}
+        <h1>Registration form</h1>
+        <form class="pure-form" method="post" action="{{ url('register') }}">
+          <input type="hidden" name="csrfmiddlewaretoken" value="{{ 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>
+          <input type="submit" value="Ok" class="pure-button 
pure-button-primary"></input>
         </form>
       </div>
     </article>
diff --git a/talerbank/app/templatetags/__init__.py 
b/talerbank/app/templatetags/__init__.py
deleted file mode 100644
index e69de29..0000000
diff --git a/talerbank/app/templatetags/mystatic.py 
b/talerbank/app/templatetags/mystatic.py
deleted file mode 100644
index 2c57841..0000000
--- a/talerbank/app/templatetags/mystatic.py
+++ /dev/null
@@ -1,30 +0,0 @@
-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
deleted file mode 100644
index 08fb084..0000000
--- a/talerbank/app/templatetags/settings.py
+++ /dev/null
@@ -1,8 +0,0 @@
-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 7b437f1..092d21c 100644
--- a/talerbank/app/tests.py
+++ b/talerbank/app/tests.py
@@ -18,8 +18,11 @@ 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
+from .models import BankAccount, BankTransaction
+from . import urls
+from . import amounts
+from .views import wire_transfer
+import json
 
 import logging
 
@@ -28,6 +31,7 @@ logger = logging.getLogger(__name__)
 def clearDb():
     User.objects.all().delete()
     BankAccount.objects.all().delete()
+    BankTransaction.objects.all().delete()
 
 
 class RegisterTestCase(TestCase):
@@ -63,11 +67,9 @@ class LoginTestCase(TestCase):
                                    currency=settings.TALER_CURRENCY)
         user_account.save()
 
-
     def tearDown(self):
         clearDb()
     
-
     def test_login(self):
         c = Client()
         response = c.post(reverse("login", urlconf=urls),
@@ -75,3 +77,192 @@ class LoginTestCase(TestCase):
                            "password": "test_password"},
                            follow=True)
         self.assertIn(("/profile", 302), response.redirect_chain)
+
+
+class AmountTestCase(TestCase):
+    
+    def test_cmp(self):
+        a1 = dict(value=1, fraction=0, currency="X")
+        _a1 = dict(value=1, fraction=0, currency="X")
+        a2 = dict(value=2, fraction=0, currency="X")
+        self.assertEqual(-1, amounts.amount_cmp(a1, a2))
+        self.assertEqual(1, amounts.amount_cmp(a2, a1))
+        self.assertEqual(0, amounts.amount_cmp(a1, _a1))
+
+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):
+        clearDb()
+
+    def test_add_incoming(self):
+        c = Client()
+        data = '{"auth": {"type": "basic"}, \
+                 "credit_account": 1, \
+                 "wtid": "TESTWTID", \
+                 "exchange_url": "https://exchange.test";, \
+                 "amount": \
+                   {"value": 1, \
+                    "fraction": 0, \
+                    "currency": "%s"}}' \
+               % settings.TALER_CURRENCY
+        response = c.post(reverse("add-incoming", urlconf=urls),
+                          data=data,
+                          content_type="application/json",
+                          follow=True, **{"HTTP_X_TALER_BANK_USERNAME": 
"user_user", "HTTP_X_TALER_BANK_PASSWORD": "user_password"})
+        self.assertEqual(200, response.status_code)
+        data = '{"auth": {"type": "basic"}, \
+                 "credit_account": 1, \
+                 "wtid": "TESTWTID", \
+                 "exchange_url": "https://exchange.test";, \
+                 "amount": \
+                   {"value": 1, \
+                    "fraction": 0, \
+                    "currency": "%s"}}' \
+               % "WRONGCURRENCY"
+        response = c.post(reverse("add-incoming", urlconf=urls),
+                          data=data,
+                          content_type="application/json",
+                          follow=True, **{"HTTP_X_TALER_BANK_USERNAME": 
"user_user", "HTTP_X_TALER_BANK_PASSWORD": "user_password"})
+        self.assertEqual(406, response.status_code)
+
+class HistoryTestCase(TestCase):
+
+    def setUp(self):
+        user = User.objects.create_user(username='User', password="Password")
+        ub = BankAccount(user=user, currency=settings.TALER_CURRENCY)
+        ub.account_no = 1
+        ub.balance_obj = dict(value=100, fraction=0, 
currency=settings.TALER_CURRENCY)
+        ub.save() 
+        user_passive = User.objects.create_user(username='UserP', 
password="PasswordP")
+        ub_p = BankAccount(user=user_passive, currency=settings.TALER_CURRENCY)
+        ub_p.account_no = 2
+        ub_p.save() 
+        wire_transfer(dict(value=1, fraction=0, 
currency=settings.TALER_CURRENCY), ub, ub_p, subject="a")
+        wire_transfer(dict(value=1, fraction=0, 
currency=settings.TALER_CURRENCY), ub, ub_p, subject="b")
+        wire_transfer(dict(value=1, fraction=0, 
currency=settings.TALER_CURRENCY), ub, ub_p, subject="c")
+        wire_transfer(dict(value=1, fraction=0, 
currency=settings.TALER_CURRENCY), ub, ub_p, subject="d")
+        wire_transfer(dict(value=1, fraction=0, 
currency=settings.TALER_CURRENCY), ub, ub_p, subject="e")
+        wire_transfer(dict(value=1, fraction=0, 
currency=settings.TALER_CURRENCY), ub, ub_p, subject="f")
+        wire_transfer(dict(value=1, fraction=0, 
currency=settings.TALER_CURRENCY), ub, ub_p, subject="g")
+        wire_transfer(dict(value=1, fraction=0, 
currency=settings.TALER_CURRENCY), ub, ub_p, subject="h")
+
+    def tearDown(self):
+        clearDb()
+
+    def test_history(self):
+        c = Client()
+
+        response = c.get(reverse("history", urlconf=urls), {"auth": "basic", 
"delta": "+4"},
+                         **{"HTTP_X_TALER_BANK_USERNAME": "User", 
"HTTP_X_TALER_BANK_PASSWORD": "Password"})
+        self.assertEqual(200, response.status_code)
+
+        # Get a delta=+1 record in the middle of the list: FAILS
+        response = c.get(reverse("history", urlconf=urls), {"auth": "basic", 
"delta": "+1", "start": "5"},
+                         **{"HTTP_X_TALER_BANK_USERNAME": "User", 
"HTTP_X_TALER_BANK_PASSWORD": "Password"})
+        data = response.content.decode("utf-8")
+        data = json.loads(data)
+        self.assertEqual(data["data"][0]["row_id"], 6)
+        # Get latest record
+        response = c.get(reverse("history", urlconf=urls), {"auth": "basic", 
"delta": "-1"},
+                         **{"HTTP_X_TALER_BANK_USERNAME": "User", 
"HTTP_X_TALER_BANK_PASSWORD": "Password"})
+        data = response.content.decode("utf-8")
+        data = json.loads(data)
+        self.assertEqual(data["data"][0]["wt_subject"], "h")
+        # Get non-existent record: the latest plus one in the future: 
transaction "h" takes row_id 11
+        response = c.get(reverse("history", urlconf=urls), {"auth": "basic", 
"delta": "1", "start": "11"},
+                         **{"HTTP_X_TALER_BANK_USERNAME": "User", 
"HTTP_X_TALER_BANK_PASSWORD": "Password"})
+        response_txt = response.content.decode("utf-8")
+        self.assertEqual(204, response.status_code)
+        # Get credit records
+        response = c.get(reverse("history", urlconf=urls), {"auth": "basic", 
"delta": "+1", "direction": "credit"},
+                         **{"HTTP_X_TALER_BANK_USERNAME": "User", 
"HTTP_X_TALER_BANK_PASSWORD": "Password"})
+        self.assertEqual(204, response.status_code)
+        # Get debit records
+        response = c.get(reverse("history", urlconf=urls), {"auth": "basic", 
"delta": "+1", "direction": "debit"},
+                         **{"HTTP_X_TALER_BANK_USERNAME": "User", 
"HTTP_X_TALER_BANK_PASSWORD": "Password"})
+        self.assertNotEqual(204, response.status_code)
+        # Query about non-owned account
+        response = c.get(reverse("history", urlconf=urls), {"auth": "basic", 
"delta": "+1", "account_number": 2},
+                         **{"HTTP_X_TALER_BANK_USERNAME": "User", 
"HTTP_X_TALER_BANK_PASSWORD": "Password"})
+        self.assertEqual(403, response.status_code)
+        # Query about non-existent account
+        response = c.get(reverse("history", urlconf=urls), {"auth": "basic", 
"delta": "-1", "account_number": 9},
+                         **{"HTTP_X_TALER_BANK_USERNAME": "User", 
"HTTP_X_TALER_BANK_PASSWORD": "Password"})
+        self.assertEqual(404, response.status_code)
+
+
+# This tests whether a bank account goes red and then
+## goes green again
+class DebitTestCase(TestCase):
+
+    def setUp(self):
+        u = User.objects.create_user(username='U')
+        u0 = User.objects.create_user(username='U0')
+        ua = BankAccount(user=u, currency=settings.TALER_CURRENCY)
+        u0a = BankAccount(user=u0, currency=settings.TALER_CURRENCY)
+
+        ua.save()
+        u0a.save()
+
+    def test_green(self):
+        u = User.objects.get(username='U')
+        ub = BankAccount.objects.get(user=u)
+        self.assertEqual(False, ub.debit)
+
+    def test_red(self):
+        u = User.objects.get(username='U')
+        u0 = User.objects.get(username='U0')
+
+        ub = BankAccount.objects.get(user=u)
+        ub0 = BankAccount.objects.get(user=u0)
+
+        wire_transfer(dict(value=10, fraction=0, 
currency=settings.TALER_CURRENCY),
+                      ub0,
+                      ub,
+                      "Go green")
+        tmp = amounts.get_zero()
+        tmp["value"] = 10
+
+        self.assertEqual(0, amounts.amount_cmp(ub.balance_obj, tmp))
+        self.assertEqual(False, ub.debit)
+        self.assertEqual(True, ub0.debit)
+
+        wire_transfer(dict(value=11, fraction=0, 
currency=settings.TALER_CURRENCY),
+                      ub,
+                      ub0,
+                      "Go red")
+
+        self.assertEqual(True, ub.debit)
+        self.assertEqual(False, ub0.debit)
+
+        tmp["value"] = 1
+
+        self.assertEqual(0, amounts.amount_cmp(ub0.balance_obj, tmp))
+
+class TestParseAmount(TestCase):
+     def test_parse_amount(self):
+         ret = amounts.parse_amount("KUDOS:4")
+         self.assertJSONEqual('{"value": 4, "fraction": 0, "currency": 
"KUDOS"}', json.dumps(ret))
+         ret = amounts.parse_amount("KUDOS:4.00")
+         self.assertJSONEqual('{"value": 4, "fraction": 0, "currency": 
"KUDOS"}', json.dumps(ret))
+         ret = amounts.parse_amount("KUDOS:4.3")
+         self.assertJSONEqual('{"value": 4, "fraction": 30000000, "currency": 
"KUDOS"}', json.dumps(ret))
+         try:
+             amounts.parse_amount("Buggy")
+         except amounts.BadFormatAmount:
+             return
+         # make sure the control doesn't get here
+         self.assertEqual(True, False)
diff --git a/talerbank/app/tests_admin.py b/talerbank/app/tests_admin.py
deleted file mode 100644
index 8adbfe1..0000000
--- a/talerbank/app/tests_admin.py
+++ /dev/null
@@ -1,62 +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.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/tests_err.py b/talerbank/app/tests_err.py
new file mode 100644
index 0000000..26b1ae7
--- /dev/null
+++ b/talerbank/app/tests_err.py
@@ -0,0 +1,301 @@
+#  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, BankTransaction
+from . import urls
+from . import amounts
+from .views import wire_transfer
+import json
+
+import logging
+
+logger = logging.getLogger(__name__)
+
+def clearDb():
+    User.objects.all().delete()
+    BankAccount.objects.all().delete()
+    BankTransaction.objects.all().delete()
+
+
+class RegisterTestCase(TestCase):
+    """User registration"""
+
+    def setUp(self):
+        bank = User.objects.create_user(username='Bank')
+        # Activating this user with a faulty currency.
+        ba = BankAccount(user=bank, currency="XYZ")
+        ba.account_no = 1
+        ba.save() 
+
+    def tearDown(self):
+        clearDb()
+
+    def test_register(self):
+        c = Client()
+        response = c.post(reverse("register", urlconf=urls),
+                          {"username": "test_register",
+                           "password": "test_register"},
+                           follow=True)
+        # A currency mismatch is expected when the 100 KUDOS will be
+        # attempted to be given to the new user.
+        # This scenario expects a 500 Internal server error response to
+        # be returned.
+        self.assertEqual(500, response.status_code)
+
+
+class LoginTestCase(TestCase):
+    """User login"""
+
+    def setUp(self):
+        user = User.objects.create_user(username="test_user",
+                                        password="test_password")
+        user_account = BankAccount(user=user,
+                                   currency=settings.TALER_CURRENCY)
+        user_account.save()
+
+    def tearDown(self):
+        clearDb()
+    
+    def test_login(self):
+        c = Client()
+        # Sending non existent credentials.
+        response = c.post(reverse("login", urlconf=urls),
+                          {"username": "test_user",
+                           "password": "test_passwordoo"},
+                           follow=True)
+        # The current logic -- django's default -- returns 200 OK
+        # even when the login didn't succeed.
+        # So the test here ensures that the bank just doesn't crash.
+        self.assertEqual(200, response.status_code)
+
+
+class AmountTestCase(TestCase):
+    
+    # Trying to compare amount of different currencies
+    def test_cmp(self):
+        a1 = dict(value=1, fraction=0, currency="X")
+        a2 = dict(value=2, fraction=0, currency="Y")
+        try:
+            amounts.amount_cmp(a1, a2)
+        except amounts.CurrencyMismatchException:
+            self.assertTrue(True)
+            return
+        # Should never get here
+        self.assertTrue(False)
+
+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()
+
+        no1 = BankAccount.objects.get(account_no=1)
+        no2 = BankAccount.objects.get(account_no=2)
+        logger.info("1 username: %s" % no1.user.username)
+        logger.info("2 username: %s" % no2.user.username)
+
+    def tearDown(self):
+        clearDb()
+
+    def test_add_incoming(self):
+        c = Client()
+        data = '{"auth": {"type": "basic"}, \
+                 "credit_account": 1, \
+                 "wtid": "TESTWTID", \
+                 "exchange_url": "https://exchange.test";, \
+                 "amount": \
+                   {"value": 1, \
+                    "fraction": 0, \
+                    "currency": "%s"}}' \
+               % settings.TALER_CURRENCY
+        # Trying with wrong credentials
+        response = c.post(reverse("add-incoming", urlconf=urls),
+                          data=data,
+                          content_type="application/json",
+                          follow=True, **{"HTTP_X_TALER_BANK_USERNAME": 
"user_useroo", "HTTP_X_TALER_BANK_PASSWORD": "user_passwordoo"})
+        self.assertEqual(401, response.status_code)
+        data = '{"auth": {"type": "basic"}, \
+                 "credit_account": 1, \
+                 "wtid": "TESTWTID", \
+                 "exchange_url": "https://exchange.test";, \
+                 "amount": \
+                   {"value": 1, \
+                    "fraction": 0, \
+                    "currency": "%s"}}' \
+               % "WRONGCURRENCY"
+        # Trying with wrong currency
+        response = c.post(reverse("add-incoming", urlconf=urls),
+                          data=data,
+                          content_type="application/json",
+                          follow=True, **{"HTTP_X_TALER_BANK_USERNAME": 
"user_user", "HTTP_X_TALER_BANK_PASSWORD": "user_password"})
+        self.assertEqual(406, response.status_code)
+        # Trying to go debit
+        data = '{"auth": {"type": "basic"}, \
+                 "credit_account": 1, \
+                 "wtid": "TESTWTID", \
+                 "exchange_url": "https://exchange.test";, \
+                 "amount": \
+                   {"value": 60, \
+                    "fraction": 0, \
+                    "currency": "%s"}}' \
+               % settings.TALER_CURRENCY
+        response = c.post(reverse("add-incoming", urlconf=urls),
+                          data=data,
+                          content_type="application/json",
+                          follow=True, **{"HTTP_X_TALER_BANK_USERNAME": 
"user_user", "HTTP_X_TALER_BANK_PASSWORD": "user_password"})
+
+
+class HistoryTestCase(TestCase):
+
+    def setUp(self):
+        user = User.objects.create_user(username='User', password="Password")
+        ub = BankAccount(user=user, currency=settings.TALER_CURRENCY)
+        ub.account_no = 1
+        ub.balance_obj = dict(value=100, fraction=0, 
currency=settings.TALER_CURRENCY)
+        ub.save() 
+        user_passive = User.objects.create_user(username='UserP', 
password="PasswordP")
+        ub_p = BankAccount(user=user_passive, currency=settings.TALER_CURRENCY)
+        ub_p.account_no = 2
+        ub_p.save() 
+        wire_transfer(dict(value=1, fraction=0, 
currency=settings.TALER_CURRENCY), ub, ub_p, subject="a")
+        wire_transfer(dict(value=1, fraction=0, 
currency=settings.TALER_CURRENCY), ub, ub_p, subject="b")
+        wire_transfer(dict(value=1, fraction=0, 
currency=settings.TALER_CURRENCY), ub, ub_p, subject="c")
+        wire_transfer(dict(value=1, fraction=0, 
currency=settings.TALER_CURRENCY), ub, ub_p, subject="d")
+        wire_transfer(dict(value=1, fraction=0, 
currency=settings.TALER_CURRENCY), ub, ub_p, subject="e")
+        wire_transfer(dict(value=1, fraction=0, 
currency=settings.TALER_CURRENCY), ub, ub_p, subject="f")
+        wire_transfer(dict(value=1, fraction=0, 
currency=settings.TALER_CURRENCY), ub, ub_p, subject="g")
+        wire_transfer(dict(value=1, fraction=0, 
currency=settings.TALER_CURRENCY), ub, ub_p, subject="h")
+
+    def tearDown(self):
+        clearDb()
+
+    def test_history(self):
+        c = Client()
+
+        response = c.get(reverse("history", urlconf=urls), {"auth": "basic", 
"delta": "+4"},
+                         **{"HTTP_X_TALER_BANK_USERNAME": "User", 
"HTTP_X_TALER_BANK_PASSWORD": "Password"})
+        self.assertEqual(200, response.status_code)
+
+        # Get a delta=+1 record in the middle of the list: FAILS
+        response = c.get(reverse("history", urlconf=urls), {"auth": "basic", 
"delta": "+1", "start": "5"},
+                         **{"HTTP_X_TALER_BANK_USERNAME": "User", 
"HTTP_X_TALER_BANK_PASSWORD": "Password"})
+        data = response.content.decode("utf-8")
+        data = json.loads(data)
+        self.assertEqual(data["data"][0]["row_id"], 6)
+        # Get latest record
+        response = c.get(reverse("history", urlconf=urls), {"auth": "basic", 
"delta": "-1"},
+                         **{"HTTP_X_TALER_BANK_USERNAME": "User", 
"HTTP_X_TALER_BANK_PASSWORD": "Password"})
+        data = response.content.decode("utf-8")
+        data = json.loads(data)
+        self.assertEqual(data["data"][0]["wt_subject"], "h")
+        # Get non-existent record: the latest plus one in the future: 
transaction "h" takes row_id 11
+        response = c.get(reverse("history", urlconf=urls), {"auth": "basic", 
"delta": "1", "start": "11"},
+                         **{"HTTP_X_TALER_BANK_USERNAME": "User", 
"HTTP_X_TALER_BANK_PASSWORD": "Password"})
+        response_txt = response.content.decode("utf-8")
+        self.assertEqual(204, response.status_code)
+        # Get credit records
+        response = c.get(reverse("history", urlconf=urls), {"auth": "basic", 
"delta": "+1", "direction": "credit"},
+                         **{"HTTP_X_TALER_BANK_USERNAME": "User", 
"HTTP_X_TALER_BANK_PASSWORD": "Password"})
+        self.assertEqual(204, response.status_code)
+        # Get debit records
+        response = c.get(reverse("history", urlconf=urls), {"auth": "basic", 
"delta": "+1", "direction": "debit"},
+                         **{"HTTP_X_TALER_BANK_USERNAME": "User", 
"HTTP_X_TALER_BANK_PASSWORD": "Password"})
+        self.assertNotEqual(204, response.status_code)
+        # Query about non-owned account
+        response = c.get(reverse("history", urlconf=urls), {"auth": "basic", 
"delta": "+1", "account_number": 2},
+                         **{"HTTP_X_TALER_BANK_USERNAME": "User", 
"HTTP_X_TALER_BANK_PASSWORD": "Password"})
+        self.assertEqual(403, response.status_code)
+        # Query about non-existent account
+        response = c.get(reverse("history", urlconf=urls), {"auth": "basic", 
"delta": "-1", "account_number": 9},
+                         **{"HTTP_X_TALER_BANK_USERNAME": "User", 
"HTTP_X_TALER_BANK_PASSWORD": "Password"})
+        self.assertEqual(404, response.status_code)
+
+
+# This tests whether a bank account goes red and then
+## goes green again
+class DebitTestCase(TestCase):
+
+    def setUp(self):
+        u = User.objects.create_user(username='U')
+        u0 = User.objects.create_user(username='U0')
+        ua = BankAccount(user=u, currency=settings.TALER_CURRENCY)
+        u0a = BankAccount(user=u0, currency=settings.TALER_CURRENCY)
+
+        ua.save()
+        u0a.save()
+
+    def test_green(self):
+        u = User.objects.get(username='U')
+        ub = BankAccount.objects.get(user=u)
+        self.assertEqual(False, ub.debit)
+
+    def test_red(self):
+        u = User.objects.get(username='U')
+        u0 = User.objects.get(username='U0')
+
+        ub = BankAccount.objects.get(user=u)
+        ub0 = BankAccount.objects.get(user=u0)
+
+        wire_transfer(dict(value=10, fraction=0, 
currency=settings.TALER_CURRENCY),
+                      ub0,
+                      ub,
+                      "Go green")
+        tmp = amounts.get_zero()
+        tmp["value"] = 10
+
+        self.assertEqual(0, amounts.amount_cmp(ub.balance_obj, tmp))
+        self.assertEqual(False, ub.debit)
+        self.assertEqual(True, ub0.debit)
+
+        wire_transfer(dict(value=11, fraction=0, 
currency=settings.TALER_CURRENCY),
+                      ub,
+                      ub0,
+                      "Go red")
+
+        self.assertEqual(True, ub.debit)
+        self.assertEqual(False, ub0.debit)
+
+        tmp["value"] = 1
+
+        self.assertEqual(0, amounts.amount_cmp(ub0.balance_obj, tmp))
+
+class TestParseAmount(TestCase):
+     def test_parse_amount(self):
+         ret = amounts.parse_amount("KUDOS:4")
+         self.assertJSONEqual('{"value": 4, "fraction": 0, "currency": 
"KUDOS"}', json.dumps(ret))
+         ret = amounts.parse_amount("KUDOS:4.00")
+         self.assertJSONEqual('{"value": 4, "fraction": 0, "currency": 
"KUDOS"}', json.dumps(ret))
+         ret = amounts.parse_amount("KUDOS:4.3")
+         self.assertJSONEqual('{"value": 4, "fraction": 30000000, "currency": 
"KUDOS"}', json.dumps(ret))
+         try:
+             amounts.parse_amount("Buggy")
+         except amounts.BadFormatAmount:
+             return
+         # make sure the control doesn't get here
+         self.assertEqual(True, False)
diff --git a/talerbank/app/urls.py b/talerbank/app/urls.py
index 752dd6a..667366c 100644
--- a/talerbank/app/urls.py
+++ b/talerbank/app/urls.py
@@ -22,11 +22,13 @@ urlpatterns = [
     url(r'^', include('talerbank.urls')),
     url(r'^$', RedirectView.as_view(pattern_name="profile"), name="index"),
     url(r'^favicon\.ico$', views.ignore),
-    url(r'^javascript(.html)?/$', views.javascript_licensing, 
name="javascript"),
+    url(r'^admin/add/incoming$', views.add_incoming, name="add-incoming"),
+    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'^history$', views.history, name="history"),
     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"),
diff --git a/talerbank/app/urlsadmin.py b/talerbank/app/urlsadmin.py
deleted file mode 100644
index e2e51f8..0000000
--- a/talerbank/app/urlsadmin.py
+++ /dev/null
@@ -1,23 +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.conf.urls import include, url
-from . import views
-
-urlpatterns = [
-    url(r'^', include('talerbank.urls')),
-    url(r'^admin/add/incoming$', views.add_incoming, name="add-incoming"),
-    ]
diff --git a/talerbank/app/views.py b/talerbank/app/views.py
index d3b8428..3607de7 100644
--- a/talerbank/app/views.py
+++ b/talerbank/app/views.py
@@ -15,6 +15,7 @@
 #  @author Marcello Stanisci
 #  @author Florian Dold
 
+import re
 import django.contrib.auth
 import django.contrib.auth.views
 import django.contrib.auth.forms
@@ -42,9 +43,13 @@ from .models import BankAccount, BankTransaction
 
 logger = logging.getLogger(__name__)
 
+class DebtLimitExceededException(Exception):
+    pass
+class SameAccountException(Exception):
+    pass
 
 class MyAuthenticationForm(django.contrib.auth.forms.AuthenticationForm):
-    def __init__(self, *args, **kwargs):           
+    def __init__(self, *args, **kwargs):
         super().__init__(*args, **kwargs)
         self.fields["username"].widget.attrs["autofocus"] = True
         self.fields["username"].widget.attrs["placeholder"] = "Username"
@@ -60,7 +65,10 @@ def javascript_licensing(request):
 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")
+            request,
+            authentication_form=MyAuthenticationForm,
+            template_name="login.html",
+            extra_context={"user": request.user})
     # 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
@@ -83,33 +91,27 @@ def get_session_flag(request, name):
 def profile_page(request):
     just_withdrawn = get_session_flag(request, "just_withdrawn")
     just_registered = get_session_flag(request, "just_registered")
+    no_initial_bonus = get_session_flag(request, "no_initial_bonus")
     user_account = BankAccount.objects.get(user=request.user)
     history = extract_history(user_account)
     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),
+        balance=amounts.stringify(amounts.floatify(user_account.balance_obj)),
         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,
+        no_initial_bonus=no_initial_bonus,
     )
     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:
+    if just_withdrawn:
        response["X-Taler-Operation"] = "confirm-reserve"
        response["X-Taler-Reserve-Pub"] = reserve_pub
        response.status_code = 202
@@ -135,11 +137,20 @@ def pin_tan_question(request):
         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"]}
+        value = int(request.GET.get("amount_value", None))
+    except ValueError:
+        return HttpResponseBadRequest("invalid parameters: \"amount_value\" 
not given or NaN")
+    try:
+        fraction = int(request.GET.get("amount_fraction", None))
     except ValueError:
-        return HttpResponseBadRequest("invalid parameters")
+        return HttpResponseBadRequest("invalid parameters: \"amount_fraction\" 
not given or NaN")
+    try:
+        currency = request.GET.get("amount_currency", None)
+    except ValueError:
+        return HttpResponseBadRequest("invalid parameters: \"amount_currency\" 
not given")
+    amount = {"value": value,
+              "fraction": fraction,
+              "currency": currency}
     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:
@@ -149,8 +160,8 @@ def pin_tan_question(request):
     try:
         schemas.validate_wiredetails(wiredetails)
         schemas.validate_amount(amount)
-    except ValueError:
-        return HttpResponseBadRequest("invalid parameters")
+    except ValueError as error:
+        return HttpResponseBadRequest("invalid parameters (%s)" % error)
     # parameters we store in the session are (more or less) validated
     request.session["exchange_account_number"] = 
wiredetails["test"]["account_number"]
     request.session["amount"] = amount
@@ -200,12 +211,45 @@ def pin_tan_verify(request):
         # 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)
+    try:
+        BankAccount.objects.get(account_no=exchange_account_number)
+    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_wiredetails,
+             # just something unique
+            transfer_details=dict(timestamp=int(time.time() * 1000)),
+            amount=amount,
+    )
+    user_account = BankAccount.objects.get(user=request.user)
+    exchange_account = 
BankAccount.objects.get(account_no=exchange_account_number)
+    try:
+        wire_transfer(amount, user_account, exchange_account, reserve_pub)
+    except DebtLimitExceededException:
+        logger.warning("Withdrawal impossible due to debt limit exceeded")
+        request.session["debt_limit"] = True
+        return redirect("profile")
+    except amounts.BadFormatAmount as e:
+        return HttpResponse(e.msg, status=e.status_code) 
+    except amounts.CurrencyMismatchException as e:
+        return HttpResponse(e.msg, status=e.status_code) 
+    except SameAccountException:
+        logger.error("Odd situation: SameAccountException should NOT occur in 
this function")
+        return HttpResponse("internal server error", status=500)
+
+    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,
+        ))
+    request.session["just_withdrawn"] = True
+    return redirect("profile")
 
 
 class UserReg(forms.Form):
@@ -232,7 +276,19 @@ def register(request):
         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")
+    try:
+        wire_transfer(amount, bank_internal_account, user_account, "Joining 
bonus")
+    except DebtLimitExceededException:
+        logger.info("Debt situation encountered")
+        request.session["no_initial_bonus"] = True
+    except amounts.CurrencyMismatchException as e:
+        return HttpResponse(e.msg, status=e.status_code)
+    except amounts.BadFormatAmount as e:
+        return HttpResponse(e.msg, status=e.status_code)
+    except SameAccountException:
+        logger.error("Odd situation: SameAccountException should NOT occur in 
this function")
+        return HttpResponse("internal server error", status=500)
+        
     request.session["just_registered"] = True
     user = django.contrib.auth.authenticate(username=username, 
password=password)
     django.contrib.auth.login(request, user)
@@ -260,7 +316,7 @@ def extract_history(account):
             counterpart = item.credit_account
             sign = -1
         entry = dict(
-            float_amount=amounts.stringify(item.amount * sign),
+            float_amount=amounts.stringify(amounts.floatify(item.amount_obj) * 
sign),
             float_currency=item.currency,
             counterpart=counterpart.account_no,
             counterpart_username=counterpart.user.username,
@@ -293,6 +349,108 @@ def public_accounts(request, name=None):
     )
     return render(request, "public_accounts.html", context)
 
address@hidden
+def history(request):
+    """
+    This API is used to get a list of transactions related to one user.
+    """
+    # login caller
+    user_account = auth_and_login(request)
+    if not user_account:
+        return JsonResponse(dict(error="authentication failed: bad credentials 
OR auth method"),
+                            status=401)
+    # delta
+    delta = request.GET.get("delta")
+    if not delta:
+        return HttpResponseBadRequest()
+    #FIXME: make the '+' sign optional
+    parsed_delta = re.search("([\+-])?([0-9]+)", delta)
+    try:
+        parsed_delta.group(0)
+    except AttributeError:
+        return JsonResponse(dict(error="Bad 'delta' parameter"), status=400)
+    delta = int(parsed_delta.group(2))
+    # start
+    start = request.GET.get("start")
+    if start:
+        start = int(start)
+
+    sign = parsed_delta.group(1)
+
+    if ("+" == sign) or (not sign):
+        sign = ""
+    # Assuming Q() means 'true'
+    sign_filter = Q()
+    if "-" == sign and start:
+        sign_filter = Q(id__lt=start)
+    elif "" == sign and start:
+        sign_filter = Q(id__gt=start)
+    # direction (debit/credit)
+    direction = request.GET.get("direction")
+
+    # target account
+    target_account = request.GET.get("account_number")
+    if not target_account:
+        target_account = user_account.bankaccount
+    else:
+        try:
+            target_account = BankAccount.objects.get(account_no=target_account)
+        except BankAccount.DoesNotExist:
+            logger.error("Attempted /history about non existent account")
+            return JsonResponse(dict(error="Queried account does not exist"), 
status=404)
+
+    if target_account != user_account.bankaccount:
+        return JsonResponse(dict(error="Querying unowned accounts not 
allowed"), status=403)
+
+    query_string = Q(debit_account=target_account) | 
Q(credit_account=target_account)
+    history = []
+
+    if "credit" == direction:
+        query_string = Q(credit_account=target_account)
+    if "debit" == direction:
+        query_string = Q(debit_account=target_account)
+
+    qs = BankTransaction.objects.filter(query_string, 
sign_filter).order_by("%sid" % sign)[:delta]
+    if 0 == qs.count():
+        return HttpResponse(status=204)
+    for entry in qs:
+        counterpart = entry.credit_account.account_no
+        sign_ = "-"
+        if entry.credit_account.account_no == 
user_account.bankaccount.account_no:
+            counterpart = entry.debit_account.account_no
+            sign_ = "+"
+        history.append(dict(counterpart=counterpart,
+                            amount=entry.amount_obj,
+                            sign=sign_,
+                            wt_subject=entry.subject,
+                            row_id=entry.id,
+                            date="/Date(" + str(int(entry.date.timestamp())) + 
")/"))
+    return JsonResponse(dict(data=history), status=200)
+
+
+def auth_and_login(request):
+    """Return user instance after checking authentication
+       credentials, False if errors occur"""
+
+    auth_type = None
+    if "POST" == request.method:
+        data = json.loads(request.body.decode("utf-8"))
+        auth_type = data["auth"]["type"]
+    if "GET" == request.method:
+        auth_type = request.GET.get("auth")
+
+    if "basic" != auth_type:
+        logger.error("auth method not supported")
+        return False
+
+    username = request.META.get("HTTP_X_TALER_BANK_USERNAME")
+    password = request.META.get("HTTP_X_TALER_BANK_PASSWORD")
+    # logger.info("Trying to log '%s/%s' in" % (username, password))
+    if not username or not password:
+        return False
+    return django.contrib.auth.authenticate(username=username,
+                                            password=password)
+
 
 @csrf_exempt
 @require_POST
@@ -304,32 +462,50 @@ def add_incoming(request):
     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()
+    except ValueError as error:
+        logger.error("Bad data POSTed: %s" % error)
+        return JsonResponse(dict(error="invalid data POSTed: %s" % error), 
status=400)
+
+    user_account = auth_and_login(request)
+
+    if not user_account:
+        return JsonResponse(dict(error="authentication failed"),
+                            status=401)
+
     try:
-        debit_account = user_account = 
BankAccount.objects.get(user=data["debit_account"])
-        credit_account = user_account = 
BankAccount.objects.get(user=data["credit_account"])
+        credit_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)
-
+    try:
+        transaction = wire_transfer(data["amount"],
+                                    user_account.bankaccount,
+                                    credit_account,
+                                    subject)
+        return JsonResponse(dict(serial_id=transaction.id, 
timestamp="/Date(%s)/" % int(transaction.date.timestamp())))
+    except amounts.BadFormatAmount as e:
+        return JsonResponse(dict(error=e.msg), status=e.status_code)
+    except SameAccountException:
+        return JsonResponse(dict(error="debit and credit account are the 
same"), status=422)
+    except DebtLimitExceededException:
+        return JsonResponse(dict(error="debit count has reached its debt 
limit", status=403 ),
+                             status=403)
+    except amounts.CurrencyMismatchException as e:
+        return JsonResponse(dict(error=e.msg), status=e.status_code)
 
 @login_required
 @require_POST
 def withdraw_nojs(request):
-    amount = amounts.parse_amount(request.POST.get("kudos_amount", ""))
-    if amount is None:
+
+    try:
+        amount = amounts.parse_amount(request.POST.get("kudos_amount", ""))
+    except amounts.BadFormatAmount:
+        logger.error("Amount did not pass parsing")
         return HttpResponseBadRequest()
+
     response = HttpResponse(status=202)
     response["X-Taler-Operation"] = "create-reserve"
     response["X-Taler-Callback-Url"] = reverse("pin-question")
@@ -340,58 +516,85 @@ def withdraw_nojs(request):
     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,
+        logger.error("Debit and credit account are the same!")
+        raise SameAccountException()
+
+    transaction_item = BankTransaction(amount_value=amount["value"],
+                                       amount_fraction=amount["fraction"],
                                        currency=amount["currency"],
                                        credit_account=credit_account,
                                        debit_account=debit_account,
                                        subject=subject)
-    debit_account.balance -= float_amount
-    credit_account.balance += float_amount
+
+    try:
+        if debit_account.debit:
+            debit_account.balance_obj = 
amounts.amount_add(debit_account.balance_obj,
+                                                           amount)
+    
+        elif -1 == amounts.amount_cmp(debit_account.balance_obj, amount):
+            debit_account.debit = True
+            debit_account.balance_obj = amounts.amount_sub(amount,
+                                                           
debit_account.balance_obj)
+        else:
+            debit_account.balance_obj = 
amounts.amount_sub(debit_account.balance_obj,
+                                                           amount)
+
+        if False == credit_account.debit:
+            credit_account.balance_obj = 
amounts.amount_add(credit_account.balance_obj,
+                                                            amount)
+    
+        elif 1 == amounts.amount_cmp(amount, credit_account.balance_obj):
+            credit_account.debit = False
+            credit_account.balance_obj = amounts.amount_sub(amount,
+                                                            
credit_account.balance_obj)
+        else:
+            credit_account.balance_obj = 
amounts.amount_sub(credit_account.balance_obj,
+                                                            amount)
+    except amounts.CurrencyMismatchException:
+        msg = "The amount to be transferred (%s) doesn't match the bank's 
currency (%s)" % (amount["currency"], settings.TALER_CURRENCY)
+        status_code = 406
+        if settings.TALER_CURRENCY != credit_account.balance_obj["currency"]:
+            logger.error("Internal inconsistency: credit account's currency 
(%s) differs from bank's (%s)" % (credit_account.balance_obj["currency"], 
settings.TALER_CURRENCY))
+            msg = "Internal server error"
+            status_code = 500
+        elif settings.TALER_CURRENCY != debit_account.balance_obj["currency"]:
+            logger.error("Internal inconsistency: debit account's currency 
(%s) differs from bank's (%s)" % (debit_account.balance_obj["currency"], 
settings.TALER_CURRENCY))
+            msg = "Internal server error"
+            status_code = 500
+        logger.error(msg)
+        raise amounts.CurrencyMismatchException(msg=msg, 
status_code=status_code)
+
+    # Check here if any account went beyond the allowed
+    # debit threshold.
+
+    try:
+        threshold = amounts.parse_amount(settings.TALER_MAX_DEBT)
+
+        if debit_account.user.username == "Bank":
+            threshold = amounts.parse_amount(settings.TALER_MAX_DEBT_BANK)
+    except amounts.BadFormatAmount:
+        logger.error("MAX_DEBT|MAX_DEBT_BANK had the wrong format")
+        raise amounts.BadFormatAmount(msg="internal server error", 
status_code=500)
+
+    try:
+        if 1 == amounts.amount_cmp(debit_account.balance_obj, threshold) \
+           and 0 != amounts.amount_cmp(amounts.get_zero(), threshold) \
+           and debit_account.debit:
+            logger.error("Negative balance '%s' not allowed." % 
json.dumps(debit_account.balance_obj))
+            logger.info("%s's threshold is: '%s'." % 
(debit_account.user.username, json.dumps(threshold)))
+            raise DebtLimitExceededException()
+    except amounts.CurrencyMismatchException:
+        logger.error("(Internal) currency mismatch between debt threshold and 
debit account")
+        raise amounts.CurrencyMismatchException(msg="internal server error", 
status_code=500)
 
     with transaction.atomic():
         debit_account.save()
         credit_account.save()
         transaction_item.save()
+
+    return transaction_item
diff --git a/talerbank/jinja2.py b/talerbank/jinja2.py
new file mode 100644
index 0000000..9169a93
--- /dev/null
+++ b/talerbank/jinja2.py
@@ -0,0 +1,74 @@
+#  This file is part of TALER
+#  (C) 2017 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 Florian Dold
+
+from django.contrib.staticfiles.storage import staticfiles_storage
+from django.core.urlresolvers import reverse
+from django.conf import settings
+from django.core.urlresolvers import get_script_prefix
+from urllib.parse import urlparse
+from jinja2 import Environment
+import os
+
+
+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
+
+
+def static(url):
+    if is_absolute(url):
+        return url
+    return join_urlparts(get_script_prefix(), settings.STATIC_URL, url)
+
+
+def settings_value(name):
+    return getattr(settings, name, "")
+
+
+def url(*args, **kwargs):
+    # strangely, Django's 'reverse' function
+    # takes a named parameter 'kwargs' instead
+    # of real kwargs.
+    return reverse(*args, kwargs=kwargs)
+
+
+def env_get(name, default=None):
+    return os.environ.get(name, default)
+
+
+def environment(**options):
+    env = Environment(**options)
+    env.globals.update({
+        'static': static,
+        'url': url,
+        'settings_value': settings_value,
+        'env': env_get,
+    })
+    return env
+
diff --git a/talerbank/settings.py b/talerbank/settings.py
index 6542acf..6bbbc46 100644
--- a/talerbank/settings.py
+++ b/talerbank/settings.py
@@ -1,2 +1,196 @@
-from talerbank.settings_base import *
+"""
+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
+import re
+
+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, ...)
+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 = ["*"]
+
+LOGIN_URL = "login"
+
+LOGIN_REDIRECT_URL = "index"
+
+
+# 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',
+]
+
+TEMPLATES = [
+    {
+        'BACKEND': 'django.template.backends.jinja2.Jinja2',
+        'DIRS': [os.path.join(BASE_DIR, "talerbank/app/static/web-common/"),
+                 os.path.join(BASE_DIR, "talerbank/app/templates")],
+        'OPTIONS': {
+            'environment': 'talerbank.jinja2.environment',
+            },
+    },
+]
+
+# Disable those, since they don't work with
+# jinja2 anyways.
+TEMPLATE_CONTEXT_PROCESSORS = []
+
+WSGI_APPLICATION = 'talerbank.wsgi.application'
+
+
+# Database
+# https://docs.djangoproject.com/en/1.9/ref/settings/#databases
+
+DATABASES = {}
+
+dbname = tc.value_string("bank", "database", required=True)
+# db given in cli argument takes precedence over config
+dbname = os.environ.get("TALER_BANK_ALTDB", dbname)
+
+if not dbname:
+    raise Exception("DB not specified (neither in config or as cli argument)")
+
+logger.info("dbname: %s" % dbname)
+
+check_dbstring_format = re.search("[a-z]+:///[a-z]+", dbname)
+if not check_dbstring_format:
+    logger.error("Bad db string given, respect the format 'dbtype:///dbname'")
+    sys.exit(1)
+
+dbconfig = {}
+db_url = urllib.parse.urlparse(dbname)
+
+
+if ((db_url.scheme not in ("postgres")) or ("" == db_url.scheme)):
+    logger.error("DB '%s' is not supported" % db_url.scheme)
+    sys.exit(1)
+if db_url.scheme == "postgres":
+    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
+
+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/'
+
 ROOT_URLCONF = "talerbank.app.urls"
+
+TALER_CURRENCY = tc.value_string("taler", "currency", required=True)
+
+TALER_MAX_DEBT = tc.value_string("bank", "MAX_DEBT",
+                                 default="%s:50" % TALER_CURRENCY)
+
+TALER_MAX_DEBT_BANK = tc.value_string("bank", "MAX_DEBT_BANK",
+                                      default="%s:0" % TALER_CURRENCY)
+
+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)
diff --git a/talerbank/settings_admin.py b/talerbank/settings_admin.py
deleted file mode 100644
index b2ce6a3..0000000
--- a/talerbank/settings_admin.py
+++ /dev/null
@@ -1,2 +0,0 @@
-from talerbank.settings_base import *
-ROOT_URLCONF = "talerbank.app.urlsadmin"
diff --git a/talerbank/settings_base.py b/talerbank/settings_base.py
deleted file mode 100644
index f92d9f3..0000000
--- a/talerbank/settings_base.py
+++ /dev/null
@@ -1,183 +0,0 @@
-"""
-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__)
-
-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, ...)
-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 = ["*"]
-
-LOGIN_URL = "login"
-
-LOGIN_REDIRECT_URL = "index"
-
-
-# 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',
-]
-
-TEMPLATES = [
-    {
-        'BACKEND': 'django.template.backends.django.DjangoTemplates',
-        'DIRS': [os.path.join(BASE_DIR, "talerbank/app/static/web-common/")],
-        '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
-
-DATABASES = {}
-
-# parse a database URL, django can't natively do this!
-dbname = tc.value_string("bank", "database", required=False)
-dbconfig = {}
-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 = db_url.netloc
-
-    if host:
-        dbconfig["HOST"] = host
-
-    logger.info("db string '%s'", dbname)
-    logger.info("db info '%s'", dbconfig)
-
-    DATABASES["default"] = dbconfig
-else:
-    DATABASES["default"] = {
-            'ENGINE': 'django.db.backends.sqlite3',
-            'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
-    }
-
-
-# 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_CURRENCY = tc.value_string("taler", "currency", required=True)
-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]