Commits

Thomas Waldmann committed e79b4f4 Merge

merged

Comments (0)

Files changed (9)

MoinMoin/apps/frontend/views.py

 from MoinMoin import config, user, util
 from MoinMoin.config import CONTENTTYPE_GROUPS
 from MoinMoin.constants.keys import *
+from MoinMoin.constants.itemtypes import ITEMTYPES
 from MoinMoin.util import crypto
 from MoinMoin.util.interwiki import url_for_item
 from MoinMoin.search import SearchForm, ValidSearch
 class RenameItemForm(TargetChangeForm):
     name = 'rename_item'
 
-class ContenttypeFilterForm(Form):
-    name = 'contenttype_filter'
-    markup_text_items = InlineCheckbox.using(label=L_('markup text'))
-    other_text_items = InlineCheckbox.using(label=L_('other text'))
-    image_items = InlineCheckbox.using(label=L_('image'))
-    audio_items = InlineCheckbox.using(label=L_('audio'))
-    video_items = InlineCheckbox.using(label=L_('video'))
-    other_items = InlineCheckbox.using(label=L_('other'))
-    unknown_items = InlineCheckbox.using(label=L_('unknown'))
-    submit = Submit.using(default=L_('Filter'))
-
-for gname, contenttypes in CONTENTTYPE_GROUPS:
-    filter_ = ContenttypeFilterForm.field_schema_mapping.get(gname.replace(' ', '_'))
-    if filter_:
-        filter_.properties['helper'] = ", ".join([ctlabel for ctname, ctlabel in contenttypes])
-
 
 @frontend.route('/+revert/+<rev>/<itemname:item_name>', methods=['GET', 'POST'])
 def revert_item(item_name, rev):
         abort(403)
 
 
+class ContenttypeFilterForm(Form):
+    markup_text_items = InlineCheckbox.using(label=L_('markup text'))
+    other_text_items = InlineCheckbox.using(label=L_('other text'))
+    image_items = InlineCheckbox.using(label=L_('image'))
+    audio_items = InlineCheckbox.using(label=L_('audio'))
+    video_items = InlineCheckbox.using(label=L_('video'))
+    other_items = InlineCheckbox.using(label=L_('other'))
+    unknown_items = InlineCheckbox.using(label=L_('unknown'))
+
+for gname, contenttypes in CONTENTTYPE_GROUPS:
+    filter_ = ContenttypeFilterForm.field_schema_mapping.get(gname.replace(' ', '_'))
+    if filter_:
+        filter_.properties['helper'] = ", ".join([ctlabel for ctname, ctlabel in contenttypes])
+
+class IndexForm(Form):
+    contenttype = ContenttypeFilterForm
+    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):
         abort(403)
 
     if request.method == 'GET':
-        form = ContenttypeFilterForm.from_defaults()
+        form = IndexForm.from_defaults()
         selected_groups = None
     elif request.method == "POST":
-        form = ContenttypeFilterForm.from_flat(request.form)
-        selected_groups = [gname.replace("_", " ") for gname, value in form.iteritems()
-                           if form[gname].value]
-        if u'submit' in selected_groups:
-            selected_groups.remove(u'submit')
-        if not selected_groups:
-            form = ContenttypeFilterForm.from_defaults()
+        form = IndexForm.from_flat(request.form)
+        selected_groups = [k.replace("_", " ") for k, v in form['contenttype'].iteritems() if v]
 
     startswith = request.values.get("startswith")
-    index = item.flat_index(startswith, selected_groups)
 
-    initials = item.name_initial(item.flat_index())
+    initials = item.name_initial(item.get_subitem_revs())
     initials = [initial.upper() for initial in initials]
     initials = list(set(initials))
     initials = sorted(initials)
-    detailed_index = item.get_detailed_index(index)
-    detailed_index = sorted(detailed_index, key=lambda name: name[0].lower())
+
+    index = item.get_index(startswith, selected_groups)
+    index = sorted(index, key=lambda e: e.relname.lower())
 
     item_names = item_name.split(u'/')
