Commits

sislau committed dc41579 Merge

merge

Comments (0)

Files changed (27)

 ^instance/
 ^wikiconfig_.+\.py
 ^MoinMoin/translations/.*/LC_MESSAGES/messages.mo$
+^MoinMoin/_tests/wiki/index/
 ^docs/_build/
 .coverage
 ^.project

MoinMoin/_tests/__init__.py

     item = flaskg.storage[name]
 
     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))

MoinMoin/_tests/test_user.py

         assert ret is None, "create_user returned: {0}".format(ret)
         # now try to use it
         u = user.User(name=name, password=password)
-        assert u.name == name
+        assert u.name == [name]
         assert u.email == email
         assert u.valid
 
         """
         Create user with {SSHA} password and check that user can login.
         """
+
         # Create test user
         name = u'Test User'
         # pass = 12345
     # 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()

MoinMoin/apps/admin/views.py

     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('admin/itemsize.html',

MoinMoin/apps/feed/views.py

             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)

MoinMoin/apps/frontend/views.py

     from MoinMoin.storage.middleware.indexing import convert_to_indexable
     item = flaskg.storage[item_name]
     rev = item[rev]
-    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')
 
 
     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>')
     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>')
     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
     :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 item_name in item_names:
         item_names.remove(item_name)
     # Get matches using wiki way, start and end of word
     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())
     """
     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("item_link_list.html",
                            headline=_("Items tagged with %(tag)s", tag=tag),
                            item_name=tag,

MoinMoin/apps/misc/views.py

 
     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 not SITEMAP_HAS_SYSTEM_ITEMS:
     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')
 

MoinMoin/auth/_tests/test_auth.py

         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
     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():

MoinMoin/auth/_tests/test_http.py

         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'

MoinMoin/config/default.py

             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.")
     ('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."),
 
       ('homewiki', u'Self',
        "interwiki name of the wiki where the user home pages are located [Unicode] - useful if you have ''many'' users. You could even link to nonwiki \"user pages\" if the wiki username is in the target URL."),
+      ('use_gravatar', False, "if True, gravatar.com will be used to find User's avatar")
     )),
 
     'mail': ('Mail settings',

MoinMoin/conftest.py

 
 
 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

MoinMoin/constants/keys.py

 # 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"
 EMAIL = "email"
 OPENID = "openid"
 
+# in which backend is some revision stored?
+BACKENDNAME = "backendname"
+
 # index names
 LATEST_REVS = 'latest_revs'
 ALL_REVS = 'all_revs'

MoinMoin/items/__init__.py

 import zipfile
 import tempfile
 import itertools
+import types
 from StringIO import StringIO
 from array import array
 
         contenttype = rev.meta.get(CONTENTTYPE) or contenttype # use contenttype in case our metadata does not provide 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)))
-
         item = item_registry.get(name, Type(contenttype), rev=rev)
         logging.debug("ItemClass {0!r} handles {1!r}".format(item.__class__, contenttype))
         return item
         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 type(oldname) is not types.ListType:
+                oldname = [oldname]
+            if name not in oldname: #this is a rename
+                meta[NAME_OLD] = oldname[:]
+                try:
+                    oldname.remove(self.name)
+                except ValueError:
+                    pass
+                oldname.append(name)
+                meta[NAME] = oldname
+        else:
+            meta[NAME] = [name]
 
         if comment:
             meta[COMMENT] = unicode(comment)
             query = Term(WIKINAME, app.cfg.interwikiname)
         # We only want the sub-item part of the item names, not the whole item objects.
         prefix_len = len(prefix)
-        revs = flaskg.storage.search(query, sortedby=NAME_EXACT, limit=None)
-        items = [(rev.meta[NAME], rev.meta[NAME][prefix_len:], rev.meta[CONTENTTYPE])
-                 for rev in revs]
-        return items
+        revs = flaskg.storage.search(query, limit=None)
+        items = []
+        for rev in revs:
+            rev.set_context(self.name)
+            items.append((rev.name, rev.name[prefix_len:], rev.meta[CONTENTTYPE]))
+
+        return sorted(items, key=lambda item: item[0])
 
     def _connect_levels(self, index):
         new_index = []
             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]
 
     def do_modify(self, contenttype, template_name):
         # XXX think about and add item template support

MoinMoin/items/_tests/test_Item.py

 
 from MoinMoin._tests import become_trusted, update_item
 from MoinMoin.items import Item, ApplicationXTar, NonExistent, Binary, Text, Image, TransformableBitmapImage, MarkupItem
-from MoinMoin.config import CONTENTTYPE, ADDRESS, COMMENT, HOSTNAME, USERID, ACTION
+from MoinMoin.config import CONTENTTYPE, ADDRESS, COMMENT, HOSTNAME, USERID, ACTION, NAME
 
 class TestItem(object):
 
-    def testNonExistent(self):
+    def _testNonExistent(self):
         item = Item.create(u'DoesNotExist')
         assert isinstance(item, NonExistent)
         meta, data = item.meta, item.data
         assert meta == {CONTENTTYPE: u'application/x-nonexistent'}
         assert data == ''
 
-    def testClassFinder(self):
+    def _testClassFinder(self):
         for contenttype, ExpectedClass in [
                 (u'application/x-foobar', Binary),
                 (u'text/plain', Text),
                                   (u'Foo/mn', u'mn', 'image/jpeg', False),
                                   ]
 
+
     def testIndexOnDisconnectedLevels(self):
         # create a toplevel and some sub-items
         basename = u'Bar'
                               (u'Bar/ij', u'ij', u'application/x-nonexistent'),
                              ]
 
+    def test_index_on_pages_with_multiple_names(self):
+        update_item(u'FooBar',
+                    {NAME: [u'FooBar',
+                            u'BarFoo',
+                            ],
+                     CONTENTTYPE: u'text/x.moin.wiki'}, u'')
+
+        update_item(u'One',
+                    {NAME: [u'One',
+                            u'FooBar/FBChild',
+                            ],
+                     CONTENTTYPE: u'text/x.moin.wiki'}, u'')
+        update_item(u'Two',
+                    {NAME: [u'BarFoo/BFChild',
+                            u'Two',
+                            ],
+                     CONTENTTYPE: u'text/x.moin.wiki'}, u'')
+
+        update_item(u'FooBar/FBChild/Grand', {CONTENTTYPE: u'text/x.moin.wiki'}, u'')
+        update_item(u'Two/TwoChild', {CONTENTTYPE: u'text/x.moin.wiki'}, u'')
+        update_item(u'One/OneChild', {CONTENTTYPE: u'text/x.moin.wiki'}, u'')
+
+        index = Item.create(u'FooBar').get_index()
+        assert index == [(u'FooBar/FBChild', u'FBChild', u'text/x.moin.wiki'),
+                         (u'FooBar/FBChild/Grand', u'FBChild/Grand', u'text/x.moin.wiki'),
+                         ]
+        index = Item.create(u'BarFoo').get_index()
+        assert index == [(u'BarFoo/BFChild', u'BFChild', u'text/x.moin.wiki')]
+
+        assert Item.create(u'BarFoo/BFChild').get_index() == []
+
+        index = Item.create(u'One').get_index()
+        assert index == [(u'One/OneChild', u'OneChild', u'text/x.moin.wiki')]
+
+        index = Item.create(u'Two').get_index()
+        assert index == [(u'Two/TwoChild', u'TwoChild', u'text/x.moin.wiki')]
+
     def test_meta_filter(self):
         name = u'Test_item'
         contenttype = u'text/plain;charset=utf-8'
         expected = {'test_key': 'test_val', CONTENTTYPE: contenttype}
         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.data == content
+
+        item2 = Item.create(u'Another name')
+        assert item2.name == u'Another name'
+        assert item2.meta[CONTENTTYPE] == 'text/x.moin.wiki'
+        assert item2.data == content
+
+        assert item1.rev.revid == item2.rev.revid
+
     def test_rename(self):
         name = u'Test_Item'
         contenttype = u'text/plain;charset=utf-8'
         # 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.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.data == content
+
+        item2 = Item.create(u'New name')
+        assert item2.name == u'New name'
+        assert item2.meta[CONTENTTYPE] == 'text/x.moin.wiki'
+        assert item2.data == content
+
+        item3 = Item.create(u'Third')
+        assert item3.name == u'Third'
+        assert item3.meta[CONTENTTYPE] == 'text/x.moin.wiki'
+        assert item3.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')
         # 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.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.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.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').data == u'Child of Page'
+        assert Item.create(u'Page/Second').meta[CONTENTTYPE] == 'application/x-nonexistent'
+        assert Item.create(u'Renamed/Second').data == u'Both'
+        assert Item.create(u'Another').data == u'Both'
+        assert Item.create(u'Page/Second/Child').meta[CONTENTTYPE] == 'application/x-nonexistent'
+        assert Item.create(u'Renamed/Second/Child').data == u'Child of Second'
+        assert Item.create(u'Other/Child2').data == u'Child of Other'
+        assert Item.create(u'Another/Child').data == u'Child of Another'
+
     def test_delete(self):
         name = u'Test_Item2'
         contenttype = u'text/plain;charset=utf-8'

MoinMoin/script/maint/moinshell.py

     Runs a Python shell inside Flask application context.
 
     :param banner: banner appearing at top of shell when started
-    :param make_context: a callable returning a dict of variables 
-                         used in the shell namespace. By default 
+    :param make_context: a callable returning a dict of variables
+                         used in the shell namespace. By default
                          returns a dict consisting of just the app.
-    :param use_ipython: use IPython shell if available, ignore if not. 
-                        The IPython shell can be turned off in command 
+    :param use_ipython: use IPython shell if available, ignore if not.
+                        The IPython shell can be turned off in command
                         line by passing the **--no-ipython** flag.
     """
 
                 Option('--no-ipython',
                        action="store_true",
                        dest='no_ipython',
-                       default=not(self.use_ipython)),)
+                       default=not(self.use_ipython)), )
 
     def get_context(self):
         """
                 sh(global_ns=dict(), local_ns=context)
                 return
             except ImportError:
+                pass
+            except AttributeError:
                 # IPython = 0.11
                 import IPython
                 sh = IPython.embed(banner2=self.banner, user_ns=context)
                 sh()
-            finally:
-                pass
 
         code.interact(self.banner, local=context)

MoinMoin/storage/__init__.py

  |                                 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
  |           |
 """
 
 
