changeset 1998:e22169dbfd44

merge
author Roger Haase <crosseyedpenguin@yahoo.com>
date Mon, 11 Feb 2013 12:04:39 -0700
parents dd0fb4845fba (current diff) bb2f526d961c (diff)
children 5a3705aeebe2
files
diffstat 200 files changed, 2796 insertions(+), 2184 deletions(-) [+]
line wrap: on
line diff
--- a/.hgignore	Fri Feb 08 08:32:26 2013 -0700
+++ b/.hgignore	Mon Feb 11 12:04:39 2013 -0700
@@ -14,6 +14,7 @@
 ^instance/
 ^wikiconfig_.+\.py
 ^MoinMoin/translations/.*/LC_MESSAGES/messages.mo$
+^MoinMoin/_tests/wiki/index/
 ^docs/_build/
 .coverage
 ^.project
--- a/MoinMoin/_tests/__init__.py	Fri Feb 08 08:32:26 2013 -0700
+++ b/MoinMoin/_tests/__init__.py	Mon Feb 11 12:04:39 2013 -0700
@@ -8,23 +8,21 @@
 """
 
 
-import os, shutil
-import socket, errno
+import socket
 from StringIO import StringIO
 
-from flask import current_app as app
 from flask import g as flaskg
 
-from MoinMoin import config, security, user
-from MoinMoin.config import NAME, CONTENTTYPE
+from MoinMoin.constants.contenttypes import CHARSET
+from MoinMoin.constants.keys import NAME, CONTENTTYPE
 from MoinMoin.items import Item
 from MoinMoin.util.crypto import random_string
-from MoinMoin.storage.error import ItemAlreadyExistsError
 
 # Promoting the test user -------------------------------------------
 # Usually the tests run as anonymous user, but for some stuff, you
 # need more privs...
 
+
 def become_valid(username=u"ValidUser"):
     """ modify flaskg.user to make the user valid.
         Note that a valid user will only be in ACL special group "Known", if
@@ -33,8 +31,8 @@
         Thus, for testing purposes (e.g. if you need delete rights), it is
         easier to use become_trusted().
     """
-    flaskg.user.profile[NAME] = username
-    flaskg.user.may.name = username
+    flaskg.user.profile[NAME] = [username, ]
+    flaskg.user.may.names = [username, ]  # see security.DefaultSecurityPolicy class
     flaskg.user.valid = 1
 
 
@@ -49,22 +47,24 @@
 def update_item(name, meta, data):
     """ creates or updates an item  """
     if isinstance(data, unicode):
-        data = data.encode(config.charset)
+        data = data.encode(CHARSET)
     item = flaskg.storage[name]
 
     meta = meta.copy()
     if NAME not in meta:
-        meta[NAME] = name
+        meta[NAME] = [name, ]
     if CONTENTTYPE not in meta:
         meta[CONTENTTYPE] = u'application/octet-stream'
     rev = item.store_revision(meta, StringIO(data), return_rev=True)
     return rev
 
+
 def create_random_string_list(length=14, count=10):
     """ creates a list of random strings """
     chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
     return [u"{0}".format(random_string(length, chars)) for counter in range(count)]
 
+
 def nuke_item(name):
     """ complete destroys an item """
     item = Item.create(name)
--- a/MoinMoin/_tests/_test_template.py	Fri Feb 08 08:32:26 2013 -0700
+++ b/MoinMoin/_tests/_test_template.py	Mon Feb 11 12:04:39 2013 -0700
@@ -45,6 +45,7 @@
     )
 
     from MoinMoin._tests import wikiconfig
+
     class Config(wikiconfig.Config):
         foo = 'bar'  # we want to have this non-default setting
 
--- a/MoinMoin/_tests/ldap_testbase.py	Fri Feb 08 08:32:26 2013 -0700
+++ b/MoinMoin/_tests/ldap_testbase.py	Mon Feb 11 12:04:39 2013 -0700
@@ -39,7 +39,11 @@
 SLAPD_EXECUTABLE = 'slapd'  # filename of LDAP server executable - if it is not
                             # in your PATH, you have to give full path/filename.
 
-import os, shutil, tempfile, time, base64
+import os
+import shutil
+import tempfile
+import time
+import base64
 from StringIO import StringIO
 import signal
 import subprocess
@@ -80,7 +84,7 @@
     def __init__(self,
                  config=None,  # config filename for -f
                  executable=SLAPD_EXECUTABLE,
-                 debug_flags='', # None,  # for -d stats,acl,args,trace,sync,config
+                 debug_flags='',  # None,  # for -d stats,acl,args,trace,sync,config
                  proto='ldap', ip='127.0.0.1', port=3890,  # use -h proto://ip:port
                  service_name=''  # defaults to -n executable:port, use None to not use -n
                 ):
@@ -90,7 +94,7 @@
         self.proto = proto
         self.ip = ip
         self.port = port
-        self.url = '{0}://{1}:{2}'.format(proto, ip, port) # can be used for ldap.initialize() call
+        self.url = '{0}://{1}:{2}'.format(proto, ip, port)  # can be used for ldap.initialize() call
         if service_name == '':
             self.service_name = '{0}:{1}'.format(executable, port)
         else:
@@ -109,7 +113,7 @@
         started = None
         if timeout:
             lo = ldap.initialize(self.url)
-            ldap.set_option(ldap.OPT_PROTOCOL_VERSION, ldap.VERSION3) # ldap v2 is outdated
+            ldap.set_option(ldap.OPT_PROTOCOL_VERSION, ldap.VERSION3)  # ldap v2 is outdated
             started = False
             wait_until = time.time() + timeout
             while time.time() < wait_until:
@@ -200,14 +204,14 @@
 
     def start_slapd(self):
         """ start a slapd and optionally wait until it talks with us """
-        self.slapd = Slapd(config=self.slapd_conf, port=3890+self.instance)
+        self.slapd = Slapd(config=self.slapd_conf, port=3890 + self.instance)
         started = self.slapd.start(timeout=self.timeout)
         return started
 
     def load_directory(self, ldif_content):
         """ load the directory with the ldif_content (str) """
         lo = ldap.initialize(self.slapd.url)
-        ldap.set_option(ldap.OPT_PROTOCOL_VERSION, ldap.VERSION3) # ldap v2 is outdated
+        ldap.set_option(ldap.OPT_PROTOCOL_VERSION, ldap.VERSION3)  # ldap v2 is outdated
         lo.simple_bind_s(self.rootdn, self.rootpw)
 
         class LDIFLoader(ldif.LDIFParser):
--- a/MoinMoin/_tests/test_error.py	Fri Feb 08 08:32:26 2013 -0700
+++ b/MoinMoin/_tests/test_error.py	Mon Feb 11 12:04:39 2013 -0700
@@ -8,8 +8,6 @@
 """
 
 
-import pytest
-
 from MoinMoin import error
 
 
--- a/MoinMoin/_tests/test_test_environ.py	Fri Feb 08 08:32:26 2013 -0700
+++ b/MoinMoin/_tests/test_test_environ.py	Mon Feb 11 12:04:39 2013 -0700
@@ -7,14 +7,10 @@
 
 from StringIO import StringIO
 
-import pytest
-
 from flask import current_app as app
 from flask import g as flaskg
 
-from MoinMoin.conftest import init_test_app, deinit_test_app
-from MoinMoin.config import NAME, CURRENT, CONTENTTYPE, IS_SYSITEM, SYSITEM_VERSION
-from MoinMoin.storage.error import NoSuchItemError
+from MoinMoin.constants.keys import NAME, CONTENTTYPE
 
 from MoinMoin._tests import wikiconfig
 
@@ -33,7 +29,7 @@
         itemname = u"this item shouldn't exist yet"
         assert not storage.has_item(itemname)
         item = storage[itemname]
-        new_rev = item.store_revision({NAME: itemname, CONTENTTYPE: u'text/plain'}, StringIO(''))
+        new_rev = item.store_revision({NAME: [itemname, ], CONTENTTYPE: u'text/plain'}, StringIO(''))
         assert storage.has_item(itemname)
 
 
--- a/MoinMoin/_tests/test_user.py	Fri Feb 08 08:32:26 2013 -0700
+++ b/MoinMoin/_tests/test_user.py	Mon Feb 11 12:04:39 2013 -0700
@@ -9,13 +9,9 @@
 """
 
 
-import pytest
-
-from flask import current_app as app
 from flask import g as flaskg
 
 from MoinMoin import user
-from MoinMoin.util import crypto
 
 
 class TestSimple(object):
@@ -25,7 +21,7 @@
         email = u"foo@example.org"
         # nonexisting user
         u = user.User(name=name, password=password)
-        assert u.name == name
+        assert u.name == [name, ]
         assert not u.valid
         assert not u.exists()
         # create a user
@@ -33,7 +29,7 @@
         assert ret is None, "create_user returned: {0}".format(ret)
         # existing user
         u = user.User(name=name, password=password)
-        assert u.name == name
+        assert u.name == [name, ]
         assert u.email == email
         assert u.valid
         assert u.exists()
--- a/MoinMoin/_tests/test_wikiutil.py	Fri Feb 08 08:32:26 2013 -0700
+++ b/MoinMoin/_tests/test_wikiutil.py	Mon Feb 11 12:04:39 2013 -0700
@@ -11,8 +11,8 @@
 
 from flask import current_app as app
 
-from MoinMoin import config, wikiutil
-from MoinMoin._tests import wikiconfig
+from MoinMoin.constants.chartypes import CHARS_SPACES
+from MoinMoin import wikiutil
 
 from werkzeug import MultiDict
 
@@ -112,7 +112,7 @@
             (u'a     b     c', u'a b c'),
             (u'a   b  /  c    d  /  e   f', u'a b/c d/e f'),
             # All 30 unicode spaces
-            (config.chars_spaces, u''),
+            (CHARS_SPACES, u''),
             )
         for test, expected in cases:
             result = wikiutil.normalize_pagename(test, app.cfg)
@@ -166,11 +166,11 @@
     assert result == expected
 
 def testdrawing2fname():
-    # with extension not in config.drawing_extensions
+    # with extension not in DRAWING_EXTENSIONS
     result = wikiutil.drawing2fname('Moin_drawing.txt')
     expected = 'Moin_drawing.txt.tdraw'
     assert result == expected
-    # with extension in config.drawing_extensions
+    # with extension in DRAWING_EXTENSIONS
     result = wikiutil.drawing2fname('Moindir.Moin_drawing.jpg')
     expected = 'Moindir.Moin_drawing.jpg'
     assert result == expected
--- a/MoinMoin/_tests/wikiconfig.py	Fri Feb 08 08:32:26 2013 -0700
+++ b/MoinMoin/_tests/wikiconfig.py	Mon Feb 11 12:04:39 2013 -0700
@@ -15,10 +15,11 @@
 from os.path import abspath, dirname, join
 from MoinMoin.config.default import DefaultConfig
 
+
 class Config(DefaultConfig):
     _here = abspath(dirname(__file__))
     _root = abspath(join(_here, '..', '..'))
-    data_dir = join(_here, 'wiki', 'data') # needed for plugins package TODO
+    data_dir = join(_here, 'wiki', 'data')  # needed for plugins package TODO
     index_storage = 'FileStorage', (join(_here, 'wiki', 'index'), ), {}
     content_acl = None
     item_root = 'FrontPage'
--- a/MoinMoin/app.py	Fri Feb 08 08:32:26 2013 -0700
+++ b/MoinMoin/app.py	Mon Feb 11 12:04:39 2013 -0700
@@ -109,8 +109,6 @@
 
             Rule('/<itemname:wikipage>')
             Rule('/<itemname:wikipage>/edit')
-
-        :param map: the :class:`Map`.
         """
         regex = '[^/]+?(/[^/]+?)*'
         weight = 200
@@ -178,7 +176,7 @@
     # A ns_mapping consists of several lines, where each line is made up like this:
     # mountpoint, unprotected backend
     # Just initialize with unprotected backends.
-    app.router = routing.Backend(app.cfg.namespace_mapping)
+    app.router = routing.Backend(app.cfg.namespace_mapping, app.cfg.backend_mapping)
     if app.cfg.create_storage:
         app.router.create()
     app.router.open()
@@ -225,7 +223,7 @@
 
     # if we still have no user obj, create a dummy:
     if not userobj:
-        userobj = user.User(auth_method='invalid')
+        userobj = user.User(name=u'anonymous', auth_method='invalid')
     # if we have a valid user we store it in the session
     if userobj.valid:
         session['user.itemid'] = userobj.itemid
--- a/MoinMoin/apps/admin/templates/admin/userbrowser.html	Fri Feb 08 08:32:26 2013 -0700
+++ b/MoinMoin/apps/admin/templates/admin/userbrowser.html	Mon Feb 11 12:04:39 2013 -0700
@@ -10,7 +10,7 @@
     </tr>
     {% for u in user_accounts %}
     <tr>
-        <td><a href="{{ url_for('frontend.show_item', item_name=u.name) }}">{{ u.name }}</a>{{ u.disabled and " (%s)" % _("disabled") or ""}}</td>
+        <td><a href="{{ url_for('frontend.show_item', item_name=u.name[0]) }}">{{ u.name|join(',') }}</a>{{ u.disabled and " (%s)" % _("disabled") or ""}}</td>
         <td>{{ u.groups|join(',') }}</td>
         <td>
             {% if u.email %}
--- a/MoinMoin/apps/admin/templates/admin/wikiconfig.html	Fri Feb 08 08:32:26 2013 -0700
+++ b/MoinMoin/apps/admin/templates/admin/wikiconfig.html	Mon Feb 11 12:04:39 2013 -0700
@@ -30,6 +30,6 @@
 </td>
 </tr>
 {% endfor %}
-</tdbody>
+</tbody>
 </table>
 {% endblock %}
--- a/MoinMoin/apps/admin/views.py	Fri Feb 08 08:32:26 2013 -0700
+++ b/MoinMoin/apps/admin/views.py	Mon Feb 11 12:04:39 2013 -0700
@@ -20,16 +20,17 @@
 from MoinMoin.themes import render_template
 from MoinMoin.apps.admin import admin
 from MoinMoin import user
-from MoinMoin.storage.error import NoSuchRevisionError
-from MoinMoin.config import NAME, ITEMID, SIZE, EMAIL
-from MoinMoin.config import SUPERUSER
+from MoinMoin.constants.keys import NAME, ITEMID, SIZE, EMAIL
+from MoinMoin.constants.rights import SUPERUSER
 from MoinMoin.security import require_permission
 
+
 @admin.route('/superuser')
 @require_permission(SUPERUSER)
 def index():
     return render_template('admin/index.html', title_name=_(u"Admin"))
 
+
 @admin.route('/user')
 def index_user():
     return render_template('user/index_user.html', title_name=_(u"User"))
@@ -42,7 +43,7 @@
     User Account Browser
     """
     groups = flaskg.groups
-    revs = user.search_users() # all users
+    revs = user.search_users()  # all users
     user_accounts = [dict(uid=rev.meta[ITEMID],
                           name=rev.meta[NAME],
                           email=rev.meta[EMAIL],
@@ -100,7 +101,7 @@
 @admin.route('/sysitems_upgrade', methods=['GET', 'POST', ])
 @require_permission(SUPERUSER)
 def sysitems_upgrade():
-    from MoinMoin.storage.backends import upgrade_sysitems
+    from MoinMoin.storage.backends import upgrade_sysitems  # XXX broken import, either fix or kill this
     from MoinMoin.storage.error import BackendError
     if request.method == 'GET':
         action = 'syspages_upgrade'
@@ -120,6 +121,7 @@
 
 from MoinMoin.config import default as defaultconfig
 
+
 @admin.route('/wikiconfig', methods=['GET', ])
 @require_permission(SUPERUSER)
 def wikiconfig():
@@ -231,7 +233,7 @@
     headings = [_('Size'),
                 _('Item name'),
                ]
-    rows = [(rev.meta[SIZE], rev.meta[NAME])
+    rows = [(rev.meta[SIZE], rev.name)
             for rev in flaskg.storage.documents(wikiname=app.cfg.interwikiname)]
     rows = sorted(rows, reverse=True)
     return render_template('user/itemsize.html',
--- a/MoinMoin/apps/feed/_tests/test_feed.py	Fri Feb 08 08:32:26 2013 -0700
+++ b/MoinMoin/apps/feed/_tests/test_feed.py	Mon Feb 11 12:04:39 2013 -0700
@@ -7,8 +7,7 @@
 
 from flask import url_for
 
-from MoinMoin.items import Item
-from MoinMoin.config import CONTENTTYPE, COMMENT
+from MoinMoin.constants.keys import COMMENT
 from MoinMoin._tests import update_item, wikiconfig
 
 class TestFeeds(object):
--- a/MoinMoin/apps/feed/views.py	Fri Feb 08 08:32:26 2013 -0700
+++ b/MoinMoin/apps/feed/views.py	Mon Feb 11 12:04:39 2013 -0700
@@ -14,7 +14,6 @@
 from flask import request, Response
 from flask import current_app as app
 from flask import g as flaskg
-from flask import url_for
 
 from werkzeug.contrib.atom import AtomFeed
 from jinja2 import Markup
@@ -26,14 +25,13 @@
 
 from MoinMoin.i18n import _, L_, N_
 from MoinMoin.apps.feed import feed
-from MoinMoin.config import (NAME, NAME_EXACT, WIKINAME, ACL, ACTION, ADDRESS,
-                            HOSTNAME, USERID, COMMENT, MTIME, REVID, ALL_REVS,
-                            PARENTID, LATEST_REVS)
+from MoinMoin.constants.keys import NAME, NAME_EXACT, WIKINAME, COMMENT, MTIME, REVID, ALL_REVS, PARENTID, LATEST_REVS
 from MoinMoin.themes import get_editor_info, render_template
 from MoinMoin.items import Item
 from MoinMoin.util.crypto import cache_key
 from MoinMoin.util.interwiki import url_for_item
 
+
 @feed.route('/atom/<itemname:item_name>')
 @feed.route('/atom', defaults=dict(item_name=''))
 def atom(item_name):
@@ -65,7 +63,7 @@
             query = And([query, Term(NAME_EXACT, item_name), ])
         history = flaskg.storage.search(query, idx_name=ALL_REVS, sortedby=[MTIME], reverse=True, limit=100)
         for rev in history:
-            name = rev.meta[NAME]
+            name = rev.name
             item = rev.item
             this_revid = rev.meta[REVID]
             previous_revid = rev.meta.get(PARENTID)
@@ -78,7 +76,8 @@
                     content = hl_item.content._render_data_diff_atom(previous_rev, this_rev)
                 else:
                     # full html rendering for new items
-                    content = render_template('atom.html', get='first_revision', rev=this_rev, content=Markup(hl_item.content._render_data()), revision=this_revid)
+                    content = render_template('atom.html', get='first_revision', rev=this_rev,
+                                              content=Markup(hl_item.content._render_data()), revision=this_revid)
                 content_type = 'html'
             except Exception as e:
                 logging.exception("content rendering crashed")
@@ -89,7 +88,8 @@
             if rev_comment:
                 # Trim down extremely long revision comment
                 if len(rev_comment) > 80:
-                    content = render_template('atom.html', get='comment_cont_merge', comment=rev_comment[79:], content=Markup(content))
+                    content = render_template('atom.html', get='comment_cont_merge', comment=rev_comment[79:],
+                                              content=Markup(content))
                     rev_comment = u"{0}...".format(rev_comment[:79])
                 feed_title = u"{0} - {1}".format(author.get(NAME, ''), rev_comment)
             else:
--- a/MoinMoin/apps/frontend/_tests/test_frontend.py	Fri Feb 08 08:32:26 2013 -0700
+++ b/MoinMoin/apps/frontend/_tests/test_frontend.py	Mon Feb 11 12:04:39 2013 -0700
@@ -8,16 +8,12 @@
 
 from StringIO import StringIO
 
-import pytest
-
 from flask import url_for
 from flask import g as flaskg
 from werkzeug import ImmutableMultiDict, FileStorage
 
 from MoinMoin.apps.frontend import views
 from MoinMoin import user
-from MoinMoin.util import crypto
-from MoinMoin._tests import wikiconfig
 
 
 class TestFrontend(object):
@@ -26,14 +22,17 @@
             viewopts = {}
         if params is None:
             params = {}
-        print 'GET %s' % url_for(viewname, **viewopts)
+
         with self.app.test_client() as c:
-            rv = c.get(url_for(viewname, **viewopts), data=params)
-            assert rv.status == status
-            assert rv.headers['Content-Type'] in content_types
-            for item in data:
-                assert item in rv.data
-            return rv
+            for method in ['HEAD', 'GET']:
+                print '%s %s' % (method, url_for(viewname, **viewopts))
+                rv = c.open(url_for(viewname, **viewopts), method=method, data=params)
+                assert rv.status == status
+                assert rv.headers['Content-Type'] in content_types
+                if method == 'GET':
+                    for item in data:
+                        assert item in rv.data
+        return rv
 
     def _test_view_post(self, viewname, status='302 FOUND', content_types=('text/html; charset=utf-8', ), data=('<html>', '</html>'), form=None, viewopts=None):
         if viewopts is None:
--- a/MoinMoin/apps/frontend/views.py	Fri Feb 08 08:32:26 2013 -0700
+++ b/MoinMoin/apps/frontend/views.py	Mon Feb 11 12:04:39 2013 -0700
@@ -21,17 +21,16 @@
 import mimetypes
 import json
 from datetime import datetime
-from itertools import chain
 from collections import namedtuple
 from functools import wraps, partial
 
-from flask import request, url_for, flash, Response, make_response, redirect, session, abort, jsonify
+from flask import request, url_for, flash, Response, make_response, redirect, abort, jsonify
 from flask import current_app as app
 from flask import g as flaskg
 from flask.ext.babel import format_date
 from flask.ext.themes import get_themes_list
 
-from flatland import Form, Enum
+from flatland import Form, Enum, List
 from flatland.validation import Validator
 
 from jinja2 import Markup
@@ -45,18 +44,19 @@
 logging = log.getLogger(__name__)
 
 from MoinMoin.i18n import _, L_, N_
-from MoinMoin.themes import render_template, get_editor_info, contenttype_to_class
+from MoinMoin.themes import render_template, contenttype_to_class
 from MoinMoin.apps.frontend import frontend
-from MoinMoin.forms import OptionalText, RequiredText, URL, YourOpenID, YourEmail, RequiredPassword, Checkbox, InlineCheckbox, Select, Tags, Natural, Submit, Hidden, MultiSelect
+from MoinMoin.forms import (OptionalText, RequiredText, URL, YourOpenID, YourEmail, RequiredPassword, Checkbox,
+                            InlineCheckbox, Select, Names, Tags, Natural, Submit, Hidden, MultiSelect)
 from MoinMoin.items import BaseChangeForm, Item, NonExistent
 from MoinMoin.items.content import content_registry
-from MoinMoin import config, user, util
+from MoinMoin import user, util
 from MoinMoin.constants.keys import *
+from MoinMoin.constants.chartypes import CHARS_UPPER, CHARS_LOWER
 from MoinMoin.util import crypto
 from MoinMoin.util.interwiki import url_for_item
-from MoinMoin.search import SearchForm, ValidSearch
-from MoinMoin.security.textcha import TextCha, TextChaizedForm, TextChaValid
-from MoinMoin.storage.error import NoSuchItemError, NoSuchRevisionError
+from MoinMoin.search import SearchForm
+from MoinMoin.security.textcha import TextCha, TextChaizedForm
 from MoinMoin.signalling import item_displayed, item_modified
 from MoinMoin.storage.middleware.protecting import AccessDenied
 
@@ -76,6 +76,7 @@
     item_name = app.cfg.item_root
     return redirect(url_for_item(item_name))
 
+
 @frontend.route('/robots.txt')
 def robots():
     return Response("""\
@@ -164,7 +165,7 @@
     # TAGS might be there multiple times, thus we need multi:
     lookup_form = LookupForm.from_flat(request.values.items(multi=True))
     valid = lookup_form.validate()
-    lookup_form['submit'].set_default() # XXX from_flat() kills all values
+    lookup_form['submit'].set_default()  # XXX from_flat() kills all values
     if valid:
         history = bool(request.values.get('history'))
         idx_name = ALL_REVS if history else LATEST_REVS
@@ -234,12 +235,13 @@
             transcluded_names.update(transclusions)
         return transcluded_names
 
+
 @frontend.route('/+search/<itemname:item_name>', methods=['GET', 'POST'])
 @frontend.route('/+search', defaults=dict(item_name=u''), methods=['GET', 'POST'])
 def search(item_name):
     search_form = SearchForm.from_flat(request.values)
     valid = search_form.validate()
-    search_form['submit'].set_default() # XXX from_flat() kills all values
+    search_form['submit'].set_default()  # XXX from_flat() kills all values
     query = search_form['q'].value
     if valid:
         history = bool(request.values.get('history'))
@@ -248,7 +250,7 @@
         q = qp.parse(query)
 
         _filter = None
-        if item_name: # Only search this item and subitems
+        if item_name:  # Only search this item and subitems
             prefix_name = item_name + u'/'
             terms = [Term(NAME_EXACT, item_name), Prefix(NAME_EXACT, prefix_name), ]
 
@@ -284,8 +286,10 @@
             key_terms_is_fast = False
             if key_terms_is_fast:
                 flaskg.clock.start('search suggestions')
-                name_suggestions = u', '.join([word for word, score in results.key_terms(NAME, docs=20, numterms=10)])
-                content_suggestions = u', '.join([word for word, score in results.key_terms(CONTENT, docs=20, numterms=10)])
+                name_suggestions = u', '.join([word
+                                               for word, score in results.key_terms(NAME, docs=20, numterms=10)])
+                content_suggestions = u', '.join([word
+                                                  for word, score in results.key_terms(CONTENT, docs=20, numterms=10)])
                 flaskg.clock.stop('search suggestions')
             else:
                 name_suggestions = u''
@@ -387,7 +391,7 @@
         rev = item[rev]
     except KeyError:
         abort(404, item_name)
-    content = convert_to_indexable(rev.meta, rev.data)
+    content = convert_to_indexable(rev.meta, rev.data, item_name)
     return Response(content, 200, mimetype='text/plain')
 
 
@@ -402,7 +406,7 @@
 @presenter('meta', add_trail=True)
 def show_item_meta(item):
     show_revision = request.view_args['rev'] != CURRENT
-    show_navigation = False # TODO
+    show_navigation = False  # TODO
     first_rev = None
     last_rev = None
     if show_navigation:
@@ -421,15 +425,11 @@
                            show_navigation=show_navigation,
                           )
 
+
 @frontend.route('/+content/+<rev>/<itemname:item_name>')
 @frontend.route('/+content/<itemname:item_name>', defaults=dict(rev=CURRENT))
 def content_item(item_name, rev):
     """ same as show_item, but we only show the content """
-    # first check whether we have a valid search query:
-    search_form = SearchForm.from_flat(request.values)
-    if search_form.validate():
-        return _search(search_form, item_name)
-    search_form['submit'].set_default() # XXX from_flat() kills all values
     item_displayed.send(app._get_current_object(),
                         item_name=item_name)
     try:
@@ -443,15 +443,18 @@
                            data_rendered=Markup(item.content._render_data()),
                            )
 
+
 @presenter('get')
 def get_item(item):
     return item.content.do_get()
 
+
 @presenter('download')
 def download_item(item):
     mimetype = request.values.get("mimetype")
     return item.content.do_get(force_attachment=True, mimetype=mimetype)
 
+
 @frontend.route('/+convert/<itemname:item_name>')
 def convert_item(item_name):
     """
@@ -503,15 +506,19 @@
 class TargetChangeForm(BaseChangeForm):
     target = RequiredText.using(label=L_('Target')).with_properties(placeholder=L_("The name of the target item"))
 
+
 class RevertItemForm(BaseChangeForm):
     name = 'revert_item'
 
+
 class DeleteItemForm(BaseChangeForm):
     name = 'delete_item'
 
+
 class DestroyItemForm(BaseChangeForm):
     name = 'destroy_item'
 
+
 class RenameItemForm(TargetChangeForm):
     name = 'rename_item'
 
@@ -526,7 +533,7 @@
         abort(403)
     if isinstance(item, NonExistent):
         abort(404, item_name)
-    if request.method == 'GET':
+    if request.method in ['GET', 'HEAD']:
         form = RevertItemForm.from_defaults()
         TextCha(form).amend_form()
     elif request.method == 'POST':
@@ -552,7 +559,7 @@
         abort(403)
     if isinstance(item, NonExistent):
         abort(404, item_name)
-    if request.method == 'GET':
+    if request.method in ['GET', 'HEAD']:
         form = RenameItemForm.from_defaults()
         TextCha(form).amend_form()
         form['target'] = item.name
@@ -580,7 +587,7 @@
         abort(403)
     if isinstance(item, NonExistent):
         abort(404, item_name)
-    if request.method == 'GET':
+    if request.method in ['GET', 'HEAD']:
         form = DeleteItemForm.from_defaults()
         TextCha(form).amend_form()
     elif request.method == 'POST':
@@ -598,6 +605,7 @@
                            form=form,
                           )
 
+
 @frontend.route('/+ajaxdelete/<itemname:item_name>', methods=['POST'])
 @frontend.route('/+ajaxdelete', defaults=dict(item_name=''), methods=['POST'])
 def ajaxdelete(item_name):
@@ -623,6 +631,7 @@
 
     return jsonify(response)
 
+
 @frontend.route('/+ajaxdestroy/<itemname:item_name>', methods=['POST'])
 @frontend.route('/+ajaxdestroy', defaults=dict(item_name=''), methods=['POST'])
 def ajaxdestroy(item_name):
@@ -666,7 +675,7 @@
 def destroy_item(item_name, rev):
     if rev is None:
         # no revision given
-        _rev = CURRENT # for item creation
+        _rev = CURRENT  # for item creation
         destroy_item = True
     else:
         _rev = rev
@@ -679,7 +688,7 @@
         abort(403)
     if isinstance(item, NonExistent):
         abort(404, item_name)
-    if request.method == 'GET':
+    if request.method in ['GET', 'HEAD']:
         form = DestroyItemForm.from_defaults()
         TextCha(form).amend_form()
     elif request.method == 'POST':
@@ -706,7 +715,7 @@
     """
     data_file = request.files.get('data_file')
     subitem_name = data_file.filename
-    contenttype = data_file.content_type # guess by browser, based on file name
+    contenttype = data_file.content_type  # guess by browser, based on file name
     data = data_file.stream
     if item_name:
         subitem_prefix = item_name + u'/'
@@ -733,17 +742,20 @@
     contenttype_group_descriptions[g] = ', '.join([e.display_name for e in content_registry.groups[g]])
 contenttype_groups.append('unknown items')
 
-ContenttypeGroup = MultiSelect.of(Enum.using(valid_values=contenttype_groups).with_properties(descriptions=contenttype_group_descriptions)).using(optional=True)
+ContenttypeGroup = MultiSelect.of(Enum.using(valid_values=contenttype_groups).with_properties(
+                                  descriptions=contenttype_group_descriptions)).using(optional=True)
+
 
 class IndexForm(Form):
     contenttype = ContenttypeGroup
     submit = Submit.using(default=L_('Filter'))
 
+
 @frontend.route('/+index/', defaults=dict(item_name=''), methods=['GET', 'POST'])
 @frontend.route('/+index/<itemname:item_name>', methods=['GET', 'POST'])
 def index(item_name):
     try:
-        item = Item.create(item_name) # when item_name='', it gives toplevel index
+        item = Item.create(item_name)  # when item_name='', it gives toplevel index
     except AccessDenied:
         abort(403)
 
@@ -753,17 +765,14 @@
     # values, eg. calling items with multi=True. See Werkzeug documentation for
     # more.
     form = IndexForm.from_flat(request.args.items(multi=True))
-    form['submit'].set_default() # XXX from_flat() kills all values
+    form['submit'].set_default()  # XXX from_flat() kills all values
     if not form['contenttype']:
         form['contenttype'].set(contenttype_groups)
 
     selected_groups = form['contenttype'].value
     startswith = request.values.get("startswith")
 
-    initials = item.name_initial(item.get_subitem_revs())
-    initials = [initial.upper() for initial in initials]
-    initials = list(set(initials))
-    initials = sorted(initials)
+    initials = item.name_initial(item.get_subitem_revs(), uppercase=True)
 
     dirs, files = item.get_index(startswith, selected_groups)
     # index = sorted(index, key=lambda e: e.relname.lower())
@@ -806,7 +815,7 @@
     q = And([Term(WIKINAME, app.cfg.interwikiname),
              Term(USERID, userid)])
     revs = flaskg.storage.search(q, idx_name=ALL_REVS)
-    return [rev.meta[NAME] for rev in revs]
+    return [rev.name for rev in revs]
 
 
 @frontend.route('/+backrefs/<itemname:item_name>')
@@ -837,7 +846,7 @@
     q = And([Term(WIKINAME, app.cfg.interwikiname),
              Or([Term(ITEMTRANSCLUSIONS, item_name), Term(ITEMLINKS, item_name)])])
     revs = flaskg.storage.search(q)
-    return [rev.meta[NAME] for rev in revs]
+    return [rev.name for rev in revs]
 
 
 @frontend.route('/+history/<itemname:item_name>')
@@ -860,7 +869,7 @@
     history = [dict((k, v) for k, v in rev.meta.iteritems() if k != CONTENT) for rev in revs]
     history_page = util.getPageContent(history, offset, results_per_page)
     return render_template('history.html',
-                           item_name=item_name, # XXX no item here
+                           item_name=item_name,  # XXX no item here
                            history_page=history_page,
                            bookmark_time=bookmark_time,
                           )
@@ -901,6 +910,7 @@
                            bookmark_time=bookmark_time,
                           )
 
+
 def _compute_item_sets():
     """
     compute sets of existing, linked, transcluded and no-revision item names
@@ -910,7 +920,7 @@
     existing = set()
     revs = flaskg.storage.documents(wikiname=app.cfg.interwikiname)
     for rev in revs:
-        existing.add(rev.meta[NAME])
+        existing.add(rev.name)
         linked.update(rev.meta.get(ITEMLINKS, []))
         transcluded.update(rev.meta.get(ITEMTRANSCLUSIONS, []))
     return existing, linked, transcluded
@@ -1003,8 +1013,8 @@
             return False
         if element['password1'].value != element['password2'].value:
             return self.note_error(element, state, 'passwords_mismatch_msg')
+        return True
 
-        return True
 
 class RegistrationForm(TextChaizedForm):
     """a simple user registration form"""
@@ -1028,6 +1038,7 @@
 
     openid = YourOpenID
 
+
 def _using_moin_auth():
     """Check if MoinAuth is being used for authentication.
 
@@ -1070,7 +1081,7 @@
         template = 'register.html'
         FormClass = RegistrationForm
 
-    if request.method == 'GET':
+    if request.method in ['GET', 'HEAD']:
         form = FormClass.from_defaults()
         if isOpenID:
             oid = request.values.get('openid_openid')
@@ -1100,7 +1111,8 @@
                     if is_ok:
                         flash(_('Account verification required, please see the email we sent to your address.'), "info")
                     else:
-                        flash(_('An error occurred while sending the verification email: "%(message)s" Please contact an administrator to activate your account.',
+                        flash(_('An error occurred while sending the verification email: "%(message)s" '
+                                'Please contact an administrator to activate your account.',
                             message=msg), "error")
                 else:
                     flash(_('Account created, please log in now.'), "info")
@@ -1114,16 +1126,16 @@
 
 @frontend.route('/+verifyemail', methods=['GET'])
 def verifyemail():
-    u = None
+    u = token = None
     if 'username' in request.values and 'token' in request.values:
         u = user.User(auth_username=request.values['username'])
         token = request.values['token']
-    if u and u.disabled and u.validate_recovery_token(token):
+    if u and u.disabled and token and u.validate_recovery_token(token):
         u.profile[DISABLED] = False
         u.save()
         flash(_("Your account has been activated, you can log in now."), "info")
     else:
-        flash(_('Your token is invalid!'), "error")
+        flash(_('Your username and/or token is invalid!'), "error")
     return redirect(url_for('.show_root'))
 
 
@@ -1160,7 +1172,7 @@
     if not _using_moin_auth():
         return Response('No MoinAuth in auth list', 403)
 
-    if request.method == 'GET':
+    if request.method in ['GET', 'HEAD']:
         form = PasswordLostForm.from_defaults()
     elif request.method == 'POST':
         form = PasswordLostForm.from_flat(request.form)
@@ -1184,6 +1196,7 @@
                            form=form,
                           )
 
+
 class ValidPasswordRecovery(Validator):
     """Validator for a valid password recovery form
     """
@@ -1202,14 +1215,18 @@
 
         return True
 
+
 class PasswordRecoveryForm(Form):
     """a simple password recovery form"""
     name = 'recoverpass'
 
     username = RequiredText.using(label=L_('Name')).with_properties(placeholder=L_("Your login name"))
-    token = RequiredText.using(label=L_('Recovery token')).with_properties(placeholder=L_("The recovery token that has been sent to you"))
-    password1 = RequiredPassword.using(label=L_('New password')).with_properties(placeholder=L_("The login password you want to use"))
-    password2 = RequiredPassword.using(label=L_('New password (repeat)')).with_properties(placeholder=L_("Repeat the same password"))
+    token = RequiredText.using(label=L_('Recovery token')).with_properties(
+        placeholder=L_("The recovery token that has been sent to you"))
+    password1 = RequiredPassword.using(label=L_('New password')).with_properties(
+        placeholder=L_("The login password you want to use"))
+    password2 = RequiredPassword.using(label=L_('New password (repeat)')).with_properties(
+        placeholder=L_("Repeat the same password"))
     submit = Submit.using(default=L_('Change password'))
 
     validators = [ValidPasswordRecovery()]
@@ -1223,7 +1240,7 @@
     if not _using_moin_auth():
         return Response('No MoinAuth in auth list', 403)
 
-    if request.method == 'GET':
+    if request.method in ['GET', 'HEAD']:
         form = PasswordRecoveryForm.from_defaults()
         form.update(request.values)
     elif request.method == 'POST':
@@ -1290,7 +1307,7 @@
     if flaskg._login_multistage_name == 'openid':
         return Response(flaskg._login_multistage, mimetype='text/html')
 
-    if request.method == 'GET':
+    if request.method in ['GET', 'HEAD']:
         form = LoginForm.from_defaults()
         for authmethod in app.cfg.auth:
             hint = authmethod.login_hint()
@@ -1347,11 +1364,15 @@
     name = 'usersettings_password'
     validators = [ValidChangePass()]
 
-    password_current = RequiredPassword.using(label=L_('Current Password')).with_properties(placeholder=L_("Your current login password"))
-    password1 = RequiredPassword.using(label=L_('New password')).with_properties(placeholder=L_("The login password you want to use"))
-    password2 = RequiredPassword.using(label=L_('New password (repeat)')).with_properties(placeholder=L_("Repeat the same password"))
+    password_current = RequiredPassword.using(label=L_('Current Password')).with_properties(
+        placeholder=L_("Your current login password"))
+    password1 = RequiredPassword.using(label=L_('New password')).with_properties(
+        placeholder=L_("The login password you want to use"))
+    password2 = RequiredPassword.using(label=L_('New password (repeat)')).with_properties(
+        placeholder=L_("Repeat the same password"))
     submit = Submit.using(default=L_('Change password'))
 
+
 class UserSettingsNotificationForm(Form):
     name = 'usersettings_notification'
     email = YourEmail
@@ -1381,9 +1402,10 @@
 
     # these forms can't be global because we need app object, which is only available within a request:
     class UserSettingsPersonalForm(Form):
-        name = 'usersettings_personal' # "name" is duplicate
-        name = RequiredText.using(label=L_('Name')).with_properties(placeholder=L_("The login name you want to use"))
-        aliasname = OptionalText.using(label=L_('Alias-Name')).with_properties(placeholder=L_("Your alias name (informational)"))
+        name = 'usersettings_personal'  # "name" is duplicate
+        name = Names.using(label=L_('Names')).with_properties(placeholder=L_("The login names you want to use"))
+        display_name = OptionalText.using(label=L_('Display-Name')).with_properties(
+            placeholder=L_("Your display name (informational)"))
         openid = YourOpenID.using(optional=True)
         #timezones_keys = sorted(Locale('en').time_zones.keys())
         timezones_keys = [unicode(tz) for tz in pytz.common_timezones]
@@ -1400,10 +1422,14 @@
         themes_available = sorted([(unicode(t.identifier), t.name) for t in get_themes_list()],
                                   key=lambda x: x[1])
         themes_keys = [t[0] for t in themes_available]
-        theme_name = Select.using(label=L_('Theme name')).with_properties(labels=dict(themes_available)).valued(*themes_keys)
-        css_url = URL.using(label=L_('User CSS URL'), optional=True).with_properties(placeholder=L_("Give the URL of your custom CSS (optional)"))
-        edit_rows = Natural.using(label=L_('Editor size')).with_properties(placeholder=L_("Editor textarea height (0=auto)"))
-        results_per_page = Natural.using(label=L_('History results per page')).with_properties(placeholder=L_("Number of results per page (0=no paging)"))
+        theme_name = Select.using(label=L_('Theme name')).with_properties(
+            labels=dict(themes_available)).valued(*themes_keys)
+        css_url = URL.using(label=L_('User CSS URL'), optional=True).with_properties(
+            placeholder=L_("Give the URL of your custom CSS (optional)"))
+        edit_rows = Natural.using(label=L_('Editor size')).with_properties(
+            placeholder=L_("Editor textarea height (0=auto)"))
+        results_per_page = Natural.using(label=L_('History results per page')).with_properties(
+            placeholder=L_("Number of results per page (0=no paging)"))
         submit = Submit.using(default=L_('Save'))
 
     form_classes = dict(
@@ -1449,14 +1475,19 @@
                     response['flash'].append((_("Your password has been changed."), "info"))
                 else:
                     if part == 'personal':
-                        if form['openid'].value and form['openid'].value != flaskg.user.openid and user.search_users(openid=form['openid'].value):
+                        if (form['openid'].value and form['openid'].value != flaskg.user.openid and
+                            user.search_users(openid=form['openid'].value)):
                             # duplicate openid
                             response['flash'].append((_("This openid is already in use."), "error"))
                             success = False
-                        if form['name'].value != flaskg.user.name and user.search_users(name_exact=form['name'].value):
-                            # duplicate name
-                            response['flash'].append((_("This username is already in use."), "error"))
-                            success = False
+                        if set(form['name'].value) != set(flaskg.user.name):
+                            new_names = set(form['name'].value) - set(flaskg.user.name)
+                            for name in new_names:
+                                if user.search_users(name_exact=name):
+                                    # duplicate name
+                                    response['flash'].append((_("The username %(name)r is already in use.", name=name),
+                                                              "error"))
+                                    success = False
                     if part == 'notification':
                         if (form['email'].value != flaskg.user.email and
                             user.search_users(email=form['email'].value) and app.cfg.user_email_unique):
@@ -1469,21 +1500,26 @@
                         d.pop('submit')
                         for k, v in d.items():
                             flaskg.user.profile[k] = v
-                        if part == 'notification' and app.cfg.user_email_verification and form['email'].value != user_old_email:
+                        if (part == 'notification' and app.cfg.user_email_verification and
+                            form['email'].value != user_old_email):
                             # disable account
                             flaskg.user.profile[DISABLED] = True
                             # send verification mail
                             is_ok, msg = flaskg.user.mail_email_verification()
                             if is_ok:
                                 flaskg.user.logout_session()
-                                response['flash'].append((_('Your account has been disabled because you changed your email address. Please see the email we sent to your address to reactivate it.'), "info"))
+                                response['flash'].append((_('Your account has been disabled because you changed your '
+                                                            'email address. Please see the email we sent to your '
+                                                            'address to reactivate it.'), "info"))
                                 response['redirect'] = url_for('.show_root')
                             else:
                                 # sending the verification email didn't work. reset email change and alert the user.
                                 flaskg.user.profile[DISABLED] = False
                                 flaskg.user.profile[EMAIL] = user_old_email
                                 flaskg.user.save()
-                                response['flash'].append((_('Your email address was not changed because sending the verification email failed. Please try again later.'), "error"))
+                                response['flash'].append((_('Your email address was not changed because sending the '
+                                                            'verification email failed. Please try again later.'),
+                                                          "error"))
                         else:
                             flaskg.user.save()
 
@@ -1668,7 +1704,7 @@
                 item_names.append(name)
     return render_template("link_list_item_panel.html",
                            headline=_("Items with similar names to '%(item_name)s'", item_name=item_name),
-                           item_name=item_name, # XXX no item
+                           item_name=item_name,  # XXX no item
                            item_names=item_names)
 
 
@@ -1681,7 +1717,8 @@
     :rtype: tuple
     :returns: start word, end word, matches dict
     """
-    item_names = [rev.meta[NAME] for rev in flaskg.storage.documents(wikiname=app.cfg.interwikiname)]
+    item_names = [rev.name for rev in flaskg.storage.documents(wikiname=app.cfg.interwikiname)
+                  if rev.name is not None]
     if item_name in item_names:
         item_names.remove(item_name)
     # Get matches using wiki way, start and end of word
@@ -1720,11 +1757,9 @@
     :returns: start, end, matches dict
     """
     if start_re is None:
-        start_re = re.compile(u'([{0}][{1}]+)'.format(config.chars_upper,
-                                                     config.chars_lower))
+        start_re = re.compile(u'([{0}][{1}]+)'.format(CHARS_UPPER, CHARS_LOWER))
     if end_re is None:
-        end_re = re.compile(u'([{0}][{1}]+)$'.format(config.chars_upper,
-                                                    config.chars_lower))
+        end_re = re.compile(u'([{0}][{1}]+)$'.format(CHARS_UPPER, CHARS_LOWER))
 
     # If we don't get results with wiki words matching, fall back to
     # simple first word and last word, using spaces.
@@ -1800,9 +1835,9 @@
     if not flaskg.storage[item_name]:
         abort(404, item_name)
     sitemap = NestedItemListBuilder().recurse_build([item_name])
-    del sitemap[0] # don't show current item name as sole toplevel list item
+    del sitemap[0]  # don't show current item name as sole toplevel list item
     return render_template('sitemap.html',
-                           item_name=item_name, # XXX no item
+                           item_name=item_name,  # XXX no item
                            sitemap=sitemap,
                           )
 
@@ -1811,7 +1846,7 @@
     def __init__(self):
         self.children = set()
         self.numnodes = 0
-        self.maxnodes = 35 # approx. max count of nodes, not strict
+        self.maxnodes = 35  # approx. max count of nodes, not strict
 
     def recurse_build(self, names):
         result = []
@@ -1856,7 +1891,7 @@
     tags_counts = {}
     for rev in revs:
         tags = rev.meta.get(TAGS, [])
-        logging.debug("name {0!r} rev {1} tags {2!r}".format(rev.meta[NAME], rev.meta[REVID], tags))
+        logging.debug("name {0!r} rev {1} tags {2!r}".format(rev.name, rev.meta[REVID], tags))
         for tag in tags:
             tags_counts[tag] = tags_counts.setdefault(tag, 0) + 1
     tags_counts = sorted(tags_counts.items())
@@ -1870,10 +1905,11 @@
             scale = weight_max / 2
         else:
             scale = weight_max / (count_max - count_min)
+
         def cls(count):
             # return the css class for this tag
             weight = scale * (count - count_min)
-            return "weight{0}".format(int(weight)) # weight0, ..., weight9
+            return "weight{0}".format(int(weight))  # weight0, ..., weight9
         tags = [(cls(count), tag) for tag, count in tags_counts]
     else:
         tags = []
@@ -1890,7 +1926,7 @@
     """
     query = And([Term(WIKINAME, app.cfg.interwikiname), Term(TAGS, tag), ])
     revs = flaskg.storage.search(query, sortedby=NAME_EXACT, limit=None)
-    item_names = [rev.meta[NAME] for rev in revs]
+    item_names = [rev.name for rev in revs]
     return render_template("link_list_no_item_panel.html",
                            headline=_("Items tagged with %(tag)s", tag=tag),
                            item_name=tag,
--- a/MoinMoin/apps/misc/views.py	Fri Feb 08 08:32:26 2013 -0700
+++ b/MoinMoin/apps/misc/views.py	Mon Feb 11 12:04:39 2013 -0700
@@ -15,12 +15,12 @@
 
 from MoinMoin.apps.misc import misc
 
-from MoinMoin.config import NAME, MTIME
+from MoinMoin.constants.keys import MTIME
 from MoinMoin.themes import render_template
-from MoinMoin import wikiutil
 
 SITEMAP_HAS_SYSTEM_ITEMS = True
 
+
 @misc.route('/sitemap')
 def sitemap():
     """
@@ -32,9 +32,9 @@
 
     sitemap = []
     for rev in flaskg.storage.documents(wikiname=app.cfg.interwikiname):
-        name = rev.meta[NAME]
+        name = rev.name
         mtime = rev.meta[MTIME]
-        if False: # was: wikiutil.isSystemItem(name)   XXX add back later, when we have that in the index
+        if False:  # was: wikiutil.isSystemItem(name)   XXX add back later, when we have that in the index
             if not SITEMAP_HAS_SYSTEM_ITEMS:
                 continue
             # system items are rather boring
@@ -66,6 +66,6 @@
     See: http://usemod.com/cgi-bin/mb.pl?SisterSitesImplementationGuide
     """
     # XXX we currently also get deleted items, fix this
-    item_names = sorted([rev.meta[NAME] for rev in flaskg.storage.documents(wikiname=app.cfg.interwikiname)])
+    item_names = sorted([rev.name for rev in flaskg.storage.documents(wikiname=app.cfg.interwikiname)])
     content = render_template('misc/urls_names.txt', item_names=item_names)
     return Response(content, mimetype='text/plain')
--- a/MoinMoin/auth/__init__.py	Fri Feb 08 08:32:26 2013 -0700
+++ b/MoinMoin/auth/__init__.py	Mon Feb 11 12:04:39 2013 -0700
@@ -177,21 +177,25 @@
         self.multistage = multistage
         self.redirect_to = redirect_to
 
+
 class ContinueLogin(LoginReturn):
     """ ContinueLogin - helper for auth method login that just continues """
     def __init__(self, user_obj, message=None):
         LoginReturn.__init__(self, user_obj, True, message=message)
 
+
 class CancelLogin(LoginReturn):
     """ CancelLogin - cancel login showing a message """
     def __init__(self, message):
         LoginReturn.__init__(self, None, False, message=message)
 
+
 class MultistageFormLogin(LoginReturn):
     """ MultistageFormLogin - require user to fill in another form """
     def __init__(self, multistage):
         LoginReturn.__init__(self, None, False, multistage=multistage)
 
+
 class MultistageRedirectLogin(LoginReturn):
     """ MultistageRedirectLogin - redirect user to another site before continuing login """
     def __init__(self, url):
@@ -202,22 +206,28 @@
     name = None
     login_inputs = []
     logout_possible = False
+
     def __init__(self, trusted=False, **kw):
         self.trusted = trusted
         if kw:
             raise TypeError("got unexpected arguments %r" % kw)
+
     def login(self, user_obj, **kw):
         return ContinueLogin(user_obj)
+
     def request(self, user_obj, **kw):
         return user_obj, True
+
     def logout(self, user_obj, **kw):
         if self.name and user_obj and user_obj.auth_method == self.name:
             logging.debug("{0}: logout - invalidating user {1!r}".format(self.name, user_obj.name))
             user_obj.valid = False
         return user_obj, True
+
     def login_hint(self):
         return None
 
+
 class MoinAuth(BaseAuth):
     """ handle login from moin login form """
     def __init__(self, **kw):
@@ -267,7 +277,7 @@
         Alternatively you can directly give a fixed user name (user_name)
         that will be considered as authenticated.
     """
-    name = 'given' # was 'http' in 1.8.x and before
+    name = 'given'  # was 'http' in 1.8.x and before
 
     def __init__(self,
                  env_var=None,  # environment variable we want to read (default: REMOTE_USER)
@@ -352,7 +362,7 @@
             u.create_or_update()
         if u and u.valid:
             logging.debug("returning valid user {0!r}".format(u))
-            return u, True # True to get other methods called, too
+            return u, True  # True to get other methods called, too
         else:
             logging.debug("returning {0!r}".format(user_obj))
             return user_obj, True
@@ -407,6 +417,7 @@
 
     return userobj
 
+
 def handle_logout(userobj):
     """ Logout the passed user from every configured authentication method. """
     if userobj is None:
@@ -419,6 +430,7 @@
             break
     return userobj
 
+
 def handle_request(userobj):
     """ Handle the per-request callbacks of the configured authentication methods. """
     for authmethod in app.cfg.auth:
@@ -427,6 +439,7 @@
             break
     return userobj
 
+
 def setup_from_session():
     userobj = None
     if 'user.itemid' in session:
--- a/MoinMoin/auth/_tests/test_auth.py	Fri Feb 08 08:32:26 2013 -0700
+++ b/MoinMoin/auth/_tests/test_auth.py	Mon Feb 11 12:04:39 2013 -0700
@@ -5,11 +5,8 @@
 Test for auth.__init__
 """
 
-from flask import current_app as app
 from flask import g as flaskg
 
-import pytest
-
 from MoinMoin._tests import wikiconfig
 from MoinMoin.auth import GivenAuth, handle_login, get_multistage_continuation_url
 from MoinMoin.user import create_user
@@ -20,7 +17,7 @@
         auth = [GivenAuth(user_name=u'JoeDoe', autocreate=True), ]
 
     def test(self):
-        assert flaskg.user.name == u'JoeDoe'
+        assert flaskg.user.name == [u'JoeDoe', ]
 
 
 class TestGivenAuth(object):
@@ -52,7 +49,7 @@
         create_user(u'Test_User', u'test_pass', u'test@moinmoin.org')
         test_user, bool_value = givenauth_obj.request(flaskg.user)
         assert test_user.valid
-        assert test_user.name == u'Test_User'
+        assert test_user.name == [u'Test_User', ]
 
 def test_handle_login():
     # no messages in the beginning
@@ -60,7 +57,7 @@
     test_user1 = handle_login(flaskg.user, login_username='test_user', login_password='test_password', stage='moin')
     test_login_message = [u'Invalid username or password.']
     assert flaskg._login_messages == test_login_message
-    assert test_user1.name == u'anonymous'
+    assert test_user1.name0 == u'anonymous'
     assert not test_user1.valid
     # pop the message
     flaskg._login_messages.pop()
@@ -72,7 +69,7 @@
     test_user, bool_value = givenauth_obj.request(flaskg.user)
     test_user2 = handle_login(test_user, login_username='Test_User', login_password='test_pass', stage='moin')
     assert not flaskg._login_messages
-    assert test_user2.name == u'Test_User'
+    assert test_user2.name == [u'Test_User', ]
     assert test_user2.valid
 
 def test_get_multistage_continuation_url():
--- a/MoinMoin/auth/_tests/test_http.py	Fri Feb 08 08:32:26 2013 -0700
+++ b/MoinMoin/auth/_tests/test_http.py	Mon Feb 11 12:04:39 2013 -0700
@@ -10,7 +10,7 @@
 
 from MoinMoin.user import create_user
 from MoinMoin.auth.http import HTTPAuthMoin
-import pytest
+
 
 class TestHTTPAuthMoin(object):
     """ Test: HTTPAuthMoin """
@@ -34,11 +34,11 @@
         httpauthmoin_obj = HTTPAuthMoin()
         test_user, bool_val = httpauthmoin_obj.request(flaskg.user)
         assert test_user.valid
-        assert test_user.name == u'ValidUser'
+        assert test_user.name == [u'ValidUser', ]
         assert bool_val
 
         # when auth_method is not 'http'
         flaskg.user.auth_method = 'invalid'
         test_user, bool_val = httpauthmoin_obj.request(flaskg.user)
         assert not test_user.valid
-        assert test_user.name == u'anonymous'
+        assert test_user.name0 == u'anonymous'
--- a/MoinMoin/auth/_tests/test_log.py	Fri Feb 08 08:32:26 2013 -0700
+++ b/MoinMoin/auth/_tests/test_log.py	Mon Feb 11 12:04:39 2013 -0700
@@ -10,7 +10,7 @@
 
 from MoinMoin.auth.log import AuthLog
 from flask import g as flaskg
-import pytest
+
 
 class TestAuthLog(object):
     """ Test: TestAuthLog """
@@ -19,13 +19,13 @@
         result = authlog_obj.login(flaskg.user)
         assert result.continue_flag
         test_user_obj = result.user_obj
-        assert test_user_obj.name == u'anonymous'
+        assert test_user_obj.name0 == u'anonymous'
 
     def test_request(self):
         authlog_obj = AuthLog()
         result = authlog_obj.request(flaskg.user)
         test_user, bool_value = result
-        assert test_user.name == u'anonymous'
+        assert test_user.name0 == u'anonymous'
         assert not test_user.valid
         assert bool_value
 
@@ -33,6 +33,6 @@
         authlog_obj = AuthLog()
         result = authlog_obj.logout(flaskg.user)
         test_user, bool_value = result
-        assert test_user.name == u'anonymous'
+        assert test_user.name0 == u'anonymous'
         assert not test_user.valid
         assert bool_value
--- a/MoinMoin/auth/http.py	Fri Feb 08 08:32:26 2013 -0700
+++ b/MoinMoin/auth/http.py	Mon Feb 11 12:04:39 2013 -0700
@@ -22,7 +22,7 @@
 
 from flask import request
 
-from MoinMoin import config, user
+from MoinMoin import user
 from MoinMoin.i18n import _, L_, N_
 from MoinMoin.auth import BaseAuth, GivenAuth
 
@@ -48,7 +48,8 @@
 
         auth = request.authorization
         if auth and auth.username and auth.password is not None:
-            logging.debug("http basic auth, received username: {0!r} password: {1!r}".format(auth.username, auth.password))
+            logging.debug("http basic auth, received username: {0!r} password: {1!r}".format(
+                auth.username, auth.password))
             u = user.User(name=auth.username.decode(self.coding),
                           password=auth.password.decode(self.coding),
                           auth_method=self.name, auth_attribs=[], trusted=self.trusted)
@@ -66,7 +67,7 @@
             u.create_or_update()
         if u and u.valid:
             logging.debug("returning valid user {0!r}".format(u))
-            return u, True # True to get other methods called, too
+            return u, True  # True to get other methods called, too
         else:
             logging.debug("returning {0!r}".format(user_obj))
             return user_obj, True
--- a/MoinMoin/auth/ldap_login.py	Fri Feb 08 08:32:26 2013 -0700
+++ b/MoinMoin/auth/ldap_login.py	Mon Feb 11 12:04:39 2013 -0700
@@ -14,7 +14,7 @@
     "Can't contact LDAP server" - more recent debian installations have tls
     support in libldap2 (see dependency on gnutls) and also in python-ldap.
 
-    TODO: allow more configuration (alias name, ...) by using callables as parameters
+    TODO: allow more configuration (display name, ...) by using callables as parameters
 """
 
 from MoinMoin import log
@@ -60,32 +60,32 @@
         bind_pw='',
         base_dn='',  # base DN we use for searching
                      #base_dn = 'ou=SOMEUNIT,dc=example,dc=org'
-        scope=ldap.SCOPE_SUBTREE, # scope of the search we do (2 == ldap.SCOPE_SUBTREE)
-        referrals=0, # LDAP REFERRALS (0 needed for AD)
+        scope=ldap.SCOPE_SUBTREE,  # scope of the search we do (2 == ldap.SCOPE_SUBTREE)
+        referrals=0,  # LDAP REFERRALS (0 needed for AD)
         search_filter='(uid=%(username)s)',  # ldap filter used for searching:
                                              #search_filter = '(sAMAccountName=%(username)s)' # (AD)
                                              #search_filter = '(uid=%(username)s)' # (OpenLDAP)
                                              # you can also do more complex filtering like:
                                              # "(&(cn=%(username)s)(memberOf=CN=WikiUsers,OU=Groups,DC=example,DC=org))"
         # some attribute names we use to extract information from LDAP:
-        givenname_attribute=None, # ('givenName') ldap attribute we get the first name from
-        surname_attribute=None, # ('sn') ldap attribute we get the family name from
-        aliasname_attribute=None, # ('displayName') ldap attribute we get the aliasname from
-        email_attribute=None, # ('mail') ldap attribute we get the email address from
-        email_callback=None, # called to make up email address
-        name_callback=None, # called to use a Wiki name different from the login name
-        coding='utf-8', # coding used for ldap queries and result values
-        timeout=10, # how long we wait for the ldap server [s]
-        start_tls=0, # 0 = No, 1 = Try, 2 = Required
+        givenname_attribute=None,  # ('givenName') ldap attribute we get the first name from
+        surname_attribute=None,  # ('sn') ldap attribute we get the family name from
+        displayname_attribute=None,  # ('displayName') ldap attribute we get the display_name from
+        email_attribute=None,  # ('mail') ldap attribute we get the email address from
+        email_callback=None,  # called to make up email address
+        name_callback=None,  # called to use a Wiki name different from the login name
+        coding='utf-8',  # coding used for ldap queries and result values
+        timeout=10,  # how long we wait for the ldap server [s]
+        start_tls=0,  # 0 = No, 1 = Try, 2 = Required
         tls_cacertdir=None,
         tls_cacertfile=None,
         tls_certfile=None,
         tls_keyfile=None,
-        tls_require_cert=0, # 0 == ldap.OPT_X_TLS_NEVER (needed for self-signed certs)
-        bind_once=False, # set to True to only do one bind - useful if configured to bind as the user on the first attempt
-        autocreate=False, # set to True if you want to autocreate user profiles
-        name='ldap', # use e.g. 'ldap_pdc' and 'ldap_bdc' (or 'ldap1' and 'ldap2') if you auth against 2 ldap servers
-        report_invalid_credentials=True, # whether to emit "invalid username or password" msg at login time or not
+        tls_require_cert=0,  # 0 == ldap.OPT_X_TLS_NEVER (needed for self-signed certs)
+        bind_once=False,  # set to True to only do one bind - useful if configured to bind as the user on first attempt
+        autocreate=False,  # set to True if you want to autocreate user profiles
+        name='ldap',  # use e.g. 'ldap_pdc' and 'ldap_bdc' (or 'ldap1' and 'ldap2') if you auth against 2 ldap servers
+        report_invalid_credentials=True,  # whether to emit "invalid username or password" msg at login time or not
         **kw
         ):
         super(LDAPAuth, self).__init__(**kw)
@@ -99,7 +99,7 @@
 
         self.givenname_attribute = givenname_attribute
         self.surname_attribute = surname_attribute
-        self.aliasname_attribute = aliasname_attribute
+        self.displayname_attribute = displayname_attribute
         self.email_attribute = email_attribute
         self.email_callback = email_callback
         self.name_callback = name_callback
@@ -133,9 +133,10 @@
             try:
                 u = None
                 dn = None
+                server = self.server_uri
                 coding = self.coding
                 logging.debug("Setting misc. ldap options...")
-                ldap.set_option(ldap.OPT_PROTOCOL_VERSION, ldap.VERSION3) # ldap v2 is outdated
+                ldap.set_option(ldap.OPT_PROTOCOL_VERSION, ldap.VERSION3)  # ldap v2 is outdated
                 ldap.set_option(ldap.OPT_REFERRALS, self.referrals)
                 ldap.set_option(ldap.OPT_NETWORK_TIMEOUT, self.timeout)
 
@@ -152,7 +153,6 @@
                         if value is not None:
                             ldap.set_option(option, value)
 
-                server = self.server_uri
                 logging.debug("Trying to initialize {0!r}.".format(server))
                 l = ldap.initialize(server)
                 logging.debug("Connected to LDAP server {0!r}.".format(server))
@@ -177,7 +177,7 @@
                 logging.debug("Searching {0!r}".format(filterstr))
                 attrs = [getattr(self, attr) for attr in [
                                          'email_attribute',
-                                         'aliasname_attribute',
+                                         'displayname_attribute',
                                          'surname_attribute',
                                          'givenname_attribute',
                                          ] if getattr(self, attr) is not None]
@@ -193,7 +193,8 @@
                 result_length = len(lusers)
                 if result_length != 1:
                     if result_length > 1:
-                        logging.warning("Search found more than one ({0}) matches for {1!r}.".format(result_length, filterstr))
+                        logging.warning("Search found more than one ({0}) matches for {1!r}.".format(
+                            result_length, filterstr))
                     if result_length == 0:
                         logging.debug("Search found no matches for {0!r}.".format(filterstr, ))
                     if self.report_invalid_credentials:
@@ -215,36 +216,40 @@
                 else:
                     email = self.email_callback(ldap_dict)
 
-                aliasname = ''
+                display_name = ''
                 try:
-                    aliasname = ldap_dict[self.aliasname_attribute][0]
+                    display_name = ldap_dict[self.displayname_attribute][0]
                 except (KeyError, IndexError):
                     pass
-                if not aliasname:
+                if not display_name:
                     sn = ldap_dict.get(self.surname_attribute, [''])[0]
                     gn = ldap_dict.get(self.givenname_attribute, [''])[0]
                     if sn and gn:
-                        aliasname = "{0}, {1}".format(sn, gn)
+                        display_name = "{0}, {1}".format(sn, gn)
                     elif sn:
-                        aliasname = sn
-                aliasname = aliasname.decode(coding)
+                        display_name = sn
+                display_name = display_name.decode(coding)
 
                 if self.name_callback:
                     username = self.name_callback(ldap_dict)
 
                 if email:
-                    u = user.User(auth_username=username, auth_method=self.name, auth_attribs=('name', 'password', 'email', 'mailto_author', ),
+                    u = user.User(auth_username=username, auth_method=self.name,
+                                  auth_attribs=('name', 'password', 'email', 'mailto_author', ),
                                   trusted=self.trusted)
                     u.email = email
                 else:
-                    u = user.User(auth_username=username, auth_method=self.name, auth_attribs=('name', 'password', 'mailto_author', ),
+                    u = user.User(auth_username=username, auth_method=self.name,
+                                  auth_attribs=('name', 'password', 'mailto_author', ),
                                   trusted=self.trusted)
                 u.name = username
-                u.aliasname = aliasname
-                logging.debug("creating user object with name {0!r} email {1!r} alias {2!r}".format(username, email, aliasname))
+                u.display_name = display_name
+                logging.debug("creating user object with name {0!r} email {1!r} display name {2!r}".format(
+                    username, email, display_name))
 
             except ldap.INVALID_CREDENTIALS as err:
-                logging.debug("invalid credentials (wrong password?) for dn {0!r} (username: {1!r})".format(dn, username))
+                logging.debug("invalid credentials (wrong password?) for dn {0!r} (username: {1!r})".format(
+                    dn, username))
                 return CancelLogin(_("Invalid username or password."))
 
             if u and self.autocreate:
--- a/MoinMoin/auth/log.py	Fri Feb 08 08:32:26 2013 -0700
+++ b/MoinMoin/auth/log.py	Mon Feb 11 12:04:39 2013 -0700
@@ -14,6 +14,7 @@
 
 from MoinMoin.auth import BaseAuth, ContinueLogin
 
+
 class AuthLog(BaseAuth):
     """ just log the call, do nothing else """
     name = "log"
--- a/MoinMoin/auth/openidrp.py	Fri Feb 08 08:32:26 2013 -0700
+++ b/MoinMoin/auth/openidrp.py	Mon Feb 11 12:04:39 2013 -0700
@@ -20,7 +20,7 @@
 from flask import current_app as app
 from MoinMoin.auth import BaseAuth, get_multistage_continuation_url
 from MoinMoin.auth import ContinueLogin, CancelLogin, MultistageFormLogin, MultistageRedirectLogin
-from MoinMoin.config import ITEMID
+from MoinMoin.constants.keys import ITEMID
 from MoinMoin import user
 from MoinMoin.i18n import _, L_, N_
 
--- a/MoinMoin/auth/smb_mount.py	Fri Feb 08 08:32:26 2013 -0700
+++ b/MoinMoin/auth/smb_mount.py	Mon Feb 11 12:04:39 2013 -0700
@@ -16,6 +16,7 @@
 
 from MoinMoin.auth import BaseAuth, CancelLogin, ContinueLogin
 
+
 class SMBMount(BaseAuth):
     """ auth plugin for (un)mounting an smb share,
         this is a wrapper around mount.cifs -o <options> //server/share mountpoint
@@ -23,17 +24,17 @@
         See man mount.cifs for details.
     """
     def __init__(self,
-        server, # mount.cifs //server/share
-        share, # mount.cifs //server/share
-        mountpoint_fn, # function of username to determine the mountpoint, e.g.:
-                       # lambda username: u'/mnt/wiki/%s' % username
-        dir_user, # username to get the uid that is used for mount.cifs -o uid=... (e.g. 'www-data')
-        domain, # mount.cifs -o domain=...
-        dir_mode='0700', # mount.cifs -o dir_mode=...
-        file_mode='0600', # mount.cifs -o file_mode=...
-        iocharset='utf-8', # mount.cifs -o iocharset=... (try 'iso8859-1' if default does not work)
-        coding='utf-8', # encoding used for username/password/cmdline (try 'iso8859-1' if default does not work)
-        log='/dev/null', # logfile for mount.cifs output
+        server,  # mount.cifs //server/share
+        share,  # mount.cifs //server/share
+        mountpoint_fn,  # function of username to determine the mountpoint, e.g.:
+                        # lambda username: u'/mnt/wiki/%s' % username
+        dir_user,  # username to get the uid that is used for mount.cifs -o uid=... (e.g. 'www-data')
+        domain,  # mount.cifs -o domain=...
+        dir_mode='0700',  # mount.cifs -o dir_mode=...
+        file_mode='0600',  # mount.cifs -o file_mode=...
+        iocharset='utf-8',  # mount.cifs -o iocharset=... (try 'iso8859-1' if default does not work)
+        coding='utf-8',  # encoding used for username/password/cmdline (try 'iso8859-1' if default does not work)
+        log='/dev/null',  # logfile for mount.cifs output
         **kw
         ):
         super(SMBMount, self).__init__(**kw)
@@ -51,13 +52,16 @@
     def do_smb(self, username, password, login):
         logging.debug("login={0} logout={1}: got name={2!r}".format(login, not login, username))
 
-        import os, pwd, subprocess
+        import os
+        import pwd
+        import subprocess
         web_username = self.dir_user
-        web_uid = pwd.getpwnam(web_username)[2] # XXX better just use current uid?
+        web_uid = pwd.getpwnam(web_username)[2]  # XXX better just use current uid?
 
         mountpoint = self.mountpoint_fn(username)
         if login:
-            cmd = u"sudo mount -t cifs -o user=%(user)s,domain=%(domain)s,uid=%(uid)d,dir_mode=%(dir_mode)s,file_mode=%(file_mode)s,iocharset=%(iocharset)s //%(server)s/%(share)s %(mountpoint)s >>%(log)s 2>&1"
+            cmd = (u"sudo mount -t cifs -o user=%(user)s,domain=%(domain)s,uid=%(uid)d,dir_mode=%(dir_mode)s,file_mode="
+                   u"%(file_mode)s,iocharset=%(iocharset)s //%(server)s/%(share)s %(mountpoint)s >>%(log)s 2>&1")
         else:
             cmd = u"sudo umount %(mountpoint)s >>%(log)s 2>&1"
 
@@ -77,7 +81,7 @@
         if login:
             try:
                 if not os.path.exists(mountpoint):
-                    os.makedirs(mountpoint) # the dir containing the mountpoint must be writeable for us!
+                    os.makedirs(mountpoint)  # the dir containing the mountpoint must be writeable for us!
             except OSError:
                 pass
             env['PASSWD'] = password.encode(self.coding)
--- a/MoinMoin/config/__init__.py	Fri Feb 08 08:32:26 2013 -0700
+++ b/MoinMoin/config/__init__.py	Mon Feb 11 12:04:39 2013 -0700
@@ -1,13 +1,4 @@
-# Copyright: 2011 MoinMoin:ThomasWaldmann
+# Copyright: 2011-2013 MoinMoin:ThomasWaldmann
 # License: GNU GPL v2 (or any later version), see LICENSE.txt for details.
 
-"""
-MoinMoin - configuration defaults and support code
-"""
-
-# provide compatibility for stuff not using MoinMoin.constants yet
-from MoinMoin.constants.rights import *
-from MoinMoin.constants.chartypes import *
-from MoinMoin.constants.contenttypes import *
-from MoinMoin.constants.keys import *
-from MoinMoin.constants.misc import *
+# Nothing to see here any more, please do direct imports from MoinMoin.constants.*
--- a/MoinMoin/config/default.py	Fri Feb 08 08:32:26 2013 -0700
+++ b/MoinMoin/config/default.py	Mon Feb 11 12:04:39 2013 -0700
@@ -20,7 +20,8 @@
 logging = log.getLogger(__name__)
 
 from MoinMoin.i18n import _, L_, N_
-from MoinMoin import config, error
+from MoinMoin import error
+from MoinMoin.constants.rights import ACL_RIGHTS_CONTENTS, ACL_RIGHTS_FUNCTIONS
 from MoinMoin import datastruct
 from MoinMoin.auth import MoinAuth
 from MoinMoin.util import plugins
@@ -113,18 +114,28 @@
         self.mail_enabled = self.mail_enabled and True or False
 
         if self.namespace_mapping is None:
-            raise error.ConfigurationError("No storage configuration specified! You need to define a namespace_mapping. "
-                                           "For further reference, please see HelpOnStorageConfiguration.")
+            raise error.ConfigurationError(
+                "No storage configuration specified! You need to define a namespace_mapping. "
+                "For further reference, please see HelpOnStorageConfiguration.")
+
+        if self.backend_mapping is None:
+            raise error.ConfigurationError(
+                "No storage configuration specified! You need to define a backend_mapping. " +
+                "For further reference, please see HelpOnStorageConfiguration.")
 
         if self.acl_mapping is None:
-            raise error.ConfigurationError("No acl configuration specified! You need to define a acl_mapping. "
-                                           "For further reference, please see HelpOnStorageConfiguration.")
+            raise error.ConfigurationError(
+                "No acl configuration specified! You need to define a acl_mapping. "
+                "For further reference, please see HelpOnStorageConfiguration.")
 
         if self.secrets is None:  # admin did not setup a real secret
-            raise error.ConfigurationError("No secret configured! You need to set secrets = 'somelongsecretstring' in your wiki config.")
+            raise error.ConfigurationError(
+                "No secret configured! You need to set secrets = 'somelongsecretstring' in your wiki config.")
 
         if self.interwikiname is None:  # admin did not setup a real interwikiname
-            raise error.ConfigurationError("No interwikiname configured! You need to set interwikiname = u'YourUniqueStableInterwikiName' in your wiki config.")
+            raise error.ConfigurationError(
+                "No interwikiname configured! "
+                "You need to set interwikiname = u'YourUniqueStableInterwikiName' in your wiki config.")
 
         secret_key_names = ['security/ticket', ]
         if self.textchas:
@@ -133,8 +144,9 @@
         secret_min_length = 10
         if isinstance(self.secrets, str):
             if len(self.secrets) < secret_min_length:
-                raise error.ConfigurationError("The secrets = '...' wiki config setting is a way too short string (minimum length is {0} chars)!".format(
-                    secret_min_length))
+                raise error.ConfigurationError(
+                    "The secrets = '...' wiki config setting is a way too short string "
+                    "(minimum length is {0} chars)!".format(secret_min_length))
             # for lazy people: set all required secrets to same value
             secrets = {}
             for key in secret_key_names:
@@ -148,7 +160,8 @@
                 if len(secret) < secret_min_length:
                     raise ValueError
             except (KeyError, ValueError):
-                raise error.ConfigurationError("You must set a (at least {0} chars long) secret string for secrets['{1}']!".format(
+                raise error.ConfigurationError(
+                    "You must set a (at least {0} chars long) secret string for secrets['{1}']!".format(
                     secret_min_length, secret_key_name))
 
         from passlib.context import CryptContext
@@ -169,7 +182,7 @@
         unknown = ['"{0}"'.format(name) for name in dir(self)
                   if not name.startswith('_') and
                   name not in DefaultConfig.__dict__ and
-                  not isinstance(getattr(self, name), (type(sys), type(DefaultConfig)))]
+                  not isinstance(getattr(self, name), (type(re), type(DefaultConfig)))]
         if unknown:
             msg = """
 Unknown configuration options: {0}.
@@ -271,9 +284,9 @@
        username_lower in password_lower or password_lower in username_lower:
         return _("Password is too easy to guess (password contains name or name contains password).")
 
-    keyboards = (ur"`1234567890-=qwertyuiop[]\asdfghjkl;'zxcvbnm,./", # US kbd
-                 ur"^1234567890ß´qwertzuiopü+asdfghjklöä#yxcvbnm,.-", # german kbd
-                ) # add more keyboards!
+    keyboards = (ur"`1234567890-=qwertyuiop[]\asdfghjkl;'zxcvbnm,./",  # US kbd
+                 ur"^1234567890ß´qwertzuiopü+asdfghjklöä#yxcvbnm,.-",  # german kbd
+                )  # TODO add more keyboards!
     for kbd in keyboards:
         rev_kbd = kbd[::-1]
         if password in kbd or password in rev_kbd or \
@@ -433,8 +446,11 @@
     ('interwiki_map', {},
      "Dictionary of wiki_name -> wiki_url"),
     ('namespace_mapping', None,
-    "This needs to point to a list of tuples, each tuple containing: Namespace identifier, backend. " +
-    "E.g.: [('/', FSBackend('wiki/data')), ]. Please see HelpOnStorageConfiguration for further reference."),
+    "A list of tuples, each tuple containing: Namespace identifier, backend name. " +
+    "E.g.: [('', 'default')), ]. Please see HelpOnStorageConfiguration for further reference."),
+    ('backend_mapping', None,
+    "A dictionary that maps backend names to backends. " +
+    "E.g.: {'default': Backend(), }. Please see HelpOnStorageConfiguration for further reference."),
     ('acl_mapping', None,
     "This needs to point to a list of tuples, each tuple containing: name prefix, acl protection to be applied to matching items. " +
     "E.g.: [('', dict(default='All:read,write,create')), ]. Please see HelpOnStorageConfiguration for further reference."),
@@ -460,8 +476,8 @@
   'user': ('User Preferences', None, (
     ('user_defaults',
       dict(
-        name=u'anonymous',
-        aliasname=None,
+        name=[],
+        display_name=None,
         email=None,
         openid=None,
         css_url=None,
@@ -482,11 +498,11 @@
             # XXX PageCopiedEvent.__name__
             # XXX PageRevertedEvent.__name__
         ],
-        theme_name=None, # None -> use cfg.theme_default
+        theme_name=None,  # None -> use cfg.theme_default
         edit_rows=0,
         results_per_page=0,
-        locale=None, # None -> do browser language detection, otherwise just use this locale
-        timezone=None, # None -> use cfg.timezone_default
+        locale=None,  # None -> do browser language detection, otherwise just use this locale
+        timezone=None,  # None -> use cfg.timezone_default
       ),
      'Default attributes of the user object'),
   )),
@@ -518,8 +534,8 @@
     ('refresh', None,
      "refresh = (minimum_delay_s, targets_allowed) enables use of `#refresh 5 PageName` processing instruction, targets_allowed must be either `'internal'` or `'external'`"),
 
-    ('siteid', 'MoinMoin', None), # XXX just default to some existing module name to
-                                  # make plugin loader etc. work for now
+    ('siteid', 'MoinMoin', None),  # XXX just default to some existing module name to
+                                   # make plugin loader etc. work for now
   )),
 }
 
@@ -552,9 +568,9 @@
     (
       ('functions', u'',
        'Access Control List for functions.'),
-      ('rights_contents', config.ACL_RIGHTS_CONTENTS,
+      ('rights_contents', ACL_RIGHTS_CONTENTS,
        'Valid tokens for right sides of content ACL entries.'),
-      ('rights_functions', config.ACL_RIGHTS_FUNCTIONS,
+      ('rights_functions', ACL_RIGHTS_FUNCTIONS,
        'Valid tokens for right sides of function ACL entries.'),
     )),
 
@@ -589,6 +605,7 @@
     )),
 }
 
+
 def _add_options_to_defconfig(opts, addgroup=True):
     for groupname in opts:
         group_short, group_doc, group_opts = opts[groupname]
--- a/MoinMoin/conftest.py	Fri Feb 08 08:32:26 2013 -0700
+++ b/MoinMoin/conftest.py	Mon Feb 11 12:04:39 2013 -0700
@@ -20,8 +20,8 @@
 
 # exclude some directories from py.test test discovery, pathes relative to this file
 collect_ignore = ['static',  # same
-                  '../wiki', # no tests there
-                  '../instance', # tw likes to use this for wiki data (non-revisioned)
+                  '../wiki',  # no tests there
+                  '../instance',  # tw likes to use this for wiki data (non-revisioned)
                  ]
 import atexit
 import os
@@ -32,8 +32,7 @@
 import py
 import MoinMoin.log
 
-""" Logging for tests to avoid useless output like timing information on stderr on test failures
-"""
+# Logging for tests to avoid useless output like timing information on stderr on test failures
 Moindir = py.path.local(__file__).dirname
 config_file = Moindir + '/_tests/test_logging.conf'
 MoinMoin.log.load_config(config_file)
@@ -44,14 +43,16 @@
 
 
 def init_test_app(given_config):
-    namespace_mapping, acl_mapping = create_simple_mapping("stores:memory:", given_config.content_acl)
+    namespace_mapping, backend_mapping, acl_mapping = \
+        create_simple_mapping("stores:memory:", given_config.content_acl)
     more_config = dict(
         namespace_mapping=namespace_mapping,
+        backend_mapping=backend_mapping,
         acl_mapping=acl_mapping,
-        create_storage=True, # create a fresh storage at each app start
-        destroy_storage=True, # kill all storage contents at app shutdown
-        create_index=True, # create a fresh index at each app start
-        destroy_index=True, # kill index contents at app shutdown
+        create_storage=True,  # create a fresh storage at each app start
+        destroy_storage=True,  # kill all storage contents at app shutdown
+        create_index=True,  # create a fresh index at each app start
+        destroy_index=True,  # kill index contents at app shutdown
     )
     app = create_app_ext(flask_config_dict=dict(SECRET_KEY='foobarfoobar'),
                          moin_config_class=given_config,
@@ -61,6 +62,7 @@
     before_wiki()
     return app, ctx
 
+
 def deinit_test_app(app, ctx):
     teardown_wiki('')
     ctx.pop()
@@ -72,6 +74,7 @@
 prev_ctx = None
 prev_cfg = None
 
+
 class MoinTestFunction(pytest.collect.Function):
     def setup(self):
         global prev_app, prev_ctx, prev_cfg
@@ -107,10 +110,12 @@
 def pytest_pycollect_makemodule(path, parent):
     return Module(path, parent=parent)
 
+
 def pytest_pycollect_makeitem(__multicall__, collector, name, obj):
     if collector.funcnamefilter(name) and inspect.isfunction(obj):
         return MoinTestFunction(name, parent=collector)
 
+
 def pytest_pyfunc_call(pyfuncitem):
     """hook to intercept generators and run them as a single test items"""
     if inspect.isgeneratorfunction(pyfuncitem.obj):
@@ -118,9 +123,11 @@
             kwarg = item[1:]
             item[0](*kwarg)
 
+
 def pytest_report_header(config):
     return "The tests here are implemented only for pytest-2"
 
+
 class Module(pytest.collect.Module):
     def run(self, *args, **kwargs):
         if coverage is not None:
--- a/MoinMoin/constants/chartypes.py	Fri Feb 08 08:32:26 2013 -0700
+++ b/MoinMoin/constants/chartypes.py	Mon Feb 11 12:04:39 2013 -0700
@@ -1,8 +1,8 @@
-
-chars_upper = u"\u0041\u0042\u0043\u0044\u0045\u0046\u0047\u0048\u0049\u004a\u004b\u004c\u004d\u004e\u004f\u0050\u0051\u0052\u0053\u0054\u0055\u0056\u0057\u0058\u0059\u005a\u00c0\u00c1\u00c2\u00c3\u00c4\u00c5\u00c6\u00c7\u00c8\u00c9\u00ca\u00cb\u00cc\u00cd\u00ce\u00cf\u00d0\u00d1\u00d2\u00d3\u00d4\u00d5\u00d6\u00d8\u00d9\u00da\u00db\u00dc\u00dd\u00de\u0100\u0102\u0104\u0106\u0108\u010a\u010c\u010e\u0110\u0112\u0114\u0116\u0118\u011a\u011c\u011e\u0120\u0122\u0124\u0126\u0128\u012a\u012c\u012e\u0130\u0132\u0134\u0136\u0139\u013b\u013d\u013f\u0141\u0143\u0145\u0147\u014a\u014c\u014e\u0150\u0152\u0154\u0156\u0158\u015a\u015c\u015e\u0160\u0162\u0164\u0166\u0168\u016a\u016c\u016e\u0170\u0172\u0174\u0176\u0178\u0179\u017b\u017d\u0181\u0182\u0184\u0186\u0187\u0189\u018a\u018b\u018e\u018f\u0190\u0191\u0193\u0194\u0196\u0197\u0198\u019c\u019d\u019f\u01a0\u01a2\u01a4\u01a6\u01a7\u01a9\u01ac\u01ae\u01af\u01b1\u01b2\u01b3\u01b5\u01b7\u01b8\u01bc\u01c4\u01c7\u01ca\u01cd\u01cf\u01d1\u01d3\u01d5\u01d7\u01d9\u01db\u01de\u01e0\u01e2\u01e4\u01e6\u01e8\u01ea\u01ec\u01ee\u01f1\u01f4\u01f6\u01f7\u01f8\u01fa\u01fc\u01fe\u0200\u0202\u0204\u0206\u0208\u020a\u020c\u020e\u0210\u0212\u0214\u0216\u0218\u021a\u021c\u021e\u0220\u0222\u0224\u0226\u0228\u022a\u022c\u022e\u0230\u0232\u0386\u0388\u0389\u038a\u038c\u038e\u038f\u0391\u0392\u0393\u0394\u0395\u0396\u0397\u0398\u0399\u039a\u039b\u039c\u039d\u039e\u039f\u03a0\u03a1\u03a3\u03a4\u03a5\u03a6\u03a7\u03a8\u03a9\u03aa\u03ab\u03d2\u03d3\u03d4\u03d8\u03da\u03dc\u03de\u03e0\u03e2\u03e4\u03e6\u03e8\u03ea\u03ec\u03ee\u03f4\u0400\u0401\u0402\u0403\u0404\u0405\u0406\u0407\u0408\u0409\u040a\u040b\u040c\u040d\u040e\u040f\u0410\u0411\u0412\u0413\u0414\u0415\u0416\u0417\u0418\u0419\u041a\u041b\u041c\u041d\u041e\u041f\u0420\u0421\u0422\u0423\u0424\u0425\u0426\u0427\u0428\u0429\u042a\u042b\u042c\u042d\u042e\u042f\u0460\u0462\u0464\u0466\u0468\u046a\u046c\u046e\u0470\u0472\u0474\u0476\u0478\u047a\u047c\u047e\u0480\u048a\u048c\u048e\u0490\u0492\u0494\u0496\u0498\u049a\u049c\u049e\u04a0\u04a2\u04a4\u04a6\u04a8\u04aa\u04ac\u04ae\u04b0\u04b2\u04b4\u04b6\u04b8\u04ba\u04bc\u04be\u04c0\u04c1\u04c3\u04c5\u04c7\u04c9\u04cb\u04cd\u04d0\u04d2\u04d4\u04d6\u04d8\u04da\u04dc\u04de\u04e0\u04e2\u04e4\u04e6\u04e8\u04ea\u04ec\u04ee\u04f0\u04f2\u04f4\u04f8\u0500\u0502\u0504\u0506\u0508\u050a\u050c\u050e\u0531\u0532\u0533\u0534\u0535\u0536\u0537\u0538\u0539\u053a\u053b\u053c\u053d\u053e\u053f\u0540\u0541\u0542\u0543\u0544\u0545\u0546\u0547\u0548\u0549\u054a\u054b\u054c\u054d\u054e\u054f\u0550\u0551\u0552\u0553\u0554\u0555\u0556\u10a0\u10a1\u10a2\u10a3\u10a4\u10a5\u10a6\u10a7\u10a8\u10a9\u10aa\u10ab\u10ac\u10ad\u10ae\u10af\u10b0\u10b1\u10b2\u10b3\u10b4\u10b5\u10b6\u10b7\u10b8\u10b9\u10ba\u10bb\u10bc\u10bd\u10be\u10bf\u10c0\u10c1\u10c2\u10c3\u10c4\u10c5\u1e00\u1e02\u1e04\u1e06\u1e08\u1e0a\u1e0c\u1e0e\u1e10\u1e12\u1e14\u1e16\u1e18\u1e1a\u1e1c\u1e1e\u1e20\u1e22\u1e24\u1e26\u1e28\u1e2a\u1e2c\u1e2e\u1e30\u1e32\u1e34\u1e36\u1e38\u1e3a\u1e3c\u1e3e\u1e40\u1e42\u1e44\u1e46\u1e48\u1e4a\u1e4c\u1e4e\u1e50\u1e52\u1e54\u1e56\u1e58\u1e5a\u1e5c\u1e5e\u1e60\u1e62\u1e64\u1e66\u1e68\u1e6a\u1e6c\u1e6e\u1e70\u1e72\u1e74\u1e76\u1e78\u1e7a\u1e7c\u1e7e\u1e80\u1e82\u1e84\u1e86\u1e88\u1e8a\u1e8c\u1e8e\u1e90\u1e92\u1e94\u1ea0\u1ea2\u1ea4\u1ea6\u1ea8\u1eaa\u1eac\u1eae\u1eb0\u1eb2\u1eb4\u1eb6\u1eb8\u1eba\u1ebc\u1ebe\u1ec0\u1ec2\u1ec4\u1ec6\u1ec8\u1eca\u1ecc\u1ece\u1ed0\u1ed2\u1ed4\u1ed6\u1ed8\u1eda\u1edc\u1ede\u1ee0\u1ee2\u1ee4\u1ee6\u1ee8\u1eea\u1eec\u1eee\u1ef0\u1ef2\u1ef4\u1ef6\u1ef8\u1f08\u1f09\u1f0a\u1f0b\u1f0c\u1f0d\u1f0e\u1f0f\u1f18\u1f19\u1f1a\u1f1b\u1f1c\u1f1d\u1f28\u1f29\u1f2a\u1f2b\u1f2c\u1f2d\u1f2e\u1f2f\u1f38\u1f39\u1f3a\u1f3b\u1f3c\u1f3d\u1f3e\u1f3f\u1f48\u1f49\u1f4a\u1f4b\u1f4c\u1f4d\u1f59\u1f5b\u1f5d\u1f5f\u1f68\u1f69\u1f6a\u1f6b\u1f6c\u1f6d\u1f6e\u1f6f\u1fb8\u1fb9\u1fba\u1fbb\u1fc8\u1fc9\u1fca\u1fcb\u1fd8\u1fd9\u1fda\u1fdb\u1fe8\u1fe9\u1fea\u1feb\u1fec\u1ff8\u1ff9\u1ffa\u1ffb\u2102\u2107\u210b\u210c\u210d\u2110\u2111\u2112\u2115\u2119\u211a\u211b\u211c\u211d\u2124\u2126\u2128\u212a\u212b\u212c\u212d\u2130\u2131\u2133\u213e\u213f\u2145\uff21\uff22\uff23\uff24\uff25\uff26\uff27\uff28\uff29\uff2a\uff2b\uff2c\uff2d\uff2e\uff2f\uff30\uff31\uff32\uff33\uff34\uff35\uff36\uff37\uff38\uff39\uff3a"
 
-chars_lower = u"\u0061\u0062\u0063\u0064\u0065\u0066\u0067\u0068\u0069\u006a\u006b\u006c\u006d\u006e\u006f\u0070\u0071\u0072\u0073\u0074\u0075\u0076\u0077\u0078\u0079\u007a\u00aa\u00b5\u00ba\u00df\u00e0\u00e1\u00e2\u00e3\u00e4\u00e5\u00e6\u00e7\u00e8\u00e9\u00ea\u00eb\u00ec\u00ed\u00ee\u00ef\u00f0\u00f1\u00f2\u00f3\u00f4\u00f5\u00f6\u00f8\u00f9\u00fa\u00fb\u00fc\u00fd\u00fe\u00ff\u0101\u0103\u0105\u0107\u0109\u010b\u010d\u010f\u0111\u0113\u0115\u0117\u0119\u011b\u011d\u011f\u0121\u0123\u0125\u0127\u0129\u012b\u012d\u012f\u0131\u0133\u0135\u0137\u0138\u013a\u013c\u013e\u0140\u0142\u0144\u0146\u0148\u0149\u014b\u014d\u014f\u0151\u0153\u0155\u0157\u0159\u015b\u015d\u015f\u0161\u0163\u0165\u0167\u0169\u016b\u016d\u016f\u0171\u0173\u0175\u0177\u017a\u017c\u017e\u017f\u0180\u0183\u0185\u0188\u018c\u018d\u0192\u0195\u0199\u019a\u019b\u019e\u01a1\u01a3\u01a5\u01a8\u01aa\u01ab\u01ad\u01b0\u01b4\u01b6\u01b9\u01ba\u01bd\u01be\u01bf\u01c6\u01c9\u01cc\u01ce\u01d0\u01d2\u01d4\u01d6\u01d8\u01da\u01dc\u01dd\u01df\u01e1\u01e3\u01e5\u01e7\u01e9\u01eb\u01ed\u01ef\u01f0\u01f3\u01f5\u01f9\u01fb\u01fd\u01ff\u0201\u0203\u0205\u0207\u0209\u020b\u020d\u020f\u0211\u0213\u0215\u0217\u0219\u021b\u021d\u021f\u0223\u0225\u0227\u0229\u022b\u022d\u022f\u0231\u0233\u0250\u0251\u0252\u0253\u0254\u0255\u0256\u0257\u0258\u0259\u025a\u025b\u025c\u025d\u025e\u025f\u0260\u0261\u0262\u0263\u0264\u0265\u0266\u0267\u0268\u0269\u026a\u026b\u026c\u026d\u026e\u026f\u0270\u0271\u0272\u0273\u0274\u0275\u0276\u0277\u0278\u0279\u027a\u027b\u027c\u027d\u027e\u027f\u0280\u0281\u0282\u0283\u0284\u0285\u0286\u0287\u0288\u0289\u028a\u028b\u028c\u028d\u028e\u028f\u0290\u0291\u0292\u0293\u0294\u0295\u0296\u0297\u0298\u0299\u029a\u029b\u029c\u029d\u029e\u029f\u02a0\u02a1\u02a2\u02a3\u02a4\u02a5\u02a6\u02a7\u02a8\u02a9\u02aa\u02ab\u02ac\u02ad\u0390\u03ac\u03ad\u03ae\u03af\u03b0\u03b1\u03b2\u03b3\u03b4\u03b5\u03b6\u03b7\u03b8\u03b9\u03ba\u03bb\u03bc\u03bd\u03be\u03bf\u03c0\u03c1\u03c2\u03c3\u03c4\u03c5\u03c6\u03c7\u03c8\u03c9\u03ca\u03cb\u03cc\u03cd\u03ce\u03d0\u03d1\u03d5\u03d6\u03d7\u03d9\u03db\u03dd\u03df\u03e1\u03e3\u03e5\u03e7\u03e9\u03eb\u03ed\u03ef\u03f0\u03f1\u03f2\u03f3\u03f5\u0430\u0431\u0432\u0433\u0434\u0435\u0436\u0437\u0438\u0439\u043a\u043b\u043c\u043d\u043e\u043f\u0440\u0441\u0442\u0443\u0444\u0445\u0446\u0447\u0448\u0449\u044a\u044b\u044c\u044d\u044e\u044f\u0450\u0451\u0452\u0453\u0454\u0455\u0456\u0457\u0458\u0459\u045a\u045b\u045c\u045d\u045e\u045f\u0461\u0463\u0465\u0467\u0469\u046b\u046d\u046f\u0471\u0473\u0475\u0477\u0479\u047b\u047d\u047f\u0481\u048b\u048d\u048f\u0491\u0493\u0495\u0497\u0499\u049b\u049d\u049f\u04a1\u04a3\u04a5\u04a7\u04a9\u04ab\u04ad\u04af\u04b1\u04b3\u04b5\u04b7\u04b9\u04bb\u04bd\u04bf\u04c2\u04c4\u04c6\u04c8\u04ca\u04cc\u04ce\u04d1\u04d3\u04d5\u04d7\u04d9\u04db\u04dd\u04df\u04e1\u04e3\u04e5\u04e7\u04e9\u04eb\u04ed\u04ef\u04f1\u04f3\u04f5\u04f9\u0501\u0503\u0505\u0507\u0509\u050b\u050d\u050f\u0561\u0562\u0563\u0564\u0565\u0566\u0567\u0568\u0569\u056a\u056b\u056c\u056d\u056e\u056f\u0570\u0571\u0572\u0573\u0574\u0575\u0576\u0577\u0578\u0579\u057a\u057b\u057c\u057d\u057e\u057f\u0580\u0581\u0582\u0583\u0584\u0585\u0586\u0587\u1e01\u1e03\u1e05\u1e07\u1e09\u1e0b\u1e0d\u1e0f\u1e11\u1e13\u1e15\u1e17\u1e19\u1e1b\u1e1d\u1e1f\u1e21\u1e23\u1e25\u1e27\u1e29\u1e2b\u1e2d\u1e2f\u1e31\u1e33\u1e35\u1e37\u1e39\u1e3b\u1e3d\u1e3f\u1e41\u1e43\u1e45\u1e47\u1e49\u1e4b\u1e4d\u1e4f\u1e51\u1e53\u1e55\u1e57\u1e59\u1e5b\u1e5d\u1e5f\u1e61\u1e63\u1e65\u1e67\u1e69\u1e6b\u1e6d\u1e6f\u1e71\u1e73\u1e75\u1e77\u1e79\u1e7b\u1e7d\u1e7f\u1e81\u1e83\u1e85\u1e87\u1e89\u1e8b\u1e8d\u1e8f\u1e91\u1e93\u1e95\u1e96\u1e97\u1e98\u1e99\u1e9a\u1e9b\u1ea1\u1ea3\u1ea5\u1ea7\u1ea9\u1eab\u1ead\u1eaf\u1eb1\u1eb3\u1eb5\u1eb7\u1eb9\u1ebb\u1ebd\u1ebf\u1ec1\u1ec3\u1ec5\u1ec7\u1ec9\u1ecb\u1ecd\u1ecf\u1ed1\u1ed3\u1ed5\u1ed7\u1ed9\u1edb\u1edd\u1edf\u1ee1\u1ee3\u1ee5\u1ee7\u1ee9\u1eeb\u1eed\u1eef\u1ef1\u1ef3\u1ef5\u1ef7\u1ef9\u1f00\u1f01\u1f02\u1f03\u1f04\u1f05\u1f06\u1f07\u1f10\u1f11\u1f12\u1f13\u1f14\u1f15\u1f20\u1f21\u1f22\u1f23\u1f24\u1f25\u1f26\u1f27\u1f30\u1f31\u1f32\u1f33\u1f34\u1f35\u1f36\u1f37\u1f40\u1f41\u1f42\u1f43\u1f44\u1f45\u1f50\u1f51\u1f52\u1f53\u1f54\u1f55\u1f56\u1f57\u1f60\u1f61\u1f62\u1f63\u1f64\u1f65\u1f66\u1f67\u1f70\u1f71\u1f72\u1f73\u1f74\u1f75\u1f76\u1f77\u1f78\u1f79\u1f7a\u1f7b\u1f7c\u1f7d\u1f80\u1f81\u1f82\u1f83\u1f84\u1f85\u1f86\u1f87\u1f90\u1f91\u1f92\u1f93\u1f94\u1f95\u1f96\u1f97\u1fa0\u1fa1\u1fa2\u1fa3\u1fa4\u1fa5\u1fa6\u1fa7\u1fb0\u1fb1\u1fb2\u1fb3\u1fb4\u1fb6\u1fb7\u1fbe\u1fc2\u1fc3\u1fc4\u1fc6\u1fc7\u1fd0\u1fd1\u1fd2\u1fd3\u1fd6\u1fd7\u1fe0\u1fe1\u1fe2\u1fe3\u1fe4\u1fe5\u1fe6\u1fe7\u1ff2\u1ff3\u1ff4\u1ff6\u1ff7\u2071\u207f\u210a\u210e\u210f\u2113\u212f\u2134\u2139\u213d\u2146\u2147\u2148\u2149\ufb00\ufb01\ufb02\ufb03\ufb04\ufb05\ufb06\ufb13\ufb14\ufb15\ufb16\ufb17\uff41\uff42\uff43\uff44\uff45\uff46\uff47\uff48\uff49\uff4a\uff4b\uff4c\uff4d\uff4e\uff4f\uff50\uff51\uff52\uff53\uff54\uff55\uff56\uff57\uff58\uff59\uff5a\u0030\u0031\u0032\u0033\u0034\u0035\u0036\u0037\u0038\u0039\u00b2\u00b3\u00b9\u0660\u0661\u0662\u0663\u0664\u0665\u0666\u0667\u0668\u0669\u06f0\u06f1\u06f2\u06f3\u06f4\u06f5\u06f6\u06f7\u06f8\u06f9\u0966\u0967\u0968\u0969\u096a\u096b\u096c\u096d\u096e\u096f\u09e6\u09e7\u09e8\u09e9\u09ea\u09eb\u09ec\u09ed\u09ee\u09ef\u0a66\u0a67\u0a68\u0a69\u0a6a\u0a6b\u0a6c\u0a6d\u0a6e\u0a6f\u0ae6\u0ae7\u0ae8\u0ae9\u0aea\u0aeb\u0aec\u0aed\u0aee\u0aef\u0b66\u0b67\u0b68\u0b69\u0b6a\u0b6b\u0b6c\u0b6d\u0b6e\u0b6f\u0be7\u0be8\u0be9\u0bea\u0beb\u0bec\u0bed\u0bee\u0bef\u0c66\u0c67\u0c68\u0c69\u0c6a\u0c6b\u0c6c\u0c6d\u0c6e\u0c6f\u0ce6\u0ce7\u0ce8\u0ce9\u0cea\u0ceb\u0cec\u0ced\u0cee\u0cef\u0d66\u0d67\u0d68\u0d69\u0d6a\u0d6b\u0d6c\u0d6d\u0d6e\u0d6f\u0e50\u0e51\u0e52\u0e53\u0e54\u0e55\u0e56\u0e57\u0e58\u0e59\u0ed0\u0ed1\u0ed2\u0ed3\u0ed4\u0ed5\u0ed6\u0ed7\u0ed8\u0ed9\u0f20\u0f21\u0f22\u0f23\u0f24\u0f25\u0f26\u0f27\u0f28\u0f29\u1040\u1041\u1042\u1043\u1044\u1045\u1046\u1047\u1048\u1049\u1369\u136a\u136b\u136c\u136d\u136e\u136f\u1370\u1371\u17e0\u17e1\u17e2\u17e3\u17e4\u17e5\u17e6\u17e7\u17e8\u17e9\u1810\u1811\u1812\u1813\u1814\u1815\u1816\u1817\u1818\u1819\u2070\u2074\u2075\u2076\u2077\u2078\u2079\u2080\u2081\u2082\u2083\u2084\u2085\u2086\u2087\u2088\u2089\u2460\u2461\u2462\u2463\u2464\u2465\u2466\u2467\u2468\u2474\u2475\u2476\u2477\u2478\u2479\u247a\u247b\u247c\u2488\u2489\u248a\u248b\u248c\u248d\u248e\u248f\u2490\u24ea\u24f5\u24f6\u24f7\u24f8\u24f9\u24fa\u24fb\u24fc\u24fd\u2776\u2777\u2778\u2779\u277a\u277b\u277c\u277d\u277e\u2780\u2781\u2782\u2783\u2784\u2785\u2786\u2787\u2788\u278a\u278b\u278c\u278d\u278e\u278f\u2790\u2791\u2792\uff10\uff11\uff12\uff13\uff14\uff15\uff16\uff17\uff18\uff19"
+CHARS_UPPER = u"\u0041\u0042\u0043\u0044\u0045\u0046\u0047\u0048\u0049\u004a\u004b\u004c\u004d\u004e\u004f\u0050\u0051\u0052\u0053\u0054\u0055\u0056\u0057\u0058\u0059\u005a\u00c0\u00c1\u00c2\u00c3\u00c4\u00c5\u00c6\u00c7\u00c8\u00c9\u00ca\u00cb\u00cc\u00cd\u00ce\u00cf\u00d0\u00d1\u00d2\u00d3\u00d4\u00d5\u00d6\u00d8\u00d9\u00da\u00db\u00dc\u00dd\u00de\u0100\u0102\u0104\u0106\u0108\u010a\u010c\u010e\u0110\u0112\u0114\u0116\u0118\u011a\u011c\u011e\u0120\u0122\u0124\u0126\u0128\u012a\u012c\u012e\u0130\u0132\u0134\u0136\u0139\u013b\u013d\u013f\u0141\u0143\u0145\u0147\u014a\u014c\u014e\u0150\u0152\u0154\u0156\u0158\u015a\u015c\u015e\u0160\u0162\u0164\u0166\u0168\u016a\u016c\u016e\u0170\u0172\u0174\u0176\u0178\u0179\u017b\u017d\u0181\u0182\u0184\u0186\u0187\u0189\u018a\u018b\u018e\u018f\u0190\u0191\u0193\u0194\u0196\u0197\u0198\u019c\u019d\u019f\u01a0\u01a2\u01a4\u01a6\u01a7\u01a9\u01ac\u01ae\u01af\u01b1\u01b2\u01b3\u01b5\u01b7\u01b8\u01bc\u01c4\u01c7\u01ca\u01cd\u01cf\u01d1\u01d3\u01d5\u01d7\u01d9\u01db\u01de\u01e0\u01e2\u01e4\u01e6\u01e8\u01ea\u01ec\u01ee\u01f1\u01f4\u01f6\u01f7\u01f8\u01fa\u01fc\u01fe\u0200\u0202\u0204\u0206\u0208\u020a\u020c\u020e\u0210\u0212\u0214\u0216\u0218\u021a\u021c\u021e\u0220\u0222\u0224\u0226\u0228\u022a\u022c\u022e\u0230\u0232\u0386\u0388\u0389\u038a\u038c\u038e\u038f\u0391\u0392\u0393\u0394\u0395\u0396\u0397\u0398\u0399\u039a\u039b\u039c\u039d\u039e\u039f\u03a0\u03a1\u03a3\u03a4\u03a5\u03a6\u03a7\u03a8\u03a9\u03aa\u03ab\u03d2\u03d3\u03d4\u03d8\u03da\u03dc\u03de\u03e0\u03e2\u03e4\u03e6\u03e8\u03ea\u03ec\u03ee\u03f4\u0400\u0401\u0402\u0403\u0404\u0405\u0406\u0407\u0408\u0409\u040a\u040b\u040c\u040d\u040e\u040f\u0410\u0411\u0412\u0413\u0414\u0415\u0416\u0417\u0418\u0419\u041a\u041b\u041c\u041d\u041e\u041f\u0420\u0421\u0422\u0423\u0424\u0425\u0426\u0427\u0428\u0429\u042a\u042b\u042c\u042d\u042e\u042f\u0460\u0462\u0464\u0466\u0468\u046a\u046c\u046e\u0470\u0472\u0474\u0476\u0478\u047a\u047c\u047e\u0480\u048a\u048c\u048e\u0490\u0492\u0494\u0496\u0498\u049a\u049c\u049e\u04a0\u04a2\u04a4\u04a6\u04a8\u04aa\u04ac\u04ae\u04b0\u04b2\u04b4\u04b6\u04b8\u04ba\u04bc\u04be\u04c0\u04c1\u04c3\u04c5\u04c7\u04c9\u04cb\u04cd\u04d0\u04d2\u04d4\u04d6\u04d8\u04da\u04dc\u04de\u04e0\u04e2\u04e4\u04e6\u04e8\u04ea\u04ec\u04ee\u04f0\u04f2\u04f4\u04f8\u0500\u0502\u0504\u0506\u0508\u050a\u050c\u050e\u0531\u0532\u0533\u0534\u0535\u0536\u0537\u0538\u0539\u053a\u053b\u053c\u053d\u053e\u053f\u0540\u0541\u0542\u0543\u0544\u0545\u0546\u0547\u0548\u0549\u054a\u054b\u054c\u054d\u054e\u054f\u0550\u0551\u0552\u0553\u0554\u0555\u0556\u10a0\u10a1\u10a2\u10a3\u10a4\u10a5\u10a6\u10a7\u10a8\u10a9\u10aa\u10ab\u10ac\u10ad\u10ae\u10af\u10b0\u10b1\u10b2\u10b3\u10b4\u10b5\u10b6\u10b7\u10b8\u10b9\u10ba\u10bb\u10bc\u10bd\u10be\u10bf\u10c0\u10c1\u10c2\u10c3\u10c4\u10c5\u1e00\u1e02\u1e04\u1e06\u1e08\u1e0a\u1e0c\u1e0e\u1e10\u1e12\u1e14\u1e16\u1e18\u1e1a\u1e1c\u1e1e\u1e20\u1e22\u1e24\u1e26\u1e28\u1e2a\u1e2c\u1e2e\u1e30\u1e32\u1e34\u1e36\u1e38\u1e3a\u1e3c\u1e3e\u1e40\u1e42\u1e44\u1e46\u1e48\u1e4a\u1e4c\u1e4e\u1e50\u1e52\u1e54\u1e56\u1e58\u1e5a\u1e5c\u1e5e\u1e60\u1e62\u1e64\u1e66\u1e68\u1e6a\u1e6c\u1e6e\u1e70\u1e72\u1e74\u1e76\u1e78\u1e7a\u1e7c\u1e7e\u1e80\u1e82\u1e84\u1e86\u1e88\u1e8a\u1e8c\u1e8e\u1e90\u1e92\u1e94\u1ea0\u1ea2\u1ea4\u1ea6\u1ea8\u1eaa\u1eac\u1eae\u1eb0\u1eb2\u1eb4\u1eb6\u1eb8\u1eba\u1ebc\u1ebe\u1ec0\u1ec2\u1ec4\u1ec6\u1ec8\u1eca\u1ecc\u1ece\u1ed0\u1ed2\u1ed4\u1ed6\u1ed8\u1eda\u1edc\u1ede\u1ee0\u1ee2\u1ee4\u1ee6\u1ee8\u1eea\u1eec\u1eee\u1ef0\u1ef2\u1ef4\u1ef6\u1ef8\u1f08\u1f09\u1f0a\u1f0b\u1f0c\u1f0d\u1f0e\u1f0f\u1f18\u1f19\u1f1a\u1f1b\u1f1c\u1f1d\u1f28\u1f29\u1f2a\u1f2b\u1f2c\u1f2d\u1f2e\u1f2f\u1f38\u1f39\u1f3a\u1f3b\u1f3c\u1f3d\u1f3e\u1f3f\u1f48\u1f49\u1f4a\u1f4b\u1f4c\u1f4d\u1f59\u1f5b\u1f5d\u1f5f\u1f68\u1f69\u1f6a\u1f6b\u1f6c\u1f6d\u1f6e\u1f6f\u1fb8\u1fb9\u1fba\u1fbb\u1fc8\u1fc9\u1fca\u1fcb\u1fd8\u1fd9\u1fda\u1fdb\u1fe8\u1fe9\u1fea\u1feb\u1fec\u1ff8\u1ff9\u1ffa\u1ffb\u2102\u2107\u210b\u210c\u210d\u2110\u2111\u2112\u2115\u2119\u211a\u211b\u211c\u211d\u2124\u2126\u2128\u212a\u212b\u212c\u212d\u2130\u2131\u2133\u213e\u213f\u2145\uff21\uff22\uff23\uff24\uff25\uff26\uff27\uff28\uff29\uff2a\uff2b\uff2c\uff2d\uff2e\uff2f\uff30\uff31\uff32\uff33\uff34\uff35\uff36\uff37\uff38\uff39\uff3a"
 
-chars_digits = u"\u0030\u0031\u0032\u0033\u0034\u0035\u0036\u0037\u0038\u0039\u00b2\u00b3\u00b9\u0660\u0661\u0662\u0663\u0664\u0665\u0666\u0667\u0668\u0669\u06f0\u06f1\u06f2\u06f3\u06f4\u06f5\u06f6\u06f7\u06f8\u06f9\u0966\u0967\u0968\u0969\u096a\u096b\u096c\u096d\u096e\u096f\u09e6\u09e7\u09e8\u09e9\u09ea\u09eb\u09ec\u09ed\u09ee\u09ef\u0a66\u0a67\u0a68\u0a69\u0a6a\u0a6b\u0a6c\u0a6d\u0a6e\u0a6f\u0ae6\u0ae7\u0ae8\u0ae9\u0aea\u0aeb\u0aec\u0aed\u0aee\u0aef\u0b66\u0b67\u0b68\u0b69\u0b6a\u0b6b\u0b6c\u0b6d\u0b6e\u0b6f\u0be7\u0be8\u0be9\u0bea\u0beb\u0bec\u0bed\u0bee\u0bef\u0c66\u0c67\u0c68\u0c69\u0c6a\u0c6b\u0c6c\u0c6d\u0c6e\u0c6f\u0ce6\u0ce7\u0ce8\u0ce9\u0cea\u0ceb\u0cec\u0ced\u0cee\u0cef\u0d66\u0d67\u0d68\u0d69\u0d6a\u0d6b\u0d6c\u0d6d\u0d6e\u0d6f\u0e50\u0e51\u0e52\u0e53\u0e54\u0e55\u0e56\u0e57\u0e58\u0e59\u0ed0\u0ed1\u0ed2\u0ed3\u0ed4\u0ed5\u0ed6\u0ed7\u0ed8\u0ed9\u0f20\u0f21\u0f22\u0f23\u0f24\u0f25\u0f26\u0f27\u0f28\u0f29\u1040\u1041\u1042\u1043\u1044\u1045\u1046\u1047\u1048\u1049\u1369\u136a\u136b\u136c\u136d\u136e\u136f\u1370\u1371\u17e0\u17e1\u17e2\u17e3\u17e4\u17e5\u17e6\u17e7\u17e8\u17e9\u1810\u1811\u1812\u1813\u1814\u1815\u1816\u1817\u1818\u1819\u2070\u2074\u2075\u2076\u2077\u2078\u2079\u2080\u2081\u2082\u2083\u2084\u2085\u2086\u2087\u2088\u2089\u2460\u2461\u2462\u2463\u2464\u2465\u2466\u2467\u2468\u2474\u2475\u2476\u2477\u2478\u2479\u247a\u247b\u247c\u2488\u2489\u248a\u248b\u248c\u248d\u248e\u248f\u2490\u24ea\u24f5\u24f6\u24f7\u24f8\u24f9\u24fa\u24fb\u24fc\u24fd\u2776\u2777\u2778\u2779\u277a\u277b\u277c\u277d\u277e\u2780\u2781\u2782\u2783\u2784\u2785\u2786\u2787\u2788\u278a\u278b\u278c\u278d\u278e\u278f\u2790\u2791\u2792\uff10\uff11\uff12\uff13\uff14\uff15\uff16\uff17\uff18\uff19"
+CHARS_LOWER = u"\u0061\u0062\u0063\u0064\u0065\u0066\u0067\u0068\u0069\u006a\u006b\u006c\u006d\u006e\u006f\u0070\u0071\u0072\u0073\u0074\u0075\u0076\u0077\u0078\u0079\u007a\u00aa\u00b5\u00ba\u00df\u00e0\u00e1\u00e2\u00e3\u00e4\u00e5\u00e6\u00e7\u00e8\u00e9\u00ea\u00eb\u00ec\u00ed\u00ee\u00ef\u00f0\u00f1\u00f2\u00f3\u00f4\u00f5\u00f6\u00f8\u00f9\u00fa\u00fb\u00fc\u00fd\u00fe\u00ff\u0101\u0103\u0105\u0107\u0109\u010b\u010d\u010f\u0111\u0113\u0115\u0117\u0119\u011b\u011d\u011f\u0121\u0123\u0125\u0127\u0129\u012b\u012d\u012f\u0131\u0133\u0135\u0137\u0138\u013a\u013c\u013e\u0140\u0142\u0144\u0146\u0148\u0149\u014b\u014d\u014f\u0151\u0153\u0155\u0157\u0159\u015b\u015d\u015f\u0161\u0163\u0165\u0167\u0169\u016b\u016d\u016f\u0171\u0173\u0175\u0177\u017a\u017c\u017e\u017f\u0180\u0183\u0185\u0188\u018c\u018d\u0192\u0195\u0199\u019a\u019b\u019e\u01a1\u01a3\u01a5\u01a8\u01aa\u01ab\u01ad\u01b0\u01b4\u01b6\u01b9\u01ba\u01bd\u01be\u01bf\u01c6\u01c9\u01cc\u01ce\u01d0\u01d2\u01d4\u01d6\u01d8\u01da\u01dc\u01dd\u01df\u01e1\u01e3\u01e5\u01e7\u01e9\u01eb\u01ed\u01ef\u01f0\u01f3\u01f5\u01f9\u01fb\u01fd\u01ff\u0201\u0203\u0205\u0207\u0209\u020b\u020d\u020f\u0211\u0213\u0215\u0217\u0219\u021b\u021d\u021f\u0223\u0225\u0227\u0229\u022b\u022d\u022f\u0231\u0233\u0250\u0251\u0252\u0253\u0254\u0255\u0256\u0257\u0258\u0259\u025a\u025b\u025c\u025d\u025e\u025f\u0260\u0261\u0262\u0263\u0264\u0265\u0266\u0267\u0268\u0269\u026a\u026b\u026c\u026d\u026e\u026f\u0270\u0271\u0272\u0273\u0274\u0275\u0276\u0277\u0278\u0279\u027a\u027b\u027c\u027d\u027e\u027f\u0280\u0281\u0282\u0283\u0284\u0285\u0286\u0287\u0288\u0289\u028a\u028b\u028c\u028d\u028e\u028f\u0290\u0291\u0292\u0293\u0294\u0295\u0296\u0297\u0298\u0299\u029a\u029b\u029c\u029d\u029e\u029f\u02a0\u02a1\u02a2\u02a3\u02a4\u02a5\u02a6\u02a7\u02a8\u02a9\u02aa\u02ab\u02ac\u02ad\u0390\u03ac\u03ad\u03ae\u03af\u03b0\u03b1\u03b2\u03b3\u03b4\u03b5\u03b6\u03b7\u03b8\u03b9\u03ba\u03bb\u03bc\u03bd\u03be\u03bf\u03c0\u03c1\u03c2\u03c3\u03c4\u03c5\u03c6\u03c7\u03c8\u03c9\u03ca\u03cb\u03cc\u03cd\u03ce\u03d0\u03d1\u03d5\u03d6\u03d7\u03d9\u03db\u03dd\u03df\u03e1\u03e3\u03e5\u03e7\u03e9\u03eb\u03ed\u03ef\u03f0\u03f1\u03f2\u03f3\u03f5\u0430\u0431\u0432\u0433\u0434\u0435\u0436\u0437\u0438\u0439\u043a\u043b\u043c\u043d\u043e\u043f\u0440\u0441\u0442\u0443\u0444\u0445\u0446\u0447\u0448\u0449\u044a\u044b\u044c\u044d\u044e\u044f\u0450\u0451\u0452\u0453\u0454\u0455\u0456\u0457\u0458\u0459\u045a\u045b\u045c\u045d\u045e\u045f\u0461\u0463\u0465\u0467\u0469\u046b\u046d\u046f\u0471\u0473\u0475\u0477\u0479\u047b\u047d\u047f\u0481\u048b\u048d\u048f\u0491\u0493\u0495\u0497\u0499\u049b\u049d\u049f\u04a1\u04a3\u04a5\u04a7\u04a9\u04ab\u04ad\u04af\u04b1\u04b3\u04b5\u04b7\u04b9\u04bb\u04bd\u04bf\u04c2\u04c4\u04c6\u04c8\u04ca\u04cc\u04ce\u04d1\u04d3\u04d5\u04d7\u04d9\u04db\u04dd\u04df\u04e1\u04e3\u04e5\u04e7\u04e9\u04eb\u04ed\u04ef\u04f1\u04f3\u04f5\u04f9\u0501\u0503\u0505\u0507\u0509\u050b\u050d\u050f\u0561\u0562\u0563\u0564\u0565\u0566\u0567\u0568\u0569\u056a\u056b\u056c\u056d\u056e\u056f\u0570\u0571\u0572\u0573\u0574\u0575\u0576\u0577\u0578\u0579\u057a\u057b\u057c\u057d\u057e\u057f\u0580\u0581\u0582\u0583\u0584\u0585\u0586\u0587\u1e01\u1e03\u1e05\u1e07\u1e09\u1e0b\u1e0d\u1e0f\u1e11\u1e13\u1e15\u1e17\u1e19\u1e1b\u1e1d\u1e1f\u1e21\u1e23\u1e25\u1e27\u1e29\u1e2b\u1e2d\u1e2f\u1e31\u1e33\u1e35\u1e37\u1e39\u1e3b\u1e3d\u1e3f\u1e41\u1e43\u1e45\u1e47\u1e49\u1e4b\u1e4d\u1e4f\u1e51\u1e53\u1e55\u1e57\u1e59\u1e5b\u1e5d\u1e5f\u1e61\u1e63\u1e65\u1e67\u1e69\u1e6b\u1e6d\u1e6f\u1e71\u1e73\u1e75\u1e77\u1e79\u1e7b\u1e7d\u1e7f\u1e81\u1e83\u1e85\u1e87\u1e89\u1e8b\u1e8d\u1e8f\u1e91\u1e93\u1e95\u1e96\u1e97\u1e98\u1e99\u1e9a\u1e9b\u1ea1\u1ea3\u1ea5\u1ea7\u1ea9\u1eab\u1ead\u1eaf\u1eb1\u1eb3\u1eb5\u1eb7\u1eb9\u1ebb\u1ebd\u1ebf\u1ec1\u1ec3\u1ec5\u1ec7\u1ec9\u1ecb\u1ecd\u1ecf\u1ed1\u1ed3\u1ed5\u1ed7\u1ed9\u1edb\u1edd\u1edf\u1ee1\u1ee3\u1ee5\u1ee7\u1ee9\u1eeb\u1eed\u1eef\u1ef1\u1ef3\u1ef5\u1ef7\u1ef9\u1f00\u1f01\u1f02\u1f03\u1f04\u1f05\u1f06\u1f07\u1f10\u1f11\u1f12\u1f13\u1f14\u1f15\u1f20\u1f21\u1f22\u1f23\u1f24\u1f25\u1f26\u1f27\u1f30\u1f31\u1f32\u1f33\u1f34\u1f35\u1f36\u1f37\u1f40\u1f41\u1f42\u1f43\u1f44\u1f45\u1f50\u1f51\u1f52\u1f53\u1f54\u1f55\u1f56\u1f57\u1f60\u1f61\u1f62\u1f63\u1f64\u1f65\u1f66\u1f67\u1f70\u1f71\u1f72\u1f73\u1f74\u1f75\u1f76\u1f77\u1f78\u1f79\u1f7a\u1f7b\u1f7c\u1f7d\u1f80\u1f81\u1f82\u1f83\u1f84\u1f85\u1f86\u1f87\u1f90\u1f91\u1f92\u1f93\u1f94\u1f95\u1f96\u1f97\u1fa0\u1fa1\u1fa2\u1fa3\u1fa4\u1fa5\u1fa6\u1fa7\u1fb0\u1fb1\u1fb2\u1fb3\u1fb4\u1fb6\u1fb7\u1fbe\u1fc2\u1fc3\u1fc4\u1fc6\u1fc7\u1fd0\u1fd1\u1fd2\u1fd3\u1fd6\u1fd7\u1fe0\u1fe1\u1fe2\u1fe3\u1fe4\u1fe5\u1fe6\u1fe7\u1ff2\u1ff3\u1ff4\u1ff6\u1ff7\u2071\u207f\u210a\u210e\u210f\u2113\u212f\u2134\u2139\u213d\u2146\u2147\u2148\u2149\ufb00\ufb01\ufb02\ufb03\ufb04\ufb05\ufb06\ufb13\ufb14\ufb15\ufb16\ufb17\uff41\uff42\uff43\uff44\uff45\uff46\uff47\uff48\uff49\uff4a\uff4b\uff4c\uff4d\uff4e\uff4f\uff50\uff51\uff52\uff53\uff54\uff55\uff56\uff57\uff58\uff59\uff5a\u0030\u0031\u0032\u0033\u0034\u0035\u0036\u0037\u0038\u0039\u00b2\u00b3\u00b9\u0660\u0661\u0662\u0663\u0664\u0665\u0666\u0667\u0668\u0669\u06f0\u06f1\u06f2\u06f3\u06f4\u06f5\u06f6\u06f7\u06f8\u06f9\u0966\u0967\u0968\u0969\u096a\u096b\u096c\u096d\u096e\u096f\u09e6\u09e7\u09e8\u09e9\u09ea\u09eb\u09ec\u09ed\u09ee\u09ef\u0a66\u0a67\u0a68\u0a69\u0a6a\u0a6b\u0a6c\u0a6d\u0a6e\u0a6f\u0ae6\u0ae7\u0ae8\u0ae9\u0aea\u0aeb\u0aec\u0aed\u0aee\u0aef\u0b66\u0b67\u0b68\u0b69\u0b6a\u0b6b\u0b6c\u0b6d\u0b6e\u0b6f\u0be7\u0be8\u0be9\u0bea\u0beb\u0bec\u0bed\u0bee\u0bef\u0c66\u0c67\u0c68\u0c69\u0c6a\u0c6b\u0c6c\u0c6d\u0c6e\u0c6f\u0ce6\u0ce7\u0ce8\u0ce9\u0cea\u0ceb\u0cec\u0ced\u0cee\u0cef\u0d66\u0d67\u0d68\u0d69\u0d6a\u0d6b\u0d6c\u0d6d\u0d6e\u0d6f\u0e50\u0e51\u0e52\u0e53\u0e54\u0e55\u0e56\u0e57\u0e58\u0e59\u0ed0\u0ed1\u0ed2\u0ed3\u0ed4\u0ed5\u0ed6\u0ed7\u0ed8\u0ed9\u0f20\u0f21\u0f22\u0f23\u0f24\u0f25\u0f26\u0f27\u0f28\u0f29\u1040\u1041\u1042\u1043\u1044\u1045\u1046\u1047\u1048\u1049\u1369\u136a\u136b\u136c\u136d\u136e\u136f\u1370\u1371\u17e0\u17e1\u17e2\u17e3\u17e4\u17e5\u17e6\u17e7\u17e8\u17e9\u1810\u1811\u1812\u1813\u1814\u1815\u1816\u1817\u1818\u1819\u2070\u2074\u2075\u2076\u2077\u2078\u2079\u2080\u2081\u2082\u2083\u2084\u2085\u2086\u2087\u2088\u2089\u2460\u2461\u2462\u2463\u2464\u2465\u2466\u2467\u2468\u2474\u2475\u2476\u2477\u2478\u2479\u247a\u247b\u247c\u2488\u2489\u248a\u248b\u248c\u248d\u248e\u248f\u2490\u24ea\u24f5\u24f6\u24f7\u24f8\u24f9\u24fa\u24fb\u24fc\u24fd\u2776\u2777\u2778\u2779\u277a\u277b\u277c\u277d\u277e\u2780\u2781\u2782\u2783\u2784\u2785\u2786\u2787\u2788\u278a\u278b\u278c\u278d\u278e\u278f\u2790\u2791\u2792\uff10\uff11\uff12\uff13\uff14\uff15\uff16\uff17\uff18\uff19"
 
-chars_spaces = u"\u0009\u000a\u000b\u000c\u000d\u001c\u001d\u001e\u001f\u0020\u0085\u00a0\u1680\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u2028\u2029\u202f\u205f\u3000"
+CHARS_DIGITS = u"\u0030\u0031\u0032\u0033\u0034\u0035\u0036\u0037\u0038\u0039\u00b2\u00b3\u00b9\u0660\u0661\u0662\u0663\u0664\u0665\u0666\u0667\u0668\u0669\u06f0\u06f1\u06f2\u06f3\u06f4\u06f5\u06f6\u06f7\u06f8\u06f9\u0966\u0967\u0968\u0969\u096a\u096b\u096c\u096d\u096e\u096f\u09e6\u09e7\u09e8\u09e9\u09ea\u09eb\u09ec\u09ed\u09ee\u09ef\u0a66\u0a67\u0a68\u0a69\u0a6a\u0a6b\u0a6c\u0a6d\u0a6e\u0a6f\u0ae6\u0ae7\u0ae8\u0ae9\u0aea\u0aeb\u0aec\u0aed\u0aee\u0aef\u0b66\u0b67\u0b68\u0b69\u0b6a\u0b6b\u0b6c\u0b6d\u0b6e\u0b6f\u0be7\u0be8\u0be9\u0bea\u0beb\u0bec\u0bed\u0bee\u0bef\u0c66\u0c67\u0c68\u0c69\u0c6a\u0c6b\u0c6c\u0c6d\u0c6e\u0c6f\u0ce6\u0ce7\u0ce8\u0ce9\u0cea\u0ceb\u0cec\u0ced\u0cee\u0cef\u0d66\u0d67\u0d68\u0d69\u0d6a\u0d6b\u0d6c\u0d6d\u0d6e\u0d6f\u0e50\u0e51\u0e52\u0e53\u0e54\u0e55\u0e56\u0e57\u0e58\u0e59\u0ed0\u0ed1\u0ed2\u0ed3\u0ed4\u0ed5\u0ed6\u0ed7\u0ed8\u0ed9\u0f20\u0f21\u0f22\u0f23\u0f24\u0f25\u0f26\u0f27\u0f28\u0f29\u1040\u1041\u1042\u1043\u1044\u1045\u1046\u1047\u1048\u1049\u1369\u136a\u136b\u136c\u136d\u136e\u136f\u1370\u1371\u17e0\u17e1\u17e2\u17e3\u17e4\u17e5\u17e6\u17e7\u17e8\u17e9\u1810\u1811\u1812\u1813\u1814\u1815\u1816\u1817\u1818\u1819\u2070\u2074\u2075\u2076\u2077\u2078\u2079\u2080\u2081\u2082\u2083\u2084\u2085\u2086\u2087\u2088\u2089\u2460\u2461\u2462\u2463\u2464\u2465\u2466\u2467\u2468\u2474\u2475\u2476\u2477\u2478\u2479\u247a\u247b\u247c\u2488\u2489\u248a\u248b\u248c\u248d\u248e\u248f\u2490\u24ea\u24f5\u24f6\u24f7\u24f8\u24f9\u24fa\u24fb\u24fc\u24fd\u2776\u2777\u2778\u2779\u277a\u277b\u277c\u277d\u277e\u2780\u2781\u2782\u2783\u2784\u2785\u2786\u2787\u2788\u278a\u278b\u278c\u278d\u278e\u278f\u2790\u2791\u2792\uff10\uff11\uff12\uff13\uff14\uff15\uff16\uff17\uff18\uff19"
+
+CHARS_SPACES = u"\u0009\u000a\u000b\u000c\u000d\u001c\u001d\u001e\u001f\u0020\u0085\u00a0\u1680\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u2028\u2029\u202f\u205f\u3000"
--- a/MoinMoin/constants/contenttypes.py	Fri Feb 08 08:32:26 2013 -0700
+++ b/MoinMoin/constants/contenttypes.py	Mon Feb 11 12:04:39 2013 -0700
@@ -8,10 +8,10 @@
 # Charset - we support only 'utf-8'. While older encodings might work,
 # we don't have the resources to test them, and there is no real
 # benefit for the user. IMPORTANT: use only lowercase 'utf-8'!
-charset = 'utf-8'
+CHARSET = 'utf-8'
 
 # Parser to use mimetype text
-parser_text_mimetype = ('plain', 'csv', 'rst', 'docbook', 'latex', 'tex', 'html', 'css',
+PARSER_TEXT_MIMETYPE = ('plain', 'csv', 'rst', 'docbook', 'latex', 'tex', 'html', 'css',
                        'xml', 'python', 'perl', 'php', 'ruby', 'javascript',
                        'cplusplus', 'java', 'pascal', 'diff', 'gettext', 'xslt', 'creole', )
 
@@ -27,3 +27,5 @@
 GROUP_VIDEO = 'video items'
 GROUP_DRAWING = 'drawing items'
 GROUP_OTHER = 'other items'
+
+DRAWING_EXTENSIONS = ['.tdraw', '.adraw', '.svg', '.png', '.jpg', '.jpeg', '.gif', ]
--- a/MoinMoin/constants/forms.py	Fri Feb 08 08:32:26 2013 -0700
+++ b/MoinMoin/constants/forms.py	Mon Feb 11 12:04:39 2013 -0700
@@ -7,7 +7,7 @@
 
 # Widget types
 
-WIDGET_TEXT = u'text' # single-line text
+WIDGET_TEXT = u'text'  # single-line text
 WIDGET_MULTILINE_TEXT = u'multiline_text'
 WIDGET_URL = u'url'
 WIDGET_EMAIL = u'email'
--- a/MoinMoin/constants/keys.py	Fri Feb 08 08:32:26 2013 -0700
+++ b/MoinMoin/constants/keys.py	Mon Feb 11 12:04:39 2013 -0700
@@ -12,6 +12,7 @@
 # metadata keys
 NAME = "name"
 NAME_OLD = "name_old"
+NAMESPACE = "namespace"
 
 # if an item is reverted, we store the revision number we used for reverting there:
 REVERTED_TO = "reverted_to"
@@ -52,7 +53,7 @@
 # data. meta[HASH_ALGORITHM] = hash(rev_data, HASH_ALGORITHM)
 # some backends may use this also for other purposes.
 HASH_ALGORITHM = 'sha1'
-HASH_LEN = 40 # length of hex str representation of hash value
+HASH_LEN = 40  # length of hex str representation of hash value
 
 # some field names for whoosh index schema / documents in index:
 NAME_EXACT = "name_exact"
@@ -69,7 +70,7 @@
 # stuff from user profiles / for whoosh index
 EMAIL = "email"
 OPENID = "openid"
-ALIASNAME = "aliasname"
+DISPLAY_NAME = "display_name"
 THEME_NAME = "theme_name"
 LOCALE = "locale"
 TIMEZONE = "timezone"
@@ -89,9 +90,12 @@
 RESULTS_PER_PAGE = "results_per_page"
 DISABLED = "disabled"
 
+# in which backend is some revision stored?
+BACKENDNAME = "backendname"
+
 USEROBJ_ATTRS = [
     # User objects proxy these attributes of the UserProfile objects:
-    NAME, DISABLED, ITEMID, ALIASNAME, ENC_PASSWORD, EMAIL, OPENID,
+    NAME, DISABLED, ITEMID, DISPLAY_NAME, ENC_PASSWORD, EMAIL, OPENID,
     MAILTO_AUTHOR, SHOW_COMMENTS, RESULTS_PER_PAGE, EDIT_ON_DOUBLECLICK, SCROLL_PAGE_AFTER_EDIT,
     EDIT_ROWS, THEME_NAME, LOCALE, TIMEZONE, SUBSCRIBED_ITEMS, QUICKLINKS,
     CSS_URL,
--- a/MoinMoin/constants/misc.py	Fri Feb 08 08:32:26 2013 -0700
+++ b/MoinMoin/constants/misc.py	Mon Feb 11 12:04:39 2013 -0700
@@ -9,7 +9,7 @@
 
 # Invalid characters - invisible characters that should not be in page
 # names. Prevent user confusion and wiki abuse, e.g u'\u202aFrontPage'.
-page_invalid_chars_regex = re.compile(
+ITEM_INVALID_CHARS_REGEX = re.compile(
     ur"""
     \u0000 | # NULL
 
@@ -23,7 +23,7 @@
     re.UNICODE | re.VERBOSE
     )
 
-clean_input_translation_map = {
+CLEAN_INPUT_TRANSLATION_MAP = {
     # these chars will be replaced by blanks
     ord(u'\t'): u' ',
     ord(u'\r'): u' ',
@@ -32,11 +32,11 @@
 for c in u'\x00\x01\x02\x03\x04\x05\x06\x07\x08\x0b\x0c\x0e\x0f' \
           '\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f':
     # these chars will be removed
-    clean_input_translation_map[ord(c)] = None
+    CLEAN_INPUT_TRANSLATION_MAP[ord(c)] = None
 del c
 
 # Other stuff
-uri_schemes = ['http', 'https', 'ftp', 'file',
+URI_SCHEMES = ['http', 'https', 'ftp', 'file',
                'mailto', 'nntp', 'news',
                'ssh', 'telnet', 'irc', 'ircs', 'xmpp', 'mumble',
                'webcal', 'ed2k', 'apt', 'rootz',
--- a/MoinMoin/constants/tools/chartypes_create.py	Fri Feb 08 08:32:26 2013 -0700
+++ b/MoinMoin/constants/tools/chartypes_create.py	Mon Feb 11 12:04:39 2013 -0700
@@ -25,18 +25,18 @@
             space.append(str)
 
     chars_upper = u''.join(uppercase)
-    chars_lower = u''.join(lowercase+digits)
+    chars_lower = u''.join(lowercase + digits)
     chars_digits = u''.join(digits)
     chars_spaces = u''.join(space)
 
     print """
-chars_upper = u"%(chars_upper)s"
+CHARS_UPPER = u"%(chars_upper)s"
 
-chars_lower = u"%(chars_lower)s"
+CHARS_LOWER = u"%(chars_lower)s"
 
-chars_digits = u"%(chars_digits)s"
+CHARS_DIGITS = u"%(chars_digits)s"
 
-chars_spaces = u"%(chars_spaces)s"
+CHARS_SPACES = u"%(chars_spaces)s"
 
 
 """ % locals()
--- a/MoinMoin/converter/_args_wiki.py	Fri Feb 08 08:32:26 2013 -0700
+++ b/MoinMoin/converter/_args_wiki.py	Mon Feb 11 12:04:39 2013 -0700
@@ -32,6 +32,7 @@
 '''
 _parse_re = re.compile(_parse_rules, re.X)
 
+
 def parse(input):
     """
     Parse <input> for positional and keyword arguments, with value quoting and
@@ -57,6 +58,7 @@
 _unparse_rules = r'''^[-\w]+$'''
 _unparse_re = re.compile(_unparse_rules, re.X)
 
+
 def unparse(args):
     """
     Generate a argument string from a Argument instance <args>.
--- a/MoinMoin/converter/_table.py	Fri Feb 08 08:32:26 2013 -0700
+++ b/MoinMoin/converter/_table.py	Mon Feb 11 12:04:39 2013 -0700
@@ -13,6 +13,7 @@
 
 WORDBREAK_LEN = 30
 
+
 class TableMixin(object):
     """
     Mixin to support building a DOM table.
--- a/MoinMoin/converter/_tests/test__args_wiki.py	Fri Feb 08 08:32:26 2013 -0700
+++ b/MoinMoin/converter/_tests/test__args_wiki.py	Mon Feb 11 12:04:39 2013 -0700
@@ -6,8 +6,6 @@
 """
 
 
-import pytest
-
 from MoinMoin.converter._args_wiki import *
 
 def test():
--- a/MoinMoin/converter/_tests/test__wiki_macro.py	Fri Feb 08 08:32:26 2013 -0700
+++ b/MoinMoin/converter/_tests/test__wiki_macro.py	Mon Feb 11 12:04:39 2013 -0700
@@ -6,7 +6,6 @@
 """
 
 
-import pytest
 import re
 
 from MoinMoin.converter._args import Arguments
--- a/MoinMoin/converter/_tests/test_creole_in.py	Fri Feb 08 08:32:26 2013 -0700
+++ b/MoinMoin/converter/_tests/test_creole_in.py	Mon Feb 11 12:04:39 2013 -0700
@@ -6,8 +6,6 @@
 """
 
 
-import pytest
-
 import re
 
 from MoinMoin.util.tree import moin_page, xlink
--- a/MoinMoin/converter/_tests/test_docbook_in.py	Fri Feb 08 08:32:26 2013 -0700
+++ b/MoinMoin/converter/_tests/test_docbook_in.py	Mon Feb 11 12:04:39 2013 -0700
@@ -11,6 +11,7 @@
 import StringIO
 
 import pytest
+
 try:
     from lxml import etree
 except:
--- a/MoinMoin/converter/_tests/test_docbook_out.py	Fri Feb 08 08:32:26 2013 -0700
+++ b/MoinMoin/converter/_tests/test_docbook_out.py	Mon Feb 11 12:04:39 2013 -0700
@@ -10,6 +10,7 @@
 import StringIO
 
 import pytest
+
 try:
     from lxml import etree
 except:
--- a/MoinMoin/converter/_tests/test_include.py	Fri Feb 08 08:32:26 2013 -0700
+++ b/MoinMoin/converter/_tests/test_include.py	Mon Feb 11 12:04:39 2013 -0700
@@ -6,11 +6,9 @@
 """
 
 
-import pytest
-
 from MoinMoin.converter.include import *
 from MoinMoin.items import Item
-from MoinMoin.config import CONTENTTYPE
+from MoinMoin.constants.keys import CONTENTTYPE
 from MoinMoin._tests import wikiconfig, update_item
 
 class TestInclude(object):
--- a/MoinMoin/converter/_tests/test_moinwiki_out.py	Fri Feb 08 08:32:26 2013 -0700
+++ b/MoinMoin/converter/_tests/test_moinwiki_out.py	Mon Feb 11 12:04:39 2013 -0700
@@ -7,7 +7,6 @@
 """
 
 
-import pytest
 import re
 
 from MoinMoin.converter.moinwiki_out import *
--- a/MoinMoin/converter/_tests/test_registry.py	Fri Feb 08 08:32:26 2013 -0700
+++ b/MoinMoin/converter/_tests/test_registry.py	Mon Feb 11 12:04:39 2013 -0700
@@ -6,8 +6,6 @@
 """
 
 
-import pytest
-
 from MoinMoin.util.mime import Type, type_moin_document, type_moin_wiki
 from MoinMoin.converter import default_registry
 from MoinMoin.converter.text_in import Converter as TextInConverter
--- a/MoinMoin/converter/_tests/test_rst_in.py	Fri Feb 08 08:32:26 2013 -0700
+++ b/MoinMoin/converter/_tests/test_rst_in.py	Mon Feb 11 12:04:39 2013 -0700
@@ -7,7 +7,6 @@
 """
 
 
-import pytest
 import re
 
 from MoinMoin.converter.rst_in import *
--- a/MoinMoin/converter/_tests/test_rst_out.py	Fri Feb 08 08:32:26 2013 -0700
+++ b/MoinMoin/converter/_tests/test_rst_out.py	Mon Feb 11 12:04:39 2013 -0700
@@ -6,7 +6,6 @@
 """
 
 
-import pytest
 import re
 
 from MoinMoin.converter.rst_out import *
--- a/MoinMoin/converter/_util.py	Fri Feb 08 08:32:26 2013 -0700
+++ b/MoinMoin/converter/_util.py	Mon Feb 11 12:04:39 2013 -0700
@@ -12,14 +12,16 @@
 from flask import g as flaskg
 from emeraldtree import ElementTree as ET
 
-from MoinMoin.config import uri_schemes
+from MoinMoin.constants.misc import URI_SCHEMES
 from MoinMoin.util.iri import Iri
 from MoinMoin.util.mime import Type
 from MoinMoin.util.tree import html, moin_page
 
+
 def allowed_uri_scheme(uri):
     parsed = Iri(uri)
-    return not parsed.scheme or parsed.scheme in uri_schemes
+    return not parsed.scheme or parsed.scheme in URI_SCHEMES
+
 
 def decode_data(data, contenttype=None):
     """
@@ -42,7 +44,8 @@
             coding = ct.parameters.get('charset', coding)
         data = data.decode(coding)
     if not isinstance(data, unicode):
-        raise TypeError("data must be rev or str (requires contenttype with charset) or unicode, but we got {0!r}".format(data))
+        raise TypeError("data must be rev or str (requires contenttype with charset) or unicode, "
+                        "but we got {0!r}".format(data))
     return data
 
 
@@ -119,7 +122,8 @@
         """
         if request.user_agent and flaskg.user.edit_on_doubleclick:
             # this is not py.test and user has option to edit on doubleclick
-            # TODO: move the 2 lines above and 2 related import statements outside of the converters (needed for standalone converter)
+            # TODO: move the 2 lines above and 2 related import statements outside of the converters
+            # (this is needed for a standalone converter)
             if self.last_lineno != self.iter_content.lineno:
                 # avoid adding same lineno to parent and multiple children or grand-children
                 elem.attrib[html.data_lineno] = self.iter_content.lineno
--- a/MoinMoin/converter/_wiki_macro.py	Fri Feb 08 08:32:26 2013 -0700
+++ b/MoinMoin/converter/_wiki_macro.py	Mon Feb 11 12:04:39 2013 -0700
@@ -16,6 +16,7 @@
 from MoinMoin.util.mime import Type
 from MoinMoin.util.tree import moin_page, xinclude
 
+
 class ConverterMacro(object):
     def _BR_repl(self, args, text, context_block):
         if context_block:
@@ -42,8 +43,8 @@
             return text
 
         pagename = args[0]
-        heading = None # TODO
-        level = None # TODO
+        heading = None  # TODO
+        level = None  # TODO
         sort = 'sort' in args and args['sort']
         if sort and sort not in ('ascending', 'descending'):
             raise RuntimeError
--- a/MoinMoin/converter/archive_in.py	Fri Feb 08 08:32:26 2013 -0700
+++ b/MoinMoin/converter/archive_in.py	Mon Feb 11 12:04:39 2013 -0700
@@ -27,6 +27,7 @@
     exception class used in case of trouble with opening/listing an archive
     """
 
+
 class ArchiveConverter(TableMixin):
     """
     Base class for archive converters, convert an archive to a DOM table
@@ -39,7 +40,8 @@
     def process_name(self, member_name):
         name = unicode(member_name, 'utf-8')
         attrib = {
-            xlink.href: Iri(scheme='wiki', authority='', path='/'+self.item_name, query=u'do=get&member={0}'.format(name)),
+            xlink.href: Iri(scheme='wiki', authority='', path='/' + self.item_name,
+                            query=u'do=get&member={0}'.format(name)),
         }
         return moin_page.a(attrib=attrib, children=[name, ])
 
@@ -113,7 +115,7 @@
                     # display only normal files, not directories
                     rows.append((
                         zinfo.file_size,
-                        datetime(*zinfo.date_time), # y,m,d,h,m,s
+                        datetime(*zinfo.date_time),  # y,m,d,h,m,s
                         zinfo.filename,
                     ))
             return rows
--- a/MoinMoin/converter/audio_video_in.py	Fri Feb 08 08:32:26 2013 -0700
+++ b/MoinMoin/converter/audio_video_in.py	Mon Feb 11 12:04:39 2013 -0700
@@ -31,7 +31,8 @@
         item_name = rev.item.name
         attrib = {
             moin_page.type_: unicode(self.input_type),
-            xlink.href: Iri(scheme='wiki', authority='', path='/'+item_name, query='do=get&rev={0}'.format(rev.revid)),
+            xlink.href: Iri(scheme='wiki', authority='', path='/' + item_name,
+                            query='do=get&rev={0}'.format(rev.revid)),
         }
         obj = moin_page.object_(attrib=attrib, children=[u'Your Browser does not support HTML5 audio/video element.', ])
         body = moin_page.body(children=(obj, ))
--- a/MoinMoin/converter/creole_in.py	Fri Feb 08 08:32:26 2013 -0700
+++ b/MoinMoin/converter/creole_in.py	Mon Feb 11 12:04:39 2013 -0700
@@ -27,7 +27,7 @@
 
 import re
 
-from MoinMoin import config
+from MoinMoin.constants.misc import URI_SCHEMES
 from MoinMoin.util.iri import Iri
 from MoinMoin.util.tree import moin_page, xlink, xinclude
 
@@ -330,9 +330,9 @@
         """Handle all kinds of links."""
 
         if link_page is not None:
-            att = 'attachment:' # moin 1.9 needed this for an attached file
+            att = 'attachment:'  # moin 1.9 needed this for an attached file
             if link_page.startswith(att):
-                link_page = '/' + link_page[len(att):] # now we have a subitem
+                link_page = '/' + link_page[len(att):]  # now we have a subitem
             target = unicode(Iri(scheme='wiki.local', path=link_page))
             text = link_page
         else:
@@ -406,9 +406,9 @@
         """Handles objects included in the page."""
 
         if object_page is not None:
-            att = 'attachment:' # moin 1.9 needed this for an attached file
+            att = 'attachment:'  # moin 1.9 needed this for an attached file
             if object_page.startswith(att):
-                object_page = '/' + object_page[len(att):] # now we have a subitem
+                object_page = '/' + object_page[len(att):]  # now we have a subitem
             target = Iri(scheme='wiki.local', path=object_page)
             text = object_page
 
@@ -436,7 +436,7 @@
             )
             ($ | (?=\s | [,.:;!?()] (\s | $)))
         )
-    """ % dict(uri_schemes='|'.join(config.uri_schemes))
+    """ % dict(uri_schemes='|'.join(URI_SCHEMES))
 
     def inline_url_repl(self, stack, url, url_target, escaped_url=None):
         """Handle raw urls in text."""
--- a/MoinMoin/converter/docbook_in.py	Fri Feb 08 08:32:26 2013 -0700
+++ b/MoinMoin/converter/docbook_in.py	Mon Feb 11 12:04:39 2013 -0700
@@ -32,6 +32,7 @@
 class NameSpaceError(Exception):
     pass
 
+
 class Converter(object):
     """
     Converter application/docbook+xml -> x.moin.document
@@ -43,7 +44,7 @@
 
     # DocBook elements which are completely ignored by our converter
     # We even do not process children of these elements
-    ignored_tags = set([# Info elements
+    ignored_tags = set([  # Info elements
                        'abstract', 'artpagenums', 'annotation',
                        'artpagenums', 'author', 'authorgroup',
                        'authorinitials', 'bibliocoverage', 'biblioid',
@@ -383,15 +384,15 @@
         for child in element:
             if isinstance(child, ET.Element):
                 if child.tag.name in self.media_tags:
-                    #XXX: Check the spec to be sure that object tag have only one child.
-                    #TODO: Better way to do it
+                    # XXX: Check the spec to be sure that object tag have only one child.
+                    # TODO: Better way to do it
                     prefered_format, data_tag, mimetype = self.media_tags[child.tag.name]
                     object_element = child
                     for grand_child in child:
                         if isinstance(grand_child, ET.Element):
                             object_data.append(grand_child)
                 if child.tag.name == 'caption':
-                    caption = self.do_children(child, depth+1)[0]
+                    caption = self.do_children(child, depth + 1)[0]
                 if child.tag.name == 'textobject':
                     text_object = child
         return self.visit_data_element(object_element, depth, object_data,
@@ -410,7 +411,7 @@
             if not text_object:
                 return
             else:
-                children = self.do_children(child, depth+1)[0]
+                children = self.do_children(child, depth + 1)[0]
                 return self.new(moin_page.p, attrib={},
                                 children=children)
         # We try to determine the best object to show
@@ -430,7 +431,7 @@
         # If we could not find any suitable object, we return
         # the text replacement.
         if not object_to_show:
-            children = self.do_children(child, depth+1)[0]
+            children = self.do_children(child, depth + 1)[0]
             return self.new(moin_page.p, attrib={},
                             children=children)
 
@@ -488,9 +489,9 @@
         for child in element:
             if isinstance(child, ET.Element):
                 if child.tag.name == "attribution":
-                    source = self.do_children(child, depth+1)
+                    source = self.do_children(child, depth + 1)
                 else:
-                    children.extend(self.do_children(child, depth+1))
+                    children.extend(self.do_children(child, depth + 1))
             else:
                 children.append(child)
         attrib = {}
@@ -555,7 +556,7 @@
             #XXX: Improve error
             raise SyntaxError("para child missing for formalpara element")
 
-        children = self.do_children(para_element, depth+1)[0]
+        children = self.do_children(para_element, depth + 1)[0]
         attrib = {}
         attrib[html('title')] = title_element[0]
         return self.new(moin_page.p, attrib=attrib, children=children)
@@ -980,7 +981,7 @@
         href = element.get(docbook.url)
         # Since it is an element of DocBook v.4,
         # The namespace does not always work, so we will try to retrive the attribute whatever
-        if not(href):
+        if not href:
             for key, value in element.attrib.iteritems():
                 if key.name == 'url' and allowed_uri_scheme(value):
                     href = value
--- a/MoinMoin/converter/docbook_out.py	Fri Feb 08 08:32:26 2013 -0700
+++ b/MoinMoin/converter/docbook_out.py	Mon Feb 11 12:04:39 2013 -0700
@@ -335,7 +335,7 @@
                     if child.tag.name == 'note-body':
                         body = self.do_children(child)
         # We process note only with note-body child
-        if not(body):
+        if not body:
             return
 
         body = self.new(docbook.simpara, attrib={}, children=body)
@@ -441,7 +441,7 @@
         for item in element:
             if item.tag.uri == moin_page and item.tag.name == 'body':
                 c = self.do_children(item)
-                if not(c):
+                if not c:
                     self.section_children = sorted(self.section_children.items(),
                                                    reverse=True)
                     section = None
--- a/MoinMoin/converter/everything.py	Fri Feb 08 08:32:26 2013 -0700
+++ b/MoinMoin/converter/everything.py	Mon Feb 11 12:04:39 2013 -0700
@@ -25,7 +25,8 @@
     def __call__(self, rev, contenttype=None, arguments=None):
         item_name = rev.item.name
         attrib = {
-            xlink.href: Iri(scheme='wiki', authority='', path='/'+item_name, query='do=get&rev={0}'.format(rev.revid)),
+            xlink.href: Iri(scheme='wiki', authority='', path='/' + item_name,
+                            query='do=get&rev={0}'.format(rev.revid)),
         }
         a = moin_page.a(attrib=attrib, children=[u"Download {0}.".format(item_name)])
         body = moin_page.body(children=(a, ))
--- a/MoinMoin/converter/html_in.py	Fri Feb 08 08:32:26 2013 -0700
+++ b/MoinMoin/converter/html_in.py	Mon Feb 11 12:04:39 2013 -0700
@@ -42,7 +42,7 @@
     list_tags = set(['ul', 'dir', 'ol'])
 
     # HTML tags which can be convert without attributes in a different DOM tag
-    simple_tags = {# Emphasis
+    simple_tags = {  # Emphasis
                    'em': moin_page.emphasis, 'i': moin_page.emphasis,
                    # Strong
                    'b': moin_page.strong, 'strong': moin_page.strong,
@@ -480,7 +480,7 @@
         # of <dt><dd>
         for child in element:
             # We need one dt tag, and one dd tag, a have a pair
-            if (child.tag.name == 'dt' or child.tag.name == 'dd'):
+            if child.tag.name == 'dt' or child.tag.name == 'dd':
                 number_pair = number_pair + 1
 
             # The following code is similar to do_children method
@@ -494,7 +494,7 @@
             else:
                 pair.append(r)
 
-            if (number_pair == 2):
+            if number_pair == 2:
                 # We have two elements of the pair
                 # So we can put it into a <list-item> element
                 list_item_element = ET.Element(moin_page.list_item,
--- a/MoinMoin/converter/html_out.py	Fri Feb 08 08:32:26 2013 -0700
+++ b/MoinMoin/converter/html_out.py	Mon Feb 11 12:04:39 2013 -0700
@@ -34,6 +34,7 @@
         return re.sub(r'\+get/\+[0-9a-fA-F]+/', '', href)
     return href
 
+
 def mark_item_as_transclusion(elem, href):
     """
     Return elem after adding a "moin-transclusion" class and a "data-href" attribute with
@@ -86,7 +87,7 @@
     visit_style = Attribute('style')
     visit_title = Attribute('title')
     visit_id = Attribute('id')
-    visit_type = Attribute('type') # IE8 needs <object... type="image/svg+xml" ...> to display svg images
+    visit_type = Attribute('type')  # IE8 needs <object... type="image/svg+xml" ...> to display svg images
 
     def __init__(self, element):
         self.element = element
@@ -371,7 +372,7 @@
 
         if obj_type == "img":
             # Images have alt text
-            alt = ''.join(unicode(e) for e in elem) # XXX handle non-text e
+            alt = ''.join(unicode(e) for e in elem)  # XXX handle non-text e
             if alt:
                 attrib[html.alt] = alt
             new_elem = html.img(attrib=attrib)
@@ -401,7 +402,8 @@
 
                     if item[0].tag.name in ('object', 'a'):
                         # png, jpg, gif are objects here, will be changed to img when they are processed
-                        # transclusion is a single inline element "My pet {{bird.jpg}} flys." or "[[SomePage|{{Logo.png}}]]"
+                        # transclusion is a single inline element "My pet {{bird.jpg}} flys." or
+                        # "[[SomePage|{{Logo.png}}]]"
                         return self.new_copy(html.span, item, attribs)
 
                     elif item[0].tag.name == 'p':
@@ -725,11 +727,15 @@
         prefixed_id = '%s-%s' % (self._id.get_id('note-placement'), id)
 
         elem_ref = ET.XML("""
-<html:sup xmlns:html="{0}" html:id="note-{1}-ref" html:class="moin-footnote"><html:a html:href="#note-{2}">{3}</html:a></html:sup>
+<html:sup xmlns:html="{0}" html:id="note-{1}-ref" html:class="moin-footnote">
+<html:a html:href="#note-{2}">{3}</html:a>
+</html:sup>
 """.format(html, prefixed_id, prefixed_id, id))
 
         elem_note = ET.XML("""
-<html:p xmlns:html="{0}" html:id="note-{1}"><html:sup><html:a html:href="#note-{2}-ref">{3}</html:a></html:sup></html:p>
+<html:p xmlns:html="{0}" html:id="note-{1}">
+<html:sup><html:a html:href="#note-{2}-ref">{3}</html:a></html:sup>
+</html:p>
 """.format(html, prefixed_id, prefixed_id, id))
 
         elem_note.extend(body)
--- a/MoinMoin/converter/image_in.py	Fri Feb 08 08:32:26 2013 -0700
+++ b/MoinMoin/converter/image_in.py	Mon Feb 11 12:04:39 2013 -0700
@@ -29,7 +29,8 @@
         item_name = rev.item.name
         attrib = {
             moin_page.type_: unicode(self.input_type),
-            xlink.href: Iri(scheme='wiki', authority='', path='/'+item_name, query='do=get&rev={0}'.format(rev.revid)),
+            xlink.href: Iri(scheme='wiki', authority='', path='/' + item_name,
+                            query='do=get&rev={0}'.format(rev.revid)),
         }
         obj = moin_page.object_(attrib=attrib, children=[item_name, ])
         body = moin_page.body(children=(obj, ))
--- a/MoinMoin/converter/include.py	Fri Feb 08 08:32:26 2013 -0700
+++ b/MoinMoin/converter/include.py	Mon Feb 11 12:04:39 2013 -0700
@@ -85,7 +85,9 @@
 from __future__ import absolute_import, division
 
 from emeraldtree import ElementTree as ET
-import re, types, copy
+import re
+import types
+import copy
 
 from MoinMoin import log
 logging = log.getLogger(__name__)
@@ -95,7 +97,7 @@
 
 from whoosh.query import Term, And, Wildcard
 
-from MoinMoin.config import NAME, NAME_EXACT, WIKINAME
+from MoinMoin.constants.keys import NAME, NAME_EXACT, WIKINAME
 from MoinMoin.items import Item
 from MoinMoin.util.mime import type_moin_document
 from MoinMoin.util.iri import Iri, IriPath
@@ -106,14 +108,14 @@
 # elements generated by moin wiki markup that cannot have block children
 NO_BLOCK_CHILDREN = [
         'p',
-        'span', # /*comment*/, ~+big+~, ~-small-~ via classes comment, moin-big, moin-small
-        'emphasis', # ''italic''
-        'strong', # '''bold'''
-        'del', # --(stroke)--
-        'ins', # __underline__
-        # 'sub', # ,,subscript,, # no markup allowed within subscripts
-        # 'sup', # ^superscript^ # no markup allowed within superscripts
-        'a', # [[SomeItem|{{logo.png}}]]
+        'span',  # /*comment*/, ~+big+~, ~-small-~ via classes comment, moin-big, moin-small
+        'emphasis',  # ''italic''
+        'strong',  # '''bold'''
+        'del',  # --(stroke)--
+        'ins',  # __underline__
+        # 'sub',  # ,,subscript,, # no markup allowed within subscripts
+        # 'sup',  # ^superscript^ # no markup allowed within superscripts
+        'a',  # [[SomeItem|{{logo.png}}]]
         ]
 
 
@@ -181,6 +183,7 @@
                 data = None
             self.append(self.Entry(''.join(name), None))
 
+
 class Converter(object):
     tag_a = moin_page.a
     tag_div = moin_page.div
@@ -198,7 +201,8 @@
             return cls()
 
     def recurse(self, elem, page_href):
-        # on first call, elem.tag.name=='page'. Descendants (body, div, p, include, page, etc.) are processed by recursing through DOM
+        # on first call, elem.tag.name=='page'.
+        # Descendants (body, div, p, include, page, etc.) are processed by recursing through DOM
 
         # stack is used to detect transclusion loops
         page_href_new = elem.get(self.tag_page_href)
@@ -215,7 +219,8 @@
         try:
             if elem.tag == self.tag_xi_include:
                 # we have already recursed several levels and found a transclusion: "{{SomePage}}" or similar
-                # process the transclusion and add it to the DOM.  Subsequent recursions will traverse through the transclusion's elements.
+                # process the transclusion and add it to the DOM.  Subsequent recursions will traverse through
+                # the transclusion's elements.
                 href = elem.get(self.tag_xi_href)
                 xpointer = elem.get(self.tag_xi_xpointer)
 
@@ -352,7 +357,8 @@
                 #  end of processing for transclusion; the "result" will get inserted into the DOM below
                 return result
 
-            # Traverse the DOM by calling self.recurse with each child of the current elem.  Starting elem.tag.name=='page'.
+            # Traverse the DOM by calling self.recurse with each child of the current elem.
+            # Starting elem.tag.name=='page'.
             container = []
             i = 0
             while i < len(elem):
@@ -370,7 +376,8 @@
                                 # the transcluded item is empty, insert an empty span into DOM
                                 attrib = Attributes(ret).convert()
                                 elem[i] = ET.Element(moin_page.span, attrib=attrib)
-                            elif isinstance(body[0], ET.Node) and (len(body) > 1 or body[0].tag.name not in ('p', 'object', 'a')):
+                            elif (isinstance(body[0], ET.Node) and
+                                  (len(body) > 1 or body[0].tag.name not in ('p', 'object', 'a'))):
                                 # Complex case: "some text {{BlockItem}} more text" or "\n{{BlockItem}}\n" where
                                 # the BlockItem body contains multiple p's, a table, preformatted text, etc.
                                 # These block elements cannot be made a child of the current elem, so we create
@@ -379,30 +386,34 @@
                                 before = copy.deepcopy(elem)
                                 after = copy.deepcopy(elem)
                                 before[:] = elem[0:i]
-                                after[:] = elem[i+1:]
+                                after[:] = elem[i + 1:]
                                 if len(before):
                                     # there are siblings before transclude, save them in container
                                     container.append(before)
                                 new_trans_ptr = len(container)
-                                # get attributes from page node; we expect {class: "moin-transclusion"; data-href: "http://some.org/somepage"}
+                                # get attributes from page node;
+                                # we expect {class: "moin-transclusion"; data-href: "http://some.org/somepage"}
                                 attrib = Attributes(ret).convert()
                                 # make new div node to hold transclusion, copy children, and save in container
                                 div = ET.Element(moin_page.div, attrib=attrib, children=body[:])
-                                container.append(div) # new_trans_ptr is index to this
+                                container.append(div)  # new_trans_ptr is index to this
                                 if len(after):
                                     container.append(after)
                                 if elem.tag.name == 'a':
-                                    # invalid input [[MyPage|{{BlockItem}}]], best option is to retain A-tag and fail html validation
+                                    # invalid input [[MyPage|{{BlockItem}}]],
+                                    # best option is to retain A-tag and fail html validation
                                     # TODO: error may not be obvious to user - add error message
                                     elem[i] = div
                                 else:
-                                    # move up 1 level in recursion where elem becomes the child and is usually replaced by container
+                                    # move up 1 level in recursion where elem becomes the child and
+                                    # is usually replaced by container
                                     return [container, new_trans_ptr]
                             else:
                                 # default action for odd things like circular transclusion error messages
                                 elem[i] = ret
                         elif isinstance(ret, types.ListType):
-                            # a container has been returned. Note: there are two places where a container may be returned
+                            # a container has been returned.
+                            # Note: there are two places where a container may be returned
                             ret_container, trans_ptr = ret
                             # trans_ptr points to the transclusion within ret_container.
                             # Here the transclusion will always contain a block level element
@@ -413,25 +424,29 @@
                                 before = copy.deepcopy(elem)
                                 after = copy.deepcopy(elem)
                                 before[:] = elem[0:i] + ret_container[0:trans_ptr]
-                                after[:] = ret_container[trans_ptr+1:] + elem[i+1:]
+                                after[:] = ret_container[trans_ptr + 1:] + elem[i + 1:]
                                 if len(before):
                                     container.append(before)
                                 new_trans_ptr = len(container)
                                 # child may have classes like "comment" that must be added to transcluded element
                                 classes = child.attrib.get(moin_page.class_, '').split()
-                                classes += ret_container[trans_ptr].attrib.get(html.class_, '').split() # this must be html, not moin_page
-                                ret_container[trans_ptr].attrib[html.class_] = ' '.join(classes) # this must be html, not moin_page
-                                container.append(ret_container[trans_ptr]) # the transclusion
+                                # this must be html, not moin_page:
+                                classes += ret_container[trans_ptr].attrib.get(html.class_, '').split()
+                                # this must be html, not moin_page:
+                                ret_container[trans_ptr].attrib[html.class_] = ' '.join(classes)
+                                container.append(ret_container[trans_ptr])  # the transclusion
                                 if len(after):
                                     container.append(after)
                                 return [container, new_trans_ptr]
                             else:
-                                # elem is a block element, replace child element with the container generated in lower recursion
-                                elem[i:i+1] = ret_container # elem[i] is the child
+                                # elem is a block element,
+                                # replace child element with the container generated in lower recursion
+                                elem[i:i + 1] = ret_container  # elem[i] is the child
                                 # avoid duplicate recursion over nodes already processed
-                                i += len(ret_container) -1
+                                i += len(ret_container) - 1
                         else:
-                            # default action for any ret not fitting special cases above, e.g. tranclusion is within a table cell
+                            # default action for any ret not fitting special cases above,
+                            # e.g. tranclusion is within a table cell
                             elem[i] = ret
                 # we are finished with this child, advance to next sibling
                 i += 1
--- a/MoinMoin/converter/link.py	Fri Feb 08 08:32:26 2013 -0700
+++ b/MoinMoin/converter/link.py	Mon Feb 11 12:04:39 2013 -0700
@@ -14,7 +14,7 @@
 from flask import g as flaskg
 
 from MoinMoin.util.interwiki import is_known_wiki, url_for_item
-from MoinMoin.util.iri import Iri, IriPath
+from MoinMoin.util.iri import Iri
 from MoinMoin.util.mime import Type, type_moin_document
 from MoinMoin.util.tree import moin_page, xlink, xinclude
 from MoinMoin.wikiutil import AbsItemName
@@ -53,7 +53,7 @@
         """
         Traverses the tree and handles each element appropriately
         """
-        new_page_href=elem.get(__tag_page_href)
+        new_page_href = elem.get(__tag_page_href)
         if new_page_href:
             page = Iri(new_page_href)
 
@@ -129,10 +129,10 @@
                     k, v = kv, ''
                 if k == 'do':
                     do = v
-                    continue # we remove do=xxx from qs
+                    continue  # we remove do=xxx from qs
                 if k == 'rev':
                     rev = v
-                    continue # we remove rev=n from qs
+                    continue  # we remove rev=n from qs
                 result.append(u'{0}={1}'.format(k, v))
         if result:
             query = separator.join(result)
--- a/MoinMoin/converter/macro.py	Fri Feb 08 08:32:26 2013 -0700
+++ b/MoinMoin/converter/macro.py	Mon Feb 11 12:04:39 2013 -0700
@@ -22,7 +22,7 @@
 from MoinMoin.converter._args import Arguments
 from MoinMoin.util import iri
 from MoinMoin.util.mime import type_moin_document, Type
-from MoinMoin.util.tree import html, moin_page
+from MoinMoin.util.tree import moin_page
 from MoinMoin.util.plugins import PluginMissingError
 
 
@@ -42,7 +42,7 @@
 
         type = Type(type)
         if not (type.type == 'x-moin' and type.subtype == 'macro'):
-            logging.debug("not a macro, skipping: %r" % type)
+            logging.debug("not a macro, skipping: %r" % (type, ))
             return
 
         name = type.parameters['name']
--- a/MoinMoin/converter/markdown_in.py	Fri Feb 08 08:32:26 2013 -0700
+++ b/MoinMoin/converter/markdown_in.py	Mon Feb 11 12:04:39 2013 -0700
@@ -25,6 +25,7 @@
 from markdown import Markdown
 import markdown.util as md_util
 
+
 def postproc_text(markdown, text):
     """
     Removes HTML or XML character references and entities from a text string.
@@ -58,10 +59,11 @@
                 text = unichr(htmlentitydefs.name2codepoint[text[1:-1]])
             except KeyError:
                 pass
-        return text # leave as is
+        return text  # leave as is
 
     return re.sub("&#?\w+;", fixup, text)
 
+
 class Converter(object):
     # {{{ html conversion
 
@@ -72,7 +74,7 @@
     list_tags = set(['ul', 'dir', 'ol'])
 
     # HTML tags which can be convert without attributes in a different DOM tag
-    simple_tags = {# Emphasis
+    simple_tags = {  # Emphasis
                    'em': moin_page.emphasis, 'i': moin_page.emphasis,
                    # Strong
                    'b': moin_page.strong, 'strong': moin_page.strong,
--- a/MoinMoin/converter/mediawiki_in.py	Fri Feb 08 08:32:26 2013 -0700
+++ b/MoinMoin/converter/mediawiki_in.py	Mon Feb 11 12:04:39 2013 -0700
@@ -19,7 +19,8 @@
 from MoinMoin import log
 logging = log.getLogger(__name__)
 
-from MoinMoin import config
+from MoinMoin.constants.contenttypes import CHARSET
+from MoinMoin.constants.misc import URI_SCHEMES
 from MoinMoin.util.iri import Iri
 from MoinMoin.util.tree import html, moin_page, xlink
 
@@ -335,7 +336,7 @@
                 # TODO: definition list doesn't work,
                 #       if definition of the term on the next line
                 splited_text = text.split(':')
-                list_definition_text=splited_text.pop(0)
+                list_definition_text = splited_text.pop(0)
                 text = ':'.join(splited_text)
 
                 self.parse_inline(list_definition_text, new_stack, self.inline_re)
@@ -482,7 +483,8 @@
         )
     """
 
-    def inline_footnote_repl(self, stack, footnote, footnote_begin=None, footnote_text=None, footnote_end=None, footnote_start=None):
+    def inline_footnote_repl(self, stack, footnote,
+                             footnote_begin=None, footnote_text=None, footnote_end=None, footnote_start=None):
         #stack.top_check('emphasis'):
         if footnote_begin is not None:
             stack.push(moin_page.note(attrib={moin_page.note_class: 'footnote'}))
@@ -591,7 +593,7 @@
             \s*
             \]
         )
-    """ % dict(uri_schemes='|'.join(config.uri_schemes))
+    """ % dict(uri_schemes='|'.join(URI_SCHEMES))
 
     def parse_args(self, input):
         """
@@ -635,7 +637,7 @@
         parsed_args = self.parse_args(link_args[1:])
         query = None
         if parsed_args.keyword:
-            query = url_encode(parsed_args.keyword, charset=config.charset, encode_keys=True)
+            query = url_encode(parsed_args.keyword, charset=CHARSET, encode_keys=True)
         # Take the last of positional parameters as link_text(caption)
         if parsed_args.positional:
             link_text = parsed_args.positional.pop()
@@ -654,7 +656,7 @@
                     if 'do' not in args:
                         # by default, we want the item's get url for transclusion of raw data:
                         args['do'] = 'get'
-                    query = url_encode(args, charset=config.charset, encode_keys=True)
+                    query = url_encode(args, charset=CHARSET, encode_keys=True)
                     target = Iri(scheme='wiki.local', path=object_item, query=query, fragment=None)
                     text = object_item
                 else:
@@ -923,7 +925,8 @@
                                     close_tag = self.Preprocessor_tag()
                                     while tag_name != close_tag.tag_name:
                                         close_tag = tags.pop()
-                                        tmp_line = '<{0}>{1}{2}</{3}>'.format(close_tag.tag, ''.join(close_tag.text), tmp_line, close_tag.tag_name)
+                                        tmp_line = '<{0}>{1}{2}</{3}>'.format(
+                                            close_tag.tag, ''.join(close_tag.text), tmp_line, close_tag.tag_name)
                                         if not len(tags):
                                             post_line.append(tmp_line)
                                         else:
--- a/MoinMoin/converter/moinwiki19_in.py	Fri Feb 08 08:32:26 2013 -0700
+++ b/MoinMoin/converter/moinwiki19_in.py	Mon Feb 11 12:04:39 2013 -0700
@@ -16,10 +16,12 @@
 from MoinMoin import log
 logging = log.getLogger(__name__)
 
-from MoinMoin import config, wikiutil
+from MoinMoin import wikiutil
+from MoinMoin.constants.misc import URI_SCHEMES
+from MoinMoin.constants.chartypes import CHARS_LOWER, CHARS_UPPER
 from MoinMoin.util.interwiki import is_known_wiki
 from MoinMoin.util.iri import Iri
-from MoinMoin.util.tree import html, moin_page, xlink
+from MoinMoin.util.tree import moin_page, xlink
 
 from .moinwiki_in import Converter
 
@@ -42,7 +44,7 @@
           )
           \:
           (?P<freelink_interwiki_page>
-           (?=\S*[%(u)s%(l)s0..9]\S* )  # make sure there is something non-blank with at least one alphanum letter following
+           (?=\S*[%(u)s%(l)s0..9]\S* )  # make sure there is something non-blank with at >= 1 alphanum letter following
            [^\s"\'}\]|:,.\)?!]+  # we take all until we hit some blank or punctuation char ...
           )
           |
@@ -74,8 +76,8 @@
           $  # ... or end of line
          )
     """ % {
-        'u': config.chars_upper,
-        'l': config.chars_lower,
+        'u': CHARS_UPPER,
+        'l': CHARS_LOWER,
         'child': re.escape(wikiutil.CHILD_PREFIX),
         'parent': re.escape(wikiutil.PARENT_PREFIX),
     }
@@ -143,7 +145,7 @@
                 )
             )
         )
-    """ % dict(uri_schemes='|'.join(config.uri_schemes))
+    """ % dict(uri_schemes='|'.join(URI_SCHEMES))
 
     def inline_url_repl(self, stack, url, url_target):
         url = Iri(url_target)
--- a/MoinMoin/converter/moinwiki_in.py	Fri Feb 08 08:32:26 2013 -0700
+++ b/MoinMoin/converter/moinwiki_in.py	Mon Feb 11 12:04:39 2013 -0700
@@ -18,9 +18,10 @@
 from MoinMoin import log
 logging = log.getLogger(__name__)
 
-from MoinMoin import config
+from MoinMoin.constants.contenttypes import CHARSET
+from MoinMoin.constants.misc import URI_SCHEMES
 from MoinMoin.util.iri import Iri
-from MoinMoin.util.tree import html, moin_page, xlink, xinclude
+from MoinMoin.util.tree import moin_page, xlink, xinclude
 from MoinMoin.util.interwiki import is_known_wiki
 from MoinMoin.i18n import _
 
@@ -694,7 +695,7 @@
             )?
             \]\]
         )
-    """ % dict(uri_schemes='|'.join(config.uri_schemes))
+    """ % dict(uri_schemes='|'.join(URI_SCHEMES))
 
     def inline_link_repl(self, stack, link, link_url=None, link_item=None,
             link_text=None, link_args=None,
@@ -717,14 +718,14 @@
                 # assume local language uses ":" inside of words, set link_item and continue
                 link_item = '{0}:{1}'.format(link_interwiki_site, link_interwiki_item)
         if link_args:
-            link_args = parse_arguments(link_args) # XXX needs different parsing
-            query = url_encode(link_args.keyword, charset=config.charset, encode_keys=True)
+            link_args = parse_arguments(link_args)  # XXX needs different parsing
+            query = url_encode(link_args.keyword, charset=CHARSET, encode_keys=True)
         else:
             query = None
         if link_item is not None:
-            att = 'attachment:' # moin 1.9 needed this for an attached file
+            att = 'attachment:'  # moin 1.9 needed this for an attached file
             if link_item.startswith(att):
-                link_item = '/' + link_item[len(att):] # now we have a subitem
+                link_item = '/' + link_item[len(att):]  # now we have a subitem
             if '#' in link_item:
                 path, fragment = link_item.rsplit('#', 1)
             else:
@@ -829,14 +830,14 @@
                            object_text=None, object_args=None):
         """Handles objects included in the page."""
         if object_args:
-            args = parse_arguments(object_args).keyword # XXX needs different parsing
+            args = parse_arguments(object_args).keyword  # XXX needs different parsing
         else:
             args = {}
         if object_item is not None:
-            query = url_encode(args, charset=config.charset, encode_keys=True)
-            att = 'attachment:' # moin 1.9 needed this for an attached file
+            query = url_encode(args, charset=CHARSET, encode_keys=True)
+            att = 'attachment:'  # moin 1.9 needed this for an attached file
             if object_item.startswith(att):
-                object_item = '/' + object_item[len(att):] # now we have a subitem
+                object_item = '/' + object_item[len(att):]  # now we have a subitem
             target = Iri(scheme='wiki.local', path=object_item, query=query, fragment=None)
             text = object_item
 
@@ -949,7 +950,8 @@
                     cell_markup = cell_markup.split('<')[1]
                     msg1 = _('Error:')
                     msg2 = _('is invalid within')
-                    cell_text = '[ {0} "{1}" {2} <{3}>&nbsp;]<<BR>>{4}'.format(msg1, error, msg2, cell_markup, cell_text)
+                    cell_text = '[ {0} "{1}" {2} <{3}>&nbsp;]<<BR>>{4}'.format(
+                        msg1, error, msg2, cell_markup, cell_text)
                     if no_errors:
                         add_attr_to_style(element.attrib, 'background-color: pink; color: black;')
                     no_errors = False
--- a/MoinMoin/converter/moinwiki_out.py	Fri Feb 08 08:32:26 2013 -0700
+++ b/MoinMoin/converter/moinwiki_out.py	Mon Feb 11 12:04:39 2013 -0700
@@ -29,8 +29,8 @@
     a_open = u'[['
     a_separator = u'|'
     a_close = u']]'
-    verbatim_open = u'{' # * 3
-    verbatim_close = u'}'# * 3
+    verbatim_open = u'{'  # * 3
+    verbatim_close = u'}'  # * 3
     monospace = u'`'
     strong = u"'''"
     emphasis = u"''"
@@ -182,7 +182,7 @@
         args = u''
         if len(href) > 1:
             # With normal
-            args = u','.join([u'&'+s for s in findall(r'(?:^|;|,|&|)(\w+=\w+)(?:,|&|$|)', href[1])])
+            args = u','.join([u'&' + s for s in findall(r'(?:^|;|,|&|)(\w+=\w+)(?:,|&|$|)', href[1])])
         href = href[0].split(u'wiki.local:')[-1]
         args = u','.join(s for s in [args, params] if s)
 
@@ -204,7 +204,8 @@
         for s in findall(r'}+', text):
             if max_subpage_lvl <= len(s):
                 max_subpage_lvl = len(s) + 1
-        ret = u'{0}\n{1}\n{2}\n'.format(Moinwiki.verbatim_open * max_subpage_lvl, text, Moinwiki.verbatim_close * max_subpage_lvl)
+        ret = u'{0}\n{1}\n{2}\n'.format(
+            Moinwiki.verbatim_open * max_subpage_lvl, text, Moinwiki.verbatim_close * max_subpage_lvl)
         return ret
 
     def open_moinpage_code(self, elem):
@@ -268,7 +269,8 @@
         if self.list_item_labels[-1] == u'' or self.list_item_labels[-1] == Moinwiki.definition_list_marker:
             self.list_item_labels[-1] = Moinwiki.definition_list_marker
             self.list_item_label = self.list_item_labels[-1] + u' '
-            ret = u' ' * (len(u''.join(self.list_item_labels[:-1])) + len(self.list_item_labels[:-1]))# self.list_level
+            ret = u' ' * (len(u''.join(self.list_item_labels[:-1])) +
+                          len(self.list_item_labels[:-1]))  # self.list_level
             if self.last_closed:
                 ret = u'\n{0}'.format(ret)
         childrens_output = self.open_children(elem)
@@ -278,7 +280,8 @@
         ret = u''
         if self.last_closed:
             ret = u'\n'
-        ret += u' ' * (len(u''.join(self.list_item_labels[:-1])) + len(self.list_item_labels[:-1])) + self.list_item_label
+        ret += u' ' * (len(u''.join(self.list_item_labels[:-1])) +
+                       len(self.list_item_labels[:-1])) + self.list_item_label
         return ret + self.open_children(elem)
 
     def open_moinpage_note(self, elem):
@@ -300,7 +303,7 @@
         href = href.split(u'?')
         args = u''
         if len(href) > 1:
-            args =u' '.join([s for s in findall(r'(?:^|;|,|&|)(\w+=\w+)(?:,|&|$)', href[1]) if s[:3] != u'do='])
+            args = u' '.join([s for s in findall(r'(?:^|;|,|&|)(\w+=\w+)(?:,|&|$)', href[1]) if s[:3] != u'do='])
         href = href[0].split(u'wiki.local:')[-1]
         # TODO: add '|' to Moinwiki class and rewrite this using % formatting
         ret = Moinwiki.object_open
@@ -354,7 +357,10 @@
             for s in findall(r'}+', childrens_output):
                 if max_subpage_lvl <= len(s):
                     max_subpage_lvl = len(s) + 1
-            return u'{0}{1}{2}{3}\n'.format(Moinwiki.verbatim_open * max_subpage_lvl, ret, childrens_output, Moinwiki.verbatim_close * max_subpage_lvl)
+            return u'{0}{1}{2}{3}\n'.format(
+                Moinwiki.verbatim_open * max_subpage_lvl,
+                ret, childrens_output,
+                Moinwiki.verbatim_close * max_subpage_lvl)
 
         self.status.append('text')
         childrens_output = self.open_children(elem)
@@ -377,7 +383,9 @@
         if len(type) == 2:
             if type[0] == "x-moin/macro":
                 if len(elem) and iter(elem).next().tag.name == "arguments":
-                    return u"<<{0}({1})>>\n".format(type[1].split(u'=')[1], u','.join([u''.join(c.itertext()) for c in iter(elem).next() if c.tag.name == u"argument"]))
+                    return u"<<{0}({1})>>\n".format(
+                        type[1].split(u'=')[1],
+                        u','.join([u''.join(c.itertext()) for c in iter(elem).next() if c.tag.name == u"argument"]))
                 else:
                     return u"<<{0}()>>\n".format(type[1].split(u'=')[1])
             elif type[0] == "x-moin/format":
@@ -406,7 +414,7 @@
         if hr_class:
             try:
                 height = int(hr_class.split(hr_class_prefix)[1]) - 1
-                if (0 <= height <= 5):
+                if 0 <= height <= 5:
                     hr_ending = (u'-' * height) + hr_ending
             except:
                 raise ElementException('page:separator has invalid class {0}'.format(hr_class))
@@ -502,7 +510,7 @@
         if table_cellstyle:
             attrib.append(u'style="{0}"'.format(table_cellstyle))
         if number_rows_spanned:
-            attrib.append(u'|'+unicode(number_rows_spanned))
+            attrib.append(u'|' + unicode(number_rows_spanned))
 
         attrib = u' '.join(attrib)
 
--- a/MoinMoin/converter/nonexistent_in.py	Fri Feb 08 08:32:26 2013 -0700
+++ b/MoinMoin/converter/nonexistent_in.py	Mon Feb 11 12:04:39 2013 -0700
@@ -27,7 +27,7 @@
     def __call__(self, rev, contenttype=None, arguments=None):
         item_name = rev.item.name
         attrib = {
-            xlink.href: Iri(scheme='wiki', authority='', path='/'+item_name, query='do=modify'),
+            xlink.href: Iri(scheme='wiki', authority='', path='/' + item_name, query='do=modify'),
         }
         a = moin_page.a(attrib=attrib, children=[_("%(item_name)s does not exist. Create it?", item_name=item_name)])
         body = moin_page.body(children=(a, ))
--- a/MoinMoin/converter/pdf_in.py	Fri Feb 08 08:32:26 2013 -0700
+++ b/MoinMoin/converter/pdf_in.py	Mon Feb 11 12:04:39 2013 -0700
@@ -20,7 +20,8 @@
 
 
 LAPARAMS = LAParams(
-    # value is specified not as an actual length, but as a proportion of the length to the size of each character in question.
+    # value is specified not as an actual length, but as a proportion of the length to the
+    # size of each character in question.
     # two text chunks whose distance is closer than the char_margin is considered
     # continuous and get grouped into one.
     char_margin=0.3,
--- a/MoinMoin/converter/pygments_in.py	Fri Feb 08 08:32:26 2013 -0700
+++ b/MoinMoin/converter/pygments_in.py	Mon Feb 11 12:04:39 2013 -0700
@@ -97,7 +97,8 @@
             """
             if lexer is None and contenttype is not None:
                 ct = Type(contenttype)
-                mimetype = '{0}/{1}'.format(ct.type, ct.subtype) # pygments can't process parameters (like e.g. ...;charset=utf-8)
+                # pygments can't process parameters (like e.g. ...;charset=utf-8):
+                mimetype = '{0}/{1}'.format(ct.type, ct.subtype)
                 try:
                     lexer = pygments.lexers.get_lexer_for_mimetype(mimetype)
                 except pygments.util.ClassNotFound:
@@ -121,7 +122,7 @@
 else:
     # we have no Pygments, minimal Converter replacement, so highlight view does not crash
     class Converter(object):
-        def __init__(self, lexer=None, mimetype=None):
+        def __init__(self, lexer=None, contenttype=None):
             pass
 
         def __call__(self, content, arguments=None):
--- a/MoinMoin/converter/rst_in.py	Fri Feb 08 08:32:26 2013 -0700
+++ b/MoinMoin/converter/rst_in.py	Mon Feb 11 12:04:39 2013 -0700
@@ -235,10 +235,10 @@
         new_element = moin_page.table_cell()
         if 'morerows' in node.attributes:
             new_element.set(moin_page.number_rows_spanned,
-                            repr(int(node['morerows'])+1))
+                            repr(int(node['morerows']) + 1))
         if 'morecols' in node.attributes:
             new_element.set(moin_page.number_cols_spanned,
-                            repr(int(node['morecols'])+1))
+                            repr(int(node['morecols']) + 1))
         self.open_moin_page_node(new_element)
 
     def depart_entry(self, node):
@@ -400,7 +400,9 @@
             for name, value in named_args:
                 args.append(moin_page.argument(attrib={moin_page.name: name}, children=[value]))
             arguments = moin_page.arguments(children=args)
-            self.open_moin_page_node(moin_page.part(children=[arguments], attrib={moin_page.content_type: "x-moin/format;name={0}".format(parser.split(' ')[0])}))
+            self.open_moin_page_node(moin_page.part(children=[arguments],
+                                                    attrib={moin_page.content_type:
+                                                                "x-moin/format;name={0}".format(parser.split(' ')[0])}))
         else:
             self.open_moin_page_node(moin_page.blockcode())
 
@@ -430,7 +432,7 @@
 
     def visit_reference(self, node):
         refuri = node.get('refuri', u'')
-        if refuri.startswith(u'<<') and refuri.endswith(u'>>'): # moin macro
+        if refuri.startswith(u'<<') and refuri.endswith(u'>>'):  # moin macro
             macro_name = refuri[2:-2].split(u'(')[0]
             if macro_name == u"TableOfContents":
                 arguments = refuri[2:-2].split(u'(')[1][:-1].split(u',')
@@ -624,8 +626,8 @@
     visitor_attributes = []
 
     def translate(self):
-        self.visitor = visitor = NodeVisitor(self.document)
-        self.document.walkabout(visitor)
+        self.visitor = visitor = NodeVisitor()
+        walkabout(self.document, visitor)
         self.output = visitor.tree()
 
 
@@ -761,12 +763,21 @@
             try:
                 docutils_tree = core.publish_doctree(source=input)
             except utils.SystemMessage as inst:
-                string_numb = re.match(re.compile(r'<string>\:([0-9]*)\:\s*\(.*?\)\s*(.*)', re.X | re.U | re.M | re.S), str(inst))
+                string_numb = re.match(re.compile(r'<string>\:([0-9]*)\:\s*\(.*?\)\s*(.*)',
+                                                  re.X | re.U | re.M | re.S), str(inst))
                 if string_numb:
                     str_num = string_numb.group(1)
                     input = input.split('\n')
                     if str_num:
-                        input = ['.. error::\n ::\n\n  Parse error on line number {0}:\n\n  {1}\n\n  Go back and try fix that.\n\n'.format(str_num, string_numb.group(2).replace('\n', '\n  '))]
+                        input = [('.. error::\n'
+                                  ' ::\n'
+                                  '\n'
+                                  '  Parse error on line number {0}:\n'
+                                  '\n'
+                                  '  {1}\n'
+                                  '\n'
+                                  '  Go back and try fix that.\n'
+                                  '\n').format(str_num, string_numb.group(2).replace('\n', '\n  '))]
                         continue
                 else:
                     input = ['.. error::\n ::\n\n  {0}\n\n'.format(str(inst).replace('\n', '\n  '))]
--- a/MoinMoin/converter/rst_out.py	Fri Feb 08 08:32:26 2013 -0700
+++ b/MoinMoin/converter/rst_out.py	Mon Feb 11 12:04:39 2013 -0700
@@ -65,7 +65,7 @@
         self.j = 0
         self.table.append(row)
         if self.i > 0:
-            if len(self.table[-2]) > (self.j):
+            if len(self.table[-2]) > self.j:
                 self.add_cell(self.table[-2][self.j][0],
                                 self.table[-2][self.j][1] - 1, Cell(''))
         return row
@@ -92,8 +92,8 @@
         if cs < 1 or rs < 1:
             return
         self.table[-1].append((cs, rs, cell))
-        for i in range(cs-1):
-            self.table[-1].append((cs-i-1, rs, Cell('')))
+        for i in range(cs - 1):
+            self.table[-1].append((cs - i - 1, rs, Cell('')))
         self.j += cs
         if self.i > 0:
             if len(self.table[-2]) > self.j:
@@ -166,7 +166,7 @@
             line = [u'+']
             row = self.table[0]
             for col in range(len(cols)):
-                line.append(u'-'*cols[col])
+                line.append(u'-' * cols[col])
                 if self.table[0][col][0] > 1:
                     line.append(u'-')
                 else:
@@ -192,11 +192,11 @@
                 line = [u'+']
                 for col in range(len(cols)):
                     if self.table[row][col][1] > 1:
-                        line.append(u' '*cols[col])
+                        line.append(u' ' * cols[col])
                     elif row == self.header_count - 1:
-                        line.append(u'='*cols[col])
+                        line.append(u'=' * cols[col])
                     else:
-                        line.append(u'-'*cols[col])
+                        line.append(u'-' * cols[col])
                     if self.table[row][col][0] > 1:
                         if row + 1 < len(rows)\
                                 and self.table[row + 1][col][0] > 1\
@@ -334,7 +334,8 @@
                         childrens_output.append(u'\n\n')
                 elif self.status[-1] == "list":
                     child =\
-                        re.sub(r"\n(.)", lambda m: u"\n{0}{1}".format(u' '*(len(u''.join(self.list_item_labels)) + len(self.list_item_labels)), m.group(1)), child)
+                        re.sub(r"\n(.)", lambda m: u"\n{0}{1}".format(u' ' *
+                            (len(u''.join(self.list_item_labels)) + len(self.list_item_labels)), m.group(1)), child)
                     if self.last_closed == "p":
                         childrens_output.append(u'\n'
                                 + u' '
@@ -346,7 +347,8 @@
                         childrens_output.append(u'\n')
                 elif self.status[-2] == "list":
                     child =\
-                        re.sub(r"\n(.)", lambda m: u"\n{0}{1}".format(u' '*(len(u''.join(self.list_item_labels)) + len(self.list_item_labels)), m.group(1)), child)
+                        re.sub(r"\n(.)", lambda m: u"\n{0}{1}".format(u' ' *
+                            (len(u''.join(self.list_item_labels)) + len(self.list_item_labels)), m.group(1)), child)
                 childrens_output.append(child)
                 self.last_closed = 'text'
         self.delete_newlines = delete_newlines
@@ -408,7 +410,8 @@
                 if i:
                     self.output[-1] = self.output[-1][:i]
             """
-        return u"::\n\n  {0}{1}\n\n".format(u' ' * (len(u''.join(self.list_item_labels)) + len(self.list_item_labels)), text)
+        return u"::\n\n  {0}{1}\n\n".format(u' ' *
+                    (len(u''.join(self.list_item_labels)) + len(self.list_item_labels)), text)
 
     def open_moinpage_code(self, elem):
         ret = u"{0}{1}{2}".format(ReST.monospace, u''.join(elem.itertext()), ReST.monospace)
@@ -510,8 +513,7 @@
         href = href.split(u'?')
         args = u''
         if len(href) > 1:
-            args =[s for s in re.findall(r'(?:^|;|,|&|)(\w+=\w+)(?:,|&|$)',
-                                            href[1]) if s[:3] != u'do=']
+            args = [s for s in re.findall(r'(?:^|;|,|&|)(\w+=\w+)(?:,|&|$)', href[1]) if s[:3] != u'do=']
         href = href[0]
         alt = elem.get(moin_page.alt, u'')
         if not alt:
@@ -520,7 +522,8 @@
             ret = u'|{0}|'.format(alt)
         args_text = u''
         if args:
-            args_text = u"\n  {0}".format(u'\n  '.join(u':{0}: {1}'.format(arg.split(u'=')[0], arg.split(u'=')[1]) for arg in args))
+            args_text = u"\n  {0}".format(u'\n  '.join(u':{0}: {1}'.format(
+                arg.split(u'=')[0], arg.split(u'=')[1]) for arg in args))
         self.objects.append(u".. {0} image:: {1}{2}".format(ret, href, args_text))
         return ret
 
@@ -586,7 +589,8 @@
         if len(type) == 2:
             if type[0] == u"x-moin/macro":
                 if len(elem) and iter(elem).next().tag.name == "arguments":
-                    alt = u"<<{0}({1})>>".format(type[1].split(u'=')[1], u','.join([u''.join(c.itertext()) for c in iter(elem).next() if c.tag.name == "argument"]))
+                    alt = u"<<{0}({1})>>".format(type[1].split(u'=')[1], u','.join(
+                            [u''.join(c.itertext()) for c in iter(elem).next() if c.tag.name == "argument"]))
                 else:
                     alt = u"<<{0}()>>".format(type[1].split(u'=')[1])
 
@@ -656,7 +660,8 @@
         table = repr(self.tablec)
         if self.status[-1] == "list":
             table =\
-                re.sub(r"\n(.)", lambda m: u"\n{0}{1}".format(u' '*(len(u''.join(self.list_item_labels)) + len(self.list_item_labels)), m.group(1)), u"\n" + table)
+                re.sub(r"\n(.)", lambda m: u"\n{0}{1}".format(u' ' *
+                        (len(u''.join(self.list_item_labels)) + len(self.list_item_labels)), m.group(1)), u"\n" + table)
             return table + ReST.p
         return table + ReST.linebreak
 
--- a/MoinMoin/converter/smiley.py	Fri Feb 08 08:32:26 2013 -0700
+++ b/MoinMoin/converter/smiley.py	Mon Feb 11 12:04:39 2013 -0700
@@ -64,7 +64,7 @@
     ($|(?=\s))  # we require either ending of line or some space after a smiley
 """ % {'smiley': u'|'.join([re.escape(s) for s in smileys])}
 
-    smiley_re = re.compile(smiley_rule, re.UNICODE|re.VERBOSE)
+    smiley_re = re.compile(smiley_rule, re.UNICODE | re.VERBOSE)
 
     # We do not process any smiley conversion within these elements.
     tags_to_ignore = set(['code', 'blockcode', ])
--- a/MoinMoin/converter/xml_in.py	Fri Feb 08 08:32:26 2013 -0700
+++ b/MoinMoin/converter/xml_in.py	Mon Feb 11 12:04:39 2013 -0700
@@ -14,7 +14,8 @@
 
 from ._util import decode_data
 
-RX_STRIPXML = re.compile(u"<[^>]*?>", re.U|re.DOTALL|re.MULTILINE)
+RX_STRIPXML = re.compile(u"<[^>]*?>", re.U | re.DOTALL | re.MULTILINE)
+
 
 def strip_xml(text):
     text = RX_STRIPXML.sub(u" ", text)
--- a/MoinMoin/datastruct/backends/__init__.py	Fri Feb 08 08:32:26 2013 -0700
+++ b/MoinMoin/datastruct/backends/__init__.py	Mon Feb 11 12:04:39 2013 -0700
@@ -209,7 +209,8 @@
         else:
             groups = flaskg.groups
             for group_name in self.member_groups:
-                if group_name not in processed_groups and group_name in groups and groups[group_name].__contains__(member, processed_groups):
+                if (group_name not in processed_groups and group_name in groups and
+                        groups[group_name].__contains__(member, processed_groups)):
                     return True
 
         return False
@@ -250,7 +251,8 @@
                     yield group_name
 
     def __repr__(self):
-        return "<{0!r} name={1!r} members={2!r} member_groups={3!r}>".format(self.__class__, self.name, self.members, self.member_groups)
+        return "<{0!r} name={1!r} members={2!r} member_groups={3!r}>".format(
+            self.__class__, self.name, self.members, self.member_groups)
 
 
 class BaseDict(object, DictMixin):
--- a/MoinMoin/datastruct/backends/_tests/test_composite_groups.py	Fri Feb 08 08:32:26 2013 -0700
+++ b/MoinMoin/datastruct/backends/_tests/test_composite_groups.py	Mon Feb 11 12:04:39 2013 -0700
@@ -13,7 +13,6 @@
 from MoinMoin.datastruct.backends._tests import GroupsBackendTest
 from MoinMoin.datastruct import ConfigGroups, CompositeGroups, GroupDoesNotExistError
 from MoinMoin._tests import wikiconfig
-from MoinMoin import security
 
 
 class TestCompositeGroupsBackend(GroupsBackendTest):
--- a/MoinMoin/datastruct/backends/_tests/test_lazy_config_groups.py	Fri Feb 08 08:32:26 2013 -0700
+++ b/MoinMoin/datastruct/backends/_tests/test_lazy_config_groups.py	Mon Feb 11 12:04:39 2013 -0700
@@ -8,7 +8,7 @@
 
 from MoinMoin.datastruct.backends._tests import GroupsBackendTest
 from MoinMoin.datastruct.backends.config_lazy_groups import ConfigLazyGroups
-from MoinMoin.datastruct import ConfigGroups, CompositeGroups, GroupDoesNotExistError
+from MoinMoin.datastruct import ConfigGroups, CompositeGroups
 from MoinMoin._tests import wikiconfig
 
 
--- a/MoinMoin/datastruct/backends/_tests/test_wiki_dicts.py	Fri Feb 08 08:32:26 2013 -0700
+++ b/MoinMoin/datastruct/backends/_tests/test_wiki_dicts.py	Mon Feb 11 12:04:39 2013 -0700
@@ -11,7 +11,7 @@
 
 from MoinMoin.datastruct.backends._tests import DictsBackendTest
 from MoinMoin.datastruct.backends import wiki_dicts
-from MoinMoin.config import SOMEDICT
+from MoinMoin.constants.keys import SOMEDICT
 from MoinMoin._tests import become_trusted, update_item
 DATA = "This is a dict item."
 
--- a/MoinMoin/datastruct/backends/_tests/test_wiki_groups.py	Fri Feb 08 08:32:26 2013 -0700
+++ b/MoinMoin/datastruct/backends/_tests/test_wiki_groups.py	Mon Feb 11 12:04:39 2013 -0700
@@ -17,7 +17,7 @@
 
 from MoinMoin.datastruct.backends._tests import GroupsBackendTest
 from MoinMoin.datastruct import GroupDoesNotExistError
-from MoinMoin.config import NAME, USERGROUP
+from MoinMoin.constants.keys import NAME, USERGROUP
 from MoinMoin.security import AccessControlList
 from MoinMoin.user import User
 from MoinMoin._tests import become_trusted, create_random_string_list, update_item
@@ -44,7 +44,7 @@
         assert u'ExampleUser' in flaskg.groups[u'SomeGroup']
         pytest.raises(GroupDoesNotExistError, lambda: flaskg.groups[u'AnotherGroup'])
 
-        item = update_item(u'SomeGroup', {NAME: u'AnotherGroup', USERGROUP: ["ExampleUser"]}, DATA)
+        item = update_item(u'SomeGroup', {NAME: [u'AnotherGroup', ], USERGROUP: ["ExampleUser"]}, DATA)
         assert u'ExampleUser' in flaskg.groups[u'AnotherGroup']
         pytest.raises(GroupDoesNotExistError, lambda: flaskg.groups[u'SomeGroup'])
 
--- a/MoinMoin/datastruct/backends/wiki_dicts.py	Fri Feb 08 08:32:26 2013 -0700
+++ b/MoinMoin/datastruct/backends/wiki_dicts.py	Mon Feb 11 12:04:39 2013 -0700
@@ -11,7 +11,7 @@
 
 from flask import g as flaskg
 
-from MoinMoin.config import CURRENT, SOMEDICT
+from MoinMoin.constants.keys import CURRENT, SOMEDICT
 from MoinMoin.datastruct.backends import BaseDict, BaseDictsBackend, DictDoesNotExistError
 
 
--- a/MoinMoin/datastruct/backends/wiki_groups.py	Fri Feb 08 08:32:26 2013 -0700
+++ b/MoinMoin/datastruct/backends/wiki_groups.py	Mon Feb 11 12:04:39 2013 -0700
@@ -16,7 +16,7 @@
 
 from flask import g as flaskg
 
-from MoinMoin.config import CURRENT, USERGROUP
+from MoinMoin.constants.keys import CURRENT, USERGROUP
 from MoinMoin.datastruct.backends import GreedyGroup, BaseGroupsBackend, GroupDoesNotExistError
 
 
--- a/MoinMoin/error.py	Fri Feb 08 08:32:26 2013 -0700
+++ b/MoinMoin/error.py	Mon Feb 11 12:04:39 2013 -0700
@@ -10,7 +10,8 @@
 
 import sys
 
-from MoinMoin import config
+from MoinMoin.constants.contenttypes import CHARSET
+
 
 class Error(Exception):
     """ Base class for moin moin errors
@@ -20,11 +21,11 @@
 
     Standard errors work safely only with strings using ascii or
     unicode. This class can be used safely with both strings using
-    config.charset and unicode.
+    CHARSET and unicode.
 
     You can init this class with either unicode or string using
-    config.charset encoding. On output, the class will convert the string
-    to unicode or the unicode to string, using config.charset.
+    CHARSET encoding. On output, the class will convert the string
+    to unicode or the unicode to string, using CHARSET.
 
     When you want to render an error, use unicode() or str() as needed.
     """
@@ -33,21 +34,21 @@
         """ Initialize an error, decode if needed
 
         :param message: unicode, str or object that support __unicode__
-            and __str__. __str__ should use config.charset.
+            and __str__. __str__ should use CHARSET.
         """
         self.message = message
 
     def __unicode__(self):
         """ Return unicode error message """
         if isinstance(self.message, str):
-            return unicode(self.message, config.charset)
+            return unicode(self.message, CHARSET)
         else:
             return unicode(self.message)
 
     def __str__(self):
         """ Return encoded message """
         if isinstance(self.message, unicode):
-            return self.message.encode(config.charset)
+            return self.message.encode(CHARSET)
         else:
             return str(self.message)
 
@@ -94,14 +95,17 @@
                 break
         return all
 
+
 class FatalError(CompositeError):
     """ Base class for fatal error we can't handle
 
     Do not use this class but its more specific sub classes.
     """
 
+
 class ConfigurationError(FatalError):
     """ Raise when fatal misconfiguration is found """
 
+
 class InternalError(FatalError):
     """ Raise when internal fatal error is found """
--- a/MoinMoin/forms.py	Fri Feb 08 08:32:26 2013 -0700
+++ b/MoinMoin/forms.py	Mon Feb 11 12:04:39 2013 -0700
@@ -9,10 +9,12 @@
 """
 
 
-import re, datetime
+import re
+import datetime
 import json
 
-from flatland import Element, Form, String, Integer, Boolean, Enum, Dict, JoinedString, List, Array, DateTime as _DateTime
+from flatland import (Element, Form, String, Integer, Boolean, Enum, Dict, JoinedString, List, Array,
+                      DateTime as _DateTime)
 from flatland.util import class_cloner, Unspecified
 from flatland.validation import Validator, Present, IsEmail, ValueBetween, URLValidator, Converted, ValueAtLeast
 from flatland.exc import AdaptationError
@@ -22,7 +24,6 @@
 from MoinMoin.constants.forms import *
 from MoinMoin.constants.keys import ITEMID, NAME
 from MoinMoin.i18n import _, L_, N_
-from MoinMoin.security.textcha import TextCha, TextChaizedForm, TextChaValid
 from MoinMoin.util.forms import FileStorage
 
 
@@ -47,7 +48,7 @@
     def validate(self, element, state):
         try:
             json.loads(element.value)
-        except: # catch ANY exception that happens due to unserializing
+        except:  # catch ANY exception that happens due to unserializing
             return self.note_error(element, state, 'invalid_json_msg')
         return True
 
@@ -59,7 +60,8 @@
 
 YourOpenID = OpenID.with_properties(placeholder=L_("Your OpenID address"))
 
-Email = String.using(label=L_('E-Mail')).with_properties(widget=WIDGET_EMAIL, placeholder=L_("E-Mail address")).validated_by(IsEmail())
+Email = String.using(label=L_('E-Mail')).with_properties(widget=WIDGET_EMAIL,
+                                                         placeholder=L_("E-Mail address")).validated_by(IsEmail())
 
 YourEmail = Email.with_properties(placeholder=L_("Your E-Mail address"))
 
@@ -88,7 +90,11 @@
     def u(self):
         return self.separator.join(child.u for child in self)
 
-Tags = MyJoinedString.of(String).with_properties(widget=WIDGET_TEXT).using(label=L_('Tags'), optional=True, separator=', ', separator_regex=re.compile(r'\s*,\s*'))
+Tags = MyJoinedString.of(String).with_properties(widget=WIDGET_TEXT).using(label=L_('Tags'), optional=True,
+                                                            separator=', ', separator_regex=re.compile(r'\s*,\s*'))
+
+Names = MyJoinedString.of(String).with_properties(widget=WIDGET_TEXT).using(label=L_('Names'), optional=True,
+                                                            separator=', ', separator_regex=re.compile(r'\s*,\s*'))
 
 Search = Text.using(default=u'', optional=True).with_properties(widget=WIDGET_SEARCH, placeholder=L_("Search Query"))
 
@@ -136,7 +142,8 @@
             dt = utctimestamp(dt)
         return dt
 
-DateTime = (DateTimeUNIX.with_properties(widget=WIDGET_DATETIME, placeholder=_("YYYY-MM-DD HH:MM:SS (example: 2013-12-31 23:59:59)"))
+DateTime = (DateTimeUNIX.with_properties(widget=WIDGET_DATETIME,
+                                         placeholder=_("YYYY-MM-DD HH:MM:SS (example: 2013-12-31 23:59:59)"))
             .validated_by(Converted(incorrect=L_("Please use the following format: YYYY-MM-DD HH:MM:SS"))))
 
 File = FileStorage.with_properties(widget=WIDGET_FILE)
@@ -164,6 +171,7 @@
             return self.note_error(element, state, 'invalid_reference_msg')
         return True
 
+
 class Reference(Select.with_properties(empty_label=L_(u'(None)')).validated_by(ValidReference())):
     """
     A metadata property that points to another item selected out of the
--- a/MoinMoin/i18n/_tests/test_i18n.py	Fri Feb 08 08:32:26 2013 -0700
+++ b/MoinMoin/i18n/_tests/test_i18n.py	Mon Feb 11 12:04:39 2013 -0700
@@ -6,7 +6,6 @@
 """
 
 from MoinMoin.i18n import get_locale, get_timezone
-import pytest
 
 from MoinMoin.i18n import _, L_, N_
 
--- a/MoinMoin/items/__init__.py	Fri Feb 08 08:32:26 2013 -0700
+++ b/MoinMoin/items/__init__.py	Mon Feb 11 12:04:39 2013 -0700
@@ -17,8 +17,6 @@
     Each class in this module corresponds to an itemtype.
 """
 
-import time
-import itertools
 import json
 from StringIO import StringIO
 from collections import namedtuple
@@ -32,7 +30,7 @@
 
 from jinja2 import Markup
 
-from whoosh.query import Term, And, Prefix
+from whoosh.query import Term, Prefix, And, Or, Not
 
 from MoinMoin import log
 logging = log.getLogger(__name__)
@@ -40,7 +38,6 @@
 from MoinMoin.security.textcha import TextCha, TextChaizedForm
 from MoinMoin.signalling import item_modified
 from MoinMoin.storage.middleware.protecting import AccessDenied
-from MoinMoin.storage.error import NoSuchItemError, NoSuchRevisionError, StorageError
 from MoinMoin.i18n import L_
 from MoinMoin.themes import render_template
 from MoinMoin.util.mime import Type
@@ -53,7 +50,7 @@
     CONTENTTYPE, SIZE, ACTION, ADDRESS, HOSTNAME, USERID, COMMENT,
     HASH_ALGORITHM, ITEMID, REVID, DATAID, CURRENT, PARENTID
     )
-from MoinMoin.constants.contenttypes import charset, CONTENTTYPE_NONEXISTENT
+from MoinMoin.constants.contenttypes import CHARSET, CONTENTTYPE_NONEXISTENT
 from MoinMoin.constants.itemtypes import (
     ITEMTYPE_NONEXISTENT, ITEMTYPE_USERPROFILE, ITEMTYPE_DEFAULT,
     )
@@ -94,8 +91,10 @@
 
 item_registry = RegistryItem()
 
+
 def register(cls):
-    item_registry.register(RegistryItem.Entry(cls._factory, cls.itemtype, cls.display_name, cls.description, cls.order), cls.shown)
+    item_registry.register(RegistryItem.Entry(cls._factory, cls.itemtype, cls.display_name, cls.description, cls.order),
+                           cls.shown)
     return cls
 
 
@@ -110,15 +109,17 @@
         self.data = StringIO('')
         self.revid = None
         if self.item:
-            self.meta[NAME] = self.item.name
+            self.meta[NAME] = [self.item.name]
 
 
 class DummyItem(object):
     """ if we have no stored Item, we use this dummy """
     def __init__(self, name):
         self.name = name
+
     def list_revisions(self):
-        return [] # same as an empty Item
+        return []  # same as an empty Item
+
     def destroy_all_revisions(self):
         return True
 
@@ -143,12 +144,12 @@
     :itemtype and :contenttype are used when creating a DummyRev, where
     metadata is not available from the storage.
     """
-    if 1: # try:
+    if 1:  # try:
         if item is None:
             item = flaskg.storage[name]
         else:
             name = item.name
-    if not item: # except NoSuchItemError:
+    if not item:  # except NoSuchItemError:
         logging.debug("No such item: {0!r}".format(name))
         item = DummyItem(name)
         rev = DummyRev(item, itemtype, contenttype)
@@ -157,11 +158,11 @@
         logging.debug("Got item: {0!r}".format(name))
         try:
             rev = item.get_revision(rev_id)
-        except KeyError: # NoSuchRevisionError:
+        except KeyError:  # NoSuchRevisionError:
             try:
-                rev = item.get_revision(CURRENT) # fall back to current revision
+                rev = item.get_revision(CURRENT)  # fall back to current revision
                 # XXX add some message about invalid revision
-            except KeyError: # NoSuchRevisionError:
+            except KeyError:  # NoSuchRevisionError:
                 logging.debug("Item {0!r} has no revisions.".format(name))
                 rev = DummyRev(item, itemtype, contenttype)
                 logging.debug("Item {0!r}, created dummy revision with contenttype {1!r}".format(name, contenttype))
@@ -199,10 +200,26 @@
         return form
 
 
+UNKNOWN_ITEM_GROUP = "unknown items"
+
+
+def _build_contenttype_query(groups):
+    """
+    Build a Whoosh query from a list of contenttype groups.
+    """
+    queries = []
+    for g in groups:
+        for e in content_registry.groups[g]:
+            ct_unicode = unicode(e.content_type)
+            queries.append(Term(CONTENTTYPE, ct_unicode))
+            queries.append(Prefix(CONTENTTYPE, ct_unicode + u';'))
+    return Or(queries)
+
 IndexEntry = namedtuple('IndexEntry', 'relname meta')
 
 MixedIndexEntry = namedtuple('MixedIndexEntry', 'relname meta hassubitems')
 
+
 class Item(object):
     """ Highlevel (not storage) Item, wraps around a storage Revision"""
     # placeholder values for registry entry properties
@@ -236,7 +253,7 @@
         property.
         """
         rev = get_storage_revision(name, itemtype, contenttype, rev_id, item)
-        contenttype = rev.meta.get(CONTENTTYPE) or contenttype # use contenttype in case our metadata does not provide CONTENTTYPE
+        contenttype = rev.meta.get(CONTENTTYPE) or contenttype
         logging.debug("Item {0!r}, got contenttype {1!r} from revision meta".format(name, contenttype))
         #logging.debug("Item %r, rev meta dict: %r" % (name, dict(rev.meta)))
 
@@ -273,11 +290,10 @@
 
     def meta_filter(self, meta):
         """ kill metadata entries that we set automatically when saving """
-        kill_keys = [# shall not get copied from old rev to new rev
+        kill_keys = [  # shall not get copied from old rev to new rev
             SYSITEM_VERSION,
             NAME_OLD,
             # are automatically implanted when saving
-            NAME,
             ITEMID, REVID, DATAID,
             HASH_ALGORITHM,
             SIZE,
@@ -313,31 +329,34 @@
             meta[PARENTID] = revid
         return meta
 
-    def _rename(self, name, comment, action):
-        self._save(self.meta, self.content.data, name=name, action=action, comment=comment)
-        old_prefixlen = len(self.subitems_prefix)
-        new_prefix = name + '/'
+    def _rename(self, name, comment, action, delete=False):
+        self._save(self.meta, self.content.data, name=name, action=action, comment=comment, delete=delete)
+        old_prefix = self.subitems_prefix
+        old_prefixlen = len(old_prefix)
+        if not delete:
+            new_prefix = name + '/'
         for child in self.get_subitem_revs():
-            child_oldname = child.meta[NAME]
-            child_newname = new_prefix + child_oldname[old_prefixlen:]
-            item = Item.create(child_oldname)
-            item._save(item.meta, item.content.data, name=child_newname, action=action, comment=comment)
+            for child_oldname in child.meta[NAME]:
+                if child_oldname.startswith(old_prefix):
+                    if delete:
+                        child_newname = None
+                    else:  # rename
+                        child_newname = new_prefix + child_oldname[old_prefixlen:]
+                    item = Item.create(child_oldname)
+                    item._save(item.meta, item.content.data,
+                               name=child_newname, action=action, comment=comment, delete=delete)
 
     def rename(self, name, comment=u''):
         """
-        rename this item to item <name>
+        rename this item to item <name> (replace current name by another name in the NAME list)
         """
         return self._rename(name, comment, action=u'RENAME')
 
     def delete(self, comment=u''):
         """
-        delete this item
+        delete this item (remove current name from NAME list)
         """
-        trash_prefix = u'Trash/' # XXX move to config
-        now = time.strftime('%Y-%m-%d %H:%M:%S', time.gmtime())
-        # make trash name unique by including timestamp:
-        trashname = u'{0}{1} ({2} UTC)'.format(trash_prefix, self.name, now)
-        return self._rename(trashname, comment, action=u'TRASH')
+        return self._rename(None, comment, action=u'TRASH', delete=True)
 
     def revert(self, comment=u''):
         return self._save(self.meta, self.content.data, action=u'REVERT', comment=comment)
@@ -392,28 +411,40 @@
         """
         raise NotImplementedError
 
-    def _save(self, meta, data=None, name=None, action=u'SAVE', contenttype_guessed=None, comment=u'', overwrite=False):
+    def _save(self, meta, data=None, name=None, action=u'SAVE', contenttype_guessed=None, comment=u'',
+              overwrite=False, delete=False):
         backend = flaskg.storage
         storage_item = backend[self.name]
         try:
             currentrev = storage_item.get_revision(CURRENT)
             rev_id = currentrev.revid
             contenttype_current = currentrev.meta.get(CONTENTTYPE)
-        except KeyError: # XXX was: NoSuchRevisionError:
+        except KeyError:  # XXX was: NoSuchRevisionError:
             currentrev = None
             rev_id = None
             contenttype_current = None
 
-        meta = dict(meta) # we may get a read-only dict-like, copy it
+        meta = dict(meta)  # we may get a read-only dict-like, copy it
 
         # we store the previous (if different) and current item name into revision metadata
         # this is useful for rename history and backends that use item uids internally
         if name is None:
             name = self.name
         oldname = meta.get(NAME)
-        if oldname and oldname != name:
-            meta[NAME_OLD] = oldname
-        meta[NAME] = name
+        if oldname:
+            if not isinstance(oldname, list):
+                oldname = [oldname]
+            if delete or name not in oldname:  # this is a delete or rename
+                meta[NAME_OLD] = oldname[:]
+                try:
+                    oldname.remove(self.name)
+                except ValueError:
+                    pass
+                if not delete:
+                    oldname.append(name)
+                meta[NAME] = oldname
+        else:
+            meta[NAME] = [name]
 
         if comment:
             meta[COMMENT] = unicode(comment)
@@ -431,7 +462,7 @@
                 data = ''
 
         if isinstance(data, unicode):
-            data = data.encode(charset) # XXX wrong! if contenttype gives a coding, we MUST use THAT.
+            data = data.encode(CHARSET)  # XXX wrong! if contenttype gives a coding, we MUST use THAT.
 
         if isinstance(data, str):
             data = StringIO(data)
@@ -490,67 +521,49 @@
         added_dir_relnames = set()
 
         for rev in subitems:
-            fullname = rev.meta[NAME]
-            relname = fullname[prefixlen:]
-            if '/' in relname:
-                # Find the *direct* subitem that is the ancestor of current
-                # (indirect) subitem. e.g. suppose when the index root is
-                # 'foo', and current item (`rev`) is 'foo/bar/lorem/ipsum',
-                # 'foo/bar' will be found.
-                direct_relname = relname.partition('/')[0]
-                if direct_relname not in added_dir_relnames:
-                    added_dir_relnames.add(direct_relname)
-                    direct_fullname = prefix + direct_relname
-                    direct_rev = get_storage_revision(direct_fullname)
-                    dirs.append(IndexEntry(direct_relname, direct_rev.meta))
-            else:
-                files.append(IndexEntry(relname, rev.meta))
+            fullnames = rev.meta[NAME]
+            for fullname in fullnames:
+                if fullname.startswith(prefix):
+                    relname = fullname[prefixlen:]
+                    if '/' in relname:
+                        # Find the *direct* subitem that is the ancestor of current
+                        # (indirect) subitem. e.g. suppose when the index root is
+                        # 'foo', and current item (`rev`) is 'foo/bar/lorem/ipsum',
+                        # 'foo/bar' will be found.
+                        direct_relname = relname.partition('/')[0]
+                        if direct_relname not in added_dir_relnames:
+                            added_dir_relnames.add(direct_relname)
+                            direct_fullname = prefix + direct_relname
+                            direct_rev = get_storage_revision(direct_fullname)
+                            dirs.append(IndexEntry(direct_relname, direct_rev.meta))
+                    else:
+                        files.append(IndexEntry(relname, rev.meta))
 
         return dirs, files
 
-    @timed()
-    def filter_index(self, index, startswith=None, selected_groups=None):
-        """
-        Filter a list of IndexEntry.
-
-        :param startswith: if set, only items whose names start with startswith
-                           are selected.
-        :param selected_groups: if set, only items whose contentypes belong to
-                                the selected contenttype_groups are selected.
-        """
-        if startswith is not None:
-            index = [e for e in index
-                     if e.relname.startswith((startswith, startswith.swapcase()))]
+    def build_index_query(self, startswith=None, selected_groups=None):
+        prefix = self.subitems_prefix
+        if startswith:
+            query = Prefix(NAME_EXACT, prefix + startswith) | Prefix(NAME_EXACT, prefix + startswith.swapcase())
+        else:
+            query = Prefix(NAME_EXACT, prefix)
 
-        def build_contenttypes(groups):
-            contenttypes = []
-            for g in groups:
-                entries = content_registry.groups.get(g, []) # .get is a temporary workaround for "unknown items" group
-                contenttypes.extend([e.content_type for e in entries])
-            return contenttypes
+        if selected_groups:
+            selected_groups = set(selected_groups)
+            has_unknown = UNKNOWN_ITEM_GROUP in selected_groups
+            if has_unknown:
+                selected_groups.remove(UNKNOWN_ITEM_GROUP)
+            ct_query = _build_contenttype_query(selected_groups)
+            if has_unknown:
+                ct_query |= Not(_build_contenttype_query(content_registry.groups))
+            query &= ct_query
 
-        def contenttype_match(tested, cts):
-            for ct in cts:
-                if ct.issupertype(tested):
-                    return True
-            return False
-
-        if selected_groups is not None:
-            selected_contenttypes = build_contenttypes(selected_groups)
-            filtered_index = [e for e in index if contenttype_match(Type(e.meta[CONTENTTYPE]), selected_contenttypes)]
-
-            unknown_item_group = "unknown items"
-            if unknown_item_group in selected_groups:
-                all_contenttypes = build_contenttypes(content_registry.group_names)
-                filtered_index.extend([e for e in index
-                                       if not contenttype_match(Type(e.meta[CONTENTTYPE]), all_contenttypes)])
-
-            index = filtered_index
-        return index
+        return query
 
     def get_index(self, startswith=None, selected_groups=None):
-        dirs, files = self.make_flat_index(self.get_subitem_revs())
-        return dirs, self.filter_index(files, startswith, selected_groups)
+        query = Term(WIKINAME, app.cfg.interwikiname) & self.build_index_query(startswith, selected_groups)
+        revs = flaskg.storage.search(query, sortedby=NAME_EXACT, limit=None)
+        return self.make_flat_index(revs)
 
     def get_mixed_index(self):
         dirs, files = self.make_flat_index(self.get_subitem_revs())
@@ -561,10 +574,24 @@
 
     index_template = 'index.html'
 
-    def name_initial(self, subitems):
-        prefixlen = len(self.subitems_prefix)
-        initials = [(item.meta[NAME][prefixlen]) for item in subitems]
-        return initials
+    def name_initial(self, subitems, uppercase=False, lowercase=False):
+        """
+        return a sorted list of first characters of subitem names,
+        optionally all uppercased or lowercased.
+        """
+        prefix = self.subitems_prefix
+        prefixlen = len(prefix)
+        initials = set()
+        for item in subitems:
+            for name in item.meta[NAME]:
+                if name.startswith(prefix):
+                    initial = name[prefixlen]
+                    if uppercase:
+                        initial = initial.upper()
+                    elif lowercase:
+                        initial = initial.lower()
+                    initials.add(initial)
+        return sorted(list(initials))
 
     delete_template = 'delete.html'
     destroy_template = 'destroy.html'
@@ -613,8 +640,8 @@
 
     def do_show(self, revid):
         show_revision = revid != CURRENT
-        show_navigation = False # TODO
-        first_rev = last_rev = None # TODO
+        show_navigation = False  # TODO
+        first_rev = last_rev = None  # TODO
         return render_template(self.show_template,
                                item=self, item_name=self.name,
                                rev=self.rev,
@@ -628,7 +655,7 @@
 
     def do_modify(self):
         method = request.method
-        if method == 'GET':
+        if method in ['GET', 'HEAD']:
             if isinstance(self.content, NonExistentContent):
                 return render_template('modify_select_contenttype.html',
                                        item_name=self.name,
@@ -721,6 +748,22 @@
                                itemtypes=item_registry.shown_entries,
                               )
 
+    def rename(self, name, comment=u''):
+        # pointless for non-existing items
+        pass
+
+    def delete(self, comment=u''):
+        # pointless for non-existing items
+        pass
+
+    def revert(self, comment=u''):
+        # pointless for non-existing items
+        pass
+
+    def destroy(self, comment=u'', destroy_item=False):
+        # pointless for non-existing items
+        pass
+
 
 from ..util.pysupport import load_package_modules
 load_package_modules(__name__, __path__)
--- a/MoinMoin/items/_tests/test_Blog.py	Fri Feb 08 08:32:26 2013 -0700
+++ b/MoinMoin/items/_tests/test_Blog.py	Mon Feb 11 12:04:39 2013 -0700
@@ -11,7 +11,7 @@
 
 from MoinMoin._tests import become_trusted, update_item
 from MoinMoin.items import Item
-from MoinMoin.config import CONTENTTYPE, ITEMTYPE, PTIME, ACL, TAGS
+from MoinMoin.constants.keys import CONTENTTYPE, ITEMTYPE, PTIME, ACL, TAGS
 from MoinMoin.items.blog import ITEMTYPE_BLOG, ITEMTYPE_BLOG_ENTRY
 from MoinMoin.items.blog import Blog, BlogEntry
 
--- a/MoinMoin/items/_tests/test_Content.py	Fri Feb 08 08:32:26 2013 -0700
+++ b/MoinMoin/items/_tests/test_Content.py	Mon Feb 11 12:04:39 2013 -0700
@@ -9,7 +9,6 @@
 
 import pytest
 
-from flask import g as flaskg
 from flask import Markup
 
 from werkzeug import escape
@@ -19,7 +18,7 @@
 from MoinMoin._tests import become_trusted, update_item
 from MoinMoin.items import Item
 from MoinMoin.items.content import Content, ApplicationXTar, Binary, Text, Image, TransformableBitmapImage, MarkupItem
-from MoinMoin.config import CONTENTTYPE, ADDRESS, COMMENT, HOSTNAME, USERID, ACTION
+from MoinMoin.constants.keys import CONTENTTYPE
 
 
 class TestContent(object):
--- a/MoinMoin/items/_tests/test_Item.py	Fri Feb 08 08:32:26 2013 -0700
+++ b/MoinMoin/items/_tests/test_Item.py	Mon Feb 11 12:04:39 2013 -0700
@@ -8,17 +8,11 @@
 
 import pytest
 
-from flask import g as flaskg
-from flask import Markup
-
 from werkzeug import escape
 
-from MoinMoin.util import diff_html
-
 from MoinMoin._tests import become_trusted, update_item
 from MoinMoin.items import Item, NonExistent, IndexEntry, MixedIndexEntry
-from MoinMoin.items.content import Binary, Text, Image, TransformableBitmapImage, MarkupItem
-from MoinMoin.constants.keys import ITEMTYPE, CONTENTTYPE, NAME, ADDRESS, COMMENT, HOSTNAME, USERID, ACTION
+from MoinMoin.constants.keys import ITEMTYPE, CONTENTTYPE, NAME, COMMENT
 
 
 def build_index(basename, relnames):
@@ -37,9 +31,10 @@
     return [(MixedIndexEntry(relname, Item.create('/'.join((basename, relname))).meta, hassubitem))
             for relname, hassubitem in spec]
 
+
 class TestItem(object):
 
-    def testNonExistent(self):
+    def _testNonExistent(self):
         item = Item.create(u'DoesNotExist')
         assert isinstance(item, NonExistent)
         meta, data = item.meta, item.content.data
@@ -102,7 +97,7 @@
 
         # test Item.make_flat_index
         # TODO: test Item.get_subitem_revs
-        dirs, files = baseitem.make_flat_index(baseitem.get_subitem_revs())
+        dirs, files = baseitem.get_index()
         assert dirs == build_index(basename, [u'cd', u'ij'])
         assert files == build_index(basename, [u'ab', u'gh', u'ij', u'mn'])
 
@@ -116,46 +111,66 @@
             (u'mn', False),
         ])
 
-        # test Item.filter_index
         # check filtered index when startswith param is passed
-        filtered_files = baseitem.filter_index(files, startswith=u'a')
-        assert filtered_files == build_index(basename, [u'ab'])
+        dirs, files = baseitem.get_index(startswith=u'a')
+        assert dirs == []
+        assert files == build_index(basename, [u'ab'])
 
         # check filtered index when contenttype_groups is passed
-        ctgroups = ["image items"]
-        filtered_files = baseitem.filter_index(files, selected_groups=ctgroups)
-        assert filtered_files == build_index(basename, [u'mn'])
-
-        # If we ask for text/plain type, should Foo/cd be returned?
+        ctgroups = ["other text items"]
+        dirs, files = baseitem.get_index(selected_groups=ctgroups)
+        assert dirs == build_index(basename, [u'cd', u'ij'])
+        assert files == build_index(basename, [u'ab', u'gh', u'ij'])
 
     def test_meta_filter(self):
         name = u'Test_item'
         contenttype = u'text/plain;charset=utf-8'
-        meta = {'test_key': 'test_val', CONTENTTYPE: contenttype, 'name': 'test_name'}
+        meta = {'test_key': 'test_val', CONTENTTYPE: contenttype, NAME: [u'test_name'], 'address': u'1.2.3.4'}
         item = Item.create(name)
         result = Item.meta_filter(item, meta)
         # keys like NAME, ITEMID, REVID, DATAID are filtered
-        expected = {'test_key': 'test_val', CONTENTTYPE: contenttype}
+        expected = {'test_key': 'test_val', CONTENTTYPE: contenttype, NAME: [u'test_name']}
         assert result == expected
 
     def test_meta_dict_to_text(self):
         name = u'Test_item'
         contenttype = u'text/plain;charset=utf-8'
-        meta = {'test_key': 'test_val', CONTENTTYPE: contenttype, 'name': 'test_name'}
+        meta = {'test_key': 'test_val', CONTENTTYPE: contenttype, NAME: [u'test_name']}
         item = Item.create(name)
         result = Item.meta_dict_to_text(item, meta)
-        expected = '{\n  "contenttype": "text/plain;charset=utf-8", \n  "test_key": "test_val"\n}'
+        expected = '{\n  "contenttype": "text/plain;charset=utf-8", \n  "name": [\n    "test_name"\n  ], \n  "test_key": "test_val"\n}'
         assert result == expected
 
     def test_meta_text_to_dict(self):
         name = u'Test_item'
         contenttype = u'text/plain;charset=utf-8'
-        text = '{\n  "contenttype": "text/plain;charset=utf-8", \n  "test_key": "test_val", \n "name": "test_name" \n}'
+        text = '{\n  "contenttype": "text/plain;charset=utf-8", \n  "test_key": "test_val", \n "name": ["test_name"] \n}'
         item = Item.create(name)
         result = Item.meta_text_to_dict(item, text)
-        expected = {'test_key': 'test_val', CONTENTTYPE: contenttype}
+        expected = {'test_key': 'test_val', CONTENTTYPE: contenttype, NAME: [u"test_name"]}
         assert result == expected
 
+    def test_item_can_have_several_names(self):
+        content = u"This is page content"
+
+        update_item(u'Page',
+                    {NAME: [u'Page',
+                            u'Another name',
+                            ],
+                     CONTENTTYPE: u'text/x.moin.wiki'}, content)
+
+        item1 = Item.create(u'Page')
+        assert item1.name == u'Page'
+        assert item1.meta[CONTENTTYPE] == 'text/x.moin.wiki'
+        assert item1.content.data == content
+
+        item2 = Item.create(u'Another name')
+        assert item2.name == u'Another name'
+        assert item2.meta[CONTENTTYPE] == 'text/x.moin.wiki'
+        assert item2.content.data == content
+
+        assert item1.rev.revid == item2.rev.revid
+
     def test_rename(self):
         name = u'Test_Item'
         contenttype = u'text/plain;charset=utf-8'
@@ -178,10 +193,43 @@
         # item at new name and its contents after renaming
         item = Item.create(new_name)
         assert item.name == u'Test_new_Item'
-        assert item.meta['name_old'] == u'Test_Item'
+        assert item.meta['name_old'] == [u'Test_Item']
         assert item.meta['comment'] == u'renamed'
         assert item.content.data == u'test_data'
 
+    def test_rename_acts_only_in_active_name_in_case_there_are_several_names(self):
+        content = u"This is page content"
+
+        update_item(u'Page',
+                    {NAME: [u'First',
+                            u'Second',
+                            u'Third',
+                            ],
+                     CONTENTTYPE: u'text/x.moin.wiki'}, content)
+
+        item = Item.create(u'Second')
+        item.rename(u'New name', comment=u'renamed')
+
+        item1 = Item.create(u'First')
+        assert item1.name == u'First'
+        assert item1.meta[CONTENTTYPE] == 'text/x.moin.wiki'
+        assert item1.content.data == content
+
+        item2 = Item.create(u'New name')
+        assert item2.name == u'New name'
+        assert item2.meta[CONTENTTYPE] == 'text/x.moin.wiki'
+        assert item2.content.data == content
+
+        item3 = Item.create(u'Third')
+        assert item3.name == u'Third'
+        assert item3.meta[CONTENTTYPE] == 'text/x.moin.wiki'
+        assert item3.content.data == content
+
+        assert item1.rev.revid == item2.rev.revid == item3.rev.revid
+
+        item4 = Item.create(u'Second')
+        assert item4.meta[CONTENTTYPE] == 'application/x-nonexistent'
+
     def test_rename_recursion(self):
         update_item(u'Page', {CONTENTTYPE: u'text/x.moin.wiki'}, u'Page 1')
         update_item(u'Page/Child', {CONTENTTYPE: u'text/x.moin.wiki'}, u'this is child')
@@ -204,22 +252,51 @@
         # item at new name and its contents after renaming
         item = Item.create(u'Renamed_Page')
         assert item.name == u'Renamed_Page'
-        assert item.meta['name_old'] == u'Page'
+        assert item.meta['name_old'] == [u'Page']
         assert item.meta['comment'] == u'renamed'
         assert item.content.data == u'Page 1'
 
         item = Item.create(u'Renamed_Page/Child')
         assert item.name == u'Renamed_Page/Child'
-        assert item.meta['name_old'] == u'Page/Child'
+        assert item.meta['name_old'] == [u'Page/Child']
         assert item.meta['comment'] == u'renamed'
         assert item.content.data == u'this is child'
 
         item = Item.create(u'Renamed_Page/Child/Another')
         assert item.name == u'Renamed_Page/Child/Another'
-        assert item.meta['name_old'] == u'Page/Child/Another'
+        assert item.meta['name_old'] == [u'Page/Child/Another']
         assert item.meta['comment'] == u'renamed'
         assert item.content.data == u'another child'
 
+    def test_rename_recursion_with_multiple_names_and_children(self):
+        update_item(u'Foo',
+                    {CONTENTTYPE: u'text/x.moin.wiki',
+                         NAME: [u'Other', u'Page', u'Foo']},
+                    u'Parent')
+        update_item(u'Page/Child', {CONTENTTYPE: u'text/x.moin.wiki'}, u'Child of Page')
+        update_item(u'Other/Child2', {CONTENTTYPE: u'text/x.moin.wiki'}, u'Child of Other')
+        update_item(u'Another',
+                    {CONTENTTYPE: u'text/x.moin.wiki',
+                     NAME: [u'Another', u'Page/Second']
+                         },
+                    u'Both')
+        update_item(u'Page/Second/Child', {CONTENTTYPE: u'text/x.moin.wiki'}, u'Child of Second')
+        update_item(u'Another/Child', {CONTENTTYPE: u'text/x.moin.wiki'}, u'Child of Another')
+
+        item = Item.create(u'Page')
+
+        item.rename(u'Renamed', comment=u'renamed')
+
+        assert Item.create(u'Page/Child').meta[CONTENTTYPE] == 'application/x-nonexistent'
+        assert Item.create(u'Renamed/Child').content.data == u'Child of Page'
+        assert Item.create(u'Page/Second').meta[CONTENTTYPE] == 'application/x-nonexistent'
+        assert Item.create(u'Renamed/Second').content.data == u'Both'
+        assert Item.create(u'Another').content.data == u'Both'
+        assert Item.create(u'Page/Second/Child').meta[CONTENTTYPE] == 'application/x-nonexistent'
+        assert Item.create(u'Renamed/Second/Child').content.data == u'Child of Second'
+        assert Item.create(u'Other/Child2').content.data == u'Child of Other'
+        assert Item.create(u'Another/Child').content.data == u'Child of Another'
+
     def test_delete(self):
         name = u'Test_Item2'
         contenttype = u'text/plain;charset=utf-8'
--- a/MoinMoin/items/blog.py	Fri Feb 08 08:32:26 2013 -0700
+++ b/MoinMoin/items/blog.py	Mon Feb 11 12:04:39 2013 -0700
@@ -20,7 +20,7 @@
 from MoinMoin.themes import render_template
 from MoinMoin.forms import OptionalText, Tags, DateTime
 from MoinMoin.storage.middleware.protecting import AccessDenied
-from MoinMoin.constants.keys import NAME, NAME_EXACT, WIKINAME, ITEMTYPE, MTIME, PTIME, TAGS
+from MoinMoin.constants.keys import NAME_EXACT, WIKINAME, ITEMTYPE, MTIME, PTIME, TAGS
 from MoinMoin.items import Item, Default, register, BaseMetaForm
 
 
@@ -32,11 +32,13 @@
     supertags = (Tags.using(label=L_('Supertags (Categories)'))
                  .with_properties(placeholder=L_("Ordered comma separated list of tags")))
 
+
 class BlogEntryMetaForm(BaseMetaForm):
     summary = (OptionalText.using(label=L_("Title"))
                .with_properties(placeholder=L_("One-line title of the blog entry")))
     ptime = DateTime.using(label=L_('Publication time (UTC)'), optional=True)
 
+
 @register
 class Blog(Default):
     itemtype = ITEMTYPE_BLOG
@@ -81,7 +83,7 @@
         ptime_sort_facet = FunctionFacet(ptime_sort_key)
 
         revs = flaskg.storage.search(query, sortedby=ptime_sort_facet, reverse=True, limit=None)
-        blog_entry_items = [Item.create(rev.meta[NAME], rev_id=rev.revid) for rev in revs]
+        blog_entry_items = [Item.create(rev.name, rev_id=rev.revid) for rev in revs]
         return render_template('blog/main.html',
                                item_name=self.name,
                                blog_item=self,
@@ -89,6 +91,7 @@
                                tag=tag,
                               )
 
+
 @register
 class BlogEntry(Default):
     itemtype = ITEMTYPE_BLOG_ENTRY
--- a/MoinMoin/items/content.py	Fri Feb 08 08:32:26 2013 -0700
+++ b/MoinMoin/items/content.py	Mon Feb 11 12:04:39 2013 -0700
@@ -17,7 +17,9 @@
     Each class in this module corresponds to a contenttype value.
 """
 
-import os, re, base64
+import os
+import re
+import base64
 import tarfile
 import zipfile
 import tempfile
@@ -43,12 +45,12 @@
     from PIL import Image as PILImage
     from PIL.ImageChops import difference as PILdiff
 except ImportError:
-    PIL = None
+    PIL = PILImage = PILdiff = None
 
 from MoinMoin import log
 logging = log.getLogger(__name__)
 
-from MoinMoin import wikiutil, config
+from MoinMoin import wikiutil
 from MoinMoin.i18n import _, L_
 from MoinMoin.themes import render_template
 from MoinMoin.storage.error import StorageError
@@ -63,11 +65,9 @@
 from MoinMoin.forms import File
 from MoinMoin.constants.contenttypes import (
     GROUP_MARKUP_TEXT, GROUP_OTHER_TEXT, GROUP_IMAGE, GROUP_AUDIO, GROUP_VIDEO,
-    GROUP_DRAWING, GROUP_OTHER, CONTENTTYPE_NONEXISTENT,
+    GROUP_DRAWING, GROUP_OTHER, CONTENTTYPE_NONEXISTENT, CHARSET
     )
-from MoinMoin.constants.keys import (
-    NAME, NAME_EXACT, WIKINAME, CONTENTTYPE, SIZE, TAGS, HASH_ALGORITHM
-    )
+from MoinMoin.constants.keys import NAME_EXACT, WIKINAME, CONTENTTYPE, SIZE, TAGS, HASH_ALGORITHM
 
 
 COLS = 80
@@ -75,7 +75,8 @@
 
 
 class RegistryContent(RegistryBase):
-    class Entry(namedtuple('Entry', 'factory content_type default_contenttype_params display_name ingroup_order priority')):
+    class Entry(namedtuple('Entry',
+                           'factory content_type default_contenttype_params display_name ingroup_order priority')):
         def __call__(self, content_type, *args, **kw):
             if self.content_type.issupertype(Type(content_type)):
                 return self.factory(content_type, *args, **kw)
@@ -116,8 +117,11 @@
     GROUP_OTHER
 ])
 
+
 def register(cls):
-    content_registry.register(RegistryContent.Entry(cls._factory, Type(cls.contenttype), cls.default_contenttype_params, cls.display_name, cls.ingroup_order, RegistryContent.PRIORITY_MIDDLE), cls.group)
+    content_registry.register(RegistryContent.Entry(cls._factory, Type(cls.contenttype),
+                                                    cls.default_contenttype_params, cls.display_name,
+                                                    cls.ingroup_order, RegistryContent.PRIORITY_MIDDLE), cls.group)
     return cls
 
 
@@ -170,7 +174,7 @@
         return self.item.name
 
     def get_data(self):
-        return '' # TODO create a better method for binary stuff
+        return ''  # TODO create a better method for binary stuff
     data = property(fget=get_data)
 
     @timed('conv_in_dom')
@@ -245,7 +249,8 @@
             # we really want to make sure that invalid data or a malfunctioning
             # converter does not crash the item view (otherwise a user might
             # not be able to fix it from the UI).
-            import time, uuid
+            import time
+            import uuid
             error_id = uuid.uuid4()
             logging.exception("An exception happened in _render_data (error_id = %s ):" % error_id)
             rendered_data = render_template('crash.html',
@@ -274,7 +279,7 @@
             terms.append(Term(CONTENTTYPE, contenttype))
         query = And(terms)
         revs = flaskg.storage.search(query, sortedby=NAME_EXACT, limit=None)
-        return [rev.meta[NAME] for rev in revs]
+        return [rev.name for rev in revs]
 
 
 @register
@@ -318,7 +323,7 @@
             if data_file:
                 data = data_file.stream
                 # this is likely a guess by the browser, based on the filename
-                contenttype_guessed = data_file.content_type # comes from form multipart data
+                contenttype_guessed = data_file.content_type  # comes from form multipart data
                 return data, contenttype_guessed
             else:
                 return None, None
@@ -344,7 +349,7 @@
 
     def do_get(self, force_attachment=False, mimetype=None):
         hash = self.rev.meta.get(HASH_ALGORITHM)
-        if is_resource_modified(request.environ, hash): # use hash as etag
+        if is_resource_modified(request.environ, hash):  # use hash as etag
             return self._do_get_modified(hash, force_attachment=force_attachment, mimetype=mimetype)
         else:
             return Response(status=304)
@@ -354,7 +359,7 @@
         return self._do_get(hash, member, force_attachment=force_attachment, mimetype=mimetype)
 
     def _do_get(self, hash, member=None, force_attachment=False, mimetype=None):
-        if member: # content = file contained within a archive item revision
+        if member:  # content = file contained within a archive item revision
             path, filename = os.path.split(member)
             mt = MimeType(filename=filename)
             content_length = None
@@ -362,7 +367,7 @@
             # force attachment download, so it uses attachment_filename
             # otherwise it will use the itemname from the URL for saving
             force_attachment = True
-        else: # content = item revision
+        else:  # content = item revision
             rev = self.rev
             filename = rev.item.name
             try:
@@ -381,7 +386,7 @@
         return send_file(file=file_to_send,
                          mimetype=content_type,
                          as_attachment=as_attachment, attachment_filename=filename,
-                         cache_timeout=10, # wiki data can change rapidly
+                         cache_timeout=10,  # wiki data can change rapidly
                          add_etags=True, etag=hash, conditional=True)
 
 
@@ -447,7 +452,7 @@
         if isinstance(content, str):
             if content_length is None:
                 content_length = len(content)
-            content = StringIO(content) # we need a file obj
+            content = StringIO(content)  # we need a file obj
         elif not hasattr(content, 'read'):
             logging.error("unsupported content object: {0!r}".format(content))
             raise StorageError("unsupported content object: {0!r}".format(content))
@@ -638,12 +643,12 @@
         try:
             # if we have EXIF data, we can transpose (e.g. rotate left),
             # so the rendered image is correctly oriented:
-            transpose_op = transpose_op or 1 # or self.exif['Orientation']
+            transpose_op = transpose_op or 1  # or self.exif['Orientation']
         except KeyError:
-            transpose_op = 1 # no change
+            transpose_op = 1  # no change
 
         if size is not None:
-            image = image.copy() # create copy first as thumbnail works in-place
+            image = image.copy()  # create copy first as thumbnail works in-place
             image.thumbnail(size, PILImage.ANTIALIAS)
 
         transpose_func = {
@@ -728,7 +733,7 @@
         c = app.cache.get(cid)
         if c is None:
             if PIL is None:
-                abort(404) # TODO render user friendly error image
+                abort(404)  # TODO render user friendly error image
 
             content_type = newrev.meta[CONTENTTYPE]
             if content_type == 'image/jpeg':
@@ -754,7 +759,7 @@
                 app.cache.set(cid, (headers, data))
             except (IOError, ValueError) as err:
                 logging.exception("error during PILdiff: {0}".format(err.message))
-                abort(404) # TODO render user friendly error image
+                abort(404)  # TODO render user friendly error image
         else:
             # XXX TODO check ACL behaviour
             headers, data = c
@@ -826,11 +831,11 @@
 
     def data_internal_to_storage(self, text):
         """ convert data from memory format to storage format """
-        return text.replace(u'\n', u'\r\n').encode(config.charset)
+        return text.replace(u'\n', u'\r\n').encode(CHARSET)
 
     def data_storage_to_internal(self, data):
         """ convert data from storage format to memory format """
-        return data.decode(config.charset).replace(u'\r\n', u'\n')
+        return data.decode(CHARSET).replace(u'\r\n', u'\n')
 
     def _get_data_diff_html(self, oldrev, newrev, template):
         from MoinMoin.util.diff_html import diff
@@ -981,7 +986,7 @@
         return send_file(file=file_to_send,
                          mimetype=content_type,
                          as_attachment=as_attachment, attachment_filename=None,
-                         cache_timeout=10, # wiki data can change rapidly
+                         cache_timeout=10,  # wiki data can change rapidly
                          add_etags=False, etag=None, conditional=True)
 
 
@@ -1074,10 +1079,10 @@
         filecontent = file_upload.stream
         content_length = None
         if ext in ['.svg', '.draw', ]:  # handle AWD (svg) and TWD (draw)
-            filecontent = filecontent.read() # read file completely into memory
+            filecontent = filecontent.read()  # read file completely into memory
             filecontent = filecontent.replace("\r", "")
         elif ext == '.map':
-            filecontent = filecontent.read() # read file completely into memory
+            filecontent = filecontent.read()  # read file completely into memory
             filecontent = filecontent.strip()
         elif ext == '.png':
             #content_length = file_upload.content_length
@@ -1124,11 +1129,12 @@
     class ModifyForm(Draw.ModifyForm):
         template = "modify_anywikidraw.html"
         help = ""
+
         def _load(self, item):
             super(AnyWikiDraw.ModifyForm, self)._load(item)
             try:
                 drawing_exists = 'drawing.svg' in item.list_members()
-            except tarfile.TarError: # item doesn't exist yet
+            except tarfile.TarError:  # item doesn't exist yet
                 drawing_exists = False
             self.drawing_exists = drawing_exists
 
--- a/MoinMoin/items/ticket.py	Fri Feb 08 08:32:26 2013 -0700
+++ b/MoinMoin/items/ticket.py	Mon Feb 11 12:04:39 2013 -0700
@@ -19,7 +19,8 @@
 
 from MoinMoin.i18n import L_
 from MoinMoin.themes import render_template
-from MoinMoin.forms import Form, OptionalText, OptionalMultilineText, Submit, SmallNatural, Tags, Reference, BackReference
+from MoinMoin.forms import (Form, OptionalText, OptionalMultilineText, Submit, SmallNatural, Tags,
+                            Reference, BackReference)
 from MoinMoin.storage.middleware.protecting import AccessDenied
 from MoinMoin.constants.keys import ITEMTYPE, CONTENTTYPE, ITEMID, CURRENT
 from MoinMoin.constants.contenttypes import CONTENTTYPE_USER
@@ -36,6 +37,7 @@
 OptionalTicketReference = Reference.to(TICKET_QUERY).using(optional=True)
 OptionalUserReference = Reference.to(USER_QUERY).using(optional=True).with_properties(empty_label='(Nobody)')
 
+
 class TicketMetaForm(Form):
     summary = OptionalText.using(label=L_("Summary")).with_properties(placeholder=L_("One-line summary of the item"))
     effort = Rating.using(label=L_("Effort"))
@@ -47,6 +49,7 @@
     superseded_by = OptionalTicketReference.using(label=L_("Superseded By"))
     depends_on = OptionalTicketReference.using(label=L_("Depends On"))
 
+
 class TicketBackRefForm(Form):
     supersedes = BackReference.using(label=L_("Supersedes"))
     required_by = BackReference.using(label=L_("Required By"))
@@ -58,6 +61,7 @@
         self['required_by'].set(Term('depends_on', id_))
         self['subscribers'].set(Term('subscribed_items', id_))
 
+
 class TicketForm(BaseModifyForm):
     meta = TicketMetaForm
     backrefs = TicketBackRefForm
@@ -103,7 +107,7 @@
             return self.do_modify()
 
     def do_modify(self):
-        if request.method == 'GET':
+        if request.method in ['GET', 'HEAD']:
             form = TicketForm.from_item(self)
         elif request.method == 'POST':
             form = TicketForm.from_request(request)
--- a/MoinMoin/log.py	Fri Feb 08 08:32:26 2013 -0700
+++ b/MoinMoin/log.py	Mon Feb 11 12:04:39 2013 -0700
@@ -108,8 +108,8 @@
     # for warnings, we just want to use the logging system, not stderr or other files
     msg = "{0}:{1}: {2}: {3}".format(filename, lineno, category.__name__, message)
     logger = getLogger(__name__)
-    logger.warning(msg) # Note: the warning will look like coming from here,
-                        # but msg contains info about where it really comes from
+    logger.warning(msg)  # Note: the warning will look like coming from here,
+                         # but msg contains info about where it really comes from
 
 
 def load_config(conf_fname=None):
@@ -125,7 +125,7 @@
             l = getLogger(__name__)
             l.info('using logging configuration read from "{0}"'.format(conf_fname))
             warnings.showwarning = _log_warning
-        except Exception as err: # XXX be more precise
+        except Exception as err:  # XXX be more precise
             err_msg = str(err)
     if not configured:
         # load builtin fallback logging config
@@ -151,6 +151,6 @@
         load_config()
     logger = logging.getLogger(name)
     for levelnumber, levelname in logging._levelNames.items():
-        if isinstance(levelnumber, int): # that list has also the reverse mapping...
+        if isinstance(levelnumber, int):  # that list has also the reverse mapping...
             setattr(logger, levelname, levelnumber)
     return logger
--- a/MoinMoin/macro/Anchor.py	Fri Feb 08 08:32:26 2013 -0700
+++ b/MoinMoin/macro/Anchor.py	Mon Feb 11 12:04:39 2013 -0700
@@ -9,6 +9,7 @@
 from MoinMoin.util.tree import moin_page
 from MoinMoin.macro._base import MacroInlineBase
 
+
 class Macro(MacroInlineBase):
     def macro(self, content, arguments, page_url, alternative):
         if not arguments:
--- a/MoinMoin/macro/Date.py	Fri Feb 08 08:32:26 2013 -0700
+++ b/MoinMoin/macro/Date.py	Mon Feb 11 12:04:39 2013 -0700
@@ -10,11 +10,11 @@
 import time
 from datetime import datetime
 
-from flask import g as flaskg
 from flask.ext.babel import format_date
 
 from MoinMoin.macro._base import MacroInlineBase
 
+
 class MacroDateTimeBase(MacroInlineBase):
     def parse_time(self, args):
         """ parse a time specification argument for usage by Date and DateTime macro
@@ -30,8 +30,8 @@
             try:
                 year, month, day = int(args[0:4]), int(args[5:7]), int(args[8:10])
                 hour, minute, second = int(args[11:13]), int(args[14:16]), int(args[17:19])
-                tz = args[19:] # +HHMM, -HHMM or Z or nothing (then we assume Z)
-                tzoffset = 0 # we assume UTC no matter if there is a Z
+                tz = args[19:]  # +HHMM, -HHMM or Z or nothing (then we assume Z)
+                tzoffset = 0  # we assume UTC no matter if there is a Z
                 if tz:
                     sign = tz[0]
                     if sign in '+-':
@@ -47,7 +47,7 @@
             try:
                 tm = time.mktime(tm) - time.timezone - tzoffset
             except (OverflowError, ValueError):
-                tm = 0 # incorrect, but we avoid an ugly backtrace
+                tm = 0  # incorrect, but we avoid an ugly backtrace
         else:
             # try raw seconds since epoch in UTC
             try:
@@ -56,10 +56,11 @@
                 raise ValueError("Bad timestamp {0!r}: {1}".format(args, err))
         return tm
 
+
 class Macro(MacroDateTimeBase):
     def macro(self, content, arguments, page_url, alternative):
         if arguments is None:
-            tm = time.time() # always UTC
+            tm = time.time()  # always UTC
         else:
             # XXX looks like args are split at ':' -> <Arguments([u'2010-12-31T23', u'59', u'00'], {})>
             stamp = arguments[0]
--- a/MoinMoin/macro/DateTime.py	Fri Feb 08 08:32:26 2013 -0700
+++ b/MoinMoin/macro/DateTime.py	Mon Feb 11 12:04:39 2013 -0700
@@ -9,15 +9,15 @@
 import time
 from datetime import datetime
 
-from flask import g as flaskg
 from flask.ext.babel import format_datetime
 
 from MoinMoin.macro.Date import MacroDateTimeBase
 
+
 class Macro(MacroDateTimeBase):
     def macro(self, content, arguments, page_url, alternative):
         if arguments is None:
-            tm = time.time() # always UTC
+            tm = time.time()  # always UTC
         else:
             stamp = arguments[0]
             tm = self.parse_time(stamp)
--- a/MoinMoin/macro/GetText.py	Fri Feb 08 08:32:26 2013 -0700
+++ b/MoinMoin/macro/GetText.py	Mon Feb 11 12:04:39 2013 -0700
@@ -15,6 +15,7 @@
 from MoinMoin.i18n import _, L_, N_
 from MoinMoin.macro._base import MacroInlineBase
 
+
 class Macro(MacroInlineBase):
     """ Return a translation of args, or args as is """
     def macro(self, content, arguments, page_url, alternative):
--- a/MoinMoin/macro/GetVal.py	Fri Feb 08 08:32:26 2013 -0700
+++ b/MoinMoin/macro/GetVal.py	Mon Feb 11 12:04:39 2013 -0700
@@ -10,6 +10,7 @@
 
 from MoinMoin.macro._base import MacroInlineBase
 
+
 class Macro(MacroInlineBase):
     def macro(self, content, arguments, page_url, alternative):
         try:
--- a/MoinMoin/macro/MailTo.py	Fri Feb 08 08:32:26 2013 -0700
+++ b/MoinMoin/macro/MailTo.py	Mon Feb 11 12:04:39 2013 -0700
@@ -12,6 +12,7 @@
 from MoinMoin.util.tree import moin_page, xlink
 from MoinMoin.macro._base import MacroInlineBase
 
+
 class Macro(MacroInlineBase):
     def macro(self, content, arguments, page_url, alternative):
         # TODO new arg parsing is not compatible, splits at blanks
--- a/MoinMoin/macro/PagenameList.py	Fri Feb 08 08:32:26 2013 -0700
+++ b/MoinMoin/macro/PagenameList.py	Mon Feb 11 12:04:39 2013 -0700
@@ -14,6 +14,7 @@
 
 from MoinMoin.macro._base import MacroPageLinkListBase
 
+
 class Macro(MacroPageLinkListBase):
     def macro(self, content, arguments, page_url, alternative):
         # needle=u'', regex=False
--- a/MoinMoin/macro/RandomItem.py	Fri Feb 08 08:32:26 2013 -0700
+++ b/MoinMoin/macro/RandomItem.py	Mon Feb 11 12:04:39 2013 -0700
@@ -58,5 +58,5 @@
             result.append(moin_page.a(attrib={xlink.href: link}, children=[name]))
             result.append(", ")
 
-        del result[-1] # kill last comma
+        del result[-1]  # kill last comma
         return result
--- a/MoinMoin/macro/Verbatim.py	Fri Feb 08 08:32:26 2013 -0700
+++ b/MoinMoin/macro/Verbatim.py	Mon Feb 11 12:04:39 2013 -0700
@@ -8,6 +8,7 @@
 
 from MoinMoin.macro._base import MacroInlineBase
 
+
 class Macro(MacroInlineBase):
     def macro(self, text=u''):
         return text
--- a/MoinMoin/macro/_base.py	Fri Feb 08 08:32:26 2013 -0700
+++ b/MoinMoin/macro/_base.py	Mon Feb 11 12:04:39 2013 -0700
@@ -9,6 +9,7 @@
 from MoinMoin.util import iri
 from MoinMoin.util.tree import moin_page, xlink
 
+
 class MacroBase(object):
     """
     Macro base class.
@@ -23,6 +24,7 @@
     def __call__(self, content, arguments, page_url, alternative, context_block):
         raise NotImplementedError
 
+
 class MacroBlockBase(MacroBase):
     """
     Macro base class for block element macros.
@@ -38,6 +40,7 @@
     def macro(self, content, arguments, page_url, alternative):
         raise NotImplementedError
 
+
 class MacroInlineBase(MacroBase):
     """
     Macro base class for inline element macros.
@@ -50,6 +53,7 @@
             return moin_page.p(children=(ret, ))
         return ret
 
+
 class MacroInlineOnlyBase(MacroBase):
     """
     Macro base class for strict inline element macros.
@@ -61,6 +65,7 @@
         if not context_block:
             return self.macro(content, arguments, page_url, alternative)
 
+
 class MacroPageLinkListBase(MacroBlockBase):
     def create_pagelink_list(self, pagenames, ordered=False):
         """ creates an ET with a list of pagelinks from a list of pagenames """
@@ -74,6 +79,7 @@
             page_list.append(item)
         return page_list
 
+
 class MacroNumberPageLinkListBase(MacroBlockBase):
     def create_number_pagelink_list(self, num_pagenames, ordered=False):
         """ creates an ET with a list of pagelinks from a list of pagenames """
@@ -88,6 +94,7 @@
             num_page_list.append(item)
         return num_page_list
 
+
 class MacroDefinitionListBase(MacroBlockBase):
     def create_definition_list(self, items):
         """ creates an ET with a definition list made from items """
--- a/MoinMoin/macro/_tests/test_GetText.py	Fri Feb 08 08:32:26 2013 -0700
+++ b/MoinMoin/macro/_tests/test_GetText.py	Mon Feb 11 12:04:39 2013 -0700
@@ -5,7 +5,6 @@
 Test for macro.GetText
 """
 
-import pytest
 from MoinMoin.converter._args import Arguments
 from MoinMoin.macro.GetText import *
 
--- a/MoinMoin/macro/_tests/test_GetVal.py	Fri Feb 08 08:32:26 2013 -0700
+++ b/MoinMoin/macro/_tests/test_GetVal.py	Mon Feb 11 12:04:39 2013 -0700
@@ -8,7 +8,7 @@
 from flask import g as flaskg
 
 from MoinMoin.macro.GetVal import *
-from MoinMoin.config import SOMEDICT
+from MoinMoin.constants.keys import SOMEDICT
 from MoinMoin._tests import become_trusted, update_item
 from MoinMoin.conftest import init_test_app, deinit_test_app
 from MoinMoin._tests import wikiconfig
--- a/MoinMoin/mail/_tests/test_sendmail.py	Fri Feb 08 08:32:26 2013 -0700
+++ b/MoinMoin/mail/_tests/test_sendmail.py	Mon Feb 11 12:04:39 2013 -0700
@@ -10,7 +10,7 @@
 from email.Charset import Charset, QP
 from email.Header import Header
 from MoinMoin.mail import sendmail
-from MoinMoin import config
+from MoinMoin.constants.contenttypes import CHARSET
 
 
 class TestdecodeSpamSafeEmail(object):
@@ -77,14 +77,14 @@
     name-addr   =   [display-name] angle-addr
     angle-addr  =   [CFWS] "<" addr-spec ">" [CFWS] / obs-angle-addr
     """
-    charset = Charset(config.charset)
+    charset = Charset(CHARSET)
     charset.header_encoding = QP
     charset.body_encoding = QP
 
     def testSimpleAddress(self):
         """ mail.sendmail: encode simple address: local@domain """
         address = u'local@domain'
-        expected = address.encode(config.charset)
+        expected = address.encode(CHARSET)
         assert sendmail.encodeAddress(address, self.charset) == expected
 
     def testComposite(self):
--- a/MoinMoin/mail/sendmail.py	Fri Feb 08 08:32:26 2013 -0700
+++ b/MoinMoin/mail/sendmail.py	Mon Feb 11 12:04:39 2013 -0700
@@ -8,7 +8,8 @@
 """
 
 
-import os, re
+import os
+import re
 from email.Header import Header
 
 from MoinMoin import log
@@ -16,7 +17,7 @@
 
 from flask import current_app as app
 
-from MoinMoin import config
+from MoinMoin.constants.contenttypes import CHARSET
 from MoinMoin.i18n import _, L_, N_
 
 _transdict = {"AT": "@", "DOT": ".", "DASH": "-"}
@@ -45,7 +46,7 @@
         try:
             str(phrase)  # is it pure ascii?
         except UnicodeEncodeError:
-            phrase = phrase.encode(config.charset)
+            phrase = phrase.encode(CHARSET)
             phrase = Header(phrase, charset)
         blanks = match.group('blanks')
         addr = match.group('addr')
@@ -79,14 +80,16 @@
     :rtype: tuple
     :returns: (is_ok, Description of error or OK message)
     """
-    import smtplib, socket
+    import smtplib
+    import socket
     from email.Message import Message
     from email.Charset import Charset, QP
     from email.Utils import formatdate, make_msgid
 
     cfg = app.cfg
     if not cfg.mail_enabled:
-        return (0, _("Contact administrator: cannot send password recovery e-mail because mail configuration is incomplete."))
+        return (0, _("Contact administrator: cannot send password recovery e-mail "
+                     "because mail configuration is incomplete."))
     mail_from = mail_from or cfg.mail_from
 
     logging.debug("send mail, from: {0!r}, subj: {1!r}".format(mail_from, subject))
@@ -95,17 +98,17 @@
     if not to and not cc and not bcc:
         return (1, _("No recipients, nothing to do"))
 
-    subject = subject.encode(config.charset)
+    subject = subject.encode(CHARSET)
 
     # Create a text/plain body using CRLF (see RFC2822)
     text = text.replace(u'\n', u'\r\n')
-    text = text.encode(config.charset)
+    text = text.encode(CHARSET)
 
-    # Create a message using config.charset and quoted printable
+    # Create a message using CHARSET and quoted printable
     # encoding, which should be supported better by mail clients.
     # TODO: check if its really works better for major mail clients
     msg = Message()
-    charset = Charset(config.charset)
+    charset = Charset(CHARSET)
     charset.header_encoding = QP
     charset.body_encoding = QP
     msg.set_charset(charset)
@@ -145,7 +148,7 @@
             try:
                 #server.set_debuglevel(1)
                 if cfg.mail_username is not None and cfg.mail_password is not None:
-                    try: # try to do tls
+                    try:  # try to do TLS
                         server.ehlo()
                         if server.has_extn('starttls'):
                             server.starttls()
@@ -213,6 +216,7 @@
 
     return address
 
+
 def decodeSpamSafeEmail(address):
     """ Decode obfuscated email address to standard email address
 
--- a/MoinMoin/script/__init__.py	Fri Feb 08 08:32:26 2013 -0700
+++ b/MoinMoin/script/__init__.py	Mon Feb 11 12:04:39 2013 -0700
@@ -18,8 +18,10 @@
 
     manager = Manager(create_app)
     manager.add_option('-c', '--config', dest='config', required=False, default=wiki_config)
-    manager.add_option('-i', '--index-create', action='store_true', dest='create_index', required=False, default=False)
-    manager.add_option('-s', '--storage-create', action='store_true', dest='create_storage', required=False, default=False)
+    manager.add_option('-i', '--index-create', action='store_true', dest='create_index',
+                       required=False, default=False)
+    manager.add_option('-s', '--storage-create', action='store_true', dest='create_storage',
+                       required=False, default=False)
     manager.add_command("moin", Server(host='127.0.0.1', port=8080))
 
     from MoinMoin.script.maint import index
--- a/MoinMoin/script/account/create.py	Fri Feb 08 08:32:26 2013 -0700
+++ b/MoinMoin/script/account/create.py	Mon Feb 11 12:04:39 2013 -0700
@@ -7,8 +7,6 @@
 """
 
 
-from flask import current_app as app
-from flask import g as flaskg
 from flask.ext.script import Command, Option
 
 from MoinMoin import user
@@ -20,8 +18,8 @@
     option_list = (
         Option('--name', '-n', required=True, dest='name', type=unicode,
                help="Set the wiki user name to NAME."),
-        Option('--alias', '-a', required=False, dest="aliasname", type=unicode,
-               help="Set the wiki user alias name to ALIAS (e.g. the real name if NAME is cryptic)."),
+        Option('--display_name', '-d', required=False, dest="display_name", type=unicode,
+               help="Set the wiki user's display name to DISPLAY_NAME (e.g. in case the NAME is cryptic)."),
         Option('--email', '-e', required=True, dest='email', type=unicode,
                help="Set the user's email address to EMAIL."),
         Option('--openid', '-o', required=False, dest='openid', type=unicode,
@@ -30,7 +28,7 @@
                help="Set the user's password to PASSWORD."),
     )
 
-    def run(self, name, aliasname, email, openid, password):
+    def run(self, name, display_name, email, openid, password):
         before_wiki()
         msg = user.create_user(username=name,
                                password=password,
--- a/MoinMoin/script/account/disable.py	Fri Feb 08 08:32:26 2013 -0700
+++ b/MoinMoin/script/account/disable.py	Mon Feb 11 12:04:39 2013 -0700
@@ -7,8 +7,6 @@
 """
 
 
-from flask import current_app as app
-from flask import g as flaskg
 from flask.ext.script import Command, Option
 
 from MoinMoin import user
@@ -42,12 +40,12 @@
             return
 
         print " {0:<20} {1:!r<25} {2:<35}".format(u.itemid, u.name, u.email),
-        if not u.disabled: # only disable once
+        if not u.disabled:  # only disable once
             u.disabled = 1
             u.name = u"{0}-{1}".format(u.name, u.id)
             if u.email:
                 u.email = u"{0}-{1}".format(u.email, u.id)
-            u.subscribed_items = [] # avoid using email
+            u.subscribed_items = []  # avoid using email
             u.save()
             print "- disabled."
         else:
--- a/MoinMoin/script/account/resetpw.py	Fri Feb 08 08:32:26 2013 -0700
+++ b/MoinMoin/script/account/resetpw.py	Mon Feb 11 12:04:39 2013 -0700
@@ -8,8 +8,6 @@
 """
 
 
-from flask import current_app as app
-from flask import g as flaskg
 from flask.ext.script import Command, Option
 
 from MoinMoin.constants.keys import ITEMID, NAME, NAME_EXACT, EMAIL
@@ -20,9 +18,11 @@
 class Fault(Exception):
     """something went wrong"""
 
+
 class NoSuchUser(Fault):
     """raised if no such user exists"""
 
+
 class MailFailed(Fault):
     """raised if e-mail sending failed"""
 
--- a/MoinMoin/script/maint/index.py	Fri Feb 08 08:32:26 2013 -0700
+++ b/MoinMoin/script/maint/index.py	Mon Feb 11 12:04:39 2013 -0700
@@ -7,14 +7,12 @@
 
 
 from flask import current_app as app
-from flask import g as flaskg
 from flask.ext.script import Command, Option
 
 from MoinMoin import log
 logging = log.getLogger(__name__)
 
-from MoinMoin.config import LATEST_REVS, ALL_REVS
-from MoinMoin.storage.middleware.indexing import ALL_REVS, LATEST_REVS
+from MoinMoin.constants.keys import LATEST_REVS, ALL_REVS
 
 
 class IndexCreate(Command):
--- a/MoinMoin/script/maint/modified_systemitems.py	Fri Feb 08 08:32:26 2013 -0700
+++ b/MoinMoin/script/maint/modified_systemitems.py	Mon Feb 11 12:04:39 2013 -0700
@@ -12,7 +12,8 @@
 from flask import current_app as app
 from flask.ext.script import Command
 
-from MoinMoin.config import IS_SYSITEM, SYSITEM_VERSION
+from MoinMoin.constants.keys import IS_SYSITEM, SYSITEM_VERSION
+
 
 class Modified_SystemItems(Command):
     description = 'This command can be used to list system items that has been edited in this wiki.'
--- a/MoinMoin/script/maint/modify_item.py	Fri Feb 08 08:32:26 2013 -0700
+++ b/MoinMoin/script/maint/modify_item.py	Mon Feb 11 12:04:39 2013 -0700
@@ -9,10 +9,9 @@
 import json
 
 from flask import current_app as app
-from flask import g as flaskg
 from flask.ext.script import Command, Option
 
-from MoinMoin.config import NAME, CURRENT, REVID, DATAID, SIZE, HASH_ALGORITHM
+from MoinMoin.constants.keys import CURRENT, ITEMID, REVID, DATAID, SIZE, HASH_ALGORITHM
 
 
 class GetItem(Command):
@@ -55,8 +54,7 @@
             meta = mf.read()
         meta = meta.decode('utf-8')
         meta = json.loads(meta)
-        name = meta[NAME]
-        to_kill = [SIZE, HASH_ALGORITHM, # gets re-computed automatically
+        to_kill = [SIZE, HASH_ALGORITHM,  # gets re-computed automatically
                    DATAID,
                   ]
         for key in to_kill:
@@ -64,6 +62,7 @@
         if not overwrite:
             # if we remove the REVID, it will create a new one and store under the new one
             meta.pop(REVID, None)
-        item = app.storage[name]
+        query = {ITEMID: meta[ITEMID]}
+        item = app.storage.get_item(**query)
         with open(data_file, 'rb') as df:
             item.store_revision(meta, df, overwrite=overwrite)
--- a/MoinMoin/script/maint/moinshell.py	Fri Feb 08 08:32:26 2013 -0700
+++ b/MoinMoin/script/maint/moinshell.py	Mon Feb 11 12:04:39 2013 -0700
@@ -2,14 +2,12 @@
 
 import code
 
-from flask import Flask, _request_ctx_stack
 from flask import current_app as app
 from flask import g as flaskg
 from flask.ext.script import Command, Option
 
-from MoinMoin import user
 from MoinMoin.app import before_wiki
-from MoinMoin.util.clock import Clock
+
 
 class MoinShell(Command):
     """
@@ -46,7 +44,7 @@
                 Option('--no-ipython',
                        action="store_true",
                        dest='no_ipython',
-                       default=not(self.use_ipython)), )
+                       default=not self.use_ipython), )
 
     def get_context(self):
         """
--- a/MoinMoin/script/maint/reduce_revisions.py	Fri Feb 08 08:32:26 2013 -0700
+++ b/MoinMoin/script/maint/reduce_revisions.py	Mon Feb 11 12:04:39 2013 -0700
@@ -16,7 +16,7 @@
 
 from whoosh.query import Every
 
-from MoinMoin.config import NAME, NAME_EXACT, REVID
+from MoinMoin.constants.keys import NAME, NAME_EXACT, REVID
 
 
 class Reduce_Revisions(Command):
--- a/MoinMoin/script/maint/serialization.py	Fri Feb 08 08:32:26 2013 -0700
+++ b/MoinMoin/script/maint/serialization.py	Mon Feb 11 12:04:39 2013 -0700
@@ -8,7 +8,6 @@
 import sys
 
 from flask import current_app as app
-from flask import g as flaskg
 from flask.ext.script import Command, Option
 
 from MoinMoin.storage.middleware.serialization import serialize, deserialize
@@ -16,6 +15,7 @@
 from MoinMoin import log
 logging = log.getLogger(__name__)
 
+
 def open_file(filename, mode):
     if filename is None:
         # Guess the IO stream from the mode:
@@ -28,7 +28,8 @@
 
         # On Windows force the stream to be in binary mode if it's needed.
         if sys.platform == "win32" and "b" in mode:
-            import os, msvcrt
+            import os
+            import msvcrt
             msvcrt.setmode(stream.fileno(), os.O_BINARY)
 
         f = stream
@@ -36,17 +37,41 @@
         f = open(filename, mode)
     return f
 
+
 class Serialize(Command):
     description = 'Serialize the backend into a file.'
 
     option_list = [
         Option('--file', '-f', dest='filename', type=unicode, required=False,
                help='Filename of the output file.'),
+        Option('--backends', '-b', dest='backends', type=unicode, required=False,
+               help='Backend names to serialize (comma separated).'),
+        Option('--all-backends', '-a', dest='all_backends', action='store_true', default=False,
+               help='Serialize all configured backends.'),
     ]
 
-    def run(self, filename=None):
-        with open_file(filename, "wb") as f:
-            serialize(app.storage.backend, f)
+    def run(self, filename=None, backends=None, all_backends=False):
+        if filename is None:
+            f = sys.stdout
+        else:
+            f = open(filename, "wb")
+        with f as f:
+            existing_backends = set(app.cfg.backend_mapping)
+            if all_backends:
+                backends = set(app.cfg.backend_mapping)
+            elif backends:
+                backends = set(backends.split(','))
+            if backends:
+                # low level - directly serialize some backend contents -
+                # this does not use the index:
+                if backends.issubset(existing_backends):
+                    for backend_name in backends:
+                        backend = app.cfg.backend_mapping.get(backend_name)
+                        serialize(backend, f)
+                else:
+                    print "Error: Wrong backend name given."
+                    print "Given Backends: %r" % backends
+                    print "Configured Backends: %r" % existing_backends
 
 
 class Deserialize(Command):
--- a/MoinMoin/script/maint/set_meta.py	Fri Feb 08 08:32:26 2013 -0700
+++ b/MoinMoin/script/maint/set_meta.py	Mon Feb 11 12:04:39 2013 -0700
@@ -19,7 +19,7 @@
 
 from whoosh.query import Every
 
-from MoinMoin.config import NAME, NAME_EXACT
+from MoinMoin.constants.keys import NAME, NAME_EXACT
 from MoinMoin.script import fatal
 
 
@@ -49,7 +49,7 @@
 
         if query:
             qp = app.storage.query_parser([NAME_EXACT, ])
-            q = qp.parse(query_text)
+            q = qp.parse(query)
         else:
             q = Every()
 
--- a/MoinMoin/script/migration/moin19/_utils19.py	Fri Feb 08 08:32:26 2013 -0700
+++ b/MoinMoin/script/migration/moin19/_utils19.py	Mon Feb 11 12:04:39 2013 -0700
@@ -7,7 +7,7 @@
 
 import re
 
-from MoinMoin.config import NAME, ACL, CONTENTTYPE, MTIME, LANGUAGE
+from MoinMoin.constants.keys import NAME, ACL, CONTENTTYPE, MTIME, LANGUAGE
 
 CHARSET = 'utf-8'
 
--- a/MoinMoin/script/migration/moin19/import19.py	Fri Feb 08 08:32:26 2013 -0700
+++ b/MoinMoin/script/migration/moin19/import19.py	Mon Feb 11 12:04:39 2013 -0700
@@ -14,7 +14,6 @@
 """
 
 
-import sys
 import os
 import re
 import codecs
@@ -30,18 +29,15 @@
 from ._utils19 import quoteWikinameFS, unquoteWikiname, split_body
 from ._logfile19 import LogFile
 
-from MoinMoin.config import ACL, CONTENTTYPE, NAME, NAME_OLD, REVERTED_TO, \
-                            ACTION, ADDRESS, HOSTNAME, USERID, MTIME, EXTRA, COMMENT, \
-                            IS_SYSITEM, SYSITEM_VERSION, \
-                            TAGS, SIZE, HASH_ALGORITHM, \
-                            ITEMID, REVID, DATAID, CONTENTTYPE_USER
+from MoinMoin.constants.keys import (ACL, CONTENTTYPE, NAME, NAME_OLD, REVERTED_TO, ACTION, ADDRESS, HOSTNAME,
+                                     USERID, MTIME, EXTRA, COMMENT, TAGS, SIZE, HASH_ALGORITHM, ITEMID, REVID)
+from MoinMoin.constants.contenttypes import CONTENTTYPE_USER
 
-UID_OLD = 'old_user_id' # dynamic field *_id, so we don't have to change schema
+UID_OLD = 'old_user_id'  # dynamic field *_id, so we don't have to change schema
 
 from MoinMoin.storage.error import NoSuchRevisionError
 from MoinMoin.util.mimetype import MimeType
 from MoinMoin.util.crypto import make_uuid
-from MoinMoin.storage.middleware.serialization import serialize_rev
 from MoinMoin import security
 
 
@@ -77,9 +73,9 @@
 
     def run(self, data_dir=None):
         indexer = app.storage
-        backend = indexer.backend # backend without indexing
+        backend = indexer.backend  # backend without indexing
         print "Users..."
-        for rev in UserBackend(os.path.join(data_dir, 'user')): # assumes user/ below data_dir
+        for rev in UserBackend(os.path.join(data_dir, 'user')):  # assumes user/ below data_dir
             backend.store(rev.meta, rev.data)
 
         print "Pages/Attachments..."
@@ -90,7 +86,8 @@
         indexer.rebuild()
 
         print "Fix userids..."
-        userid_map = dict([(rev.meta[UID_OLD], rev.meta[ITEMID]) for rev in indexer.documents(contenttype=CONTENTTYPE_USER)])
+        userid_map = dict([(rev.meta[UID_OLD], rev.meta[ITEMID])
+                           for rev in indexer.documents(contenttype=CONTENTTYPE_USER)])
         for mountpoint, revid in backend:
             meta, data = backend.retrieve(mountpoint, revid)
             if USERID in meta:
@@ -102,7 +99,7 @@
                     del meta[USERID]
                 backend.store(meta, data)
             elif meta.get(CONTENTTYPE) == CONTENTTYPE_USER:
-                meta.pop(UID_OLD, None) # not needed any more
+                meta.pop(UID_OLD, None)  # not needed any more
                 backend.store(meta, data)
 
         print "Rebuilding the index..."
@@ -171,11 +168,11 @@
             self.current = int(f.read().strip())
         editlogpath = os.path.join(self.path, 'edit-log')
         self.editlog = EditLog(editlogpath)
-        self.acl = None # TODO
+        self.acl = None  # TODO
         self.itemid = make_uuid()
         if backend.deleted_mode == DELETED_MODE_KILL:
             revpath = os.path.join(self.path, 'revisions', '{0:08d}'.format(self.current))
-            PageRevision(self, self.current, revpath) # will raise exception if killing is requested
+            PageRevision(self, self.current, revpath)  # will raise exception if killing is requested
 
     def iter_revisions(self):
         revisionspath = os.path.join(self.path, 'revisions')
@@ -200,7 +197,8 @@
         for fname in fnames:
             attachname = fname.decode('utf-8')
             try:
-                yield AttachmentRevision(self.name, attachname, os.path.join(attachmentspath, fname), self.editlog, self.acl)
+                yield AttachmentRevision(self.name, attachname, os.path.join(attachmentspath, fname),
+                                         self.editlog, self.acl)
             except Exception as err:
                 logging.exception("AttachmentRevision {0!r}/{1!r} raised exception:".format(self.name, attachname))
 
@@ -223,13 +221,13 @@
                 raise KillRequested('deleted_mode wants killing/ignoring')
             # handle deleted revisions (for all revnos with 0<=revno<=current) here
             # we prepare some values for the case we don't find a better value in edit-log:
-            meta = {MTIME: -1, # fake, will get 0 in the end
-                    NAME: item_name, # will get overwritten with name from edit-log
-                                     # if we have an entry there
+            meta = {MTIME: -1,  # fake, will get 0 in the end
+                    NAME: item_name,  # will get overwritten with name from edit-log
+                                      # if we have an entry there
                    }
             try:
-                revpath = os.path.join(item.path, 'revisions', '{0:08d}'.format(revno-1))
-                previous_meta = PageRevision(item, revno-1, revpath).meta
+                revpath = os.path.join(item.path, 'revisions', '{0:08d}'.format(revno - 1))
+                previous_meta = PageRevision(item, revno - 1, revpath).meta
                 # if this page revision is deleted, we have no on-page metadata.
                 # but some metadata is required, thus we have to copy it from the
                 # (non-deleted) revision revno-1:
@@ -237,24 +235,25 @@
                     if key in previous_meta:
                         meta[key] = previous_meta[key]
             except NoSuchRevisionError:
-                pass # should not happen
-            meta[MTIME] += 1 # it is now either 0 or prev rev mtime + 1
+                pass  # should not happen
+            meta[MTIME] += 1  # it is now either 0 or prev rev mtime + 1
             data = u''
             try:
                 editlog_data = editlog.find_rev(revno)
             except KeyError:
                 if 0 <= revno <= item._fs_current:
-                    editlog_data = { # make something up
+                    editlog_data = {  # make something up
                         ACTION: u'SAVE/DELETE',
                     }
                 else:
-                    raise NoSuchRevisionError('Item {0!r} has no revision {1} (not even a deleted one)!'.format(item.name, revno))
+                    raise NoSuchRevisionError('Item {0!r} has no revision {1} (not even a deleted one)!'.format(
+                                              item.name, revno))
         else:
             try:
                 editlog_data = editlog.find_rev(revno)
             except KeyError:
                 if 1 <= revno <= item.current:
-                    editlog_data = { # make something up
+                    editlog_data = {  # make something up
                         NAME: item.name,
                         MTIME: int(os.path.getmtime(path)),
                         ACTION: u'SAVE',
@@ -329,7 +328,7 @@
         try:
             meta = editlog.find_attach(attach_name)
         except KeyError:
-            meta = { # make something up
+            meta = {  # make something up
                 MTIME: int(os.path.getmtime(attpath)),
                 ACTION: u'SAVE',
             }
@@ -361,14 +360,14 @@
         keys = (MTIME, '__rev', ACTION, NAME, ADDRESS, HOSTNAME, USERID, EXTRA, COMMENT)
         result = dict(zip(keys, fields))
         # do some conversions/cleanups/fallbacks:
-        result[MTIME] = int(long(result[MTIME] or 0) / 1000000) # convert usecs to secs
-        result['__rev'] = int(result['__rev']) - 1 # old storage is 1-based, we want 0-based
+        result[MTIME] = int(long(result[MTIME] or 0) / 1000000)  # convert usecs to secs
+        result['__rev'] = int(result['__rev']) - 1  # old storage is 1-based, we want 0-based
         result[NAME] = unquoteWikiname(result[NAME])
         action = result[ACTION]
         extra = result[EXTRA]
         if extra:
             if action.startswith('ATT'):
-                result[NAME] += u'/' + extra # append filename to pagename
+                result[NAME] += u'/' + extra  # append filename to pagename
                 # keep EXTRA for find_attach
             elif action == 'SAVE/RENAME':
                 if extra:
@@ -395,7 +394,7 @@
             self.to_begin()
             raise KeyError
         del meta['__rev']
-        meta = dict([(k, v) for k, v in meta.items() if v]) # remove keys with empty values
+        meta = dict([(k, v) for k, v in meta.items() if v])  # remove keys with empty values
         if meta.get(ACTION) == u'SAVENEW':
             # replace SAVENEW with just SAVE
             meta[ACTION] = u'SAVE'
@@ -403,7 +402,7 @@
 
     def find_attach(self, attachname):
         """ Find metadata for some attachment name in the edit-log. """
-        for meta in self.reverse(): # use reverse iteration to get the latest upload's data
+        for meta in self.reverse():  # use reverse iteration to get the latest upload's data
             if (meta['__rev'] == 99999998 and  # 99999999-1 because of 0-based
                 meta[ACTION] == 'ATTNEW' and
                 meta[EXTRA] == attachname):
@@ -412,9 +411,9 @@
             self.to_end()
             raise KeyError
         del meta['__rev']
-        del meta[EXTRA] # we have full name in NAME
+        del meta[EXTRA]  # we have full name in NAME
         meta[ACTION] = u'SAVE'
-        meta = dict([(k, v) for k, v in meta.items() if v]) # remove keys with empty values
+        meta = dict([(k, v) for k, v in meta.items() if v])  # remove keys with empty values
         return meta
 
 
@@ -429,7 +428,7 @@
             result.append("{0}{1}:{2}".format(
                           modifier,
                           u','.join(entries),
-                          u','.join(rights) # iterator has removed invalid rights
+                          u','.join(rights)  # iterator has removed invalid rights
                          ))
     result = u' '.join(result)
     logging.debug("regenerate_acl {0!r} -> {1!r}".format(acl_string, result))
@@ -448,6 +447,7 @@
     items = [item for item in items if item]
     return tuple(items)
 
+
 def _decode_dict(line):
     """
     Decode dict of key:value pairs from user data file
@@ -501,7 +501,7 @@
 
     def _process_usermeta(self, metadata):
         # stuff we want to have stored as boolean:
-        bool_defaults = [ # taken from cfg.checkbox_defaults
+        bool_defaults = [  # taken from cfg.checkbox_defaults
             ('show_comments', 'False'),
             ('edit_on_doubleclick', 'True'),
             ('scroll_page_after_edit', 'True'),
@@ -522,39 +522,43 @@
         # rename last_saved to MTIME, int MTIME should be enough:
         metadata[MTIME] = int(float(metadata.get('last_saved', '0')))
 
+        # rename aliasname to display_name:
+        metadata['display_name'] = metadata.get('aliasname')
+
         # rename subscribed_pages to subscribed_items
         metadata['subscribed_items'] = metadata.get('subscribed_pages', [])
 
         # convert bookmarks from usecs (and str) to secs (int)
-        metadata['bookmarks'] = [(interwiki, int(long(bookmark)/1000000))
+        metadata['bookmarks'] = [(interwiki, int(long(bookmark) / 1000000))
                                  for interwiki, bookmark in metadata.get('bookmarks', {}).items()]
 
         # stuff we want to get rid of:
-        kill = ['real_language', # crap (use 'language')
-                'wikiname_add_spaces', # crap magic (you get it like it is)
-                'recoverpass_key', # user can recover again if needed
-                'editor_default', # not used any more
-                'editor_ui', # not used any more
-                'external_target', # ancient, not used any more
-                'passwd', # ancient, not used any more (use enc_password)
-                'show_emoticons', # ancient, not used any more
-                'show_fancy_diff', # kind of diff display now depends on mimetype
-                'show_fancy_links', # not used any more (now link rendering depends on theme)
-                'show_toolbar', # not used any more
-                'show_topbottom', # crap
-                'show_nonexist_qm', # crap, can be done by css
-                'show_page_trail', # theme decides whether to show trail
-                'remember_last_visit', # we show trail, user can click there
-                'remember_me', # don't keep sessions open for a long time
-                'subscribed_pages', # renamed to subscribed_items
-                'edit_cols', # not used any more
-                'jid', # no jabber support
-                'tz_offset', # we have real timezone now
-                'date_fmt', # not used any more
-                'datetime_fmt', # not used any more
-                'last_saved', # renamed to MTIME
-                'email_subscribed_events', # XXX no support yet
-                'jabber_subscribed_events', # XXX no support yet
+        kill = ['aliasname',  # renamed to display_name
+                'real_language',  # crap (use 'language')
+                'wikiname_add_spaces',  # crap magic (you get it like it is)
+                'recoverpass_key',  # user can recover again if needed
+                'editor_default',  # not used any more
+                'editor_ui',  # not used any more
+                'external_target',  # ancient, not used any more
+                'passwd',  # ancient, not used any more (use enc_password)
+                'show_emoticons',  # ancient, not used any more
+                'show_fancy_diff',  # kind of diff display now depends on mimetype
+                'show_fancy_links',  # not used any more (now link rendering depends on theme)
+                'show_toolbar',  # not used any more
+                'show_topbottom',  # crap
+                'show_nonexist_qm',  # crap, can be done by css
+                'show_page_trail',  # theme decides whether to show trail
+                'remember_last_visit',  # we show trail, user can click there
+                'remember_me',  # don't keep sessions open for a long time
+                'subscribed_pages',  # renamed to subscribed_items
+                'edit_cols',  # not used any more
+                'jid',  # no jabber support
+                'tz_offset',  # we have real timezone now
+                'date_fmt',  # not used any more
+                'datetime_fmt',  # not used any more
+                'last_saved',  # renamed to MTIME
+                'email_subscribed_events',  # XXX no support yet
+                'jabber_subscribed_events',  # XXX no support yet
                ]
         for key in kill:
             if key in metadata:
@@ -562,8 +566,8 @@
 
         # finally, remove some empty values (that have empty defaults anyway or
         # make no sense when empty):
-        empty_kill = ['aliasname', 'bookmarks', 'enc_password',
-                      'language', 'css_url', 'email', ] # XXX check subscribed_items, quicklinks
+        empty_kill = ['aliasname', 'display_name', 'bookmarks', 'enc_password',
+                      'language', 'css_url', 'email', ]  # XXX check subscribed_items, quicklinks
         for key in empty_kill:
             if key in metadata and metadata[key] in [u'', tuple(), {}, [], ]:
                 del metadata[key]
--- a/MoinMoin/search/__init__.py	Fri Feb 08 08:32:26 2013 -0700
+++ b/MoinMoin/search/__init__.py	Mon Feb 11 12:04:39 2013 -0700
@@ -11,6 +11,7 @@
 from flatland import Form, String, Boolean
 from flatland.validation import Validator
 
+
 class ValidSearch(Validator):
     """Validator for a valid search form
     """
@@ -24,6 +25,7 @@
             return self.note_error(element, state, 'too_short_query_msg')
         return True
 
+
 class SearchForm(Form):
     q = Search
     history = InlineCheckbox.using(label=L_('search all revisions'))
--- a/MoinMoin/search/analyzers.py	Fri Feb 08 08:32:26 2013 -0700
+++ b/MoinMoin/search/analyzers.py	Mon Feb 11 12:04:39 2013 -0700
@@ -32,7 +32,7 @@
         :param positions: Whether to record token positions in the token.
         """
         assert isinstance(value, unicode), "{0!r} is not unicode".format(value)
-        if u'/' not in value: # Add '/' if user forgot do this
+        if u'/' not in value:  # Add '/' if user forgot do this
             value += u'/'
         pos = start_pos
         tk = Token()
@@ -71,7 +71,7 @@
 
     def __init__(self, acl_rights_contents):
         """
-        :param cfg: wiki config
+        :param acl_rights_contents: ACL for contents
         """
         self._acl_rights_contents = acl_rights_contents
 
--- a/MoinMoin/security/__init__.py	Fri Feb 08 08:32:26 2013 -0700
+++ b/MoinMoin/security/__init__.py	Mon Feb 11 12:04:39 2013 -0700
@@ -19,6 +19,7 @@
 from MoinMoin.constants import rights
 from MoinMoin import user
 from MoinMoin.i18n import _, L_, N_
+from MoinMoin.util.pysupport import AutoNe
 
 
 def require_permission(permission):
@@ -64,7 +65,7 @@
             return super(MySecPol, self).read(itemname)
     """
     def __init__(self, user):
-        self.name = user.name
+        self.names = user.name
 
     def read(self, itemname):
         """read permission is special as we have 2 kinds of read capabilities:
@@ -72,9 +73,9 @@
            * READ - gives permission to read, unconditionally
            * PUBREAD - gives permission to read, when published
         """
-        return (flaskg.storage.may(itemname, rights.READ, username=self.name)
+        return (flaskg.storage.may(itemname, rights.READ, usernames=self.names)
                 or
-                flaskg.storage.may(itemname, rights.PUBREAD, username=self.name))
+                flaskg.storage.may(itemname, rights.PUBREAD, usernames=self.names))
 
     def __getattr__(self, attr):
         """ Shortcut to handle all known ACL rights.
@@ -87,14 +88,20 @@
         :returns: checking function for that right
         """
         if attr in app.cfg.acl_rights_contents:
-            return lambda itemname: flaskg.storage.may(itemname, attr, username=self.name)
+            return lambda itemname: flaskg.storage.may(itemname, attr, usernames=self.names)
         if attr in app.cfg.acl_rights_functions:
-            may = app.cfg.cache.acl_functions.may
-            return lambda: may(self.name, attr)
+            def multiuser_may():
+                # TODO: if "may" would accept multiple names, we could get rid of this
+                may = app.cfg.cache.acl_functions.may
+                for name in self.names:
+                    if may(name, attr):
+                        return True
+                return False
+            return multiuser_may
         raise AttributeError(attr)
 
 
-class AccessControlList(object):
+class AccessControlList(AutoNe):
     """
     Access Control List - controls who may do what.
 
@@ -168,7 +175,7 @@
         useful in the wiki configuration though.
     """
 
-    special_users = ["All", "Known", "Trusted"] # order is important
+    special_users = ["All", "Known", "Trusted"]  # order is important
 
     def __init__(self, lines=[], default='', valid=None):
         """ Initialize an ACL, starting from <nothing>.
@@ -178,7 +185,7 @@
         self.default = default
         assert isinstance(lines, (list, tuple))
         if lines:
-            self.acl = [] # [ ('User', {"read": 0, ...}), ... ]
+            self.acl = []  # [ ('User', {"read": 0, ...}), ... ]
             self.acl_lines = []
             for line in lines:
                 self._addLine(line)
@@ -236,7 +243,7 @@
         allowed = None
         for entry, rightsdict in self.acl:
             if entry in self.special_users:
-                handler = getattr(self, "_special_"+entry, None)
+                handler = getattr(self, "_special_" + entry, None)
                 allowed = handler(name, dowhat, rightsdict)
             elif entry in groups:
                 this_group = groups[entry]
@@ -247,12 +254,13 @@
                         if special in this_group:
                             handler = getattr(self, "_special_" + special, None)
                             allowed = handler(name, dowhat, rightsdict)
-                            break # order of self.special_users is important
-            elif entry == name:
+                            break  # order of self.special_users is important
+            elif entry == name:  # XXX TODO maybe change this to "entry in names"
+                                 # to check users with multiple names in one go
                 allowed = rightsdict.get(dowhat)
             if allowed is not None:
                 return allowed
-        return allowed # should be None
+        return allowed  # should be None
 
     def _special_All(self, name, dowhat, rightsdict):
         return rightsdict.get(dowhat)
@@ -262,7 +270,7 @@
             that means that there is a valid user account present.
             works for subscription emails.
         """
-        if user.search_users(name_exact=name): # is a user with this name known?
+        if user.search_users(name_exact=name):  # is a user with this name known?
             return rightsdict.get(dowhat)
         return None
 
@@ -279,9 +287,6 @@
     def __eq__(self, other):
         return self.acl_lines == other.acl_lines
 
-    def __ne__(self, other):
-        return self.acl_lines != other.acl_lines
-
 
 class ACLStringIterator(object):
     """ Iterator for acl string
--- a/MoinMoin/security/_tests/test_security.py	Fri Feb 08 08:32:26 2013 -0700
+++ b/MoinMoin/security/_tests/test_security.py	Mon Feb 11 12:04:39 2013 -0700
@@ -15,7 +15,7 @@
 from MoinMoin.security import AccessControlList, ACLStringIterator
 
 from MoinMoin.user import User
-from MoinMoin.config import ACL
+from MoinMoin.constants.keys import NAME, ACL
 from MoinMoin.datastruct import ConfigGroups
 
 from MoinMoin._tests import update_item
@@ -423,7 +423,7 @@
             (self.mainitem_name, u'JaneDoe', ['read', 'write']), # by item acl
             (self.mainitem_name, u'JoeDoe', []), # by item acl
             (self.subitem1_name, u'WikiAdmin', ['read', 'write', 'admin', 'create', 'destroy']),
-            (self.subitem1_name, u'AnyUser', ['read', 'write']), # by default acl
+            (self.subitem1_name, u'AnyUser', ['read']), # by after acl
             (self.subitem1_name, u'JoeDoe', []), # by inherited acl from main item
             (self.subitem1_name, u'JaneDoe', ['read', 'write']), # by inherited acl from main item
             (self.subitem2_name, u'WikiAdmin', ['read', 'write', 'admin', 'create', 'destroy']),
@@ -457,4 +457,83 @@
                 yield _not_have_right, u, right, itemname
 
 
+class TestItemHierachicalAclsMultiItemNames(object):
+    """ security: real-life access control list on items testing
+    """
+    # parent / child item names
+    p1 = [u'p1', ]
+    c1 = [u'p1/c1', ]
+    p2 = [u'p2', ]
+    c2 = [u'p2/c2', ]
+    c12 = [u'p1/c12', u'p2/c12', ]
+    items = [
+        # itemnames, acl, content
+        (p1, u'Editor:', p1),  # deny access (due to hierarchic acl mode also effective for children)
+        (c1, None, c1),  # no own acl -> inherit from parent
+        (p2, None, p2),  # default acl effective (also for children)
+        (c2, None, c2),  # no own acl -> inherit from parent
+        (c12, None, c12),  # no own acl -> inherit from parents
+        ]
+
+    from MoinMoin._tests import wikiconfig
+
+    class Config(wikiconfig.Config):
+        content_acl = dict(hierarchic=True, before=u"WikiAdmin:admin,read,write,create,destroy", default=u"Editor:read,write", after=u"All:read")
+
+    def setup_method(self, method):
+        become_trusted(username=u'WikiAdmin')
+        for item_names, item_acl, item_content in self.items:
+            meta = {NAME: item_names}
+            if item_acl is not None:
+                meta.update({ACL: item_acl})
+            update_item(item_names[0], meta, item_content)
+
+    def testItemACLs(self):
+        """ security: test item acls """
+        tests = [
+            # itemname, username, expected_rights
+            (self.p1, u'WikiAdmin', ['read', 'write', 'admin', 'create', 'destroy']),  # by before acl
+            (self.p2, u'WikiAdmin', ['read', 'write', 'admin', 'create', 'destroy']),  # by before acl
+            (self.c1, u'WikiAdmin', ['read', 'write', 'admin', 'create', 'destroy']),  # by before acl
+            (self.c2, u'WikiAdmin', ['read', 'write', 'admin', 'create', 'destroy']),  # by before acl
+            (self.c12, u'WikiAdmin', ['read', 'write', 'admin', 'create', 'destroy']),  # by before acl
+            (self.p1, u'Editor', []),  # by p1 acl
+            (self.c1, u'Editor', []),  # by p1 acl
+            (self.p1, u'SomeOne', ['read']),  # by after acl
+            (self.c1, u'SomeOne', ['read']),  # by after acl
+            (self.p2, u'Editor', ['read', 'write']),  # by default acl
+            (self.c2, u'Editor', ['read', 'write']),  # by default acl
+            (self.p2, u'SomeOne', ['read']),  # by after acl
+            (self.c2, u'SomeOne', ['read']),  # by after acl
+            (self.c12, u'SomeOne', ['read']),  # by after acl
+            # now check the rather special stuff:
+            (self.c12, u'Editor', ['read', 'write']),  # disallowed via p1, but allowed via p2 via default acl
+        ]
+
+        for itemnames, username, may in tests:
+            u = User(auth_username=username)
+            u.valid = True
+            itemname = itemnames[0]
+
+            def _have_right(u, right, itemname):
+                can_access = getattr(u.may, right)(itemname)
+                assert can_access, "{0!r} may {1} {2!r} (hierarchic)".format(u.name, right, itemname)
+
+            # User should have these rights...
+            for right in may:
+                yield _have_right, u, right, itemname
+
+            def _not_have_right(u, right, itemname):
+                can_access = getattr(u.may, right)(itemname)
+                assert not can_access, "{0!r} may not {1} {2!r} (hierarchic)".format(u.name, right, itemname)
+
+            # User should NOT have these rights:
+            mayNot = [right for right in app.cfg.acl_rights_contents
+                      if right not in may]
+            for right in mayNot:
+                yield _not_have_right, u, right, itemname
+
+
+# XXX TODO add tests for a user having multiple usernames (one resulting in more permissions than other)
+
 coverage_modules = ['MoinMoin.security']
--- a/MoinMoin/security/_tests/test_textcha.py	Fri Feb 08 08:32:26 2013 -0700
+++ b/MoinMoin/security/_tests/test_textcha.py	Mon Feb 11 12:04:39 2013 -0700
@@ -8,8 +8,6 @@
 from flask import current_app as app
 from flask import g as flaskg
 
-import pytest
-
 from MoinMoin.security.textcha import TextCha, TextChaValid, TextChaizedForm
 from MoinMoin.constants.keys import LOCALE
 
--- a/MoinMoin/security/_tests/test_ticket.py	Fri Feb 08 08:32:26 2013 -0700
+++ b/MoinMoin/security/_tests/test_ticket.py	Mon Feb 11 12:04:39 2013 -0700
@@ -6,8 +6,6 @@
 """
 
 
-import py
-
 from MoinMoin.security.ticket import createTicket, checkTicket
 
 
--- a/MoinMoin/security/textcha.py	Fri Feb 08 08:32:26 2013 -0700
+++ b/MoinMoin/security/textcha.py	Mon Feb 11 12:04:39 2013 -0700
@@ -41,8 +41,9 @@
 
 from MoinMoin.i18n import _, L_, N_
 
-SHA1_LEN = 40 # length of hexdigest
-TIMESTAMP_LEN = 10 # length of timestamp
+SHA1_LEN = 40  # length of hexdigest
+TIMESTAMP_LEN = 10  # length of timestamp
+
 
 class TextCha(object):
     """ Text CAPTCHA support """
@@ -112,12 +113,13 @@
 
             try:
                 self.answer_regex = self.textchas[self.question]
-                self.answer_re = re.compile(self.answer_regex, re.U|re.I)
+                self.answer_re = re.compile(self.answer_regex, re.U | re.I)
             except KeyError:
                 # this question does not exist, thus there is no answer
                 self.answer_regex = ur"[Invalid question]"
                 self.answer_re = None
-                logging.warning(u"TextCha: Non-existing question '{0}' for {1}. May be invalid or user may be trying to cheat.".format(self.question, self.user_info))
+                logging.warning(u"TextCha: Non-existing question '{0}' for {1}. "
+                                u"May be invalid or user may be trying to cheat.".format(self.question, self.user_info))
             except re.error:
                 logging.error(u"TextCha: Invalid regex in answer for question '{0}'".format(self.question))
                 self.init_qa()
@@ -137,7 +139,7 @@
                     # ...
                 }
         """
-        return not not self.textchas # we don't want to return the dict
+        return not not self.textchas  # we don't want to return the dict
 
     def amend_form(self):
         """ Amend the form by doing the following:
@@ -147,11 +149,13 @@
         """
         if self.is_enabled():
             if self.question:
-                self.form['textcha_question'].set("{0} {1}{2}".format(self.question, int(self.timestamp), self.signature))
+                self.form['textcha_question'].set("{0} {1}{2}".format(
+                    self.question, int(self.timestamp), self.signature))
         else:
             self.form['textcha_question'].optional = True
             self.form['textcha'].optional = True
 
+
 class TextChaValid(Validator):
     """Validator for TextChas
     """
@@ -172,6 +176,7 @@
 
         return True
 
+
 class TextChaizedForm(Form):
     """a form providing TextCha support"""
     textcha_question = String
--- a/MoinMoin/security/ticket.py	Fri Feb 08 08:32:26 2013 -0700
+++ b/MoinMoin/security/ticket.py	Mon Feb 11 12:04:39 2013 -0700
@@ -10,7 +10,8 @@
 
 
 import time
-import hmac, hashlib
+import hmac
+import hashlib
 
 from MoinMoin import log
 logging = log.getLogger(__name__)
@@ -62,5 +63,6 @@
         logging.debug("checkTicket: too old ticket, timestamp {0!r}".format(timestamp))
         return False
     ourticket = createTicket(timestamp_str, **kw)
-    logging.debug("checkTicket: returning {0!r}, got {1!r}, expected {2!r}".format(ticket == ourticket, ticket, ourticket))
+    logging.debug("checkTicket: returning {0!r}, got {1!r}, expected {2!r}".format(
+        ticket == ourticket, ticket, ourticket))
     return ticket == ourticket
--- a/MoinMoin/signalling/log.py	Fri Feb 08 08:32:26 2013 -0700
+++ b/MoinMoin/signalling/log.py	Mon Feb 11 12:04:39 2013 -0700
@@ -17,6 +17,7 @@
     wiki_name = app.cfg.interwikiname
     logging.info(u"item {0}:{1} displayed".format(wiki_name, item_name))
 
+
 @item_modified.connect_via(ANY)
 def log_item_modified(app, item_name):
     wiki_name = app.cfg.interwikiname
--- a/MoinMoin/storage/__init__.py	Fri Feb 08 08:32:26 2013 -0700
+++ b/MoinMoin/storage/__init__.py	Mon Feb 11 12:04:39 2013 -0700
@@ -12,7 +12,7 @@
  |                                 listing, lookup by name, ACL checks, ...
  v
  Routing  Middleware               dispatches to multiple backends based on the
- |                 |               name, cares about absolute and relative names
+ |                 |               namespace
  v                 v
  "stores" Backend  Other Backend   simple stuff: store, get, destroy revisions
  |           |
@@ -22,7 +22,7 @@
 """
 
 
-CONTENT, USERPROFILES = 'content', 'userprofiles'
+CONTENT, USERPROFILES = u'content', u'userprofiles'
 
 BACKENDS_PACKAGE = 'MoinMoin.storage.backends'
 
@@ -39,15 +39,17 @@
     return module.MutableBackend.from_uri(backend_uri)
 
 
-def create_mapping(uri, mounts, acls):
-    namespace_mapping = [(mounts[nsname],
-                          backend_from_uri(uri % dict(nsname=nsname, kind="%(kind)s")))
-                         for nsname in mounts]
+def create_mapping(uri, namespaces, backends, acls):
+    namespace_mapping = namespaces.items()
     acl_mapping = acls.items()
+    backend_mapping = [(backend_name,
+                        backend_from_uri(uri % dict(backend=backend_name, kind="%(kind)s")))
+                        for backend_name in backends]
     # we need the longest mountpoints first, shortest last (-> '' is very last)
     namespace_mapping = sorted(namespace_mapping, key=lambda x: len(x[0]), reverse=True)
     acl_mapping = sorted(acl_mapping, key=lambda x: len(x[0]), reverse=True)
-    return namespace_mapping, acl_mapping
+    return namespace_mapping, dict(backend_mapping), acl_mapping
+
 
 def create_simple_mapping(uri='stores:fs:instance',
                           content_acl=None, user_profile_acl=None):
@@ -60,9 +62,9 @@
 
     :params uri: '<backend_name>:<backend_uri>' (general form)
                  backend_name must be a backend module name (e.g. stores)
-                 the backend_uri must have a %(nsname)s placeholder, it gets replaced
-                 by the CONTENT, USERPROFILES strings and result is given to
-                 to that backend's constructor
+                 the backend_uri must have a %(backend)s placeholder, it gets replaced
+                 by the name of the backend (a simple, ascii string) and result
+                 is given to to that backend's constructor
 
                  for the 'stores' backend, backend_uri looks like '<store_name>:<store_uri>'
                  store_name must be a store module name (e.g. fs)
@@ -70,7 +72,7 @@
                  by 'meta' or 'data' and the result is given to that store's constructor
 
                  e.g.:
-                 'stores:fs:/path/to/store/%(nsname)s/%(kind)s' will create a mapping
+                 'stores:fs:/path/to/store/%(backend)s/%(kind)s' will create a mapping
                  using the 'stores' backend with 'fs' stores and everything will be stored
                  to below /path/to/store/.
     """
@@ -79,12 +81,16 @@
         content_acl = dict(before=u'', default=u'All:read,write,create', after=u'', hierarchic=False)
     if not user_profile_acl:
         user_profile_acl = dict(before=u'All:', default=u'', after=u'', hierarchic=False)
-    mounts = {
-        CONTENT: '',
-        USERPROFILES: 'UserProfile',
+    namespaces = {
+        u'': CONTENT,
+        u'userprofiles:': USERPROFILES,
+    }
+    backends = {
+        CONTENT: None,
+        USERPROFILES: None,
     }
     acls = {
-        'UserProfile/': user_profile_acl,
+        'userprofiles:': user_profile_acl,
         '': content_acl,
     }
-    return create_mapping(uri, mounts, acls)
+    return create_mapping(uri, namespaces, backends, acls)
--- a/MoinMoin/storage/backends/_tests/__init__.py	Fri Feb 08 08:32:26 2013 -0700
+++ b/MoinMoin/storage/backends/_tests/__init__.py	Mon Feb 11 12:04:39 2013 -0700
@@ -12,7 +12,7 @@
 
 import pytest
 
-from MoinMoin.config import SIZE, HASH_ALGORITHM
+from MoinMoin.constants.keys import SIZE, HASH_ALGORITHM
 
 class BackendTestBase(object):
     def setup_method(self, method):
--- a/MoinMoin/storage/backends/_tests/test_fileserver.py	Fri Feb 08 08:32:26 2013 -0700
+++ b/MoinMoin/storage/backends/_tests/test_fileserver.py	Mon Feb 11 12:04:39 2013 -0700
@@ -11,9 +11,7 @@
 import os
 import tempfile
 
-import pytest
-
-from MoinMoin.config import NAME, MTIME, REVID, ITEMID, HASH_ALGORITHM
+from MoinMoin.constants.keys import NAME, MTIME, REVID, ITEMID, HASH_ALGORITHM
 from ..fileserver import Backend
 from . import BackendTestBase
 
--- a/MoinMoin/storage/backends/_tests/test_stores.py	Fri Feb 08 08:32:26 2013 -0700
+++ b/MoinMoin/storage/backends/_tests/test_stores.py	Mon Feb 11 12:04:39 2013 -0700
@@ -11,8 +11,6 @@
 
 from __future__ import absolute_import, division
 
-import pytest
-
 from ..stores import MutableBackend
 from . import MutableBackendTestBase
 
--- a/MoinMoin/storage/backends/fileserver.py	Fri Feb 08 08:32:26 2013 -0700
+++ b/MoinMoin/storage/backends/fileserver.py	Mon Feb 11 12:04:39 2013 -0700
@@ -22,7 +22,7 @@
 from StringIO import StringIO
 from werkzeug import url_quote, url_unquote
 
-from MoinMoin.config import NAME, ITEMID, REVID, MTIME, SIZE, CONTENTTYPE, HASH_ALGORITHM
+from MoinMoin.constants.keys import NAME, ITEMID, REVID, MTIME, SIZE, CONTENTTYPE, HASH_ALGORITHM
 from . import BackendBase
 
 from MoinMoin.util.mimetype import MimeType
@@ -76,7 +76,7 @@
         st = os.stat(path)
         root = self.path
         assert path.startswith(root)
-        relpath = path[len(root)+1:]
+        relpath = path[len(root) + 1:]
         # we always want to give NAME_SEP-separated names (not backslash):
         if os.sep == NAME_SEP:
             itemname = relpath
@@ -104,10 +104,10 @@
             raise
         meta = {}
         meta[NAME] = itemname
-        meta[MTIME] = int(st.st_mtime) # use int, not float
+        meta[MTIME] = int(st.st_mtime)  # use int, not float
         meta[REVID] = unicode(self._encode('%s.%d' % (meta[NAME], meta[MTIME])))
         meta[ITEMID] = meta[REVID]
-        meta[HASH_ALGORITHM] = u'' # XXX crap, but sendfile needs it for etag
+        meta[HASH_ALGORITHM] = u''  # XXX crap, but sendfile needs it for etag
         if stat.S_ISDIR(st.st_mode):
             # directory
             # we create a virtual wiki page listing links to subitems:
@@ -116,7 +116,7 @@
         elif stat.S_ISREG(st.st_mode):
             # normal file
             ct = unicode(MimeType(filename=itemname).content_type())
-            size = int(st.st_size) # use int instead of long
+            size = int(st.st_size)  # use int instead of long
         else:
             # symlink, device file, etc.
             ct = u'application/octet-stream'
@@ -169,7 +169,7 @@
         # update() method can efficiently update the index).
         for dirpath, dirnames, filenames in os.walk(self.path):
             key, mtime = self._mkkey(dirpath)
-            if 1: # key:
+            if 1:  # key:
                 yield self._encode('%s.%d' % (key, mtime))
             for filename in filenames:
                 yield self._encode('%s.%d' % self._mkkey(os.path.join(dirpath, filename)))
--- a/MoinMoin/storage/backends/stores.py	Fri Feb 08 08:32:26 2013 -0700
+++ b/MoinMoin/storage/backends/stores.py	Mon Feb 11 12:04:39 2013 -0700
@@ -23,7 +23,7 @@
 
 import json
 
-from MoinMoin.config import REVID, DATAID, SIZE, HASH_ALGORITHM
+from MoinMoin.constants.keys import REVID, DATAID, SIZE, HASH_ALGORITHM
 from MoinMoin.util.crypto import make_uuid
 
 from . import BackendBase, MutableBackendBase
@@ -136,21 +136,28 @@
             size_expected = meta.get(SIZE)
             size_real = tfw.size
             if size_expected is not None and size_expected != size_real:
-                raise ValueError("computed data size ({0}) does not match data size declared in metadata ({1})".format(size_real, size_expected))
+                raise ValueError("computed data size ({0}) does not match data size declared in metadata ({1})".format(
+                                 size_real, size_expected))
             meta[SIZE] = size_real
             hash_expected = meta.get(HASH_ALGORITHM)
             hash_real = tfw.hash.hexdigest()
             if hash_expected is not None and hash_expected != hash_real:
-                raise ValueError("computed data hash ({0}) does not match data hash declared in metadata ({1})".format(hash_real, hash_expected))
+                raise ValueError("computed data hash ({0}) does not match data hash declared in metadata ({1})".format(
+                                 hash_real, hash_expected))
             meta[HASH_ALGORITHM] = hash_real
         else:
             dataid = meta[DATAID]
             # we will just asume stuff is correct if you pass it with a data id
             if dataid not in self.data_store:
-                # XXX issue: if we do not store if we already have the dataid in the store,
-                # XXX deserialization does not work as the fpos does not advance to the next record,
-                # XXX because we do not read from the source file. Remove the check?
                 self.data_store[dataid] = data
+            else:
+                # this is reading the data to avoid this issue:
+                # if we do not store if we already have the dataid in the store,
+                # deserialization does not work as the fpos does not advance to the next record,
+                # because we do not read from the source file. Remove the check?
+                while data.read(64 * 1024):
+                    pass
+
         # if something goes wrong below, the data shall be purged by a garbage collection
         metaid = self._store_meta(meta)
         return metaid
--- a/MoinMoin/storage/error.py	Fri Feb 08 08:32:26 2013 -0700
+++ b/MoinMoin/storage/error.py	Mon Feb 11 12:04:39 2013 -0700
@@ -16,26 +16,31 @@
     General class for exceptions on the storage layer.
     """
 
+
 class BackendError(StorageError):
     """
     Raised if the backend couldn't commit the action.
     """
 
+
 class NoSuchItemError(BackendError):
     """
     Raised if the requested item does not exist.
     """
 
+
 class ItemAlreadyExistsError(BackendError):
     """
     Raised if the Item you are trying to create already exists.
     """
 
+
 class NoSuchRevisionError(BackendError):
     """
     Raised if the requested revision of an item does not exist.
     """
 
+
 class RevisionAlreadyExistsError(BackendError):
     """
     Raised if the Revision you are trying to create already exists.
--- a/MoinMoin/storage/middleware/_tests/test_indexing.py	Fri Feb 08 08:32:26 2013 -0700
+++ b/MoinMoin/storage/middleware/_tests/test_indexing.py	Mon Feb 11 12:04:39 2013 -0700
@@ -15,18 +15,11 @@
 
 from flask import g as flaskg
 
-from MoinMoin.config import NAME, SIZE, ITEMID, REVID, DATAID, HASH_ALGORITHM, CONTENT, COMMENT, \
-                            LATEST_REVS, ALL_REVS
-
-from ..indexing import IndexingMiddleware
+from MoinMoin.constants.keys import (NAME, SIZE, ITEMID, REVID, DATAID, HASH_ALGORITHM, CONTENT, COMMENT,
+                                     LATEST_REVS, ALL_REVS, NAMESPACE)
 
 from MoinMoin.auth import GivenAuth
 from MoinMoin._tests import wikiconfig
-from MoinMoin.storage.backends.stores import MutableBackend
-from MoinMoin.storage.stores.memory import BytesStore as MemoryBytesStore
-from MoinMoin.storage.stores.memory import FileStore as MemoryFileStore
-from MoinMoin.storage import create_simple_mapping
-from MoinMoin.storage.middleware import routing
 
 
 def dumper(indexer, idx_name):
@@ -54,14 +47,14 @@
         item_name = u'foo'
         data = 'bar'
         item = self.imw[item_name]
-        rev = item.store_revision(dict(name=item_name), StringIO(data),
+        rev = item.store_revision(dict(name=[item_name, ]), StringIO(data),
                                   return_rev=True)
         revid = rev.revid
         # check if we have the revision now:
         item = self.imw[item_name]
         assert item # does exist
         rev = item.get_revision(revid)
-        assert rev.meta[NAME] == item_name
+        assert rev.name == item_name
         assert rev.data.read() == data
         revids = [rev.revid for rev in item.iter_revs()]
         assert revids == [revid]
@@ -71,15 +64,15 @@
         data = 'bar'
         newdata = 'baz'
         item = self.imw[item_name]
-        rev = item.store_revision(dict(name=item_name, comment=u'spam'), StringIO(data),
+        rev = item.store_revision(dict(name=[item_name, ], comment=u'spam'), StringIO(data),
                                   return_rev=True)
         revid = rev.revid
         # clear revision:
-        item.store_revision(dict(name=item_name, revid=revid, comment=u'no spam'), StringIO(newdata), overwrite=True)
+        item.store_revision(dict(name=[item_name, ], revid=revid, comment=u'no spam'), StringIO(newdata), overwrite=True)
         # check if the revision was overwritten:
         item = self.imw[item_name]
         rev = item.get_revision(revid)
-        assert rev.meta[NAME] == item_name
+        assert rev.name == item_name
         assert rev.meta[COMMENT] == u'no spam'
         assert rev.data.read() == newdata
         revids = [rev.revid for rev in item.iter_revs()]
@@ -89,13 +82,13 @@
     def test_destroy_revision(self):
         item_name = u'foo'
         item = self.imw[item_name]
-        rev = item.store_revision(dict(name=item_name, mtime=1),
+        rev = item.store_revision(dict(name=[item_name, ], mtime=1),
                                   StringIO('bar'), trusted=True, return_rev=True)
         revid0 = rev.revid
-        rev = item.store_revision(dict(name=item_name, mtime=2),
+        rev = item.store_revision(dict(name=[item_name, ], mtime=2),
                                   StringIO('baz'), trusted=True, return_rev=True)
         revid1 = rev.revid
-        rev = item.store_revision(dict(name=item_name, mtime=3),
+        rev = item.store_revision(dict(name=[item_name, ], mtime=3),
                                   StringIO('...'), trusted=True, return_rev=True)
         revid2 = rev.revid
         print "revids:", revid0, revid1, revid2
@@ -131,10 +124,10 @@
         revids = []
         item_name = u'foo'
         item = self.imw[item_name]
-        rev = item.store_revision(dict(name=item_name, mtime=1),
+        rev = item.store_revision(dict(name=[item_name, ], mtime=1),
                                   StringIO('bar'), trusted=True, return_rev=True)
         revids.append(rev.revid)
-        rev = item.store_revision(dict(name=item_name, mtime=2),
+        rev = item.store_revision(dict(name=[item_name, ], mtime=2),
                                   StringIO('baz'), trusted=True, return_rev=True)
         revids.append(rev.revid)
         # destroy item:
@@ -146,11 +139,11 @@
     def test_all_revisions(self):
         item_name = u'foo'
         item = self.imw[item_name]
-        item.store_revision(dict(name=item_name), StringIO('does not count, different name'))
+        item.store_revision(dict(name=[item_name, ]), StringIO('does not count, different name'))
         item_name = u'bar'
         item = self.imw[item_name]
-        item.store_revision(dict(name=item_name), StringIO('1st'))
-        item.store_revision(dict(name=item_name), StringIO('2nd'))
+        item.store_revision(dict(name=[item_name, ]), StringIO('1st'))
+        item.store_revision(dict(name=[item_name, ]), StringIO('2nd'))
         item = self.imw[item_name]
         revs = [rev.data.read() for rev in item.iter_revs()]
         assert len(revs) == 2
@@ -159,11 +152,11 @@
     def test_latest_revision(self):
         item_name = u'foo'
         item = self.imw[item_name]
-        item.store_revision(dict(name=item_name), StringIO('does not count, different name'))
+        item.store_revision(dict(name=[item_name, ]), StringIO('does not count, different name'))
         item_name = u'bar'
         item = self.imw[item_name]
-        item.store_revision(dict(name=item_name), StringIO('1st'))
-        expected_rev = item.store_revision(dict(name=item_name), StringIO('2nd'),
+        item.store_revision(dict(name=[item_name, ]), StringIO('1st'))
+        expected_rev = item.store_revision(dict(name=[item_name, ]), StringIO('2nd'),
                                            return_rev=True)
         revs = list(self.imw.documents(name=item_name))
         assert len(revs) == 1  # there is only 1 latest revision
@@ -173,9 +166,9 @@
         item_name = u'foo'
         data = 'bar'
         item = self.imw[item_name]
-        rev = item.store_revision(dict(name=item_name), StringIO(data), return_rev=True)
+        rev = item.store_revision(dict(name=[item_name, ]), StringIO(data), return_rev=True)
         print repr(rev.meta)
-        assert rev.meta[NAME] == item_name
+        assert rev.name == item_name
         assert rev.meta[SIZE] == len(data)
         assert rev.meta[HASH_ALGORITHM] == hashlib.new(HASH_ALGORITHM, data).hexdigest()
         assert ITEMID in rev.meta
@@ -185,9 +178,9 @@
     def test_documents(self):
         item_name = u'foo'
         item = self.imw[item_name]
-        rev1 = item.store_revision(dict(name=item_name), StringIO('x'), return_rev=True)
-        rev2 = item.store_revision(dict(name=item_name), StringIO('xx'), return_rev=True)
-        rev3 = item.store_revision(dict(name=item_name), StringIO('xxx'), return_rev=True)
+        rev1 = item.store_revision(dict(name=[item_name, ]), StringIO('x'), return_rev=True)
+        rev2 = item.store_revision(dict(name=[item_name, ]), StringIO('xx'), return_rev=True)
+        rev3 = item.store_revision(dict(name=[item_name, ]), StringIO('xxx'), return_rev=True)
         rev = self.imw.document(idx_name=ALL_REVS, size=2)
         assert rev
         assert rev.revid == rev2.revid
@@ -200,15 +193,15 @@
         expected_latest_revids = []
         item_name = u'foo'
         item = self.imw[item_name]
-        r = item.store_revision(dict(name=item_name, mtime=1),
+        r = item.store_revision(dict(name=[item_name, ], mtime=1),
                                 StringIO('does not count, different name'),
                                 trusted=True, return_rev=True)
         expected_latest_revids.append(r.revid)
         item_name = u'bar'
         item = self.imw[item_name]
-        item.store_revision(dict(name=item_name, mtime=1),
+        item.store_revision(dict(name=[item_name, ], mtime=1),
                             StringIO('1st'), trusted=True)
-        r = item.store_revision(dict(name=item_name, mtime=2),
+        r = item.store_revision(dict(name=[item_name, ], mtime=2),
                                 StringIO('2nd'), trusted=True, return_rev=True)
         expected_latest_revids.append(r.revid)
 
@@ -250,14 +243,14 @@
         missing_revids = []
         item_name = u'updated'
         item = self.imw[item_name]
-        r = item.store_revision(dict(name=item_name, mtime=1),
+        r = item.store_revision(dict(name=[item_name, ], mtime=1),
                                 StringIO('updated 1st'),
                                 trusted=True, return_rev=True)
         expected_all_revids.append(r.revid)
         # we update this item below, so we don't add it to expected_latest_revids
         item_name = u'destroyed'
         item = self.imw[item_name]
-        r = item.store_revision(dict(name=item_name, mtime=1),
+        r = item.store_revision(dict(name=[item_name, ], mtime=1),
                                 StringIO('destroyed 1st'),
                                 trusted=True, return_rev=True)
         destroy_revid = r.revid
@@ -265,12 +258,12 @@
         # we destroy this item below, so we don't add it to expected_latest_revids
         item_name = u'stayssame'
         item = self.imw[item_name]
-        r = item.store_revision(dict(name=item_name, mtime=1),
+        r = item.store_revision(dict(name=[item_name, ], mtime=1),
                                 StringIO('stayssame 1st'),
                                 trusted=True, return_rev=True)
         expected_all_revids.append(r.revid)
         # we update this item below, so we don't add it to expected_latest_revids
-        r = item.store_revision(dict(name=item_name, mtime=2),
+        r = item.store_revision(dict(name=[item_name, ], mtime=2),
                                 StringIO('stayssame 2nd'),
                                 trusted=True, return_rev=True)
         expected_all_revids.append(r.revid)
@@ -287,7 +280,7 @@
         # this will not change the fresh index, but the old index we are still using.
         item_name = u'updated'
         item = self.imw[item_name]
-        r = item.store_revision(dict(name=item_name, mtime=2),
+        r = item.store_revision(dict(name=[item_name, ], mtime=2),
                                 StringIO('updated 2nd'), trusted=True,
                                 return_rev=True)
         expected_all_revids.append(r.revid)
@@ -295,7 +288,7 @@
         missing_revids.append(r.revid)
         item_name = u'added'
         item = self.imw[item_name]
-        r = item.store_revision(dict(name=item_name, mtime=1),
+        r = item.store_revision(dict(name=[item_name, ], mtime=1),
                                 StringIO('added 1st'),
                                 trusted=True, return_rev=True)
         expected_all_revids.append(r.revid)
@@ -344,7 +337,7 @@
     def test_revision_contextmanager(self):
         # check if rev.data is closed after leaving the with-block
         item_name = u'foo'
-        meta = dict(name=item_name)
+        meta = dict(name=[item_name, ])
         data = 'some test content'
         item = self.imw[item_name]
         data_file = StringIO(data)
@@ -362,7 +355,7 @@
         # TODO: this is a very simple check that assumes that data is put 1:1
         # into index' CONTENT field.
         item_name = u'foo'
-        meta = dict(name=item_name, contenttype=u'text/plain')
+        meta = dict(name=[item_name, ], contenttype=u'text/plain')
         data = 'some test content\n'
         item = self.imw[item_name]
         data_file = StringIO(data)
@@ -373,6 +366,33 @@
         assert expected_revid == doc[REVID]
         assert unicode(data) == doc[CONTENT]
 
+    def test_namespaces(self):
+        item_name_n = u'normal'
+        item = self.imw[item_name_n]
+        rev_n = item.store_revision(dict(name=[item_name_n, ], contenttype=u'text/plain'),
+                                    StringIO(str(item_name_n)), return_rev=True)
+        item_name_u = u'userprofiles:userprofile'
+        item = self.imw[item_name_u]
+        rev_u = item.store_revision(dict(name=[item_name_u, ], contenttype=u'text/plain'),
+                                    StringIO(str(item_name_u)), return_rev=True)
+        item = self.imw[item_name_n]
+        rev_n = item.get_revision(rev_n.revid)
+        assert rev_n.meta[NAMESPACE] == u''
+        assert rev_n.meta[NAME] == [item_name_n, ]
+        item = self.imw[item_name_u]
+        rev_u = item.get_revision(rev_u.revid)
+        assert rev_u.meta[NAMESPACE] == u'userprofiles'
+        assert rev_u.meta[NAME] == [item_name_u.split(':')[1]]
+
+    def test_parentnames(self):
+        item_name = u'child'
+        item = self.imw[item_name]
+        item.store_revision(dict(name=[u'child', u'p1/a', u'p2/b', u'p2/c', u'p3/p4/d', ],
+                                 contenttype=u'text/plain'),
+                            StringIO(''))
+        item = self.imw[item_name]
+        assert item.parentnames == [u'p1', u'p2', u'p3/p4', ]  # one p2 duplicate removed
+
 class TestProtectedIndexingMiddleware(object):
     reinit_storage = True # cleanup after each test method
 
@@ -388,17 +408,17 @@
     def test_documents(self):
         item_name = u'public'
         item = self.imw[item_name]
-        r = item.store_revision(dict(name=item_name, acl=u'joe:read'),
+        r = item.store_revision(dict(name=[item_name, ], acl=u'joe:read'),
                                 StringIO('public content'), return_rev=True)
         revid_public = r.revid
         revids = [rev.revid for rev in self.imw.documents()
-                  if rev.meta[NAME] != u'joe'] # the user profile is a revision in the backend
+                  if rev.name != u'joe'] # the user profile is a revision in the backend
         assert revids == [revid_public]
 
     def test_getitem(self):
         item_name = u'public'
         item = self.imw[item_name]
-        r = item.store_revision(dict(name=item_name, acl=u'joe:read'),
+        r = item.store_revision(dict(name=[item_name, ], acl=u'joe:read'),
                                 StringIO('public content'), return_rev=True)
         revid_public = r.revid
         # now testing:
@@ -414,7 +434,7 @@
         item_name = u'foo'
         item = self.imw[item_name]
         for i in xrange(100):
-            item.store_revision(dict(name=item_name, acl=u'joe:create joe:read'), StringIO('some content'))
+            item.store_revision(dict(name=[item_name, ], acl=u'joe:create joe:read'), StringIO('some content'))
 
     def test_perf_create_read(self):
         pytest.skip("usually we do no performance tests")
@@ -424,7 +444,7 @@
         item_name = u'foo'
         item = self.imw[item_name]
         for i in xrange(100):
-            item.store_revision(dict(name=item_name, acl=u'joe:create joe:read'), StringIO('rev number {0}'.format(i)))
+            item.store_revision(dict(name=[item_name, ], acl=u'joe:create joe:read'), StringIO('rev number {0}'.format(i)))
         for r in item.iter_revs():
             #print r.meta
             #print r.data.read()
--- a/MoinMoin/storage/middleware/_tests/test_protecting.py	Fri Feb 08 08:32:26 2013 -0700
+++ b/MoinMoin/storage/middleware/_tests/test_protecting.py	Mon Feb 11 12:04:39 2013 -0700
@@ -12,8 +12,6 @@
 
 import pytest
 
-from MoinMoin.config import ACL
-
 from ..protecting import ProtectingMiddleware, AccessDenied
 
 from .test_indexing import TestIndexingMiddleware
@@ -28,17 +26,21 @@
     ('', dict(before=u'', default=u'All:read,write,create', after=u'', hierarchic=False)),
 ]
 
-class User(object):
+class FakeUser(object):
     """
     fake user object, just to give user.name
     """
     def __init__(self, name):
-        self.name = name
+        self.name = [name, ]
+    @property
+    def name0(self):
+        return self.name[0]
+
 
 class TestProtectingMiddleware(TestIndexingMiddleware):
     def setup_method(self, method):
         super(TestProtectingMiddleware, self).setup_method(method)
-        self.imw = ProtectingMiddleware(self.imw, User(u'joe'), acl_mapping=acl_mapping)
+        self.imw = ProtectingMiddleware(self.imw, FakeUser(u'joe'), acl_mapping=acl_mapping)
 
     def teardown_method(self, method):
         self.imw = self.imw.indexer
@@ -59,7 +61,7 @@
         revids = []
         for item_name, acl, content in items:
             item = self.imw[item_name]
-            r = item.store_revision(dict(name=item_name, acl=acl),
+            r = item.store_revision(dict(name=[item_name, ], acl=acl, contenttype=u'text/plain'),
                                     StringIO(content), return_rev=True)
             revids.append(r.revid)
         return revids
@@ -83,26 +85,26 @@
         revid_unprotected, revid_protected = self.make_items(u'joe:write', u'boss:write')
         # now testing:
         item = self.imw[UNPROTECTED]
-        item.store_revision(dict(name=UNPROTECTED, acl=u'joe:write'), StringIO(UNPROTECTED_CONTENT))
+        item.store_revision(dict(name=[UNPROTECTED, ], acl=u'joe:write', contenttype=u'text/plain'), StringIO(UNPROTECTED_CONTENT))
         item = self.imw[PROTECTED]
         with pytest.raises(AccessDenied):
-            item.store_revision(dict(name=PROTECTED, acl=u'boss:write'), StringIO(UNPROTECTED_CONTENT))
+            item.store_revision(dict(name=[PROTECTED, ], acl=u'boss:write', contenttype=u'text/plain'), StringIO(UNPROTECTED_CONTENT))
 
     def test_write_create(self):
         # now testing:
         item_name = u'newitem'
         item = self.imw[item_name]
-        item.store_revision(dict(name=item_name), StringIO('new content'))
+        item.store_revision(dict(name=[item_name, ], contenttype=u'text/plain'), StringIO('new content'))
 
     def test_overwrite_revision(self):
         revid_unprotected, revid_protected = self.make_items(u'joe:write,destroy', u'boss:write,destroy')
         # now testing:
         item = self.imw[UNPROTECTED]
-        item.store_revision(dict(name=UNPROTECTED, acl=u'joe:write,destroy', revid=revid_unprotected),
+        item.store_revision(dict(name=[UNPROTECTED, ], acl=u'joe:write,destroy', contenttype=u'text/plain', revid=revid_unprotected),
                             StringIO(UNPROTECTED_CONTENT), overwrite=True)
         item = self.imw[PROTECTED]
         with pytest.raises(AccessDenied):
-            item.store_revision(dict(name=PROTECTED, acl=u'boss:write,destroy', revid=revid_protected),
+            item.store_revision(dict(name=[PROTECTED, ], acl=u'boss:write,destroy', contenttype=u'text/plain', revid=revid_protected),
                                 StringIO(UNPROTECTED_CONTENT), overwrite=True)
 
     def test_destroy_revision(self):
--- a/MoinMoin/storage/middleware/_tests/test_routing.py	Fri Feb 08 08:32:26 2013 -0700
+++ b/MoinMoin/storage/middleware/_tests/test_routing.py	Mon Feb 11 12:04:39 2013 -0700
@@ -2,7 +2,7 @@
 # License: GNU GPL v2 (or any later version), see LICENSE.txt for details.
 
 """
-MoinMoin - router middleware tests
+MoinMoin - routing middleware tests
 """
 
 
@@ -12,9 +12,9 @@
 
 import pytest
 
-from MoinMoin.config import NAME, REVID
+from MoinMoin.constants.keys import NAME, NAMESPACE
 
-from ..routing import Backend as RouterBackend
+from ..routing import Backend as RoutingBackend
 
 from MoinMoin.storage.backends.stores import MutableBackend as StoreBackend, Backend as ROBackend
 from MoinMoin.storage.stores.memory import BytesStore as MemoryBytesStore
@@ -31,10 +31,12 @@
 
 
 def pytest_funcarg__router(request):
-    root_be = StoreBackend(MemoryBytesStore(), MemoryFileStore())
-    sub_be = StoreBackend(MemoryBytesStore(), MemoryFileStore())
+    default_be = StoreBackend(MemoryBytesStore(), MemoryFileStore())
+    other_be = StoreBackend(MemoryBytesStore(), MemoryFileStore())
     ro_be = make_ro_backend()
-    router = RouterBackend([('sub', sub_be), ('ro', ro_be), ('', root_be)])
+    namespaces = [(u'other:', 'other'), (u'ro:', 'ro'), (u'', 'default')]
+    backends = {'other': other_be, 'ro': ro_be, 'default': default_be, }
+    router = RoutingBackend(namespaces, backends)
     router.create()
     router.open()
 
@@ -46,42 +48,39 @@
     return router
 
 def test_store_get_del(router):
-    root_name = u'foo'
-    root_revid = router.store(dict(name=root_name), StringIO(''))
-    sub_name = u'sub/bar'
-    sub_revid = router.store(dict(name=sub_name), StringIO(''))
+    default_name = u'foo'
+    default_backend_name, default_revid = router.store(dict(name=[default_name, ]), StringIO(''))
+    other_name = u'other:bar'
+    other_backend_name, other_revid = router.store(dict(name=[other_name, ]), StringIO(''))
 
-    # when going via the router backend, we get back fully qualified names:
-    root_meta, _ = router.retrieve(root_name, root_revid)
-    sub_meta, _ = router.retrieve(sub_name, sub_revid)
-    assert root_name == root_meta[NAME]
-    assert sub_name == sub_meta[NAME]
+    # check if store() updates the to-store metadata with correct NAMESPACE and NAME
+    default_meta, _ = router.retrieve(default_backend_name, default_revid)
+    other_meta, _ = router.retrieve(other_backend_name, other_revid)
+    assert u'' == default_meta[NAMESPACE]
+    assert [default_name, ] == default_meta[NAME]
+    assert other_name.split(':')[0] == other_meta[NAMESPACE]
+    assert other_name.split(':')[1] == other_meta[NAME][0]
 
-    # when looking into the storage backend, we see relative names (without mountpoint):
-    root_meta, _ = router.mapping[-1][1].retrieve(root_revid)
-    sub_meta, _ = router.mapping[0][1].retrieve(sub_revid)
-    assert root_name == root_meta[NAME]
-    assert sub_name == 'sub' + '/' + sub_meta[NAME]
     # delete revs:
-    router.remove(root_name, root_revid)
-    router.remove(sub_name, sub_revid)
+    router.remove(default_backend_name, default_revid)
+    router.remove(other_backend_name, other_revid)
 
 
 def test_store_readonly_fails(router):
     with pytest.raises(TypeError):
-        router.store(dict(name=u'ro/testing'), StringIO(''))
+        router.store(dict(name=[u'ro:testing', ]), StringIO(''))
 
 def test_del_readonly_fails(router):
-    ro_id = next(iter(router)) # we have only readonly items
-    print ro_id
+    ro_be_name, ro_id = next(iter(router)) # we have only readonly items
+    print ro_be_name, ro_id
     with pytest.raises(TypeError):
-        router.remove(ro_id)
+        router.remove(ro_be_name, ro_id)
 
 
 def test_destroy_create_dont_touch_ro(router):
     existing = set(router)
-    root_revid = router.store(dict(name=u'foo'), StringIO(''))
-    sub_revid = router.store(dict(name=u'sub/bar'), StringIO(''))
+    default_be_name, default_revid = router.store(dict(name=[u'foo', ]), StringIO(''))
+    other_be_name, other_revid = router.store(dict(name=[u'other:bar', ]), StringIO(''))
 
     router.close()
     router.destroy()
@@ -92,8 +91,8 @@
 
 
 def test_iter(router):
-    existing_before = set([revid for mountpoint, revid in router])
-    root_revid = router.store(dict(name=u'foo'), StringIO(''))
-    sub_revid = router.store(dict(name=u'sub/bar'), StringIO(''))
-    existing_now = set([revid for mountpoint, revid in router])
-    assert existing_now == set([root_revid, sub_revid]) | existing_before
+    existing_before = set([revid for be_name, revid in router])
+    default_be_name, default_revid = router.store(dict(name=[u'foo', ]), StringIO(''))
+    other_be_name, other_revid = router.store(dict(name=[u'other:bar', ]), StringIO(''))
+    existing_now = set([revid for be_name, revid in router])
+    assert existing_now == set([default_revid, other_revid]) | existing_before
--- a/MoinMoin/storage/middleware/_tests/test_serialization.py	Fri Feb 08 08:32:26 2013 -0700
+++ b/MoinMoin/storage/middleware/_tests/test_serialization.py	Mon Feb 11 12:04:39 2013 -0700
@@ -19,11 +19,11 @@
 
 
 contents = [
-    (u'Foo', {'name': u'Foo'}, ''),
-    (u'Foo', {'name': u'Foo'}, '2nd'),
-    (u'Subdir', {'name': u'Subdir'}, ''),
-    (u'Subdir/Foo', {'name': u'Subdir/Foo'}, ''),
-    (u'Subdir/Bar', {'name': u'Subdir/Bar'}, ''),
+    (u'Foo', {'name': [u'Foo', ], 'contenttype': u'text/plain'}, ''),
+    (u'Foo', {'name': [u'Foo', ], 'contenttype': u'text/plain'}, '2nd'),
+    (u'Subdir', {'name': [u'Subdir', ], 'contenttype': u'text/plain'}, ''),
+    (u'Subdir/Foo', {'name': [u'Subdir/Foo', ], 'contenttype': u'text/plain'}, ''),
+    (u'Subdir/Bar', {'name': [u'Subdir/Bar', ], 'contenttype': u'text/plain'}, ''),
 ]
 
 
@@ -51,8 +51,9 @@
     meta_store = BytesStore()
     data_store = FileStore()
     _backend = MutableBackend(meta_store, data_store)
-    mapping = [('', _backend)]
-    backend = RoutingBackend(mapping)
+    namespaces = [('', u'backend')]
+    backends = {u'backend': _backend}
+    backend = RoutingBackend(namespaces, backends)
     backend.create()
     backend.open()
     request.addfinalizer(backend.destroy)
@@ -70,7 +71,7 @@
 def test_serialize_deserialize(source, target):
     i = 0
     for name, meta, data in contents:
-        item = source['name']
+        item = source[u'name']
         item.store_revision(dict(meta, mtime=i), StringIO(data))
         i += 1
 
--- a/MoinMoin/storage/middleware/_tests/test_validation.py	Fri Feb 08 08:32:26 2013 -0700
+++ b/MoinMoin/storage/middleware/_tests/test_validation.py	Mon Feb 11 12:04:39 2013 -0700
@@ -8,12 +8,10 @@
 
 from __future__ import absolute_import, division
 
-import pytest
-
 from MoinMoin.storage.middleware.validation import ContentMetaSchema, UserMetaSchema
 
 from MoinMoin.constants import keys
-from MoinMoin.config import CONTENTTYPE_USER
+from MoinMoin.constants.contenttypes import CONTENTTYPE_USER
 
 from MoinMoin.util.crypto import make_uuid
 
@@ -31,7 +29,8 @@
         meta = {
             keys.REVID: make_uuid(),
             keys.PARENTID: make_uuid(),
-            keys.NAME: u"a",
+            keys.NAME: [u"a", ],
+            keys.NAMESPACE: u"",
             keys.ACL: u"All:read",
             keys.TAGS: [u"foo", u"bar"],
         }
@@ -45,6 +44,7 @@
                  keys.HASH_ALGORITHM: u'b9064b9a5efd8c6cef2d38a8169a0e1cbfdb41ba',
                  keys.SIZE: 0,
                  keys.WIKINAME: u'ThisWiki',
+                 keys.NAMESPACE: u'',
                  'rev_parent': rev,
                  'acl_parent': u"All:read",
                  'contenttype_current': u'text/x.moin.wiki;charset=utf-8',
@@ -64,7 +64,8 @@
         meta = {
             keys.ITEMID: make_uuid(),
             keys.REVID: make_uuid(),
-            keys.NAME: u"user name",
+            keys.NAME: [u"user name", ],
+            keys.NAMESPACE: u"userprofiles",
             keys.EMAIL: u"foo@example.org",
         }
 
@@ -74,6 +75,7 @@
                  keys.HOSTNAME: u'localhost',
                  keys.ADDRESS: u'127.0.0.1',
                  keys.WIKINAME: u'ThisWiki',
+                 keys.NAMESPACE: u'',
                 }
 
         m = UserMetaSchema(meta)
--- a/MoinMoin/storage/middleware/indexing.py	Fri Feb 08 08:32:26 2013 -0700
+++ b/MoinMoin/storage/middleware/indexing.py	Mon Feb 11 12:04:39 2013 -0700
@@ -53,17 +53,16 @@
 
 import os
 import shutil
-import itertools
-import time
 import datetime
-from StringIO import StringIO
+
+from MoinMoin import log
+logging = log.getLogger(__name__)
 
 from flask import request
 from flask import g as flaskg
 from flask import current_app as app
 
 from whoosh.fields import Schema, TEXT, ID, IDLIST, NUMERIC, DATETIME, KEYWORD, BOOLEAN
-from whoosh.index import EmptyIndexError
 from whoosh.writing import AsyncWriter
 from whoosh.qparser import QueryParser, MultifieldParser, RegexPlugin, \
                            PseudoFieldPlugin
@@ -74,20 +73,17 @@
 from MoinMoin import log
 logging = log.getLogger(__name__)
 
-from MoinMoin.config import WIKINAME, NAME, NAME_EXACT, MTIME, CONTENTTYPE, TAGS, \
-                            LANGUAGE, USERID, ADDRESS, HOSTNAME, SIZE, ACTION, COMMENT, SUMMARY, \
-                            CONTENT, EXTERNALLINKS, ITEMLINKS, ITEMTRANSCLUSIONS, ACL, EMAIL, OPENID, \
-                            ITEMID, REVID, CURRENT, PARENTID, \
-                            PTIME, \
-                            LATEST_REVS, ALL_REVS, \
-                            CONTENTTYPE_USER
+from MoinMoin.constants.keys import (WIKINAME, NAMESPACE, NAME, NAME_EXACT, MTIME, CONTENTTYPE, TAGS, LANGUAGE,
+                                     USERID, ADDRESS, HOSTNAME, SIZE, ACTION, COMMENT, SUMMARY, CONTENT,
+                                     EXTERNALLINKS, ITEMLINKS, ITEMTRANSCLUSIONS, ACL, EMAIL, OPENID,
+                                     ITEMID, REVID, CURRENT, PARENTID, PTIME, LATEST_REVS, ALL_REVS, BACKENDNAME)
+from MoinMoin.constants.contenttypes import CONTENTTYPE_USER
 from MoinMoin.constants import keys
 from MoinMoin.constants.keys import ITEMTYPE
 
 from MoinMoin import user
 from MoinMoin.search.analyzers import item_name_analyzer, MimeTokenizer, AclTokenizer
 from MoinMoin.themes import utctimestamp
-from MoinMoin.util.crypto import make_uuid
 from MoinMoin.storage.middleware.validation import ContentMetaSchema, UserMetaSchema
 from MoinMoin.storage.error import NoSuchItemError, ItemAlreadyExistsError
 
@@ -96,7 +92,40 @@
 INDEXES = [LATEST_REVS, ALL_REVS, ]
 
 
-def backend_to_index(meta, content, schema, wikiname):
+def get_names(meta):
+    """
+    Get the (list of) names from meta data and deal with misc. bad things that
+    can happen then (while not all code is fixed to do it correctly).
+
+    TODO make sure meta[NAME] is always a list of unicode
+
+    :param meta: a metadata dictionary that might have a NAME key
+    :return: list of names
+    """
+    msg = "NAME is not a list but %r - fix this! Workaround enabled."
+    names = meta.get(NAME)
+    if names is None:
+        logging.warning(msg % names)
+        names = []
+    elif isinstance(names, str):
+        logging.warning(msg % names)
+        names = [names.decode('utf-8'), ]
+    elif isinstance(names, unicode):
+        logging.warning(msg % names)
+        names = [names, ]
+    elif isinstance(names, tuple):
+        logging.warning(msg % names)
+        names = list(names)
+    elif not isinstance(names, list):
+        raise TypeError("NAME is not a list but %r - fix this!" % names)
+    if not names:
+        # we currently never return an empty list, some code
+        # might not be able to deal with it:
+        names = [u'DoesNotExist', ]
+    return names
+
+
+def backend_to_index(meta, content, schema, wikiname, backend_name):
     """
     Convert backend metadata/data to a whoosh document.
 
@@ -116,6 +145,7 @@
     doc[NAME_EXACT] = doc[NAME]
     doc[WIKINAME] = wikiname
     doc[CONTENT] = content
+    doc[BACKENDNAME] = backend_name
     return doc
 
 
@@ -124,7 +154,8 @@
 from MoinMoin.converter import default_registry
 from MoinMoin.util.iri import Iri
 
-def convert_to_indexable(meta, data, is_new=False):
+
+def convert_to_indexable(meta, data, item_name=None, is_new=False):
     """
     Convert revision data to a indexable content.
 
@@ -143,17 +174,24 @@
             self.meta = meta
             self.data = data
             self.revid = meta.get(REVID)
+
             class PseudoItem(object):
                 def __init__(self, name):
                     self.name = name
-            self.item = PseudoItem(meta.get(NAME))
+            self.item = PseudoItem(item_name)
+
         def read(self, *args, **kw):
             return self.data.read(*args, **kw)
+
         def seek(self, *args, **kw):
             return self.data.seek(*args, **kw)
+
         def tell(self, *args, **kw):
             return self.data.tell(*args, **kw)
 
+    if not item_name:
+        item_name = get_names(meta)[0]
+
     rev = PseudoRev(meta, data)
     try:
         # TODO use different converter mode?
@@ -184,7 +222,7 @@
             # transclusions.
             if is_new:
                 # we only can modify new, uncommitted revisions, not stored revs
-                i = Iri(scheme='wiki', authority='', path='/' + meta[NAME])
+                i = Iri(scheme='wiki', authority='', path='/' + item_name)
                 doc.set(moin_page.page_href, unicode(i))
                 refs_conv(doc)
                 # side effect: we update some metadata:
@@ -195,8 +233,9 @@
             return doc
         # no way
         raise TypeError("No converter for {0} --> {1}".format(input_contenttype, output_contenttype))
-    except Exception as e: # catch all exceptions, we don't want to break an indexing run
-        logging.exception("Exception happened in conversion of item {0!r} rev {1} contenttype {2}:".format(meta[NAME], meta.get(REVID, 'new'), meta.get(CONTENTTYPE, '')))
+    except Exception as e:  # catch all exceptions, we don't want to break an indexing run
+        logging.exception("Exception happened in conversion of item {0!r} rev {1} contenttype {2}:".format(
+                          item_name, meta.get(REVID, 'new'), meta.get(CONTENTTYPE, '')))
         doc = u'ERROR [{0!s}]'.format(e)
         return doc
 
@@ -225,6 +264,8 @@
             REVID: ID(unique=True, stored=True),
             # parent revision id
             PARENTID: ID(stored=True),
+            # backend name (which backend is this rev stored in?)
+            BACKENDNAME: ID(stored=True),
             # MTIME from revision metadata (converted to UTC datetime)
             MTIME: DATETIME(stored=True),
             # publish time from metadata (converted to UTC datetime)
@@ -401,7 +442,7 @@
             index_dir, index_dir_tmp = params[0], params_tmp[0]
             os.rename(index_dir_tmp, index_dir)
 
-    def index_revision(self, meta, content, async=True):
+    def index_revision(self, meta, content, backend_name, async=False):  # True
         """
         Index a single revision, add it to all-revs and latest-revs index.
 
@@ -409,14 +450,14 @@
         :param content: preprocessed (filtered) indexable content
         :param async: if True, use the AsyncWriter, otherwise use normal writer
         """
-        doc = backend_to_index(meta, content, self.schemas[ALL_REVS], self.wikiname)
+        doc = backend_to_index(meta, content, self.schemas[ALL_REVS], self.wikiname, backend_name)
         if async:
             writer = AsyncWriter(self.ix[ALL_REVS])
         else:
             writer = self.ix[ALL_REVS].writer()
         with writer as writer:
-            writer.update_document(**doc) # update, because store_revision() may give us an existing revid
-        doc = backend_to_index(meta, content, self.schemas[LATEST_REVS], self.wikiname)
+            writer.update_document(**doc)  # update, because store_revision() may give us an existing revid
+        doc = backend_to_index(meta, content, self.schemas[LATEST_REVS], self.wikiname, backend_name)
         if async:
             writer = AsyncWriter(self.ix[LATEST_REVS])
         else:
@@ -446,20 +487,21 @@
                     itemid = searcher.stored_fields(docnum_remove)[ITEMID]
             if docnum_remove is not None:
                 # we are removing a revid that is in latest revs index
-                latest_names_revids = self._find_latest_names_revids(self.ix[ALL_REVS], Term(ITEMID, itemid))
-                if latest_names_revids:
+                latest_backends_revids = self._find_latest_backends_revids(self.ix[ALL_REVS], Term(ITEMID, itemid))
+                if latest_backends_revids:
                     # we have a latest revision, just update the document in the index:
-                    assert len(latest_names_revids) == 1 # this item must have only one latest revision
-                    latest_name_revid = latest_names_revids[0]
+                    assert len(latest_backends_revids) == 1  # this item must have only one latest revision
+                    latest_backend_revid = latest_backends_revids[0]
                     # we must fetch from backend because schema for LATEST_REVS is different than for ALL_REVS
                     # (and we can't be sure we have all fields stored, too)
-                    meta, _ = self.backend.retrieve(*latest_name_revid)
+                    meta, _ = self.backend.retrieve(*latest_backend_revid)
                     # we only use meta (not data), because we do not want to transform data->content again (this
                     # is potentially expensive) as we already have the transformed content stored in ALL_REVS index:
                     with self.ix[ALL_REVS].searcher() as searcher:
-                        doc = searcher.document(revid=latest_name_revid[1])
+                        doc = searcher.document(revid=latest_backend_revid[1])
                         content = doc[CONTENT]
-                    doc = backend_to_index(meta, content, self.schemas[LATEST_REVS], self.wikiname)
+                    doc = backend_to_index(meta, content, self.schemas[LATEST_REVS], self.wikiname,
+                                           backend_name=latest_backend_revid[0])
                     writer.update_document(**doc)
                 else:
                     # this is no revision left in this item that could be the new "latest rev", just kill the rev
@@ -473,11 +515,11 @@
               documents in the index.
         """
         with index.writer(procs=procs, limitmb=limitmb) as writer:
-            for mountpoint, revid in revids:
+            for backend_name, revid in revids:
                 if mode in ['add', 'update', ]:
-                    meta, data = self.backend.retrieve(mountpoint, revid)
+                    meta, data = self.backend.retrieve(backend_name, revid)
                     content = convert_to_indexable(meta, data, is_new=False)
-                    doc = backend_to_index(meta, content, schema, wikiname)
+                    doc = backend_to_index(meta, content, schema, wikiname, backend_name)
                 if mode == 'update':
                     writer.update_document(**doc)
                 elif mode == 'add':
@@ -487,13 +529,13 @@
                 else:
                     raise ValueError("mode must be 'update', 'add' or 'delete', not '{0}'".format(mode))
 
-    def _find_latest_names_revids(self, index, query=None):
+    def _find_latest_backends_revids(self, index, query=None):
         """
-        find the latest revids using the all-revs index
+        find the latest revision identifiers using the all-revs index
 
         :param index: an up-to-date and open ALL_REVS index
         :param query: query to search only specific revisions (optional, default: all items/revisions)
-        :returns: a list of tuples (name, latest revid)
+        :returns: a list of tuples (backend name, latest revid)
         """
         if query is None:
             query = Every()
@@ -501,10 +543,10 @@
             result = searcher.search(query, groupedby=ITEMID, sortedby=FieldFacet(MTIME, reverse=True))
             by_item = result.groups(ITEMID)
             # values in v list are in same relative order as in results, so latest MTIME is first:
-            latest_names_revids = [(searcher.stored_fields(v[0])[NAME],
-                                    searcher.stored_fields(v[0])[REVID])
-                                   for v in by_item.values()]
-        return latest_names_revids
+            latest_backends_revids = [(searcher.stored_fields(v[0])[BACKENDNAME],
+                                      searcher.stored_fields(v[0])[REVID])
+                                      for v in by_item.values()]
+        return latest_backends_revids
 
     def rebuild(self, tmp=False, procs=1, limitmb=256):
         """
@@ -519,15 +561,16 @@
         index = storage.open_index(ALL_REVS)
         try:
             # build an index of all we have (so we know what we have)
-            all_revids = self.backend # the backend is an iterator over all revids
+            all_revids = self.backend  # the backend is an iterator over all revids
             self._modify_index(index, self.schemas[ALL_REVS], self.wikiname, all_revids, 'add', procs, limitmb)
-            latest_names_revids = self._find_latest_names_revids(index)
+            latest_backends_revids = self._find_latest_backends_revids(index)
         finally:
             index.close()
         # now build the index of the latest revisions:
         index = storage.open_index(LATEST_REVS)
         try:
-            self._modify_index(index, self.schemas[LATEST_REVS], self.wikiname, latest_names_revids, 'add', procs, limitmb)
+            self._modify_index(index, self.schemas[LATEST_REVS], self.wikiname, latest_backends_revids, 'add',
+                               procs, limitmb)
         finally:
             index.close()
 
@@ -547,24 +590,24 @@
         storage = self.get_storage(tmp)
         index_all = storage.open_index(ALL_REVS)
         try:
-            # NOTE: self.backend iterator gives (mountpoint, revid) tuples, which is NOT
+            # NOTE: self.backend iterator gives (backend_name, revid) tuples, which is NOT
             # the same as (name, revid), thus we do the set operations just on the revids.
             # first update ALL_REVS index:
-            revids_mountpoints = dict((revid, mountpoint) for mountpoint, revid in self.backend)
-            backend_revids = set(revids_mountpoints)
+            revids_backends = dict((revid, backend_name) for backend_name, revid in self.backend)
+            backend_revids = set(revids_backends)
             with index_all.searcher() as searcher:
-                ix_revids_names = dict((doc[REVID], doc[NAME]) for doc in searcher.all_stored_fields())
-            revids_mountpoints.update(ix_revids_names) # this is needed for stuff that was deleted from storage
-            ix_revids = set(ix_revids_names)
+                ix_revids_backends = dict((doc[REVID], doc[BACKENDNAME]) for doc in searcher.all_stored_fields())
+            revids_backends.update(ix_revids_backends)  # this is needed for stuff that was deleted from storage
+            ix_revids = set(ix_revids_backends)
             add_revids = backend_revids - ix_revids
             del_revids = ix_revids - backend_revids
             changed = add_revids or del_revids
-            add_revids = [(revids_mountpoints[revid], revid) for revid in add_revids]
-            del_revids = [(revids_mountpoints[revid], revid) for revid in del_revids]
+            add_revids = [(revids_backends[revid], revid) for revid in add_revids]
+            del_revids = [(revids_backends[revid], revid) for revid in del_revids]
             self._modify_index(index_all, self.schemas[ALL_REVS], self.wikiname, add_revids, 'add')
             self._modify_index(index_all, self.schemas[ALL_REVS], self.wikiname, del_revids, 'delete')
 
-            backend_latest_names_revids = set(self._find_latest_names_revids(index_all))
+            backend_latest_backends_revids = set(self._find_latest_backends_revids(index_all))
         finally:
             index_all.close()
         index_latest = storage.open_index(LATEST_REVS)
@@ -572,9 +615,9 @@
             # now update LATEST_REVS index:
             with index_latest.searcher() as searcher:
                 ix_revids = set(doc[REVID] for doc in searcher.all_stored_fields())
-            backend_latest_revids = set(revid for name, revid in backend_latest_names_revids)
+            backend_latest_revids = set(revid for name, revid in backend_latest_backends_revids)
             upd_revids = backend_latest_revids - ix_revids
-            upd_revids = [(revids_mountpoints[revid], revid) for revid in upd_revids]
+            upd_revids = [(revids_backends[revid], revid) for revid in upd_revids]
             self._modify_index(index_latest, self.schemas[LATEST_REVS], self.wikiname, upd_revids, 'update')
             self._modify_index(index_latest, self.schemas[LATEST_REVS], self.wikiname, del_revids, 'delete')
         finally:
@@ -632,6 +675,7 @@
         else:
             raise ValueError("default_fields list must at least contain one field name")
         qp.add_plugin(RegexPlugin())
+
         def userid_pseudo_field_factory(fieldname):
             """generate a translator function, that searches for the userid
                in the given fieldname when provided with the username
@@ -650,7 +694,7 @@
             # username:JoeDoe searches for revisions modified by JoeDoe
             username=userid_pseudo_field_factory(keys.USERID),
             # assigned:JoeDoe searches for tickets assigned to JoeDoe
-            assigned=userid_pseudo_field_factory('assigned_to'), # XXX should be keys.ASSIGNED_TO
+            assigned=userid_pseudo_field_factory('assigned_to'),  # XXX should be keys.ASSIGNED_TO
         )))
         return qp
 
@@ -769,13 +813,25 @@
         """
         self.indexer = indexer
         self.backend = self.indexer.backend
+        self._name = query.get('name_exact')
         if latest_doc is None:
             # we need to call the method without acl check to avoid endless recursion:
-            latest_doc = self.indexer._document(**query) or {}
+            latest_doc = self.indexer._document(**query)
+            if latest_doc is None:
+                # no such item, create a dummy doc that has a NAME entry to
+                # avoid issues in the name(s) property code. if this was a
+                # lookup for some specific item (using a name_exact query), we
+                # put that name into the NAME list, otherwise it'll be empty:
+                if self._name is not None:
+                    names = [self._name, ]
+                else:
+                    names = []
+                latest_doc = {NAME: names}
         self._current = latest_doc
 
     def _get_itemid(self):
         return self._current.get(ITEMID)
+
     def _set_itemid(self, value):
         self._current[ITEMID] = value
     itemid = property(_get_itemid, _set_itemid)
@@ -785,12 +841,50 @@
         return self._current.get(ACL)
 
     @property
+    def namespace(self):
+        return self._current.get(NAMESPACE)
+
+    @property
     def ptime(self):
         dt = self._current.get(PTIME)
         if dt is not None:
             return utctimestamp(dt)
 
     @property
+    def names(self):
+        return get_names(self._current)
+
+    @property
+    def parentnames(self):
+        """
+        compute list of parent names (same order as in names, but no dupes)
+
+        :return: parent names (list of unicode)
+        """
+        parent_names = []
+        for name in self.names:
+            parentname_tail = name.rsplit('/', 1)
+            if len(parentname_tail) == 2:
+                parent_name = parentname_tail[0]
+                if parent_name not in parent_names:
+                    parent_names.append(parent_name)
+        return parent_names
+
+    @property
+    def parentids(self):
+        """
+        compute list of parent itemids
+
+        :return: parent itemids (set)
+        """
+        parent_ids = set()
+        for parent_name in self.parentnames:
+            rev = self.indexer._document(idx_name=LATEST_REVS, name_exact=parent_name)
+            if rev:
+                parent_ids.add(rev[ITEMID])
+        return parent_ids
+
+    @property
     def mtime(self):
         dt = self._current.get(MTIME)
         if dt is not None:
@@ -798,7 +892,50 @@
 
     @property
     def name(self):
-        return self._current.get(NAME, 'DoesNotExist')
+        if self._name and self._name in self.names:
+            name = self._name
+        else:
+            try:
+                name = self.names[0]
+            except IndexError:
+                # empty name list, no name:
+                name = None
+        assert name is None or isinstance(name, unicode)
+        return name
+
+    def _fqname(self, name):
+        """
+        return the fully qualified name including the namespace: NS:NAME
+        """
+        ns = self.namespace
+        name = name or u''
+        if ns:
+            fqn = ns + u':' + name
+        else:
+            fqn = name
+        assert isinstance(fqn, unicode)
+        return fqn
+
+    @property
+    def fqname(self):
+        """
+        return the fully qualified name including the namespace: NS:NAME
+        """
+        return self._fqname(self.name)
+
+    @property
+    def fqnames(self):
+        """
+        return the fully qualified names including the namespace: NS:NAME
+        """
+        return [self._fqname(name) for name in self.names]
+
+    @property
+    def fqparentnames(self):
+        """
+        return the fully qualified parent names including the namespace: NS:NAME
+        """
+        return [self._fqname(name) for name in self.parentnames]
 
     @classmethod
     def create(cls, indexer, **query):
@@ -851,12 +988,12 @@
         """
         preprocess a revision before it gets stored and put into index.
         """
-        content = convert_to_indexable(meta, data, is_new=True)
+        content = convert_to_indexable(meta, data, self.name, is_new=True)
         return meta, data, content
 
     def store_revision(self, meta, data, overwrite=False,
-                       trusted=False, # True for loading a serialized representation or other trusted sources
-                       name=None, # TODO name we decoded from URL path
+                       trusted=False,  # True for loading a serialized representation or other trusted sources
+                       name=None,  # TODO name we decoded from URL path
                        action=u'SAVE',
                        remote_addr=None,
                        userid=None,
@@ -894,12 +1031,13 @@
         if wikiname is None:
             wikiname = app.cfg.interwikiname
         state = {'trusted': trusted,
-                 keys.NAME: name,
+                 keys.NAME: [name],
                  keys.ACTION: action,
                  keys.ADDRESS: remote_addr,
                  keys.USERID: userid,
                  keys.WIKINAME: wikiname,
-                 keys.ITEMID: self.itemid, # real itemid or None
+                 keys.NAMESPACE: None,
+                 keys.ITEMID: self.itemid,  # real itemid or None
                  'contenttype_current': contenttype_current,
                  'contenttype_guessed': contenttype_guessed,
                  'acl_parent': acl_parent,
@@ -923,7 +1061,8 @@
         # just update the meta dict with the validated stuff:
         meta.update(dict(m.value.items()))
         # we do not want None / empty values:
-        meta = dict([(k, v) for k, v in meta.items() if v not in [None, []]])
+        # XXX do not kick out empty lists before fixing NAME processing:
+        meta = dict([(k, v) for k, v in meta.items() if v not in [None, ]])
 
         if self.itemid is None:
             self.itemid = meta[ITEMID]
@@ -934,9 +1073,9 @@
                 raise ValueError('need overwrite=True to overwrite existing revisions')
         meta, data, content = self.preprocess(meta, data)
         data.seek(0)  # rewind file
-        revid = backend.store(meta, data)
+        backend_name, revid = backend.store(meta, data)
         meta[REVID] = revid
-        self.indexer.index_revision(meta, content)
+        self.indexer.index_revision(meta, content, backend_name)
         if not overwrite:
             self._current = self.indexer._document(revid=revid)
         if return_rev:
@@ -955,7 +1094,7 @@
         Destroy revision <revid>.
         """
         rev = Revision(self, revid)
-        self.backend.remove(rev.name, revid)
+        self.backend.remove(rev.backend_name, revid)
         self.indexer.remove_revision(revid)
 
     def destroy_all_revisions(self):
@@ -970,7 +1109,7 @@
     """
     An existing revision (exists in the backend).
     """
-    def __init__(self, item, revid, doc=None):
+    def __init__(self, item, revid, doc=None, name=None):
         is_current = revid == CURRENT
         if doc is None:
             if is_current:
@@ -986,18 +1125,40 @@
         self.item = item
         self.revid = revid
         self.backend = item.backend
+        self.backend_name = doc[BACKENDNAME]
         self._doc = doc
         self.meta = Meta(self, self._doc)
         self._data = None
+        if name and name in self.names:
+            self._name = name
+        else:
+            self._name = None
         # Note: this does not immediately raise a KeyError for non-existing revs any more
         # If you access data or meta, it will, though.
 
     @property
+    def names(self):
+        return get_names(self.meta)
+
+    @property
     def name(self):
-        return self.meta.get(NAME, 'DoesNotExist')
+        name = self._name
+        if name is None:
+            try:
+                name = self.names[0]
+            except IndexError:
+                # empty name list, no name:
+                name = None
+        return name
+
+    def set_context(self, context):
+        for name in self.names:
+            if name.startswith(context):
+                self._name = name
+                return
 
     def _load(self):
-        meta, data = self.backend.retrieve(self._doc[NAME], self.revid) # raises KeyError if rev does not exist
+        meta, data = self.backend.retrieve(self.backend_name, self.revid)  # raises KeyError if rev does not exist
         self.meta = Meta(self, self._doc, meta)
         self._data = data
         return meta, data
@@ -1024,6 +1185,7 @@
 
 from collections import Mapping
 
+
 class Meta(Mapping):
     def __init__(self, revision, doc, meta=None):
         self.revision = revision
@@ -1065,7 +1227,7 @@
         return cmp(self[MTIME], other[MTIME])
 
     def __len__(self):
-        return 0 # XXX
+        return 0  # XXX
 
     def __repr__(self):
         return "Meta _doc: {0!r} _meta: {1!r}".format(self._doc, self._meta)
--- a/MoinMoin/storage/middleware/protecting.py	Fri Feb 08 08:32:26 2013 -0700
+++ b/MoinMoin/storage/middleware/protecting.py	Mon Feb 11 12:04:39 2013 -0700
@@ -19,11 +19,18 @@
 from MoinMoin import log
 logging = log.getLogger(__name__)
 
-from MoinMoin.config import ACL, CREATE, READ, PUBREAD, WRITE, DESTROY, ADMIN, \
-                            PTIME, ACL_RIGHTS_CONTENTS, \
-                            ALL_REVS, LATEST_REVS
+from whoosh.util import lru_cache
+
+from MoinMoin.constants.rights import (CREATE, READ, PUBREAD, WRITE, DESTROY, ACL_RIGHTS_CONTENTS)
+from MoinMoin.constants.keys import ALL_REVS, LATEST_REVS
+
 from MoinMoin.security import AccessControlList
 
+# max sizes of some lru caches:
+LOOKUP_CACHE = 100  # ACL lookup for some itemname
+PARSE_CACHE = 100  # ACL string -> ACL object parsing
+EVAL_CACHE = 500  # ACL evaluation for some username / capability
+
 
 class AccessDenied(Exception):
     """
@@ -48,7 +55,7 @@
     def __init__(self, indexer, user, acl_mapping):
         """
         :param indexer: indexing middleware instance
-        :param user_name: the user's name (used for checking permissions)
+        :param user: a User instance (used for checking permissions)
         :param acl_mapping: list of (name_prefix, acls) tuples, longest prefix first, '' last
                             acls = dict with before, default, after, hierarchic entries
         """
@@ -56,14 +63,78 @@
         self.user = user
         self.acl_mapping = acl_mapping
         self.valid_rights = ACL_RIGHTS_CONTENTS
+        # The ProtectingMiddleware exists just 1 request long, but might have
+        # to parse and evaluate huge amounts of ACLs. We avoid doing same stuff
+        # again and again by using some fresh lru caches for each PMW instance.
+        lru_cache_decorator = lru_cache(PARSE_CACHE)
+        self.parse_acl = lru_cache_decorator(self._parse_acl)
+        lru_cache_decorator = lru_cache(EVAL_CACHE)
+        self.eval_acl = lru_cache_decorator(self._eval_acl)
+        lru_cache_decorator = lru_cache(LOOKUP_CACHE)
+        self.get_acls = lru_cache_decorator(self._get_acls)
 
-    def get_acls(self, itemname):
+    def _clear_acl_cache(self):
+        # if we have modified the backend somehow so ACL lookup is influenced,
+        # this functions need to get called, so it clears the ACL cache.
+        # ACL lookups afterwards will fetch fresh info from the lower layers.
+        self.get_acls.cache_clear()
+
+    def _get_configured_acls(self, itemname):
+        """
+        for a fully-qualified itemname (namespace:name), get the acl configuration
+        for that (part of the) namespace.
+
+        @param itemname: fully qualified itemname
+        @returns: acl configuration (acl dict from the acl_mapping)
+        """
         for prefix, acls in self.acl_mapping:
             if itemname.startswith(prefix):
                 return acls
         else:
             raise ValueError('No acl_mapping entry found for item {0!r}'.format(itemname))
 
+    def _get_acls(self, itemid=None, fqname=None):
+        """
+        return a list of (alternatively valid) effective acls for the item
+        identified via itemid or fqname.
+        this can be a list just containing the item's own acl (as only alternative),
+        or a list with None, indicating no acl was found (in non-hierarchic mode).
+        if hierarchic acl mode is enabled, a list of all valid parent acls will
+        be returned.
+        All lists are without considering before/default/after acls.
+        """
+
+        if itemid is not None:
+            item = self.get_item(itemid=itemid)
+        elif fqname is not None:
+            # itemid might be None for new, not yet stored items,
+            # but we have fqname then
+            item = self.get_item(name_exact=fqname)
+        else:
+            raise ValueError("need itemid or fqname")
+        acl = item.acl
+        fqname = item.fqname
+        if acl is not None:
+            return [acl, ]
+        acl_cfg = self._get_configured_acls(fqname)
+        if acl_cfg['hierarchic']:
+            # check parent(s), recursively
+            parentids = item.parentids
+            if parentids:
+                acl_list = []
+                for parentid in parentids:
+                    pacls = self.get_acls(parentid, None)
+                    acl_list.extend(pacls)
+                return acl_list
+        return [None, ]
+
+    def _parse_acl(self, acl, default=''):
+        return AccessControlList([acl, ], default=default, valid=self.valid_rights)
+
+    def _eval_acl(self, acl, default_acl, user_name, right):
+        aclobj = self.parse_acl(acl, default_acl)
+        return aclobj.may(user_name, right)
+
     def query_parser(self, default_fields, idx_name=LATEST_REVS):
         return self.indexer.query_parser(default_fields, idx_name=idx_name)
 
@@ -111,9 +182,17 @@
         item = self.indexer.existing_item(**query)
         return ProtectedItem(self, item)
 
-    def may(self, itemname, capability, username=None):
+    def may(self, itemname, capability, usernames=None):
+        if usernames is not None and isinstance(usernames, (str, unicode)):
+            # we got a single username (maybe str), make a list of unicode:
+            if isinstance(usernames, str):
+                usernames = usernames.decode('utf-8')
+            usernames = [usernames, ]
+        if isinstance(itemname, list):
+            # if we get a list of names, just use first one to fetch item
+            itemname = itemname[0]
         item = self[itemname]
-        allowed = item.allows(capability, user_name=username)
+        allowed = item.allows(capability, user_names=usernames)
         return allowed
 
 
@@ -131,85 +210,63 @@
         return self.item.itemid
 
     @property
+    def fqname(self):
+        return self.item.fqname
+
+    @property
+    def parentids(self):
+        return self.item.parentids
+
+    @property
+    def parentnames(self):
+        return self.item.parentnames
+
+    @property
     def name(self):
         return self.item.name
 
+    @property
+    def acl(self):
+        return self.item.acl
+
     def __nonzero__(self):
         return bool(self.item)
 
-    def _allows(self, right, user_name):
-        """
-        check permissions in this item without considering before/after acls
+    def full_acls(self):
         """
-        acls = self.protector.get_acls(self.item.name)
-        acl = self.item.acl
-        if acl is not None:
-            # If the item has an acl (even one that doesn't match) we *do not*
-            # check the parents. We only check the parents if there's no acl on
-            # the item at all.
-            acl = AccessControlList([acl, ], acls['default'], valid=self.protector.valid_rights)
-            allowed = acl.may(user_name, right)
-            if allowed is not None:
-                return pchecker(right, allowed, self.item)
-        else:
-            if acls['hierarchic']:
-                # check parent(s), recursively
-                parent_tail = self.item.name.rsplit('/', 1)
-                if len(parent_tail) == 2:
-                    parent, _ = parent_tail
-                    parent_item = self.protector[parent]
-                    allowed = parent_item._allows(right, user_name)
-                    if allowed is not None:
-                        return pchecker(right, allowed, self.item)
+        iterator over all alternatively possible full acls for this item,
+        including before/default/after acl.
+        """
+        fqname = self.item.fqname
+        itemid = self.item.itemid
+        acl_cfg = self.protector._get_configured_acls(fqname)
+        before_acl = acl_cfg['before']
+        after_acl = acl_cfg['after']
+        for item_acl in self.protector.get_acls(itemid, fqname):
+            if item_acl is None:
+                item_acl = acl_cfg['default']
+            yield u' '.join([before_acl, item_acl, after_acl])
 
-            acl = AccessControlList([acls['default'], ], valid=self.protector.valid_rights)
-            allowed = acl.may(user_name, right)
-            if allowed is not None:
-                return pchecker(right, allowed, self.item)
-
-    def allows(self, right, user_name=None):
-        """ Check if username may have <right> access on item <itemname>.
-
-        For hierarchic=False we just check the item in question.
+    def allows(self, right, user_names=None):
+        """ Check if usernames may have <right> access on this item.
 
-        For hierarchic=True, we check each item in the hierarchy. We
-        start with the deepest item and recurse to the top of the tree.
-        If one of those permits, True is returned.
-        This is done *only* if there is *no ACL at all* (not even an empty one)
-        on the items we 'recurse over'.
-
-        For both configurations, we check `before` before the item/default
-        acl and `after` after the item/default acl, of course.
-
-        `default` is only used if there is no ACL on the item (and none on
-        any of the item's parents when using hierarchic.)
-
-        :param itemname: item to get permissions from
         :param right: the right to check
-        :param username: username to use for permissions check (default is to
-                         use the username doing the current request)
+        :param user_names: user names to use for permissions check (default is to
+                          use the user names doing the current request)
         :rtype: bool
         :returns: True if you have permission or False
         """
-        if user_name is None:
-            user_name = self.protector.user.name
-
-        acls = self.protector.get_acls(self.item.name)
-
-        before = AccessControlList([acls['before'], ], valid=self.protector.valid_rights)
-        allowed = before.may(user_name, right)
-        if allowed is not None:
-            return pchecker(right, allowed, self.item)
-
-        allowed = self._allows(right, user_name)
-        if allowed is not None:
-            return pchecker(right, allowed, self.item)
-
-        after = AccessControlList([acls['after'], ], valid=self.protector.valid_rights)
-        allowed = after.may(user_name, right)
-        if allowed is not None:
-            return pchecker(right, allowed, self.item)
-
+        if user_names is None:
+            user_names = self.protector.user.name
+        # must be a non-empty list of user names
+        assert isinstance(user_names, list)
+        assert user_names
+        acl_cfg = self.protector._get_configured_acls(self.item.fqname)
+        for user_name in user_names:
+            for full_acl in self.full_acls():
+                allowed = self.protector.eval_acl(full_acl, acl_cfg['default'], user_name, right)
+                if allowed is True and pchecker(right, allowed, self.item):
+                    return True
         return False
 
     def require(self, *capabilities):
@@ -240,16 +297,19 @@
         if overwrite:
             self.require(DESTROY)
         rev = self.item.store_revision(meta, data, overwrite=overwrite, return_rev=return_rev, **kw)
+        self.protector._clear_acl_cache()
         if return_rev:
             return ProtectedRevision(self.protector, rev, p_item=self)
 
     def store_all_revisions(self, meta, data):
         self.require(DESTROY)
         self.item.store_all_revisions(meta, data)
+        self.protector._clear_acl_cache()
 
     def destroy_revision(self, revid):
         self.require(DESTROY)
         self.item.destroy_revision(revid)
+        self.protector._clear_acl_cache()
 
     def destroy_all_revisions(self):
         for rev in self.item.iter_revs():
@@ -283,6 +343,10 @@
         return self.rev.revid
 
     @property
+    def name(self):
+        return self.rev.name
+
+    @property
     def meta(self):
         self.require(READ, PUBREAD)
         return self.rev.meta
@@ -292,6 +356,9 @@
         self.require(READ, PUBREAD)
         return self.rev.data
 
+    def set_context(self, context):
+        self.rev.set_context(context)
+
     def close(self):
         self.rev.close()
 
--- a/MoinMoin/storage/middleware/routing.py	Fri Feb 08 08:32:26 2013 -0700
+++ b/MoinMoin/storage/middleware/routing.py	Mon Feb 11 12:04:39 2013 -0700
@@ -1,116 +1,136 @@
-# Copyright: 2008-2011 MoinMoin:ThomasWaldmann
+# Copyright: 2011 MoinMoin:ThomasWaldmann
 # Copyright: 2011 MoinMoin:RonnyPfannschmidt
-# Copyright: 2009 MoinMoin:ChristopherDenter
 # License: GNU GPL v2 (or any later version), see LICENSE.txt for details.
 
 """
-MoinMoin - routing middleware
-
-Routes requests to different backends depending on the item name.
+MoinMoin - namespaces middleware
 
-Just think of UNIX filesystems, fstab and mount.
-
-This middleware lets you mount backends that store items belonging to some
-specific part of the namespace. Routing middleware has same API as a backend.
+Routes requests to different backends depending on the namespace.
 """
 
 
 from __future__ import absolute_import, division
 
-from MoinMoin.config import NAME
+from MoinMoin.constants.keys import NAME, BACKENDNAME, NAMESPACE
 
 from MoinMoin.storage.backends import BackendBase, MutableBackendBase
 
 
 class Backend(MutableBackendBase):
     """
-    router, behaves readonly for readonly mounts
+    namespace dispatcher, behaves readonly for readonly mounts
     """
-    def __init__(self, mapping):
+    def __init__(self, namespaces, backends):
         """
-        Initialize router backend.
+        Initialize.
 
-        The mapping given must satisfy the following criteria:
+        The namespace mapping given must satisfy the following criteria:
             * Order matters.
-            * Mountpoints are just item names, including the special '' (empty)
-              root item name.
-            * Trailing '/' of a mountpoint will be stripped.
-            * There *must* be a backend with mountpoint '' at the very
-              end of the mapping. That backend is then used as root, which means
-              that all items that don't lie in the namespace of any other
-              backend are stored there.
+            * Namespaces are unicode strings like u'' (default ns), u'userprofiles:'
+              (used to store userprofiles) or u'files:' (could map to a fileserver
+              backend). Can be also a hierarchic ns spec like u'foo:bar:'.
+            * There *must* be a default namespace entry for u'' at the end of
+              the list.
 
-        :type mapping: list of tuples of mountpoint -> backend mappings
-        :param mapping: [(mountpoint, backend), ...]
+        namespaces = [
+            (u'userprofiles:', 'user_be'),
+            (u'', 'default_be'), # default (u'') must be last
+        ]
+
+        The backends mapping maps backend names to backend instances:
+
+        backends = {
+            'default_be': BackendInstance1,
+            'user_be': BackendInstance2,
+        }
+
+        :type namespaces: list of tuples of namespace specifier -> backend names
+        :param namespaces: [(namespace, backend_name), ...]
+        :type backends: dict backend names -> backends
+        :param backends: {backend_name: backend, ...}
         """
-        self.mapping = [(mountpoint.rstrip('/'), backend) for mountpoint, backend in mapping]
+        self.namespaces = namespaces
+        self.backends = backends
 
     def open(self):
-        for mountpoint, backend in self.mapping:
+        for backend in self.backends.values():
             backend.open()
 
     def close(self):
-        for mountpoint, backend in self.mapping:
+        for backend in self.backends.values():
             backend.close()
 
-    def _get_backend(self, itemname):
+    def _get_backend(self, fq_names):
         """
-        For a given fully-qualified itemname (i.e. something like Company/Bosses/Mr_Joe)
-        find the backend it belongs to (given by this instance's mapping), the local
-        itemname inside that backend and the mountpoint of the backend.
+        For a given fully-qualified itemname (i.e. something like ns:itemname)
+        find the backend it belongs to, the itemname without namespace
+        spec and the namespace of the backend.
 
-        :param itemname: fully-qualified itemname
-        :returns: tuple of (backend, local itemname, mountpoint)
+        :param fq_names: fully-qualified itemnames
+        :returns: tuple of (backend name, local item name, namespace)
         """
-        for mountpoint, backend in self.mapping:
-            if itemname == mountpoint or itemname.startswith(mountpoint and mountpoint + '/' or ''):
-                lstrip = mountpoint and len(mountpoint)+1 or 0
-                return backend, itemname[lstrip:], mountpoint
-        raise AssertionError("No backend found for {0!r}. Available backends: {1!r}".format(itemname, self.mapping))
+        fq_name = fq_names[0]
+        for namespace, backend_name in self.namespaces:
+            if fq_name.startswith(namespace):
+                item_names = [fq_name[len(namespace):] for fq_name in fq_names]
+                return backend_name, item_names, namespace.rstrip(':')
+        raise AssertionError("No backend found for {0!r}. Namespaces: {1!r}".format(fq_name, self.namespaces))
 
     def __iter__(self):
-        # Note: yields <backend_mountpoint>/<backend_revid> as router revid, so that this
-        #       can be given to get_revision and be routed to the right backend.
-        for mountpoint, backend in self.mapping:
-            for revid in backend:
-                yield (mountpoint, revid)
+        # Note: yields enough information so we can retrieve the revision from
+        #       the right backend later (this is more than just the revid).
+        for backend_name, backend in self.backends.items():
+            for revid in backend:  # TODO maybe directly yield the backend?
+                yield (backend_name, revid)
 
-    def retrieve(self, name, revid):
-        backend, _, mountpoint = self._get_backend(name)
+    def retrieve(self, backend_name, revid):
+        backend = self.backends[backend_name]
         meta, data = backend.retrieve(revid)
-        if mountpoint:
-            name = meta[NAME]
-            if name:
-                meta[NAME] = u'{0}/{1}'.format(mountpoint, meta[NAME])
-            else:
-                meta[NAME] = mountpoint # no trailing slash!
         return meta, data
 
     # writing part
     def create(self):
-        for mountpoint, backend in self.mapping:
+        for backend in self.backends.values():
             if isinstance(backend, MutableBackendBase):
                 backend.create()
-            #XXX else: log info?
 
     def destroy(self):
-        for mountpoint, backend in self.mapping:
+        for backend in self.backends.values():
             if isinstance(backend, MutableBackendBase):
                 backend.destroy()
-            #XXX else: log info?
 
     def store(self, meta, data):
-        mountpoint_itemname = meta[NAME]
-        backend, itemname, mountpoint = self._get_backend(mountpoint_itemname)
+        namespace = meta.get(NAMESPACE)
+        if namespace is None:
+            # if there is no NAMESPACE in metadata, we assume that the NAME
+            # is fully qualified and determine the namespace from it:
+            fq_names = meta[NAME]
+            assert isinstance(fq_names, list)
+            if fq_names:
+                backend_name, item_names, namespace = self._get_backend(fq_names)
+                # side effect: update the metadata with namespace and short item name (no ns)
+                meta[NAMESPACE] = namespace
+                meta[NAME] = item_names
+            else:
+                raise ValueError('can not determine namespace: empty NAME list, no NAMESPACE metadata present')
+        else:
+            if namespace:
+                namespace += u':'  # needed for _get_backend
+            backend_name, _, _ = self._get_backend([namespace])
+        backend = self.backends[backend_name]
+
         if not isinstance(backend, MutableBackendBase):
-            raise TypeError('backend {0!r} mounted at {1!r} is readonly'.format(backend, mountpoint))
-        meta[NAME] = itemname
+            raise TypeError('backend {0} is readonly!'.format(backend_name))
+
         revid = backend.store(meta, data)
-        meta[NAME] = mountpoint_itemname # restore the original name
-        return revid
 
-    def remove(self, name, revid):
-        backend, _, mountpoint = self._get_backend(name)
+        # add the BACKENDNAME after storing, so it gets only into
+        # the index, but not in stored metadata:
+        meta[BACKENDNAME] = backend_name
+        return backend_name, revid
+
+    def remove(self, backend_name, revid):
+        backend = self.backends[backend_name]
         if not isinstance(backend, MutableBackendBase):
-            raise TypeError('backend {0!r} mounted at {1!r} is readonly'.format(backend, mountpoint))
+            raise TypeError('backend {0} is readonly'.format(backend_name))
         backend.remove(revid)
--- a/MoinMoin/storage/middleware/serialization.py	Fri Feb 08 08:32:26 2013 -0700
+++ b/MoinMoin/storage/middleware/serialization.py	Mon Feb 11 12:04:39 2013 -0700
@@ -44,14 +44,21 @@
                 break
             yield block
 
+
 def serialize_iter(backend):
-    for mountpoint, revid in backend:
-        meta, data = backend.retrieve(mountpoint, revid)
+    for revid in backend:
+        if isinstance(revid, tuple):
+            # router middleware gives tuples and wants both values for retrieve:
+            meta, data = backend.retrieve(*revid)
+        else:
+            # lower level backends have simple revids
+            meta, data = backend.retrieve(revid)
         for data in serialize_rev(meta, data):
             yield data
     for data in serialize_rev(None, None):
         yield data
 
+
 def deserialize(src, backend):
     while True:
         meta_size_bytes = src.read(4)
@@ -61,6 +68,13 @@
         meta_str = src.read(meta_size)
         text = meta_str.decode('utf-8')
         meta = json.loads(text)
+        name = meta.get('name')
+        if isinstance(name, unicode):
+            # if we encounter single names, make a list of names:
+            meta['name'] = [name, ]
+        if 'itemtype' not in meta:
+            # temporary hack to upgrade serialized item files:
+            meta['itemtype'] = u'default'
         data_size = meta[u'size']
         curr_pos = src.tell()
         limited = LimitedStream(src, data_size)
--- a/MoinMoin/storage/middleware/validation.py	Fri Feb 08 08:32:26 2013 -0700
+++ b/MoinMoin/storage/middleware/validation.py	Mon Feb 11 12:04:39 2013 -0700
@@ -29,7 +29,7 @@
 from flatland import Dict, List, Unset, Boolean, Integer, String
 
 from MoinMoin.constants import keys
-from MoinMoin.config import CONTENTTYPE_DEFAULT, CONTENTTYPE_USER
+from MoinMoin.constants.contenttypes import CONTENTTYPE_DEFAULT, CONTENTTYPE_USER
 
 from MoinMoin.util.crypto import make_uuid, UUID_LEN
 from MoinMoin.util.mime import Type
@@ -102,7 +102,7 @@
         return False
     if v != v.strip():
         return False
-    if v.startswith(u'+'): # used for views, like /+meta/<itemname>
+    if v.startswith(u'+'):  # used for views, like /+meta/<itemname>
         return False
     if v.endswith(u'/'):
         return False
@@ -132,6 +132,15 @@
     return name_validator(element, state)
 
 
+def namespace_validator(element, state):
+    """
+    a namespace (part of a wiki site)
+    """
+    if element.raw is Unset:
+        element.set(state[keys.NAMESPACE])
+    return name_validator(element, state)
+
+
 def user_contenttype_validator(element, state):
     """
     user profile content type
@@ -216,7 +225,7 @@
         if element.value is None:
             return False
         return True
-    else: # untrusted
+    else:  # untrusted
         v = element.value
         if not isinstance(v, unicode):
             return False
@@ -309,8 +318,9 @@
     String.named(keys.REVID).validated_by(revid_validator),
     String.named(keys.PARENTID).validated_by(uuid_validator).using(optional=True),
     String.named(keys.WIKINAME).using(strip=False).validated_by(wikiname_validator),
-    String.named(keys.NAME).using(strip=False).validated_by(name_validator),
-    String.named(keys.NAME_OLD).using(strip=False).validated_by(name_validator).using(optional=True),
+    String.named(keys.NAMESPACE).using(strip=False).validated_by(namespace_validator),
+    List.named(keys.NAME).of(String.using(strip=False).validated_by(name_validator)),
+    List.named(keys.NAME_OLD).of(String.using(strip=False).validated_by(name_validator)).using(optional=True),
     Integer.named(keys.MTIME).validated_by(mtime_validator),
     String.named(keys.ACTION).validated_by(action_validator),
     String.named(keys.ACL).validated_by(acl_validator),
@@ -328,7 +338,8 @@
     String.named(keys.DATAID).validated_by(uuid_validator).using(optional=True),
     # markup items may have this:
     List.named(keys.ITEMLINKS).of(String.named('itemlink').validated_by(wikiname_validator)).using(optional=True),
-    List.named(keys.ITEMTRANSCLUSIONS).of(String.named('itemtransclusion').validated_by(wikiname_validator)).using(optional=True),
+    List.named(keys.ITEMTRANSCLUSIONS).of(String.named('itemtransclusion').validated_by(wikiname_validator)).using(
+        optional=True),
     # TODO: CONTENT validation? can we do it here?
     *common_meta
 )
--- a/MoinMoin/storage/stores/__init__.py	Fri Feb 08 08:32:26 2013 -0700
+++ b/MoinMoin/storage/stores/__init__.py	Mon Feb 11 12:04:39 2013 -0700
@@ -18,6 +18,7 @@
 
 from MoinMoin.util.StringIOClosing import StringIO
 
+
 class StoreBase(Mapping):
     """
     A simple read-only key/value store.
@@ -142,6 +143,7 @@
               file pointer position after we return.
         """
 
+
 class FileMutableStoreMixin(object):
     """
     mix this into a BytesMutableStore to get a FileMutableStore, like shown here:
--- a/MoinMoin/storage/stores/kc.py	Fri Feb 08 08:32:26 2013 -0700
+++ b/MoinMoin/storage/stores/kc.py	Mon Feb 11 12:04:39 2013 -0700
@@ -17,7 +17,8 @@
 
 from __future__ import absolute_import, division
 
-import os, errno
+import os
+import errno
 
 from kyotocabinet import *
 
@@ -33,7 +34,7 @@
     def from_uri(cls, uri):
         return cls(uri)
 
-    def __init__(self, path, mode=DB.OWRITER|DB.OAUTOTRAN, db_opts=DB.GCONCURRENT):
+    def __init__(self, path, mode=DB.OWRITER | DB.OAUTOTRAN, db_opts=DB.GCONCURRENT):
         """
         Store params for .open(). Please refer to kyotocabinet-python-legacy docs for more information.
 
@@ -56,7 +57,7 @@
         except OSError as e:
             if e.errno != errno.EEXIST:
                 raise
-        self.open(mode=self.mode|DB.OCREATE)
+        self.open(mode=self.mode | DB.OCREATE)
         self.close()
 
     def destroy(self):
--- a/MoinMoin/storage/stores/kt.py	Fri Feb 08 08:32:26 2013 -0700
+++ b/MoinMoin/storage/stores/kt.py	Mon Feb 11 12:04:39 2013 -0700
@@ -142,7 +142,7 @@
         response = self.client.getresponse()
         if response.status != 200:
             return None
-        return response # XXX can we do that?
+        return response  # XXX can we do that?
 
     def set(self, key, value, xt=None):
         if isinstance(key, unicode):
@@ -152,7 +152,7 @@
         if xt is not None:
             xt = int(time.time()) + xt
             headers["X-Kt-Xt"] = str(xt)
-        value = value.read() # XXX reads value file into memory
+        value = value.read()  # XXX reads value file into memory
         self.client.request("PUT", key, value, headers)
         response = self.client.getresponse()
         body = response.read()
--- a/MoinMoin/storage/stores/mongodb.py	Fri Feb 08 08:32:26 2013 -0700
+++ b/MoinMoin/storage/stores/mongodb.py	Mon Feb 11 12:04:39 2013 -0700
@@ -10,7 +10,8 @@
 
 from __future__ import absolute_import, division
 
-import pymongo, gridfs
+import pymongo
+import gridfs
 
 from . import MutableStoreBase, BytesMutableStoreBase, FileMutableStoreBase
 
@@ -21,7 +22,7 @@
     """
     @classmethod
     def from_uri(cls, uri):
-        params = uri.split('::') # moin_uri -> mongodb_uri::collection_name
+        params = uri.split('::')  # moin_uri -> mongodb_uri::collection_name
         return cls(*params)
 
     def __init__(self, uri='mongodb://127.0.0.1/moin_db', collection_name='moin_coll'):
--- a/MoinMoin/storage/stores/sqla.py	Fri Feb 08 08:32:26 2013 -0700
+++ b/MoinMoin/storage/stores/sqla.py	Mon Feb 11 12:04:39 2013 -0700
@@ -17,7 +17,7 @@
                BytesMutableStoreMixin, FileMutableStoreMixin)
 
 KEY_LEN = 128
-VALUE_LEN = 1024 * 1024 # 1MB binary data
+VALUE_LEN = 1024 * 1024  # 1MB binary data
 
 
 class BytesStore(BytesMutableStoreBase):
@@ -32,8 +32,8 @@
         :param cls: Class to create
         :param uri: The database uri that we pass on to SQLAlchemy.
         """
-        params = uri.split("::") # using "::" to support windows pathnames that
-                                 # may include ":" after the drive letter.
+        params = uri.split("::")  # using "::" to support windows pathnames that
+                                  # may include ":" after the drive letter.
         return cls(*params)
 
     def __init__(self, db_uri=None, table_name='store', verbose=False):
--- a/MoinMoin/storage/stores/sqlite.py	Fri Feb 08 08:32:26 2013 -0700
+++ b/MoinMoin/storage/stores/sqlite.py	Mon Feb 11 12:04:39 2013 -0700
@@ -35,8 +35,8 @@
                     db_name::table_name::compression_level
                     where table_name and compression level are optional
         """
-        params = uri.split("::") # using "::" to support windows pathnames that
-                                 # may include ":" after the drive letter.
+        params = uri.split("::")  # using "::" to support windows pathnames that
+                                  # may include ":" after the drive letter.
         if len(params) == 3:
             params[2] = int(params[2])
         return cls(*params)
@@ -69,7 +69,7 @@
 
     def open(self):
         self.conn = connect(self.db_name)
-        self.conn.row_factory = Row # make column access by ['colname'] possible
+        self.conn.row_factory = Row  # make column access by ['colname'] possible
 
     def close(self):
         pass
--- a/MoinMoin/templates/global_history.html	Fri Feb 08 08:32:26 2013 -0700
+++ b/MoinMoin/templates/global_history.html	Mon Feb 11 12:04:39 2013 -0700
@@ -28,13 +28,13 @@
                                 <span class="moin-history-{{ meta.action|lower }}"></span>
                             </td>
                             <td class="moin-history-links">
-                                <a href="{{ url_for('frontend.history', item_name=meta.name, bookmark=bookmark_time) }}">HIST</a>
+                                <a href="{{ url_for('frontend.history', item_name=meta.name[0], bookmark=bookmark_time) }}">HIST</a>
                                 {% if bookmark_time -%}
-                                    <a href="{{ url_for('frontend.diff', item_name=meta.name, bookmark=bookmark_time) }}">DIFF</a>
+                                    <a href="{{ url_for('frontend.diff', item_name=meta.name[0], bookmark=bookmark_time) }}">DIFF</a>
                                 {%- endif %}
                             </td>
                             <td class="moin-history-time">{{ meta.mtime|timeformat }}</td>
-                            <td class="moin-history-item"><a class="{{ meta.contenttype|contenttype_to_class }}" href="{{ url_for('frontend.show_item', item_name=meta.name) }}" title="{{ meta.contenttype }}">{{ meta.name }}</a></td>
+                            <td class="moin-history-item"><a class="{{ meta.contenttype|contenttype_to_class }}" href="{{ url_for('frontend.show_item', item_name=meta.name[0]) }}" title="{{ meta.contenttype }}">{{ meta.name|join(' | ') }}</a></td>
                         </tr>
                     {% endfor %}
                     </table>
--- a/MoinMoin/templates/history.html	Fri Feb 08 08:32:26 2013 -0700
+++ b/MoinMoin/templates/history.html	Mon Feb 11 12:04:39 2013 -0700
@@ -35,7 +35,7 @@
             <tbody>
                 {% for doc in history %}
                 <tr>
-                    <td class="moin-wordbreak">{{ doc.name }}</td>
+                    <td class="moin-wordbreak">{{ doc.name|join(' | ') }}</td>
                     <td>{{ doc.revid | shorten_id }}</td>
                     <td>{{ doc.mtime|datetimeformat }}</td>
                     <td class="moin-integer">{{ doc.size }}</td>
@@ -48,15 +48,15 @@
                     <td class="moin-wordbreak">{{ utils.editor_info(doc) }}</td>
                     <td class="moin-wordbreak">{{ doc.contenttype }}</td>
                     <td class="moin-wordbreak">{{ doc.comment }}</td>
-                    <td><a href="{{ url_for('frontend.show_item', item_name=doc.name, rev=doc.revid) }}">{{ _('show') }}</a></td>
-                    <td><a href="{{ url_for('frontend.show_item_meta', item_name=doc.name, rev=doc.revid) }}">{{ _('meta') }}</a></td>
-                    <td><a href="{{ url_for('frontend.download_item', item_name=doc.name, rev=doc.revid) }}">{{ _('download') }}</a></td>
-                    <td><a href="{{ url_for('frontend.highlight_item', item_name=doc.name, rev=doc.revid) }}">{{ _('highlight') }}</a></td>
+                    <td><a href="{{ url_for('frontend.show_item', item_name=doc.name[0], rev=doc.revid) }}">{{ _('show') }}</a></td>
+                    <td><a href="{{ url_for('frontend.show_item_meta', item_name=doc.name[0], rev=doc.revid) }}">{{ _('meta') }}</a></td>
+                    <td><a href="{{ url_for('frontend.download_item', item_name=doc.name[0], rev=doc.revid) }}">{{ _('download') }}</a></td>
+                    <td><a href="{{ url_for('frontend.highlight_item', item_name=doc.name[0], rev=doc.revid) }}">{{ _('highlight') }}</a></td>
                     {% if user.may.write(item_name) -%}
-                    <td><a href="{{ url_for('frontend.revert_item', item_name=doc.name, rev=doc.revid) }}">{{ _('revert') }}</a></td>
+                    <td><a href="{{ url_for('frontend.revert_item', item_name=doc.name[0], rev=doc.revid) }}">{{ _('revert') }}</a></td>
                     {%- endif %}
                     {% if user.may.destroy(item_name) -%}
-                    <td><a href="{{ url_for('frontend.destroy_item', item_name=doc.name, rev=doc.revid) }}">{{ _('destroy') }}</a></td>
+                    <td><a href="{{ url_for('frontend.destroy_item', item_name=doc.name[0], rev=doc.revid) }}">{{ _('destroy') }}</a></td>
                     {%- endif %}
                 </tr>
                 {% endfor %}
--- a/MoinMoin/templates/layout.html	Fri Feb 08 08:32:26 2013 -0700
+++ b/MoinMoin/templates/layout.html	Mon Feb 11 12:04:39 2013 -0700
@@ -42,9 +42,9 @@
                 <img id="moin-avatar" src="{{ avatar }}" />
             {%- endif %}
             {% if user.name -%}
-                {% set wiki_href, aliasname, title, exists = theme_supp.userhome() %}
+                {% set wiki_href, display_name, title, exists = theme_supp.userhome() %}
                 <a href="{{ wiki_href }}" {% if not exists %}class="moin-nonexistent"{% endif %} rel="nofollow" title="{{ title }}">
-                    {{ aliasname }}
+                    {{ display_name }}
                 </a>
                 {% if 'frontend.usersettings' not in cfg.endpoints_excluded -%}
                     <span class="sep"> | </span>
--- a/MoinMoin/templates/search.html	Fri Feb 08 08:32:26 2013 -0700
+++ b/MoinMoin/templates/search.html	Mon Feb 11 12:04:39 2013 -0700
@@ -44,7 +44,7 @@
                         {% if result['wikiname'] == cfg.interwikiname %}
                             <tr>
                                 <td class="moin-wordbreak">{{ result.pos + 1 }}
-                                <a href="{{ url_for_item(item_name=result['name'], wiki_name='Self', rev=result['revid']) }}"><b>{{ result['name'] }}</b></a>
+                                <a href="{{ url_for_item(item_name=result['name'][0], wiki_name='Self', rev=result['revid']) }}"><b>{{ result['name'] | join(' | ')}}</b></a>
                                 </td>
                             </tr>
                             {% if result['summary'] %}
--- a/MoinMoin/templates/usersettings_forms.html	Fri Feb 08 08:32:26 2013 -0700
+++ b/MoinMoin/templates/usersettings_forms.html	Mon Feb 11 12:04:39 2013 -0700
@@ -5,7 +5,7 @@
 {{ forms.render_errors(form) }}
 <dl>
     {{ forms.render(form['name']) }}
-    {{ forms.render(form['aliasname']) }}
+    {{ forms.render(form['display_name']) }}
     {{ forms.render(form['openid']) }}
     {{ forms.render(form['timezone']) }}
     {{ forms.render(form['locale']) }}
--- a/MoinMoin/themes/__init__.py	Fri Feb 08 08:32:26 2013 -0700
+++ b/MoinMoin/themes/__init__.py	Mon Feb 11 12:04:39 2013 -0700
@@ -11,7 +11,6 @@
 import urllib
 
 from json import dumps
-from operator import attrgetter
 
 from flask import current_app as app
 from flask import g as flaskg
@@ -23,7 +22,7 @@
 
 from MoinMoin.i18n import _, L_, N_
 from MoinMoin import wikiutil, user
-from MoinMoin.config import USERID, ADDRESS, HOSTNAME
+from MoinMoin.constants.keys import USERID, ADDRESS, HOSTNAME
 from MoinMoin.search import SearchForm
 from MoinMoin.util.interwiki import split_interwiki, getInterwikiHome, is_local_wiki, is_known_wiki, url_for_item
 from MoinMoin.util.crypto import cache_key
@@ -42,7 +41,8 @@
     try:
         return get_theme(theme_name)
     except KeyError:
-        logging.warning("Theme {0!r} was not found; using default of {1!r} instead.".format(theme_name, app.cfg.theme_default))
+        logging.warning("Theme {0!r} was not found; using default of {1!r} instead.".format(
+            theme_name, app.cfg.theme_default))
         theme_name = app.cfg.theme_default
         return get_theme(theme_name)
 
@@ -51,6 +51,7 @@
 def render_template(template, **context):
     return render_theme_template(get_current_theme(), template, **context)
 
+
 def themed_error(e):
     item_name = request.view_args.get('item_name', u'')
     if e.code == 403:
@@ -73,11 +74,11 @@
         self.cfg = cfg
         self.user = flaskg.user
         self.storage = flaskg.storage
-        self.ui_lang = 'en' # XXX
-        self.ui_dir = 'ltr' # XXX
-        self.content_lang = flaskg.content_lang # XXX
-        self.content_dir = 'ltr' # XXX
-        self.meta_items = [] # list of (name, content) for html head <meta>
+        self.ui_lang = 'en'  # XXX
+        self.ui_dir = 'ltr'  # XXX
+        self.content_lang = flaskg.content_lang  # XXX
+        self.content_dir = 'ltr'  # XXX
+        self.meta_items = []  # list of (name, content) for html head <meta>
 
     def location_breadcrumbs(self, item_name):
         """
@@ -105,9 +106,9 @@
         breadcrumbs = []
         trail = user.get_trail()
         for interwiki_item_name in trail:
-            wiki_name, item_name = split_interwiki(interwiki_item_name)
+            wiki_name, namespace, item_name = split_interwiki(interwiki_item_name)
             err = not is_known_wiki(wiki_name)
-            href = url_for_item(item_name, wiki_name=wiki_name)
+            href = url_for_item(item_name, namespace=namespace, wiki_name=wiki_name)
             if is_local_wiki(wiki_name):
                 exists = self.storage.has_item(item_name)
                 wiki_name = ''  # means "this wiki" for the theme code
@@ -132,16 +133,13 @@
         Assemble arguments used to build user homepage link
 
         :rtype: tuple
-        :returns: arguments of user homepage link in tuple (wiki_href, aliasname, title, exists)
+        :returns: arguments of user homepage link in tuple (wiki_href, display_name, title, exists)
         """
         user = self.user
-        name = user.name
-        aliasname = user.aliasname
-        if not aliasname:
-            aliasname = name
-
+        name = user.name0
+        display_name = user.display_name or name
         wikiname, itemname = getInterwikiHome(name)
-        title = u"{0} @ {1}".format(aliasname, wikiname)
+        title = u"{0} @ {1}".format(display_name, wikiname)
         # link to (interwiki) user homepage
         if is_local_wiki(wikiname):
             exists = self.storage.has_item(itemname)
@@ -149,7 +147,7 @@
             # We cannot check if wiki pages exists in remote wikis
             exists = True
         wiki_href = url_for_item(itemname, wiki_name=wikiname)
-        return wiki_href, aliasname, title, exists
+        return wiki_href, display_name, title, exists
 
     def split_navilink(self, text):
         """
@@ -194,10 +192,10 @@
         if target.startswith("wiki:"):
             target = target[5:]
 
-        wiki_name, item_name = split_interwiki(target)
+        wiki_name, namespace, item_name = split_interwiki(target)
         if wiki_name == 'Self':
             wiki_name = ''
-        href = url_for_item(item_name, wiki_name=wiki_name)
+        href = url_for_item(item_name, namespace=namespace, wiki_name=wiki_name)
         if not title:
             title = item_name
         return href, title, wiki_name
@@ -239,12 +237,12 @@
                                 item_url, item_name = line.split(' ', 1)
                                 sisteritems[item_name.decode('utf-8')] = item_url
                             except:
-                                pass # ignore invalid lines
+                                pass  # ignore invalid lines
                         f.close()
                         app.cache.set(cid, sisteritems)
                         logging.info("Site: {0!r} Status: Updated. Pages: {1}".format(sistername, len(sisteritems)))
                     except IOError as err:
-                        (title, code, msg, headers) = err.args # code e.g. 304
+                        (title, code, msg, headers) = err.args  # code e.g. 304
                         logging.warning("Site: {0!r} Status: Not updated.".format(sistername))
                         logging.exception("exception was:")
                 if current in sisteritems:
@@ -305,16 +303,14 @@
     userid = meta.get(USERID)
     if userid:
         u = user.User(userid)
-        name = u.name
+        name = u.name0
         text = name
-        aliasname = u.aliasname
-        if not aliasname:
-            aliasname = name
+        display_name = u.display_name or name
         if title:
             # we already have some address info
-            title = u"{0} @ {1}".format(aliasname, title)
+            title = u"{0} @ {1}".format(display_name, title)
         else:
-            title = aliasname
+            title = display_name
         if u.mailto_author and u.email:
             email = u.email
             css = 'editor mail'
@@ -333,6 +329,7 @@
         result['email'] = email
     return result
 
+
 def shorten_item_name(name, length=25):
     """
     Shorten item names
@@ -355,6 +352,7 @@
             name = u'{0}...{1}'.format(name[:half + left], name[-half:])
     return name
 
+
 def shorten_id(name, length=7):
     """
     Shorten IDs to specified length
@@ -380,6 +378,7 @@
     'application/x-svgdraw': 'drawing',
 }
 
+
 def contenttype_to_class(contenttype):
     """
     Convert a contenttype string to a css class.
@@ -423,7 +422,7 @@
                             'storage': flaskg.storage,
                             'clock': flaskg.clock,
                             'cfg': app.cfg,
-                            'item_name': 'handlers need to give it',
+                            'item_name': u'handlers need to give it',
                             'url_for_item': url_for_item,
                             'get_editor_info': lambda meta: get_editor_info(meta),
                             'utctimestamp': lambda dt: utctimestamp(dt),
--- a/MoinMoin/translations/MoinMoin.pot	Fri Feb 08 08:32:26 2013 -0700
+++ b/MoinMoin/translations/MoinMoin.pot	Mon Feb 11 12:04:39 2013 -0700
@@ -1,14 +1,14 @@
 # Translations template for moin.
-# Copyright (C) 2012 Moin Core Team, see http://moinmo.in/MoinCoreTeamGroup
+# Copyright (C) 2011 Moin Core Team, see http://moinmo.in/MoinCoreTeamGroup
 # This file is distributed under the same license as the moin project.
-# FIRST AUTHOR <EMAIL@ADDRESS>, 2012.
+# FIRST AUTHOR <EMAIL@ADDRESS>, 2011.
 #
 #, fuzzy
 msgid ""
 msgstr ""
 "Project-Id-Version: moin 2.0.0a0\n"
 "Report-Msgid-Bugs-To: English <moin-user@lists.sourceforge.net>\n"
-"POT-Creation-Date: 2012-08-26 22:24+0200\n"
+"POT-Creation-Date: 2011-10-30 23:56-0200\n"
 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
 "Language-Team: LANGUAGE <LL@li.org>\n"
@@ -17,66 +17,6 @@
 "Content-Transfer-Encoding: 8bit\n"
 "Generated-By: Babel 0.9.6\n"
 
-#: MoinMoin/forms.py:45
-msgid "Invalid JSON."
-msgstr ""
-
-#: MoinMoin/forms.py:58
-msgid "OpenID"
-msgstr ""
-
-#: MoinMoin/forms.py:58
-msgid "OpenID address"
-msgstr ""
-
-#: MoinMoin/forms.py:60
-msgid "Your OpenID address"
-msgstr ""
-
-#: MoinMoin/forms.py:62
-msgid "E-Mail"
-msgstr ""
-
-#: MoinMoin/forms.py:62
-msgid "E-Mail address"
-msgstr ""
-
-#: MoinMoin/forms.py:64
-msgid "Your E-Mail address"
-msgstr ""
-
-#: MoinMoin/forms.py:66
-msgid "Password"
-msgstr ""
-
-#: MoinMoin/forms.py:91 MoinMoin/config/default.py:336
-msgid "Tags"
-msgstr ""
-
-#: MoinMoin/forms.py:93
-msgid "Search Query"
-msgstr ""
-
-#: MoinMoin/forms.py:139
-msgid "YYYY-MM-DD HH:MM:SS (example: 2013-12-31 23:59:59)"
-msgstr ""
-
-#: MoinMoin/forms.py:140
-msgid "Please use the following format: YYYY-MM-DD HH:MM:SS"
-msgstr ""
-
-#: MoinMoin/forms.py:144
-msgid "OK"
-msgstr ""
-
-#: MoinMoin/forms.py:160
-msgid "Invalid Reference."
-msgstr ""
-
-#: MoinMoin/forms.py:167
-msgid "(None)"
-msgstr ""
-
 #: MoinMoin/user.py:54
 #, python-format
 msgid ""
@@ -89,26 +29,26 @@
 msgid "This user name already belongs to somebody else."
 msgstr ""
 
-#: MoinMoin/user.py:68
+#: MoinMoin/user.py:66
 #, python-format
 msgid "Password not acceptable: %(msg)s"
 msgstr ""
 
-#: MoinMoin/user.py:78
+#: MoinMoin/user.py:81
 msgid ""
 "Please provide your email address. If you lose your login information, "
 "you can get it by email."
 msgstr ""
 
-#: MoinMoin/user.py:84
+#: MoinMoin/user.py:87
 msgid "This email already belongs to somebody else."
 msgstr ""
 
-#: MoinMoin/user.py:90
+#: MoinMoin/user.py:92
 msgid "This OpenID already belongs to somebody else."
 msgstr ""
 
-#: MoinMoin/user.py:730
+#: MoinMoin/user.py:699
 #, python-format
 msgid ""
 "Somebody has requested to email you a password recovery link.\n"
@@ -121,132 +61,145 @@
 "\n"
 msgstr ""
 
-#: MoinMoin/user.py:733
+#: MoinMoin/user.py:702
 #, python-format
 msgid "[%(sitename)s] Your wiki password recovery link"
 msgstr ""
 
-#: MoinMoin/user.py:751
-#, python-format
-msgid ""
-"Somebody has created an account with this email address.\n"
-"\n"
-"Please use the link below to verify your email address:\n"
-"\n"
-"%(link)s\n"
-"\n"
-"If you didn't create this account, please ignore this email.\n"
-"\n"
-msgstr ""
-
-#: MoinMoin/user.py:754
-#, python-format
-msgid "[%(sitename)s] Please verify your email address"
-msgstr ""
-
-#: MoinMoin/apps/admin/views.py:31 MoinMoin/config/default.py:338
+#: MoinMoin/apps/admin/views.py:30
 msgid "Admin"
 msgstr ""
 
-#: MoinMoin/apps/admin/views.py:35 MoinMoin/config/default.py:337
-msgid "User"
-msgstr ""
-
-#: MoinMoin/apps/admin/views.py:53
-#: MoinMoin/apps/admin/templates/admin/index.html:5
+#: MoinMoin/apps/admin/views.py:48
+#: MoinMoin/apps/admin/templates/admin/index.html:9
 msgid "User Browser"
 msgstr ""
 
-#: MoinMoin/apps/admin/views.py:65
+#: MoinMoin/apps/admin/views.py:60
 #, python-format
 msgid "User profile of %(username)s: %(email)r"
 msgstr ""
 
-#: MoinMoin/apps/admin/views.py:109
+#: MoinMoin/apps/admin/views.py:104
 #: MoinMoin/apps/admin/templates/admin/sysitems_upgrade.html:3
 msgid "System items upgrade"
 msgstr ""
 
-#: MoinMoin/apps/admin/views.py:115
+#: MoinMoin/apps/admin/views.py:110
 #, python-format
 msgid "System items upgrade failed due to the following error: %(error)s."
 msgstr ""
 
-#: MoinMoin/apps/admin/views.py:117
+#: MoinMoin/apps/admin/views.py:112
 msgid "System items have been upgraded successfully!"
 msgstr ""
 
-#: MoinMoin/apps/admin/views.py:164
+#: MoinMoin/apps/admin/views.py:159
 msgid "Wiki Configuration"
 msgstr ""
 
-#: MoinMoin/apps/admin/views.py:193
+#: MoinMoin/apps/admin/views.py:188
 msgid "Wiki Configuration Help"
 msgstr ""
 
-#: MoinMoin/apps/admin/views.py:201
+#: MoinMoin/apps/admin/views.py:196
 msgid "Lexer description"
 msgstr ""
 
-#: MoinMoin/apps/admin/views.py:202
+#: MoinMoin/apps/admin/views.py:197
 msgid "Lexer names"
 msgstr ""
 
-#: MoinMoin/apps/admin/views.py:203
+#: MoinMoin/apps/admin/views.py:198
 msgid "File patterns"
 msgstr ""
 
-#: MoinMoin/apps/admin/views.py:204
+#: MoinMoin/apps/admin/views.py:199
 msgid "Mimetypes"
 msgstr ""
 
-#: MoinMoin/apps/admin/views.py:210
+#: MoinMoin/apps/admin/views.py:205
 msgid "Highlighter Help"
 msgstr ""
 
+#: MoinMoin/apps/admin/views.py:213
+msgid "InterWiki name"
+msgstr ""
+
+#: MoinMoin/apps/admin/views.py:214
+msgid "URL"
+msgstr ""
+
 #: MoinMoin/apps/admin/views.py:218
-msgid "InterWiki name"
-msgstr ""
-
-#: MoinMoin/apps/admin/views.py:219
-msgid "URL"
-msgstr ""
-
-#: MoinMoin/apps/admin/views.py:223
 msgid "Interwiki Help"
 msgstr ""
 
-#: MoinMoin/apps/admin/views.py:231 MoinMoin/converter/archive_in.py:59
-#: MoinMoin/templates/atom.html:8 MoinMoin/templates/diff_text_atom.html:9
-#: MoinMoin/templates/history.html:27
+#: MoinMoin/apps/admin/views.py:226 MoinMoin/templates/history.html:23
 msgid "Size"
 msgstr ""
 
-#: MoinMoin/apps/admin/views.py:232 MoinMoin/templates/index.html:153
+#: MoinMoin/apps/admin/views.py:227 MoinMoin/templates/index.html:153
 msgid "Item name"
 msgstr ""
 
-#: MoinMoin/apps/admin/views.py:238
+#: MoinMoin/apps/admin/views.py:233
 msgid "Item Size"
 msgstr ""
 
+#: MoinMoin/apps/admin/templates/admin/highlighterhelp.html:4
+#: MoinMoin/apps/admin/templates/admin/index.html:20
+msgid "Available Highlighters"
+msgstr ""
+
 #: MoinMoin/apps/admin/templates/admin/index.html:3
+msgid "Documentation"
+msgstr ""
+
+#: MoinMoin/apps/admin/templates/admin/index.html:5
+msgid "Documentation (local)"
+msgstr ""
+
+#: MoinMoin/apps/admin/templates/admin/index.html:7
 msgid "Admin Menu"
 msgstr ""
 
-#: MoinMoin/apps/admin/templates/admin/index.html:6
+#: MoinMoin/apps/admin/templates/admin/index.html:10
 #: MoinMoin/apps/admin/templates/admin/sysitems_upgrade.html:10
 msgid "Upgrade system items"
 msgstr ""
 
-#: MoinMoin/apps/admin/templates/admin/index.html:7
+#: MoinMoin/apps/admin/templates/admin/index.html:11
 msgid "Show Wiki Configuration"
 msgstr ""
 
-#: MoinMoin/apps/admin/templates/admin/index.html:8
+#: MoinMoin/apps/admin/templates/admin/index.html:12
 msgid "Show Wiki Configuration Help"
 msgstr ""
 
+#: MoinMoin/apps/admin/templates/admin/index.html:14
+msgid "User Menu"
+msgstr ""
+
+#: MoinMoin/apps/admin/templates/admin/index.html:16
+#: MoinMoin/apps/frontend/views.py:824 MoinMoin/apps/frontend/views.py:826
+msgid "Wanted Items"
+msgstr ""
+
+#: MoinMoin/apps/admin/templates/admin/index.html:17
+#: MoinMoin/apps/frontend/views.py:840 MoinMoin/apps/frontend/views.py:843
+msgid "Orphaned Items"
+msgstr ""
+
+#: MoinMoin/apps/admin/templates/admin/index.html:18
+#: MoinMoin/apps/admin/templates/admin/itemsize.html:4
+msgid "Item sizes (latest revision)"
+msgstr ""
+
+#: MoinMoin/apps/admin/templates/admin/index.html:19
+#: MoinMoin/apps/admin/templates/admin/interwikihelp.html:4
+msgid "Known InterWiki names"
+msgstr ""
+
 #: MoinMoin/apps/admin/templates/admin/sysitems_upgrade.html:5
 msgid ""
 "You can upgrade your system items by uploading an xml file with new items"
@@ -266,7 +219,7 @@
 msgstr ""
 
 #: MoinMoin/apps/admin/templates/admin/userbrowser.html:8
-#: MoinMoin/templates/history.html:32
+#: MoinMoin/templates/history.html:28
 msgid "Actions"
 msgstr ""
 
@@ -312,7 +265,6 @@
 msgstr ""
 
 #: MoinMoin/apps/admin/templates/admin/wikiconfighelp.html:13
-#: MoinMoin/constants/itemtypes.py:29
 msgid "Default"
 msgstr ""
 
@@ -320,402 +272,401 @@
 msgid "Description"
 msgstr ""
 
-#: MoinMoin/apps/admin/templates/user/highlighterhelp.html:4
-#: MoinMoin/apps/admin/templates/user/index_user.html:13
-msgid "Available Highlighters"
-msgstr ""
-
-#: MoinMoin/apps/admin/templates/user/index_user.html:3
-msgid "Documentation"
-msgstr ""
-
-#: MoinMoin/apps/admin/templates/user/index_user.html:5
-msgid "Documentation (local)"
-msgstr ""
-
-#: MoinMoin/apps/admin/templates/user/index_user.html:7
-msgid "User Menu"
-msgstr ""
-
-#: MoinMoin/apps/admin/templates/user/index_user.html:9
-#: MoinMoin/apps/frontend/views.py:928 MoinMoin/apps/frontend/views.py:930
-msgid "Wanted Items"
-msgstr ""
-
-#: MoinMoin/apps/admin/templates/user/index_user.html:10
-#: MoinMoin/apps/frontend/views.py:944 MoinMoin/apps/frontend/views.py:947
-msgid "Orphaned Items"
-msgstr ""
-
-#: MoinMoin/apps/admin/templates/user/index_user.html:11
-#: MoinMoin/apps/admin/templates/user/itemsize.html:4
-msgid "Item sizes (latest revision)"
-msgstr ""
-
-#: MoinMoin/apps/admin/templates/user/index_user.html:12
-#: MoinMoin/apps/admin/templates/user/interwikihelp.html:4
-msgid "Known InterWiki names"
-msgstr ""
-
-#: MoinMoin/apps/feed/views.py:85
+#: MoinMoin/apps/feed/views.py:81
 msgid "MoinMoin feels unhappy."
 msgstr ""
 
-#: MoinMoin/apps/frontend/views.py:139 MoinMoin/search/__init__.py:29
+#: MoinMoin/apps/frontend/views.py:130
+msgid "Search query too short."
+msgstr ""
+
+#: MoinMoin/apps/frontend/views.py:141
+msgid "Search Query"
+msgstr ""
+
+#: MoinMoin/apps/frontend/views.py:142
 msgid "search also in non-current revisions"
 msgstr ""
 
-#: MoinMoin/apps/frontend/views.py:140 MoinMoin/apps/frontend/views.py:164
-#: MoinMoin/templates/lookup.html:6
-msgid "Lookup"
+#: MoinMoin/apps/frontend/views.py:143 MoinMoin/apps/frontend/views.py:150
+msgid "Search"
 msgstr ""
 
-#: MoinMoin/apps/frontend/views.py:503
+#: MoinMoin/apps/frontend/views.py:391 MoinMoin/templates/history.html:27
+msgid "Comment"
+msgstr ""
+
+#: MoinMoin/apps/frontend/views.py:391
+msgid "Comment about your change"
+msgstr ""
+
+#: MoinMoin/apps/frontend/views.py:392
+msgid "OK"
+msgstr ""
+
+#: MoinMoin/apps/frontend/views.py:395
 msgid "Target"
 msgstr ""
 
-#: MoinMoin/apps/frontend/views.py:503
+#: MoinMoin/apps/frontend/views.py:395
 msgid "The name of the target item"
 msgstr ""
 
-#: MoinMoin/apps/frontend/views.py:740
+#: MoinMoin/apps/frontend/views.py:411
+msgid "markup text"
+msgstr ""
+
+#: MoinMoin/apps/frontend/views.py:412
+msgid "other text"
+msgstr ""
+
+#: MoinMoin/apps/frontend/views.py:413
+msgid "image"
+msgstr ""
+
+#: MoinMoin/apps/frontend/views.py:414
+msgid "audio"
+msgstr ""
+
+#: MoinMoin/apps/frontend/views.py:415
+msgid "video"
+msgstr ""
+
+#: MoinMoin/apps/frontend/views.py:416
+msgid "other"
+msgstr ""
+
+#: MoinMoin/apps/frontend/views.py:417
+msgid "unknown"
+msgstr ""
+
+#: MoinMoin/apps/frontend/views.py:418
 msgid "Filter"
 msgstr ""
 
-#: MoinMoin/apps/frontend/views.py:791 MoinMoin/apps/frontend/views.py:792
+#: MoinMoin/apps/frontend/views.py:663 MoinMoin/config/default.py:337
+#: MoinMoin/templates/index.html:71 MoinMoin/templates/index.html:81
+msgid "Global Index"
+msgstr ""
+
+#: MoinMoin/apps/frontend/views.py:684 MoinMoin/apps/frontend/views.py:685
 msgid "My Changes"
 msgstr ""
 
-#: MoinMoin/apps/frontend/views.py:823
-#, python-format
-msgid "Items which refer to '%(item_name)s'"
+#: MoinMoin/apps/frontend/views.py:716
+msgid "Refers Here"
 msgstr ""
 
-#: MoinMoin/apps/frontend/views.py:894 MoinMoin/config/default.py:334
+#: MoinMoin/apps/frontend/views.py:790 MoinMoin/config/default.py:336
 #: MoinMoin/templates/global_history.html:10
 msgid "Global History"
 msgstr ""
 
-#: MoinMoin/apps/frontend/views.py:957 MoinMoin/apps/frontend/views.py:976
+#: MoinMoin/apps/frontend/views.py:853 MoinMoin/apps/frontend/views.py:872
 #, python-format
 msgid "You must login to use this action: %(action)s."
 msgstr ""
 
-#: MoinMoin/apps/frontend/views.py:960
+#: MoinMoin/apps/frontend/views.py:856
 msgid "A quicklink to this page could not be added for you."
 msgstr ""
 
-#: MoinMoin/apps/frontend/views.py:963
+#: MoinMoin/apps/frontend/views.py:859
 msgid "Your quicklink to this page could not be removed."
 msgstr ""
 
-#: MoinMoin/apps/frontend/views.py:978
+#: MoinMoin/apps/frontend/views.py:874
 msgid "You are not allowed to subscribe to an item you may not read."
 msgstr ""
 
-#: MoinMoin/apps/frontend/views.py:982
+#: MoinMoin/apps/frontend/views.py:878
 msgid "Can't remove regular expression subscription!"
 msgstr ""
 
-#: MoinMoin/apps/frontend/views.py:983
+#: MoinMoin/apps/frontend/views.py:879
 msgid "Edit the subscription regular expressions in your settings."
 msgstr ""
 
-#: MoinMoin/apps/frontend/views.py:987
+#: MoinMoin/apps/frontend/views.py:883
 msgid "You could not get subscribed to this item."
 msgstr ""
 
-#: MoinMoin/apps/frontend/views.py:996 MoinMoin/apps/frontend/views.py:1189
-#: MoinMoin/apps/frontend/views.py:1322
+#: MoinMoin/apps/frontend/views.py:892 MoinMoin/apps/frontend/views.py:1086
+#: MoinMoin/apps/frontend/views.py:1223
 msgid "The passwords do not match."
 msgstr ""
 
-#: MoinMoin/apps/frontend/views.py:1012 MoinMoin/apps/frontend/views.py:1147
-#: MoinMoin/apps/frontend/views.py:1207 MoinMoin/apps/frontend/views.py:1274
-#: MoinMoin/apps/frontend/views.py:1387 MoinMoin/converter/archive_in.py:59
-#: MoinMoin/templates/history.html:24
+#: MoinMoin/apps/frontend/views.py:908 MoinMoin/apps/frontend/views.py:924
+#: MoinMoin/apps/frontend/views.py:1044 MoinMoin/apps/frontend/views.py:1104
+#: MoinMoin/apps/frontend/views.py:1171 MoinMoin/apps/frontend/views.py:1289
+#: MoinMoin/templates/history.html:20
 msgid "Name"
 msgstr ""
 
-#: MoinMoin/apps/frontend/views.py:1012 MoinMoin/apps/frontend/views.py:1387
+#: MoinMoin/apps/frontend/views.py:908 MoinMoin/apps/frontend/views.py:924
+#: MoinMoin/apps/frontend/views.py:1289
 msgid "The login name you want to use"
 msgstr ""
 
-#: MoinMoin/apps/frontend/views.py:1013 MoinMoin/apps/frontend/views.py:1209
-#: MoinMoin/apps/frontend/views.py:1348
+#: MoinMoin/apps/frontend/views.py:909 MoinMoin/apps/frontend/views.py:910
+#: MoinMoin/apps/frontend/views.py:925 MoinMoin/apps/frontend/views.py:926
+#: MoinMoin/apps/frontend/views.py:1172
+msgid "Password"
+msgstr ""
+
+#: MoinMoin/apps/frontend/views.py:909 MoinMoin/apps/frontend/views.py:925
+#: MoinMoin/apps/frontend/views.py:1106 MoinMoin/apps/frontend/views.py:1247
 msgid "The login password you want to use"
 msgstr ""
 
-#: MoinMoin/apps/frontend/views.py:1014 MoinMoin/apps/frontend/views.py:1210
-#: MoinMoin/apps/frontend/views.py:1349
+#: MoinMoin/apps/frontend/views.py:910 MoinMoin/apps/frontend/views.py:926
+#: MoinMoin/apps/frontend/views.py:1107 MoinMoin/apps/frontend/views.py:1248
 msgid "Repeat the same password"
 msgstr ""
 
-#: MoinMoin/apps/frontend/views.py:1017 MoinMoin/apps/frontend/views.py:1056
+#: MoinMoin/apps/frontend/views.py:911 MoinMoin/apps/frontend/views.py:928
+#: MoinMoin/apps/frontend/views.py:1045 MoinMoin/apps/frontend/views.py:1255
+msgid "E-Mail"
+msgstr ""
+
+#: MoinMoin/apps/frontend/views.py:911 MoinMoin/apps/frontend/views.py:928
+#: MoinMoin/apps/frontend/views.py:1045 MoinMoin/apps/frontend/views.py:1255
+msgid "Your E-Mail address"
+msgstr ""
+
+#: MoinMoin/apps/frontend/views.py:912 MoinMoin/apps/frontend/views.py:929
+#: MoinMoin/apps/frontend/views.py:1173 MoinMoin/apps/frontend/views.py:1291
+msgid "OpenID"
+msgstr ""
+
+#: MoinMoin/apps/frontend/views.py:912 MoinMoin/apps/frontend/views.py:929
+#: MoinMoin/apps/frontend/views.py:1291
+msgid "Your OpenID address"
+msgstr ""
+
+#: MoinMoin/apps/frontend/views.py:913 MoinMoin/apps/frontend/views.py:960
+#: MoinMoin/templates/openid_register.html:21
 msgid "Register"
 msgstr ""
 
-#: MoinMoin/apps/frontend/views.py:1100
-msgid ""
-"Account verification required, please see the email we sent to your "
-"address."
+#: MoinMoin/apps/frontend/views.py:991 MoinMoin/apps/frontend/views.py:1017
+msgid "Account created, please log in now."
 msgstr ""
 
-#: MoinMoin/apps/frontend/views.py:1102
-#, python-format
-msgid ""
-"An error occurred while sending the verification email: \"%(message)s\" "
-"Please contact an administrator to activate your account."
+#: MoinMoin/apps/frontend/views.py:1029
+msgid "Your user name or your email address is needed."
+msgstr ""
+
+#: MoinMoin/apps/frontend/views.py:1044 MoinMoin/apps/frontend/views.py:1104
+msgid "Your login name"
+msgstr ""
+
+#: MoinMoin/apps/frontend/views.py:1046
+msgid "Recover password"
+msgstr ""
+
+#: MoinMoin/apps/frontend/views.py:1054 MoinMoin/templates/lostpass.html:5
+msgid "Lost Password"
+msgstr ""
+
+#: MoinMoin/apps/frontend/views.py:1076
+msgid "If this account exists, you will be notified."
+msgstr ""
+
+#: MoinMoin/apps/frontend/views.py:1087 MoinMoin/apps/frontend/views.py:1225
+msgid "New password is unacceptable, encoding trouble."
 msgstr ""
 
 #: MoinMoin/apps/frontend/views.py:1105
-msgid "Account created, please log in now."
+msgid "Recovery token"
 msgstr ""
 
-#: MoinMoin/apps/frontend/views.py:1123
-msgid "Your account has been activated, you can log in now."
+#: MoinMoin/apps/frontend/views.py:1105
+msgid "The recovery token that has been sent to you"
 msgstr ""
 
-#: MoinMoin/apps/frontend/views.py:1125 MoinMoin/apps/frontend/views.py:1234
+#: MoinMoin/apps/frontend/views.py:1106 MoinMoin/apps/frontend/views.py:1247
+msgid "New password"
+msgstr ""
+
+#: MoinMoin/apps/frontend/views.py:1107 MoinMoin/apps/frontend/views.py:1248
+msgid "New password (repeat)"
+msgstr ""
+
+#: MoinMoin/apps/frontend/views.py:1108 MoinMoin/apps/frontend/views.py:1249
+#: MoinMoin/templates/usersettings.html:9
+msgid "Change password"
+msgstr ""
+
+#: MoinMoin/apps/frontend/views.py:1116 MoinMoin/templates/recoverpass.html:5
+msgid "Recover Password"
+msgstr ""
+
+#: MoinMoin/apps/frontend/views.py:1129
+msgid "Your password has been changed, you can log in now."
+msgstr ""
+
+#: MoinMoin/apps/frontend/views.py:1131
 msgid "Your token is invalid!"
 msgstr ""
 
-#: MoinMoin/apps/frontend/views.py:1132
-msgid "Your user name or your email address is needed."
-msgstr ""
-
-#: MoinMoin/apps/frontend/views.py:1147 MoinMoin/apps/frontend/views.py:1207
-msgid "Your login name"
-msgstr ""
-
-#: MoinMoin/apps/frontend/views.py:1149
-msgid "Recover password"
-msgstr ""
-
-#: MoinMoin/apps/frontend/views.py:1157 MoinMoin/templates/lostpass.html:5
-msgid "Lost Password"
-msgstr ""
-
-#: MoinMoin/apps/frontend/views.py:1179
-msgid "If this account exists, you will be notified."
-msgstr ""
-
-#: MoinMoin/apps/frontend/views.py:1190 MoinMoin/apps/frontend/views.py:1324
-msgid "New password is unacceptable, encoding trouble."
-msgstr ""
-
-#: MoinMoin/apps/frontend/views.py:1208
-msgid "Recovery token"
-msgstr ""
-
-#: MoinMoin/apps/frontend/views.py:1208
-msgid "The recovery token that has been sent to you"
-msgstr ""
-
-#: MoinMoin/apps/frontend/views.py:1209 MoinMoin/apps/frontend/views.py:1348
-msgid "New password"
-msgstr ""
-
-#: MoinMoin/apps/frontend/views.py:1210 MoinMoin/apps/frontend/views.py:1349
-msgid "New password (repeat)"
-msgstr ""
-
-#: MoinMoin/apps/frontend/views.py:1211 MoinMoin/apps/frontend/views.py:1350
-#: MoinMoin/templates/usersettings_forms.html:28
-msgid "Change password"
-msgstr ""
-
-#: MoinMoin/apps/frontend/views.py:1219 MoinMoin/templates/recoverpass.html:5
-msgid "Recover Password"
-msgstr ""
-
-#: MoinMoin/apps/frontend/views.py:1232
-msgid "Your password has been changed, you can log in now."
-msgstr ""
-
-#: MoinMoin/apps/frontend/views.py:1246
+#: MoinMoin/apps/frontend/views.py:1143
 msgid "Either your username or password was invalid."
 msgstr ""
 
-#: MoinMoin/apps/frontend/views.py:1247
+#: MoinMoin/apps/frontend/views.py:1144
 msgid "Failed to authenticate with this OpenID."
 msgstr ""
 
-#: MoinMoin/apps/frontend/views.py:1277
-msgid "Log in"
-msgstr ""
-
-#: MoinMoin/apps/frontend/views.py:1285 MoinMoin/templates/layout.html:63
+#: MoinMoin/apps/frontend/views.py:1184 MoinMoin/templates/layout.html:61
 msgid "Login"
 msgstr ""
 
-#: MoinMoin/apps/frontend/views.py:1314
+#: MoinMoin/apps/frontend/views.py:1213
 msgid "You are now logged out."
 msgstr ""
 
-#: MoinMoin/apps/frontend/views.py:1323
+#: MoinMoin/apps/frontend/views.py:1224
 msgid "The current password was wrong."
 msgstr ""
 
-#: MoinMoin/apps/frontend/views.py:1347
+#: MoinMoin/apps/frontend/views.py:1246
 msgid "Current Password"
 msgstr ""
 
-#: MoinMoin/apps/frontend/views.py:1347
+#: MoinMoin/apps/frontend/views.py:1246
 msgid "Your current login password"
 msgstr ""
 
-#: MoinMoin/apps/frontend/views.py:1355 MoinMoin/apps/frontend/views.py:1361
-#: MoinMoin/apps/frontend/views.py:1376 MoinMoin/apps/frontend/views.py:1398
-#: MoinMoin/apps/frontend/views.py:1409
-#: MoinMoin/templates/usersettings_forms.html:14
-#: MoinMoin/templates/usersettings_forms.html:42
-#: MoinMoin/templates/usersettings_forms.html:56
-#: MoinMoin/templates/usersettings_forms.html:67
-#: MoinMoin/templates/usersettings_forms.html:81
+#: MoinMoin/apps/frontend/views.py:1256 MoinMoin/apps/frontend/views.py:1262
+#: MoinMoin/apps/frontend/views.py:1277 MoinMoin/apps/frontend/views.py:1300
+#: MoinMoin/apps/frontend/views.py:1311
 msgid "Save"
 msgstr ""
 
-#: MoinMoin/apps/frontend/views.py:1372
+#: MoinMoin/apps/frontend/views.py:1273
 msgid "Publish my email (not my wiki homepage) in author info"
 msgstr ""
 
-#: MoinMoin/apps/frontend/views.py:1373
+#: MoinMoin/apps/frontend/views.py:1274
 msgid "Open editor on double click"
 msgstr ""
 
-#: MoinMoin/apps/frontend/views.py:1374
+#: MoinMoin/apps/frontend/views.py:1275
 msgid "Show comment sections"
 msgstr ""
 
-#: MoinMoin/apps/frontend/views.py:1375
+#: MoinMoin/apps/frontend/views.py:1276
 msgid "Disable this account forever"
 msgstr ""
 
-#: MoinMoin/apps/frontend/views.py:1382 MoinMoin/templates/usersettings.html:5
+#: MoinMoin/apps/frontend/views.py:1284 MoinMoin/templates/usersettings.html:6
 msgid "User Settings"
 msgstr ""
 
-#: MoinMoin/apps/frontend/views.py:1388
+#: MoinMoin/apps/frontend/views.py:1290
 msgid "Alias-Name"
 msgstr ""
 
-#: MoinMoin/apps/frontend/views.py:1388
+#: MoinMoin/apps/frontend/views.py:1290
 msgid "Your alias name (informational)"
 msgstr ""
 
-#: MoinMoin/apps/frontend/views.py:1392
+#: MoinMoin/apps/frontend/views.py:1294
 msgid "Timezone"
 msgstr ""
 
-#: MoinMoin/apps/frontend/views.py:1397
+#: MoinMoin/apps/frontend/views.py:1299
 msgid "Locale"
 msgstr ""
 
-#: MoinMoin/apps/frontend/views.py:1405
+#: MoinMoin/apps/frontend/views.py:1307
 msgid "Theme name"
 msgstr ""
 
-#: MoinMoin/apps/frontend/views.py:1406
+#: MoinMoin/apps/frontend/views.py:1308
 msgid "User CSS URL"
 msgstr ""
 
-#: MoinMoin/apps/frontend/views.py:1406
+#: MoinMoin/apps/frontend/views.py:1308
 msgid "Give the URL of your custom CSS (optional)"
 msgstr ""
 
-#: MoinMoin/apps/frontend/views.py:1407
+#: MoinMoin/apps/frontend/views.py:1309
 msgid "Editor size"
 msgstr ""
 
-#: MoinMoin/apps/frontend/views.py:1407
+#: MoinMoin/apps/frontend/views.py:1309
 msgid "Editor textarea height (0=auto)"
 msgstr ""
 
-#: MoinMoin/apps/frontend/views.py:1408
+#: MoinMoin/apps/frontend/views.py:1310
 msgid "History results per page"
 msgstr ""
 
-#: MoinMoin/apps/frontend/views.py:1408
+#: MoinMoin/apps/frontend/views.py:1310
 msgid "Number of results per page (0=no paging)"
 msgstr ""
 
-#: MoinMoin/apps/frontend/views.py:1451
+#: MoinMoin/apps/frontend/views.py:1339
 msgid "Your password has been changed."
 msgstr ""
 
-#: MoinMoin/apps/frontend/views.py:1456
+#: MoinMoin/apps/frontend/views.py:1344
 msgid "This openid is already in use."
 msgstr ""
 
-#: MoinMoin/apps/frontend/views.py:1460
+#: MoinMoin/apps/frontend/views.py:1348
 msgid "This username is already in use."
 msgstr ""
 
-#: MoinMoin/apps/frontend/views.py:1466
+#: MoinMoin/apps/frontend/views.py:1354
 msgid "This email is already in use"
 msgstr ""
 
-#: MoinMoin/apps/frontend/views.py:1481
-msgid ""
-"Your account has been disabled because you changed your email address. "
-"Please see the email we sent to your address to reactivate it."
-msgstr ""
-
-#: MoinMoin/apps/frontend/views.py:1488
-msgid ""
-"Your email address was not changed because sending the verification email"
-" failed. Please try again later."
-msgstr ""
-
-#: MoinMoin/apps/frontend/views.py:1494
-msgid "Your changes have been saved."
-msgstr ""
-
-#: MoinMoin/apps/frontend/views.py:1546
+#: MoinMoin/apps/frontend/views.py:1392
 msgid "You must log in to use bookmarks."
 msgstr ""
 
-#: MoinMoin/apps/frontend/views.py:1672
-#, python-format
-msgid "Items with similar names to '%(item_name)s'"
+#: MoinMoin/apps/frontend/views.py:1501 MoinMoin/config/default.py:380
+msgid "Items with similar names"
 msgstr ""
 
-#: MoinMoin/apps/frontend/views.py:1856 MoinMoin/apps/frontend/views.py:1883
+#: MoinMoin/apps/frontend/views.py:1680 MoinMoin/apps/frontend/views.py:1707
 msgid "All tags in this wiki"
 msgstr ""
 
-#: MoinMoin/apps/frontend/views.py:1897
+#: MoinMoin/apps/frontend/views.py:1721
 #, python-format
 msgid "Items tagged with %(tag)s"
 msgstr ""
 
-#: MoinMoin/auth/__init__.py:244 MoinMoin/