-    if item_name:
-        args = dict(item_name=item_name)
-    else:
-        args = dict(item_name=u'', title_name=_(u'Global Index'))
     return render_template(item.index_template,
                            item_names=item_names,
-                           index=detailed_index,
+                           item_name=item_name,
+                           index=index,
                            initials=initials,
                            startswith=startswith,
                            form=form,
-                           **args
                           )
 
 

MoinMoin/constants/forms.py

 WIDGET_READONLY_STRING_LIST = u'readonly_string_list'
 WIDGET_READONLY_ITEM_LINK_LIST = u'readonly_item_link_list'
 
-WIDGET_MULTI_COLUMN_FORM = u'multi_column_form'
-WIDGET_TABBED_FORM = u'tabbed_form'
-
 # CSS Classes
 CLASS_BUTTON = u'button'

MoinMoin/constants/itemtypes.py

+# Copyright: 2012 MoinMoin:CheerXiao
+# License: GNU GPL v2 (or any later version), see LICENSE.txt for details.
+
+"""
+MoinMoin - itemtype related constants
+"""
+
+
+from __future__ import absolute_import, division
+
+from collections import namedtuple
+
+from MoinMoin.i18n import L_
+
+
+ItemtypeSpec = namedtuple('ItemtypeSpec', 'itemtype display_name description')
+
+ITEMTYPE_DEFAULT = u'default'
+ITEMTYPE_TICKET = u'ticket'
+ITEMTYPE_BLOG = u'blog'
+ITEMTYPE_BLOGENTRY = u'blogentry'
+
+# TODO Perhaps construct this list from the item_registry instead of having it
+# as a constant, which is more extensible (we can have itemtype plugins in
+# future and plugged-in itemtypes will be included too). Two more fields (ie.
+# display name and description) are needed in the registry then to support the
+# automatic construction.
+ITEMTYPES = [
+    ItemtypeSpec(ITEMTYPE_DEFAULT, L_('Default'), L_('Wiki item')),
+    ItemtypeSpec(ITEMTYPE_TICKET, L_('Ticket'), L_('Ticket item')),
+    ItemtypeSpec(ITEMTYPE_BLOG, L_('Blog'), L_('Blog item')),
+    ItemtypeSpec(ITEMTYPE_BLOGENTRY, L_('Blog entry'), L_('Blog entry item')),
+]

MoinMoin/items/__init__.py

     HASH_ALGORITHM, ITEMID, REVID, DATAID, CURRENT, PARENTID
     )
 from MoinMoin.constants.contenttypes import charset, CONTENTTYPE_GROUPS
+from MoinMoin.constants.itemtypes import ITEMTYPES
 
 from .content import Content, NonExistentContent, Draw, content_registry
 
         return form
 
 
+IndexEntry = namedtuple('IndexEntry', 'relname meta hassubitems')
+
 class Item(object):
     """ Highlevel (not storage) Item, wraps around a storage Revision"""
     @classmethod
 
     def _rename(self, name, comment, action):
         self._save(self.meta, self.content.data, name=name, action=action, comment=comment)
-        for child in self.get_index():
-            item = Item.create(child[0])
-            item._save(item.meta, item.content.data, name='/'.join((name, child[1])), action=action, comment=comment)
+        old_prefixlen = len(self.subitems_prefix)
+        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)
 
     def rename(self, name, comment=u''):
         """
         item_modified.send(app._get_current_object(), item_name=name)
         return newrev.revid, newrev.meta[SIZE]
 
-    def get_index(self):
-        """ create an index of sub items of this item """
+    @property
+    def subitems_prefix(self):
+        return self.name + u'/' if self.name else u''
+
+    def get_subitem_revs(self):
+        """
+        Create a list of subitems of this item.
+
+        Subitems are in the form of storage Revisions.
+        """
+        query = Term(WIKINAME, app.cfg.interwikiname)
+        # trick: an item of empty name can be considered as "virtual root item"
+        # that has all wiki items as sub items
         if self.name:
-            prefix = self.name + u'/'
-            query = And([Term(WIKINAME, app.cfg.interwikiname), Prefix(NAME_EXACT, prefix)])
-        else:
-            # trick: an item of empty name can be considered as "virtual root item",
-            # that has all wiki items as sub items
-            prefix = u''
-            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)
+            query = And([query, Prefix(NAME_EXACT, self.subitems_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
+        return revs
 
-    def _connect_levels(self, index):
-        new_index = []
-        last = self.name
-        for item in index:
-            name = item[0]
+    def make_flat_index(self, subitems):
+        """
+        Create a list of IndexEntry from a list of subitems.
 