-CONTENT, USERPROFILES = 'content', 'userprofiles'
+CONTENT, USERPROFILES = u'content', u'userprofiles'
 
 BACKENDS_PACKAGE = 'MoinMoin.storage.backends'
 
     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):
 
     :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)
                  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/.
     """
         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)
 

MoinMoin/storage/middleware/_tests/test_indexing.py

 from flask import g as flaskg
 
 from MoinMoin.config import NAME, SIZE, ITEMID, REVID, DATAID, HASH_ALGORITHM, CONTENT, COMMENT, \
-                            LATEST_REVS, ALL_REVS
+                            LATEST_REVS, ALL_REVS, NAMESPACE
 
 from ..indexing import IndexingMiddleware
 
         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]
         # 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()]
         item = self.imw[item_name]
         rev = item.store_revision(dict(name=item_name), StringIO(data))
         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
         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)))
+        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)))
+        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]]
+
 class TestProtectedIndexingMiddleware(object):
     reinit_storage = True # cleanup after each test method
 
         r = item.store_revision(dict(name=item_name, acl=u'joe:read'), StringIO('public content'))
         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):

MoinMoin/storage/middleware/_tests/test_routing.py

 # License: GNU GPL v2 (or any later version), see LICENSE.txt for details.
 
 """
-MoinMoin - router middleware tests
+MoinMoin - routing middleware tests
 """
 
 
 
 import pytest
 
-from MoinMoin.config import NAME, REVID
+from MoinMoin.config import NAME, NAMESPACE, REVID
 
-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
 
 
 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()
 
     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_name == default_meta[NAMESPACE], default_meta[NAME])
+    assert (other_name.split(':') == other_meta[NAMESPACE], other_meta[NAME])
 
-    # 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()
 
 
 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
 

MoinMoin/storage/middleware/_tests/test_serialization.py

 
 
 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'}, ''),
 ]
 
 
     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)

MoinMoin/storage/middleware/indexing.py

 import itertools
 import time
 import datetime
+import types
 from StringIO import StringIO
 
 import logging
                             LANGUAGE, USERID, ADDRESS, HOSTNAME, SIZE, ACTION, COMMENT, \
                             CONTENT, ITEMLINKS, ITEMTRANSCLUSIONS, ACL, EMAIL, OPENID, \
                             ITEMID, REVID, CURRENT, PARENTID, \
-                            LATEST_REVS, ALL_REVS
+                            LATEST_REVS, ALL_REVS, BACKENDNAME
 from MoinMoin import user
 from MoinMoin.search.analyzers import item_name_analyzer, MimeTokenizer, AclTokenizer
 from MoinMoin.themes import utctimestamp
 INDEXES = [LATEST_REVS, ALL_REVS, ]
 
 
-def backend_to_index(meta, content, schema, wikiname):
+def backend_to_index(meta, content, schema, wikiname, backend_name):
     """
     Convert backend metadata/data to a whoosh document.
 
     doc[NAME_EXACT] = doc[NAME]
     doc[WIKINAME] = wikiname
     doc[CONTENT] = content