-            while not name.startswith(last):
-                last = last.rpartition('/')[0]
+        The resulting list contains only IndexEntry for *direct* subitems, e.g.
+        'foo' but not 'foo/bar'. When the latter is encountered, the former has
+        its `hassubitems` flag set in its IndexEntry.
 
-            missing_layers = name.split('/')[last.count('/')+1:-1]
+        When disconnected levels are detected, e.g. when there is foo/bar but no
+        foo, a dummy IndexEntry is created for the latter, with 'nonexistent'
+        itemtype and 'application/x.nonexistent' contenttype. Its `hassubitems`
+        flag is also set.
+        """
+        prefix = self.subitems_prefix
+        prefixlen = len(prefix)
+        index = []
 
-            for layer in missing_layers:
-                last = '/'.join([last, layer])
-                new_index.append((last, last[len(self.name)+1:], u'application/x-nonexistent'))
+        # relnames of all encountered subitems
+        relnames = set()
+        # relnames of subitems that need to have `hassubitems` flag set (but didn't)
+        relnames_to_patch = set()
 
-            last = item[0]
-            new_index.append(item)
+        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]
+                direct_fullname = prefix + direct_relname
+                if direct_relname not in relnames:
+                    # Join disconnected level with a dummy IndexEntry.
+                    # NOTE: Patching the index when encountering a disconnected
+                    # subitem might break the ordering. e.g. suppose the global
+                    # index has ['lorem-', 'lorem/ipsum'] (thus 'lorem' is a
+                    # disconnected level; also, note that ord('-') < ord('/'))
+                    # the patched index will have lorem after lorem-, requiring
+                    # one more pass of sorting after generating the index.
+                    e = IndexEntry(direct_relname, DummyRev(DummyItem(direct_fullname)).meta, True)
+                    index.append(e)
+                    relnames.add(direct_relname)
+                else:
+                    relnames_to_patch.add(direct_relname)
+            else:
+                e = IndexEntry(relname, rev.meta, False)
+                index.append(e)
+                relnames.add(relname)
 
-        return new_index
-
-    def flat_index(self, startswith=None, selected_groups=None):
-        """
-        creates a top level index of sub items of this item
-        if startswith is set, filtering is done on the basis of starting letter of item name
-        if selected_groups is set, items whose contentype belonging to the selected contenttype_groups, are filtered.
-        """
-        index = self.get_index()
-        index = self._connect_levels(index)
-
-        all_ctypes = [[ctype for ctype, clabel in contenttypes]
-                      for gname, contenttypes in CONTENTTYPE_GROUPS]
-        all_ctypes_chain = itertools.chain(*all_ctypes)
-        all_contenttypes = list(all_ctypes_chain)
-        contenttypes_without_encoding = [contenttype[:contenttype.index(u';')]
-                                         for contenttype in all_contenttypes
-                                         if u';' in contenttype]
-        all_contenttypes.extend(contenttypes_without_encoding) # adding more mime-types without the encoding term
-
-        if selected_groups:
-            ctypes = [[ctype for ctype, clabel in contenttypes]
-                      for gname, contenttypes in CONTENTTYPE_GROUPS
-                      if gname in selected_groups]
-            ctypes_chain = itertools.chain(*ctypes)
-            selected_contenttypes = list(ctypes_chain)
-            contenttypes_without_encoding = [contenttype[:contenttype.index(u';')]
-                                             for contenttype in selected_contenttypes
-                                             if u';' in contenttype]
-            selected_contenttypes.extend(contenttypes_without_encoding)
-        else:
-            selected_contenttypes = all_contenttypes
-
-        unknown_item_group = "unknown items"
-        if startswith:
-            startswith = (u'{0}'.format(startswith), u'{0}'.format(startswith.swapcase()))
-            if not selected_groups or unknown_item_group in selected_groups:
-                index = [(fullname, relname, contenttype)
-                         for fullname, relname, contenttype in index
-                         if u'/' not in relname
-                         and relname.startswith(startswith)
-                         and (contenttype not in all_contenttypes or contenttype in selected_contenttypes)]
-                         # If an item's contenttype not present in the default contenttype list,
-                         # then it will be shown without going through any filter.
-            else:
-                index = [(fullname, relname, contenttype)
-                         for fullname, relname, contenttype in index
-                         if u'/' not in relname
-                         and relname.startswith(startswith)
-                         and (contenttype in selected_contenttypes)]
-
-        else:
-            if not selected_groups or unknown_item_group in selected_groups:
-                index = [(fullname, relname, contenttype)
-                         for fullname, relname, contenttype in index
-                         if u'/' not in relname
-                         and (contenttype not in all_contenttypes or contenttype in selected_contenttypes)]
-            else:
-                index = [(fullname, relname, contenttype)
-                         for fullname, relname, contenttype in index
-                         if u'/' not in relname
-                         and contenttype in selected_contenttypes]
+        for i in xrange(len(index)):
+            if index[i].relname in relnames_to_patch:
+                index[i] = index[i]._replace(hassubitems=True)
 
         return index
 
+    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_contenttypes(groups):
+            ctypes = [[ctype for ctype, clabel in contenttypes]
+                      for gname, contenttypes in CONTENTTYPE_GROUPS
+                      if gname in groups]
+            ctypes_chain = itertools.chain(*ctypes)
+            contenttypes = list(ctypes_chain)
+            contenttypes_without_encoding = [contenttype[:contenttype.index(u';')]
+                                             for contenttype in contenttypes
+                                             if u';' in contenttype]
+            contenttypes.extend(contenttypes_without_encoding) # adding more mime-types without the encoding term
+            return contenttypes
+
+        if selected_groups is not None:
+            all_groups = [gname for gname, contenttypes in CONTENTTYPE_GROUPS]
+            selected_contenttypes = build_contenttypes(selected_groups)
+            filtered_index = [e for e in index
+                              if e.meta[CONTENTTYPE] in selected_contenttypes]
+
+            unknown_item_group = "unknown items"
+            if unknown_item_group in selected_groups:
+                all_contenttypes = build_contenttypes(all_groups)
+                filtered_index.extend([e for e in index
+                                       if e.meta[CONTENTTYPE] not in all_contenttypes])
+
+            index = filtered_index
+        return index
+
+    def get_index(self, startswith=None, selected_groups=None):
+        return self.filter_index(self.make_flat_index(self.get_subitem_revs()), startswith, selected_groups)
+
     index_template = 'index.html'
 
-    def get_detailed_index(self, index):
-        """ appends a flag in the index of items indicating that the parent has sub items """
-        detailed_index = []
-        all_item_index = self.get_index()
-        all_item_text = "\n".join(item_info[1] for item_info in all_item_index)
-        for fullname, relname, contenttype in index:
-            hassubitem = False
-            subitem_name_re = u"^{0}/[^/]+$".format(re.escape(relname))
-            regex = re.compile(subitem_name_re, re.UNICODE|re.M)
-            if regex.search(all_item_text):
-                hassubitem = True
-            detailed_index.append((fullname, relname, contenttype, hassubitem))
-        return detailed_index
-
-    def name_initial(self, names=None):
-        initials = [(name[1][0])
-                   for name in names]
+    def name_initial(self, subitems):
+        prefixlen = len(self.subitems_prefix)
+        initials = [(item.meta[NAME][prefixlen]) for item in subitems]
         return initials
 
     delete_template = 'delete.html'
         return self._select_itemtype()
 
     def _select_itemtype(self):
-        # TODO Construct this list from the item_registry. Two more fields (ie.
-        # display name and description) are needed in the registry then to
-        # support the automatic construction.
-        ITEMTYPES = [
-            (u'default', u'Default', 'Wiki item'),
-            (u'ticket', u'Ticket', 'Ticket item'),
-            (u'blog', u'Blog', 'Blog item'),
-            (u'blogentry', u'Blog entry', 'Blog entry item'),
-        ]
-
         return render_template('modify_select_itemtype.html',
                                item_name=self.name,
                                itemtypes=ITEMTYPES,

MoinMoin/items/_tests/test_Item.py

 from MoinMoin.util import diff_html
 
 from MoinMoin._tests import become_trusted, update_item
-from MoinMoin.items import Item, NonExistent
+from MoinMoin.items import Item, NonExistent, IndexEntry
 from MoinMoin.items.content import Binary, Text, Image, TransformableBitmapImage, MarkupItem
 from MoinMoin.constants.keys import ITEMTYPE, CONTENTTYPE, NAME, ADDRESS, COMMENT, HOSTNAME, USERID, ACTION
 
+
+def build_index(basename, spec):
+    """
+    Build an index by hand, useful as a test helper.
+    """
+    return [(IndexEntry(relname, Item.create('/'.join((basename, relname))).meta, hassubitem))
+            for relname, hassubitem in spec]
+
 class TestItem(object):
 
     def testNonExistent(self):
             item._save({CONTENTTYPE: u'text/plain;charset=utf-8'}, "foo")
         item = Item.create(basename + '/mn')
         item._save({CONTENTTYPE: u'image/jpeg'}, "JPG")
-        # check index
+
         baseitem = Item.create(basename)
-        index = baseitem.get_index()
-        assert index == [(u'Foo/ab', u'ab', 'text/plain;charset=utf-8'),
-                         (u'Foo/cd/ef', u'cd/ef', 'text/plain;charset=utf-8'),
-                         (u'Foo/gh', u'gh', 'text/plain;charset=utf-8'),
-                         (u'Foo/ij', u'ij', 'text/plain;charset=utf-8'),
-                         (u'Foo/ij/kl', u'ij/kl', 'text/plain;charset=utf-8'),
-                         (u'Foo/mn', u'mn', 'image/jpeg'),
-                        ]
-        flat_index = baseitem.flat_index()
-        assert flat_index == [(u'Foo/ab', u'ab', u'text/plain;charset=utf-8'),
-                              (u'Foo/cd', u'cd', u'application/x-nonexistent'),
-                              (u'Foo/gh', u'gh', u'text/plain;charset=utf-8'),
-                              (u'Foo/ij', u'ij', u'text/plain;charset=utf-8'),
-                              (u'Foo/mn', u'mn', u'image/jpeg'),
-                             ]
-        # check index when startswith param is passed
-        flat_index = baseitem.flat_index(startswith=u'a')
-        assert flat_index == [(u'Foo/ab', u'ab', 'text/plain;charset=utf-8')]
 
-        #check that virtual container item is returned with startswith
-        flat_index = baseitem.flat_index(startswith=u'c')
-        assert flat_index == [(u'Foo/cd', u'cd', u'application/x-nonexistent')]
+        # test Item.make_flat_index
+        # TODO: test Item.get_subitem_revs
+        index = baseitem.make_flat_index(baseitem.get_subitem_revs())
+        assert index == build_index(basename, [
+            (u'ab', False),
+            (u'cd', True),
+            (u'gh', False),
+            (u'ij', True),
+            (u'mn', False)
+        ])
 
-        # check index when contenttype_groups is passed
+        # test Item.filter_index
+        # check filtered index when startswith param is passed
+        filtered = baseitem.filter_index(index, startswith=u'a')
+        assert filtered == build_index(basename, [(u'ab', False)])
+
+        # check that virtual container item is returned with startswith
+        filtered = baseitem.filter_index(index, startswith=u'c')
+        assert filtered == build_index(basename, [(u'cd', True)])
+
+        # check filtered index when contenttype_groups is passed
         ctgroups = ["image items"]
-        flat_index = baseitem.flat_index(selected_groups=ctgroups)
-        assert flat_index == [(u'Foo/mn', u'mn', 'image/jpeg')]
+        filtered = baseitem.filter_index(index, selected_groups=ctgroups)
+        assert filtered == build_index(basename, [(u'mn', False)])
 
         # If we ask for text/plain type, should Foo/cd be returned?
 
-        # check detailed index
-        detailed_index = baseitem.get_detailed_index(baseitem.flat_index())
-        assert detailed_index == [(u'Foo/ab', u'ab', 'text/plain;charset=utf-8', False),
-                                  (u'Foo/cd', u'cd', 'application/x-nonexistent', True),
-                                  (u'Foo/gh', u'gh', 'text/plain;charset=utf-8', False),
-                                  (u'Foo/ij', u'ij', 'text/plain;charset=utf-8', True),
-                                  (u'Foo/mn', u'mn', 'image/jpeg', False),
-                                  ]
-
-    def testIndexOnDisconnectedLevels(self):
-        # create a toplevel and some sub-items
-        basename = u'Bar'
-        for name in ['', '/ab', '/ab/cd/ef/gh', '/ij/kl/mn/op', '/ij/kl/rs']:
-            item = Item.create(basename + name)
-            item._save({CONTENTTYPE: u'text/plain;charset=utf-8'}, "foo")
-
-        baseitem = Item.create(basename)
-        index = baseitem.get_index()
-        index = baseitem._connect_levels(index)
-
-        assert index == [(u'Bar/ab', u'ab', u'text/plain;charset=utf-8'),
-                         (u'Bar/ab/cd', u'ab/cd', u'application/x-nonexistent'),
-                         (u'Bar/ab/cd/ef', u'ab/cd/ef', u'application/x-nonexistent'),
-                         (u'Bar/ab/cd/ef/gh', u'ab/cd/ef/gh', u'text/plain;charset=utf-8'),
-                         (u'Bar/ij', u'ij', u'application/x-nonexistent'),
-                         (u'Bar/ij/kl', u'ij/kl', u'application/x-nonexistent'),
-                         (u'Bar/ij/kl/mn', u'ij/kl/mn', u'application/x-nonexistent'),
-                         (u'Bar/ij/kl/mn/op', u'ij/kl/mn/op', u'text/plain;charset=utf-8'),
-                         (u'Bar/ij/kl/rs', u'ij/kl/rs', u'text/plain;charset=utf-8')]
-
-        flat_index = baseitem.flat_index()
-        assert flat_index == [(u'Bar/ab', u'ab', u'text/plain;charset=utf-8'),
-                              (u'Bar/ij', u'ij', u'application/x-nonexistent'),
-                             ]
-
     def test_meta_filter(self):
         name = u'Test_item'
         contenttype = u'text/plain;charset=utf-8'

MoinMoin/templates/index.html

                 </ul>
             </div>
             </li>
-            {% if index %}
             <li class="action-bar">
             <div class="moin-contenttypes-wrapper">
                 <div class="ct-hide">{{ _("Filter by content type") }}</div>
                         'other_items',
                         'unknown_items',
                         ] %}
-                        <li>{{ forms.render(form[e]) }}</li>
+                        <li>{{ forms.render(form['contenttype'][e]) }}</li>
                     {% endfor %}
                 </ul>
                 {{ forms.render(form['submit']) }}
                 {{ gen.form.close() }}
             </div>
             </li>
-            {% endif %}
         </ul>
     </div>
     <div>
     </div>
     <div class="moin-item-index">
         {% set maxchars = 20 %}
-        {% for fullname, relname, contenttype, hassubitem in index %}
+        {% for e in index %}
             <div>
                 <span class="moin-select-item">&nbsp;</span>
                 {% set mimetype = "application/x.moin.download" %}
-                <a href="{{ url_for('.download_item', item_name=fullname, mimetype=mimetype) }}" class="moin-download-link">
+                <a href="{{ url_for('.download_item', item_name=e.meta['name'], mimetype=mimetype) }}" class="moin-download-link">
                 </a>
-                <a href="{{ url_for('.show_item', item_name=fullname) }}"
-                   class="{{ contenttype|contenttype_to_class }} moin-item"
-                   title="{{ relname }}">
-                   {{ relname|truncate(maxchars, true, '..') }}
+                <a href="{{ url_for('.show_item', item_name=e.meta['name']) }}"
+                   {# TODO .moin-itemtype-* classes are not styled yet #}
+                   class="{{ e.meta['contenttype']|contenttype_to_class }} moin-itemtype-{{ e.meta['itemtype'] }} moin-item"
+                   title="{{ e.relname }}">
+                   {{ e.relname|truncate(maxchars, true, '..') }}
                 </a>
-                {% if hassubitem %}
-                    <a href="{{ url_for('frontend.index', item_name=fullname) }}"
+                {% if e.hassubitems %}
+                    <a href="{{ url_for('frontend.index', item_name=e.meta['name']) }}"
                        title="{{ _("More") }}"
                        class="moin-more-index">&nbsp;
                 </a>

MoinMoin/templates/modify_select_itemtype.html

 {{ _("Item '%(name)s' does not exist (yet), but you can try creating it now. Please select the type of the item you want to create.", name=item_name) }}
 </p>
 <table class="zebra">
-    {% for itname, itlabel, itdesc in itemtypes %}
+    {% for it in itemtypes %}
     <tr>
         <td>
-            <a href="{{ url_for('frontend.modify_item', item_name=item_name, itemtype=itname) }}">{{ itlabel }}</a> - {{ itdesc }}
+            <a href="{{ url_for('frontend.modify_item', item_name=item_name, itemtype=it.itemtype) }}">{{ it.display_name }}</a> - {{ it.description }}
         </td>
     </tr>
     {% endfor %}

MoinMoin/templates/utils.html

 </table>
 {% endmacro %}
 
-{% macro _render_subitem_navigation_tree(subitems, newtab, parentcaller) %}
+{% macro _render_subitem_navigation_tree(index, newtab, parentcaller) %}
     <ul>
-        {% for fullname, shortname, contenttype, has_children in subitems %}
+        {% for e in index %}
             <li>
                 {# call our parent's caller with all the data we have if they exist, used to implement
                     transclude/link actions in the modify view #}
                 {% if parentcaller %}
-                    {{ parentcaller(fullname, shortname, contenttype, has_children) }}
+                    {{ parentcaller(e.meta['name'], e.relname, e.meta['contenttype'], e.hassubitems) }}
                 {% endif %}
-                <a href="{{ url_for('frontend.show_item', item_name=fullname) }}"
-                    title="{{ shortname }}" class="subitem-link"
-                    {% if newtab %}target="_blank"{% endif %}>{{ shortname }}</a>
-                {% if has_children %}
+                <a href="{{ url_for('frontend.show_item', item_name=e.meta['name']) }}"
+                    title="{{ e.relname }}" class="subitem-link"
+                    {% if newtab %}target="_blank"{% endif %}>{{ e.relname }}</a>
+                {% if e.hassubitems %}
                     <button class="expander" title="{{ _('Expand Subitem') }}"
                         onclick="toggleSubtree(this)"></button>
-                    {{ _render_subitem_navigation_tree(theme_supp.subitem_index(fullname), newtab, parentcaller) }}
+                    {{ _render_subitem_navigation_tree(theme_supp.subitem_index(e.meta['name']), newtab, parentcaller) }}
                 {% endif %}
             </li>
         {% endfor %}

MoinMoin/themes/__init__.py

 import urllib
 
 from json import dumps
-from operator import itemgetter
+from operator import attrgetter
 
 from flask import current_app as app
 from flask import g as flaskg
         """
         from MoinMoin.items import Item
         item = Item.create(item_name)
-        item_index = item.get_detailed_index(item.flat_index())
+        item_index = item.get_index()
         # Sort items by whether or not they have children, then by name:
-        item_index = sorted(item_index, key=itemgetter(-1, 0))
+        item_index = sorted(item_index, key=attrgetter('hassubitems', 'relname'))
         return item_index
 
     def userhome(self):