+    doc[BACKENDNAME] = backend_name
     return doc
 
 
 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.
 
             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):
         def tell(self, *args, **kw):
             return self.data.tell(*args, **kw)
 
+    if not item_name:
+        # only used for logging, below
+        item_name = unicode(meta[NAME])
+
     rev = PseudoRev(meta, data)
     try:
         # TODO use different converter mode?
             # 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:
         # 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, '')))
+        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
 
             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),
             # tokenized CONTENTTYPE from metadata
         self.destroy()
         os.rename(self.index_dir_tmp, self.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.
 
         :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)
+        doc = backend_to_index(meta, content, self.schemas[LATEST_REVS], self.wikiname, backend_name)
         if async:
             writer = AsyncWriter(self.ix[LATEST_REVS])
         else:
             if docnum_remove is not None:
                 # we are removing a revid that is in latest revs index
                 try:
-                    latest_names_revids = self._find_latest_names_revids(self.ix[ALL_REVS], Term(ITEMID, itemid))
+                    latest_backends_revids = self._find_latest_backends_revids(self.ix[ALL_REVS], Term(ITEMID, itemid))
                 except AttributeError:
                     # workaround for bug #200 AttributeError: 'FieldCache' object has no attribute 'code'
-                    latest_names_revids = []
-                if latest_names_revids:
+                    latest_backends_revids = []
+                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
         else:
             writer = MultiSegmentWriter(index, procs, limitmb)
         with writer as writer:
-            for mountpoint, revid in revids:
+            for backend_name, revid in revids:
+                print backend_name, revid
                 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':
                 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()
             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):
         """
             # 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
             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 = open_dir(index_dir, indexname=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()
 
         index_dir = self.index_dir_tmp if tmp else self.index_dir
         index_all = open_dir(index_dir, indexname=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 = open_dir(index_dir, indexname=LATEST_REVS)
             # 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:
             # we need to call the method without acl check to avoid endless recursion:
             latest_doc = self.indexer._document(**query) or {}
         self._current = latest_doc
+        self._name = query.get('name_exact')
 
     def _get_itemid(self):
         return self._current.get(ITEMID)
 
     @property
     def name(self):
-        return self._current.get(NAME, 'DoesNotExist')
+        name = self._current.get(NAME, self._name)
+        if type(name) is types.ListType:
+            if self._name and self._name in name:
+                name = self._name
+            else:
+                name = name[0]
+        elif not name:
+            name = 'DoesNotExist'
+        return name
 
     @classmethod
     def create(cls, indexer, **query):
             meta[MTIME] = int(time.time())
         #if CONTENTTYPE not in meta:
         #    meta[CONTENTTYPE] = u'application/octet-stream'
-        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):
                 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)
         return Revision(self, revid)
         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):
     """
     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:
         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):
+        names = self.meta.get(NAME, 'DoesNotExist')
+        if type(names) is not types.ListType:
+            names = [names]
+        return names
+
+    @property
     def name(self):
-        return self.meta.get(NAME, 'DoesNotExist')
+        return self._name or self.names[0]
+
+    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

MoinMoin/storage/middleware/protecting.py

         return self.rev.revid
 
     @property
+    def name(self):
+        return self.rev.name
+
+    @property
     def meta(self):
         self.require(READ)
         return self.rev.meta
         self.require(READ)
         return self.rev.data
 
+    def set_context(self, context):
+        self.rev.set_context(context)
+
     def close(self):
         self.rev.close()
 

MoinMoin/storage/middleware/routing.py

-# 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
+MoinMoin - namespaces middleware
 
-Routes requests to different backends depending on the item name.
-
-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.config 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 mapping: [(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_name: 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(itemname, 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]
+            if not isinstance(fq_names, list):
+                fq_names = [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:
+            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)
 

MoinMoin/templates/layout.html

 
     <div id="moin-username">
         {% if user.valid -%}
-	    {% if user.avatar %}
-	        <img id="moin-avatar" src="{{ user.avatar }}" />
+	    {% set avatar = user.avatar(20) %}
+	    {% if avatar %}
+	        <img id="moin-avatar" src="{{ avatar }}" />
 	    {%- endif %}
             {% if user.name -%}
                 {% set wiki_href, aliasname, title, exists = theme_supp.userhome() %}

MoinMoin/translations/MoinMoin.pot

 msgstr ""
 "Project-Id-Version: moin 2.0.0a0\n"
 "Report-Msgid-Bugs-To: English <moin-user@lists.sourceforge.net>\n"
-"POT-Creation-Date: 2011-07-10 21:27+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"
 "Content-Transfer-Encoding: 8bit\n"
 "Generated-By: Babel 0.9.6\n"
 
-#: MoinMoin/user.py:48
+#: MoinMoin/user.py:54
 #, python-format
 msgid ""
 "Invalid user name '%(name)s'.\n"
 "space between words. Group page name is not allowed."
 msgstr ""
 
-#: MoinMoin/user.py:54
+#: MoinMoin/user.py:60
 msgid "This user name already belongs to somebody else."
 msgstr ""
 
-#: MoinMoin/user.py:60
+#: MoinMoin/user.py:66
 #, python-format
 msgid "Password not acceptable: %(msg)s"
 msgstr ""
 
-#: MoinMoin/user.py:72
+#: 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:78
+#: MoinMoin/user.py:87
 msgid "This email already belongs to somebody else."
 msgstr ""
 
-#: MoinMoin/user.py:83
+#: MoinMoin/user.py:92
 msgid "This OpenID already belongs to somebody else."
 msgstr ""
 
-#: MoinMoin/user.py:726
-msgid "<unknown>"
-msgstr ""
-
-#: MoinMoin/user.py:784
+#: MoinMoin/user.py:699
 #, python-format
 msgid ""
 "Somebody has requested to email you a password recovery link.\n"
 "\n"
 msgstr ""
 
-#: MoinMoin/user.py:787
+#: MoinMoin/user.py:702
 #, python-format
 msgid "[%(sitename)s] Your wiki password recovery link"
 msgstr ""
 
-#: MoinMoin/apps/admin/views.py:63
+#: MoinMoin/apps/admin/views.py:30
+msgid "Admin"
+msgstr ""
+
+#: MoinMoin/apps/admin/views.py:48
+#: MoinMoin/apps/admin/templates/admin/index.html:9
+msgid "User Browser"
+msgstr ""
+
+#: MoinMoin/apps/admin/views.py:60
 #, python-format
 msgid "User profile of %(username)s: %(email)r"
 msgstr ""
 
-#: MoinMoin/apps/admin/views.py:113
+#: 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:110
 #, python-format
 msgid "System items upgrade failed due to the following error: %(error)s."
 msgstr ""
 
-#: MoinMoin/apps/admin/views.py:115
+#: MoinMoin/apps/admin/views.py:112
 msgid "System items have been upgraded successfully!"
 msgstr ""
 
-#: MoinMoin/apps/admin/views.py:199
+#: MoinMoin/apps/admin/views.py:159
+msgid "Wiki Configuration"
+msgstr ""
+
+#: MoinMoin/apps/admin/views.py:188
+msgid "Wiki Configuration Help"
+msgstr ""
+
+#: MoinMoin/apps/admin/views.py:196
 msgid "Lexer description"
 msgstr ""
 
-#: MoinMoin/apps/admin/views.py:200
+#: MoinMoin/apps/admin/views.py:197
 msgid "Lexer names"
 msgstr ""
 
-#: MoinMoin/apps/admin/views.py:201
+#: MoinMoin/apps/admin/views.py:198
 msgid "File patterns"
 msgstr ""
 
-#: MoinMoin/apps/admin/views.py:202
+#: MoinMoin/apps/admin/views.py:199
 msgid "Mimetypes"
 msgstr ""
 
-#: MoinMoin/apps/admin/views.py:216
+#: 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:217
+#: MoinMoin/apps/admin/views.py:214
 msgid "URL"
 msgstr ""
 
-#: MoinMoin/apps/admin/views.py:229 MoinMoin/templates/history.html:13
+#: MoinMoin/apps/admin/views.py:218
+msgid "Interwiki Help"
+msgstr ""
+
+#: MoinMoin/apps/admin/views.py:226 MoinMoin/templates/history.html:23
 msgid "Size"
 msgstr ""
 
-#: MoinMoin/apps/admin/views.py:230
+#: MoinMoin/apps/admin/views.py:227 MoinMoin/templates/index.html:153
 msgid "Item name"
 msgstr ""
 
+#: 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"
 msgid "Admin Menu"
 msgstr ""
 
-#: MoinMoin/apps/admin/templates/admin/index.html:9
-msgid "User Browser"
-msgstr ""
-
 #: 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:16
-#: MoinMoin/apps/frontend/views.py:588
+#: 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:636
+#: MoinMoin/apps/frontend/views.py:840 MoinMoin/apps/frontend/views.py:843
 msgid "Orphaned Items"
 msgstr ""
 
 msgid "Known InterWiki names"
 msgstr ""
 
-#: MoinMoin/apps/admin/templates/admin/sysitems_upgrade.html:3
-msgid "System items upgrade"
-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"
 msgstr ""
 
 #: MoinMoin/apps/admin/templates/admin/userbrowser.html:8
-#: MoinMoin/templates/history.html:18
+#: MoinMoin/templates/history.html:28
 msgid "Actions"
 msgstr ""
 
 msgid "Description"
 msgstr ""
 
-#: MoinMoin/apps/feed/views.py:63
+#: MoinMoin/apps/feed/views.py:81
 msgid "MoinMoin feels unhappy."
 msgstr ""
 
-#: MoinMoin/apps/frontend/views.py:114
+#: MoinMoin/apps/frontend/views.py:130
 msgid "Search query too short."
 msgstr ""
 
-#: MoinMoin/apps/frontend/views.py:125
+#: MoinMoin/apps/frontend/views.py:141
 msgid "Search Query"
 msgstr ""
 
-#: MoinMoin/apps/frontend/views.py:126
+#: MoinMoin/apps/frontend/views.py:142
+msgid "search also in non-current revisions"
+msgstr ""
+
+#: MoinMoin/apps/frontend/views.py:143 MoinMoin/apps/frontend/views.py:150
 msgid "Search"
 msgstr ""
 
-#: MoinMoin/apps/frontend/views.py:308
-#: MoinMoin/templates/global_history.html:20 MoinMoin/templates/history.html:17
+#: MoinMoin/apps/frontend/views.py:391 MoinMoin/templates/history.html:27
 msgid "Comment"
 msgstr ""
 
-#: MoinMoin/apps/frontend/views.py:308
+#: MoinMoin/apps/frontend/views.py:391
 msgid "Comment about your change"
 msgstr ""
 
-#: MoinMoin/apps/frontend/views.py:309
+#: MoinMoin/apps/frontend/views.py:392
 msgid "OK"
 msgstr ""
 
-#: MoinMoin/apps/frontend/views.py:312
+#: MoinMoin/apps/frontend/views.py:395
 msgid "Target"
 msgstr ""
 
-#: MoinMoin/apps/frontend/views.py:312
+#: MoinMoin/apps/frontend/views.py:395
 msgid "The name of the target item"
 msgstr ""
 
-#: MoinMoin/apps/frontend/views.py:531
+#: 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: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:716
 msgid "Refers Here"
 msgstr ""
 
-#: MoinMoin/apps/frontend/views.py:672 MoinMoin/apps/frontend/views.py:691
+#: 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: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:675
+#: MoinMoin/apps/frontend/views.py:856
 msgid "A quicklink to this page could not be added for you."
 msgstr ""
 
-#: MoinMoin/apps/frontend/views.py:678
+#: MoinMoin/apps/frontend/views.py:859
 msgid "Your quicklink to this page could not be removed."
 msgstr ""
 
-#: MoinMoin/apps/frontend/views.py:693
+#: 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:697
+#: MoinMoin/apps/frontend/views.py:878
 msgid "Can't remove regular expression subscription!"
 msgstr ""
 
-#: MoinMoin/apps/frontend/views.py:698
+#: MoinMoin/apps/frontend/views.py:879
 msgid "Edit the subscription regular expressions in your settings."
 msgstr ""
 
-#: MoinMoin/apps/frontend/views.py:702
+#: MoinMoin/apps/frontend/views.py:883
 msgid "You could not get subscribed to this item."
 msgstr ""
 
-#: MoinMoin/apps/frontend/views.py:711 MoinMoin/apps/frontend/views.py:904
-#: MoinMoin/apps/frontend/views.py:1041
+#: 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:727 MoinMoin/apps/frontend/views.py:743
-#: MoinMoin/apps/frontend/views.py:863 MoinMoin/apps/frontend/views.py:922
-#: MoinMoin/apps/frontend/views.py:989 MoinMoin/apps/frontend/views.py:1107
-#: MoinMoin/templates/global_history.html:16 MoinMoin/templates/history.html:10
+#: 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:727 MoinMoin/apps/frontend/views.py:743
-#: MoinMoin/apps/frontend/views.py:1107
+#: 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:728 MoinMoin/apps/frontend/views.py:729
-#: MoinMoin/apps/frontend/views.py:744 MoinMoin/apps/frontend/views.py:745
-#: MoinMoin/apps/frontend/views.py:990
+#: 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:728 MoinMoin/apps/frontend/views.py:744
-#: MoinMoin/apps/frontend/views.py:924 MoinMoin/apps/frontend/views.py:1065
+#: 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:729 MoinMoin/apps/frontend/views.py:745
-#: MoinMoin/apps/frontend/views.py:925 MoinMoin/apps/frontend/views.py:1066
+#: 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:730 MoinMoin/apps/frontend/views.py:747
-#: MoinMoin/apps/frontend/views.py:864 MoinMoin/apps/frontend/views.py:1073
+#: 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:730 MoinMoin/apps/frontend/views.py:747
-#: MoinMoin/apps/frontend/views.py:864 MoinMoin/apps/frontend/views.py:1073
+#: 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:731 MoinMoin/apps/frontend/views.py:748
-#: MoinMoin/apps/frontend/views.py:991 MoinMoin/apps/frontend/views.py:1109
+#: 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:731 MoinMoin/apps/frontend/views.py:748
-#: MoinMoin/apps/frontend/views.py:1109
+#: 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:732
+#: 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:810 MoinMoin/apps/frontend/views.py:836
+#: 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:848
+#: MoinMoin/apps/frontend/views.py:1029
 msgid "Your user name or your email address is needed."
 msgstr ""
 
-#: MoinMoin/apps/frontend/views.py:863 MoinMoin/apps/frontend/views.py:922
+#: MoinMoin/apps/frontend/views.py:1044 MoinMoin/apps/frontend/views.py:1104
 msgid "Your login name"
 msgstr ""
 
-#: MoinMoin/apps/frontend/views.py:865
+#: MoinMoin/apps/frontend/views.py:1046
 msgid "Recover password"
 msgstr ""
 
-#: MoinMoin/apps/frontend/views.py:894
+#: 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:905 MoinMoin/apps/frontend/views.py:1043
+#: 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:923
+#: MoinMoin/apps/frontend/views.py:1105
 msgid "Recovery token"
 msgstr ""
 
-#: MoinMoin/apps/frontend/views.py:923
+#: MoinMoin/apps/frontend/views.py:1105
 msgid "The recovery token that has been sent to you"
 msgstr ""
 
-#: MoinMoin/apps/frontend/views.py:924 MoinMoin/apps/frontend/views.py:1065
+#: MoinMoin/apps/frontend/views.py:1106 MoinMoin/apps/frontend/views.py:1247
 msgid "New password"
 msgstr ""
 
-#: MoinMoin/apps/frontend/views.py:925 MoinMoin/apps/frontend/views.py:1066
+#: MoinMoin/apps/frontend/views.py:1107 MoinMoin/apps/frontend/views.py:1248
 msgid "New password (repeat)"
 msgstr ""
 
-#: MoinMoin/apps/frontend/views.py:926 MoinMoin/apps/frontend/views.py:1067
+#: MoinMoin/apps/frontend/views.py:1108 MoinMoin/apps/frontend/views.py:1249
 #: MoinMoin/templates/usersettings.html:9
 msgid "Change password"
 msgstr ""