diff MoinMoin/items/__init__.py @ 1832:ff503e7ea7a0 namespaces

merged default into namespaces branch, some stuff XXX BROKEN
author Thomas Waldmann <tw AT waldmann-edv DOT de>
date Sat, 06 Oct 2012 17:46:24 +0200
parents c5432dbae81c a38ad19c9e7c
children 2bd864ed640e
line wrap: on
line diff
--- a/MoinMoin/items/__init__.py	Tue Mar 27 23:48:23 2012 +0200
+++ b/MoinMoin/items/__init__.py	Sat Oct 06 17:46:24 2012 +0200
@@ -1,3 +1,4 @@
+# Copyright: 2012 MoinMoin:CheerXiao
 # Copyright: 2009 MoinMoin:ThomasWaldmann
 # Copyright: 2009-2011 MoinMoin:ReimarBauer
 # Copyright: 2009 MoinMoin:ChristopherDenter
@@ -7,150 +8,107 @@
 # License: GNU GPL v2 (or any later version), see LICENSE.txt for details.
 
 """
-    MoinMoin - misc. mimetype items
+    MoinMoin - high-level (frontend) items
 
     While MoinMoin.storage cares for backend storage of items,
     this module cares for more high-level, frontend items,
     e.g. showing, editing, etc. of wiki items.
-"""
-# TODO: split this huge module into multiple ones after code has stabilized
 
-import os, re, time, datetime, base64
-import tarfile
-import zipfile
-import tempfile
+    Each class in this module corresponds to an itemtype.
+"""
+
+import time
 import itertools
 import types
+import json
 from StringIO import StringIO
-from array import array
+from collections import namedtuple
+from operator import attrgetter
 
-from flatland import Form, String, Integer, Boolean, Enum
-from flatland.validation import Validator, Present, IsEmail, ValueBetween, URLValidator, Converted
+from flask import current_app as app
+from flask import g as flaskg
+from flask import request, Response, redirect, abort, escape
+
+from flatland import Form
+
+from jinja2 import Markup
 
 from whoosh.query import Term, And, Prefix
 
-from MoinMoin.util.forms import FileStorage
-
-from MoinMoin.security.textcha import TextCha, TextChaizedForm, TextChaValid
-from MoinMoin.signalling import item_modified
-from MoinMoin.util.mimetype import MimeType
-from MoinMoin.util.mime import Type, type_moin_document
-from MoinMoin.util.tree import moin_page, html, xlink, docbook
-from MoinMoin.util.iri import Iri
-from MoinMoin.util.crypto import cache_key
-from MoinMoin.storage.middleware.protecting import AccessDenied
-
-try:
-    import PIL
-    from PIL import Image as PILImage
-    from PIL.ImageChops import difference as PILdiff
-except ImportError:
-    PIL = None
-
 from MoinMoin import log
 logging = log.getLogger(__name__)
 
-try:
-    import json
-except ImportError:
-    import simplejson as json
-
-from flask import current_app as app
-from flask import g as flaskg
-
-from flask import request, url_for, flash, Response, redirect, abort, escape
+from MoinMoin.security.textcha import TextCha, TextChaizedForm
+from MoinMoin.signalling import item_modified
+from MoinMoin.storage.middleware.protecting import AccessDenied
+from MoinMoin.storage.error import NoSuchItemError, NoSuchRevisionError, StorageError
+from MoinMoin.i18n import L_
+from MoinMoin.themes import render_template
+from MoinMoin.util.mime import Type
+from MoinMoin.util.interwiki import url_for_item
+from MoinMoin.util.registry import RegistryBase
+from MoinMoin.util.clock import timed
+from MoinMoin.forms import RequiredText, OptionalText, JSON, Tags, Submit
+from MoinMoin.constants.keys import (
+    NAME, NAME_OLD, NAME_EXACT, WIKINAME, MTIME, SYSITEM_VERSION, ITEMTYPE,
+    CONTENTTYPE, SIZE, ACTION, ADDRESS, HOSTNAME, USERID, COMMENT,
+    HASH_ALGORITHM, ITEMID, REVID, DATAID, CURRENT, PARENTID
+    )
+from MoinMoin.constants.contenttypes import charset
 
-from werkzeug import is_resource_modified
-from jinja2 import Markup
+from .content import content_registry, Content, NonExistentContent, Draw
 
-from MoinMoin.i18n import _, L_, N_
-from MoinMoin.themes import render_template
-from MoinMoin import wikiutil, config, user
-from MoinMoin.util.send_file import send_file
-from MoinMoin.util.interwiki import url_for_item
-from MoinMoin.storage.error import NoSuchItemError, NoSuchRevisionError, StorageError
-from MoinMoin.config import NAME, NAME_OLD, NAME_EXACT, WIKINAME, MTIME, REVERTED_TO, ACL, \
-                            IS_SYSITEM, SYSITEM_VERSION,  USERGROUP, SOMEDICT, \
-                            CONTENTTYPE, SIZE, LANGUAGE, ITEMLINKS, ITEMTRANSCLUSIONS, \
-                            TAGS, ACTION, ADDRESS, HOSTNAME, USERID, EXTRA, COMMENT, \
-                            HASH_ALGORITHM, CONTENTTYPE_GROUPS, ITEMID, REVID, DATAID, \
-                            CURRENT, PARENTID
 
 COLS = 80
-ROWS_DATA = 20
 ROWS_META = 10
 
 
-from ..util.registry import RegistryBase
-
-
 class RegistryItem(RegistryBase):
-    class Entry(object):
-        def __init__(self, factory, content_type, priority):
-            self.factory = factory
-            self.content_type = content_type
-            self.priority = priority
-
-        def __call__(self, name, content_type, kw):
-            if self.content_type.issupertype(content_type):
-                return self.factory(name, content_type, **kw)
-
-        def __eq__(self, other):
-            if isinstance(other, self.__class__):
-                return (self.factory == other.factory and
-                        self.content_type == other.content_type and
-                        self.priority == other.priority)
-            return NotImplemented
+    class Entry(namedtuple('Entry', 'factory itemtype display_name description order')):
+        def __call__(self, itemtype, *args, **kw):
+            if self.itemtype == itemtype:
+                return self.factory(*args, **kw)
 
         def __lt__(self, other):
             if isinstance(other, self.__class__):
-                if self.priority < other.priority:
-                    return True
-                if self.content_type != other.content_type:
-                    return other.content_type.issupertype(self.content_type)
-                return False
+                return self.itemtype < other.itemtype
             return NotImplemented
 
-        def __repr__(self):
-            return '<{0}: {1}, prio {2} [{3!r}]>' % (self.__class__.__name__,
-                    self.content_type,
-                    self.priority,
-                    self.factory)
+    def __init__(self):
+        super(RegistryItem, self).__init__()
+        self.shown_entries = []
 
-    def get(self, name, content_type, **kw):
-        for entry in self._entries:
-            item = entry(name, content_type, kw)
-            if item is not None:
-                return item
-
-    def register(self, factory, content_type, priority=RegistryBase.PRIORITY_MIDDLE):
+    def register(self, e, shown):
         """
         Register a factory
 
         :param factory: Factory to register. Callable, must return an object.
         """
-        return self._register(self.Entry(factory, content_type, priority))
+        if shown:
+            self.shown_entries.append(e)
+            self.shown_entries.sort(key=attrgetter('order'))
+        return self._register(e)
 
 
 item_registry = RegistryItem()
 
-
-def conv_serialize(doc, namespaces):
-    out = array('u')
-    flaskg.clock.start('conv_serialize')
-    doc.write(out.fromunicode, namespaces=namespaces, method='xml')
-    out = out.tounicode()
-    flaskg.clock.stop('conv_serialize')
-    return out
+def register(cls):
+    item_registry.register(RegistryItem.Entry(cls._factory, cls.itemtype, cls.display_name, cls.description, cls.order), cls.shown)
+    return cls
 
 
 class DummyRev(dict):
     """ if we have no stored Revision, we use this dummy """
-    def __init__(self, item, contenttype):
+    def __init__(self, item, itemtype=None, contenttype=None):
         self.item = item
-        self.meta = {CONTENTTYPE: contenttype}
+        self.meta = {
+            ITEMTYPE: itemtype or u'nonexistent',
+            CONTENTTYPE: contenttype or u'application/x-nonexistent'
+        }
         self.data = StringIO('')
         self.revid = None
+        if self.item:
+            self.meta[NAME] = [self.item.name]
 
 
 class DummyItem(object):
@@ -163,165 +121,153 @@
         return True
 
 
-class Item(object):
-    """ Highlevel (not storage) Item """
+def get_storage_revision(name, itemtype=None, contenttype=None, rev_id=CURRENT, item=None):
+    """
+    Get a storage Revision.
+
+    If :item is supplied it is used as the storage Item; otherwise the storage
+    Item is looked up with :name. If it is not found (either because the item
+    doesn't exist or the user does not have the required permissions) a
+    DummyItem is created, and a DummyRev is created with appropriate metadata
+    properties and the "item" property pointing to the DummyItem. The DummyRev
+    is then returned.
+
+    If the previous step didn't end up with a DummyRev, the revision
+    designated by :rev_id is then looked up. If it is not found, current
+    revision is looked up and returned instead. If current revision is not
+    found (i.e. the item has no revision), a DummyRev is created. (TODO: in
+    the last two cases, emit warnings or throw exceptions.)
+
+    :itemtype and :contenttype are used when creating a DummyRev, where
+    metadata is not available from the storage.
+    """
+    if 1: # try:
+        if item is None:
+            item = flaskg.storage[name]
+        else:
+            name = item.name
+    if not item: # except NoSuchItemError:
+        logging.debug("No such item: {0!r}".format(name))
+        item = DummyItem(name)
+        rev = DummyRev(item, itemtype, contenttype)
+        logging.debug("Item {0!r}, created dummy revision with contenttype {1!r}".format(name, contenttype))
+    else:
+        logging.debug("Got item: {0!r}".format(name))
+        try:
+            rev = item.get_revision(rev_id)
+        except KeyError: # NoSuchRevisionError:
+            try:
+                rev = item.get_revision(CURRENT) # fall back to current revision
+                # XXX add some message about invalid revision
+            except KeyError: # NoSuchRevisionError:
+                logging.debug("Item {0!r} has no revisions.".format(name))
+                rev = DummyRev(item, itemtype, contenttype)
+                logging.debug("Item {0!r}, created dummy revision with contenttype {1!r}".format(name, contenttype))
+        logging.debug("Got item {0!r}, revision: {1!r}".format(name, rev_id))
+    return rev
+
+
+class BaseChangeForm(TextChaizedForm):
+    comment = OptionalText.using(label=L_('Comment')).with_properties(placeholder=L_("Comment about your change"))
+    submit = Submit
+
+
+class BaseMetaForm(Form):
+    itemtype = RequiredText.using(label=L_("Item type")).with_properties(placeholder=L_("Item type"))
+    contenttype = RequiredText.using(label=L_("Content type")).with_properties(placeholder=L_("Content type"))
+    # Disabled - Flatland doesn't distinguish emtpy value and nonexistent
+    # value, while an emtpy acl and no acl have different semantics
+    #acl = OptionalText.using(label=L_('ACL')).with_properties(placeholder=L_("Access Control List"))
+    summary = OptionalText.using(label=L_("Summary")).with_properties(placeholder=L_("One-line summary of the item"))
+    tags = Tags
+
+
+class BaseModifyForm(BaseChangeForm):
     @classmethod
-    def _factory(cls, name=u'', contenttype=None, **kw):
-        return cls(name, contenttype=unicode(contenttype), **kw)
+    def from_item(cls, item):
+        form = cls.from_defaults()
+        TextCha(form).amend_form()
+        form._load(item)
+        return form
 
     @classmethod
-    def create(cls, name=u'', contenttype=None, rev_id=CURRENT, item=None):
-        if contenttype is None:
-            contenttype = u'application/x-nonexistent'
+    def from_request(cls, request):
+        form = cls.from_flat(request.form.items() + request.files.items())
+        TextCha(form).amend_form()
+        return form
 
-        if 1: # try:
-            if item is None:
-                item = flaskg.storage[name]
-            else:
-                name = item.name
-        if not item: # except NoSuchItemError:
-            logging.debug("No such item: {0!r}".format(name))
-            item = DummyItem(name)
-            rev = DummyRev(item, contenttype)
-            logging.debug("Item {0!r}, created dummy revision with contenttype {1!r}".format(name, contenttype))
-        else:
-            logging.debug("Got item: {0!r}".format(name))
-            try:
-                rev = item.get_revision(rev_id)
-                contenttype = u'application/octet-stream' # it exists
-            except KeyError: # NoSuchRevisionError:
-                try:
-                    rev = item.get_revision(CURRENT) # fall back to current revision
-                    # XXX add some message about invalid revision
-                except KeyError: # NoSuchRevisionError:
-                    logging.debug("Item {0!r} has no revisions.".format(name))
-                    rev = DummyRev(item, contenttype)
-                    logging.debug("Item {0!r}, created dummy revision with contenttype {1!r}".format(name, contenttype))
-            logging.debug("Got item {0!r}, revision: {1!r}".format(name, rev_id))
+
+IndexEntry = namedtuple('IndexEntry', 'relname meta')
+
+MixedIndexEntry = namedtuple('MixedIndexEntry', 'relname meta hassubitems')
+
+class Item(object):
+    """ Highlevel (not storage) Item, wraps around a storage Revision"""
+    # placeholder values for registry entry properties
+    itemtype = ''
+    display_name = u''
+    description = u''
+    shown = True
+    order = 0
+
+    @classmethod
+    def _factory(cls, *args, **kw):
+        return cls(*args, **kw)
+
+    @classmethod
+    def create(cls, name=u'', itemtype=None, contenttype=None, rev_id=CURRENT, item=None):
+        """
+        Create a highlevel Item by looking up :name or directly wrapping
+        :item and extract the Revision designated by :rev_id revision.
+
+        The highlevel Item is created by creating an instance of Content
+        subclass according to the item's contenttype metadata entry; The
+        :contenttype argument can be used to override contenttype. It is used
+        only when handling +convert (when deciding the contenttype of target
+        item), +modify (when creating a new item whose contenttype is not yet
+        decided), +diff and +diffraw (to coerce the Content to a common
+        super-contenttype of both revisions).
+
+        After that the Content instance, an instance of Item subclass is
+        created according to the item's itemtype metadata entry, and the
+        previously created Content instance is assigned to its content
+        property.
+        """
+        rev = get_storage_revision(name, itemtype, contenttype, rev_id, item)
         contenttype = rev.meta.get(CONTENTTYPE) or contenttype # use contenttype in case our metadata does not provide CONTENTTYPE
         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))
+
+        # XXX Cannot pass item=item to Content.__init__ via
+        # content_registry.get yet, have to patch it later.
+        content = Content.create(contenttype)
+
+        itemtype = rev.meta.get(ITEMTYPE) or itemtype or u'default'
+        logging.debug("Item {0!r}, got itemtype {1!r} from revision meta".format(name, itemtype))
+
+        item = item_registry.get(itemtype, name, rev=rev, content=content)
+        logging.debug("Item class {0!r} handles {1!r}".format(item.__class__, itemtype))
+
+        content.item = item
+
         return item
 
-    def __init__(self, name, rev=None, contenttype=None):
+    def __init__(self, name, rev=None, content=None):
         self.name = name
         self.rev = rev
-        self.contenttype = contenttype
+        self.content = content
 
     def get_meta(self):
         return self.rev.meta
     meta = property(fget=get_meta)
 
-    def _render_meta(self):
-        # override this in child classes
-        return ''
-
-    def internal_representation(self, converters=['smiley']):
-        """
-        Return the internal representation of a document using a DOM Tree
-        """
-        flaskg.clock.start('conv_in_dom')
-        hash_name = HASH_ALGORITHM
-        hash_hexdigest = self.rev.meta.get(hash_name)
-        if hash_hexdigest:
-            cid = cache_key(usage="internal_representation",
-                            hash_name=hash_name,
-                            hash_hexdigest=hash_hexdigest)
-            doc = app.cache.get(cid)
-        else:
-            # likely a non-existing item
-            doc = cid = None
-        if doc is None:
-            # We will see if we can perform the conversion:
-            # FROM_mimetype --> DOM
-            # if so we perform the transformation, otherwise we don't
-            from MoinMoin.converter import default_registry as reg
-            input_conv = reg.get(Type(self.contenttype), type_moin_document)
-            if not input_conv:
-                raise TypeError("We cannot handle the conversion from {0} to the DOM tree".format(self.contenttype))
-            smiley_conv = reg.get(type_moin_document, type_moin_document,
-                    icon='smiley')
-
-            # We can process the conversion
-            links = Iri(scheme='wiki', authority='', path='/' + self.name)
-            doc = input_conv(self.rev, self.contenttype)
-            # XXX is the following assuming that the top element of the doc tree
-            # is a moin_page.page element? if yes, this is the wrong place to do that
-            # as not every doc will have that element (e.g. for images, we just get
-            # moin_page.object, for a tar item, we get a moin_page.table):
-            doc.set(moin_page.page_href, unicode(links))
-            for conv in converters:
-                if conv == 'smiley':
-                    doc = smiley_conv(doc)
-            if cid:
-                app.cache.set(cid, doc)
-        flaskg.clock.stop('conv_in_dom')
-        return doc
+    # XXX Backward compatibility, remove soon
+    @property
+    def contenttype(self):
+        return self.content.contenttype if self.content else None
 
-    def _expand_document(self, doc):
-        from MoinMoin.converter import default_registry as reg
-        include_conv = reg.get(type_moin_document, type_moin_document, includes='expandall')
-        macro_conv = reg.get(type_moin_document, type_moin_document, macros='expandall')
-        link_conv = reg.get(type_moin_document, type_moin_document, links='extern')
-        flaskg.clock.start('conv_include')
-        doc = include_conv(doc)
-        flaskg.clock.stop('conv_include')
-        flaskg.clock.start('conv_macro')
-        doc = macro_conv(doc)
-        flaskg.clock.stop('conv_macro')
-        flaskg.clock.start('conv_link')
-        doc = link_conv(doc)
-        flaskg.clock.stop('conv_link')
-        return doc
-
-    def _render_data(self):
-        from MoinMoin.converter import default_registry as reg
-        include_conv = reg.get(type_moin_document, type_moin_document, includes='expandall')
-        macro_conv = reg.get(type_moin_document, type_moin_document, macros='expandall')
-        # TODO: Real output format
-        html_conv = reg.get(type_moin_document, Type('application/x-xhtml-moin-page'))
-        doc = self.internal_representation()
-        doc = self._expand_document(doc)
-        flaskg.clock.start('conv_dom_html')
-        doc = html_conv(doc)
-        flaskg.clock.stop('conv_dom_html')
-        rendered_data = conv_serialize(doc, {html.namespace: ''})
-        # This is a work-around to avoid the invalid <div /> tag from being passed
-        # and causing layout issues in many browsers
-        # Instead, send a <div></div> tag which is valid according to the HTML spec
-        # The wider issue with serialization is covered here:
-        # https://bitbucket.org/thomaswaldmann/moin-2.0/issue/145/xml-mode-serialization-returns-self
-        return "<div></div>" if rendered_data == "<div xmlns=\"http://www.w3.org/1999/xhtml\" />" \
-                             else rendered_data
-
-    def _render_data_xml(self):
-        doc = self.internal_representation()
-        return conv_serialize(doc,
-                              {moin_page.namespace: '',
-                               xlink.namespace: 'xlink',
-                               html.namespace: 'html',
-                               })
-
-    def _render_data_highlight(self):
-        # override this in child classes
-        return ''
-
-    def _do_modify_show_templates(self):
-        # call this if the item is still empty
-        rev_ids = []
-        item_templates = self.get_templates(self.contenttype)
-        return render_template('modify_show_template_selection.html',
-                               item_name=self.name,
-                               rev=self.rev,
-                               contenttype=self.contenttype,
-                               templates=item_templates,
-                               first_rev_id=rev_ids and rev_ids[0],
-                               last_rev_id=rev_ids and rev_ids[-1],
-                               meta_rendered='',
-                               data_rendered='',
-                               )
+    def _render_meta(self):
+        return "<pre>{0}</pre>".format(escape(self.meta_dict_to_text(self.meta, use_filter=False)))
 
     def meta_filter(self, meta):
         """ kill metadata entries that we set automatically when saving """
@@ -364,33 +310,15 @@
             meta[PARENTID] = revid
         return meta
 
-    def get_data(self):
-        return '' # TODO create a better method for binary stuff
-    data = property(fget=get_data)
-
-    def _write_stream(self, content, new_rev, bufsize=8192):
-        written = 0
-        if hasattr(content, "read"):
-            while True:
-                buf = content.read(bufsize)
-                if not buf:
-                    break
-                new_rev.data.write(buf)
-                written += len(buf)
-        elif isinstance(content, str):
-            new_rev.data.write(content)
-            written += len(content)
-        else:
-            raise StorageError("unsupported content object: {0!r}".format(content))
-        return written
-
     def _rename(self, name, comment, action, delete=False):
-        new_name = name
-        self._save(self.meta, self.data, name=new_name, action=action, comment=comment, delete=delete)
-        for child in self.get_index():
-            item = Item.create(child[0])
-            new_name = u'/'.join((name, child[1]))
-            item._save(item.meta, item.data, name=new_name, action=action, comment=comment, delete=delete)
+        self._save(self.meta, self.content.data, name=name, action=action, comment=comment, delete=delete)
+        old_prefixlen = len(self.subitems_prefix)
+        new_prefix = name + '/'  # XXX BROKEN for name==None
+        for child in self.get_subitem_revs():
+            child_oldname = child.meta[NAME]  # XXX BROKEN - this is a list of names now
+            child_newname = new_prefix + child_oldname[old_prefixlen:]
+            item = Item.create(child_oldname)
+            item._save(item.meta, item.content.data, name=child_newname, action=action, comment=comment, delete=delete)
 
     def rename(self, name, comment=u''):
         """
@@ -404,10 +332,8 @@
         """
         return self._rename(None, comment, action=u'TRASH', delete=True)
 
-    def revert(self):
-        # called from revert UI/POST
-        comment = request.form.get('comment')
-        self._save(self.meta, self.data, action=u'REVERT', comment=comment)
+    def revert(self, comment=u''):
+        return self._save(self.meta, self.content.data, action=u'REVERT', comment=comment)
 
     def destroy(self, comment=u'', destroy_item=False):
         # called from destroy UI/POST
@@ -418,45 +344,47 @@
             # just destroy this revision
             self.rev.item.destroy_revision(self.rev.revid)
 
-    def modify(self):
-        # called from modify UI/POST
-        meta = data = contenttype_guessed = None
-        contenttype_qs = request.values.get('contenttype')
-        data_file = request.files.get('data_file')
-        if data_file and data_file.filename: # XXX is this the right way to check if there was a file uploaded?
-            data = data_file.stream
-            # this is likely a guess by the browser, based on the filename
-            contenttype_guessed = data_file.content_type # comes from form multipart data
-        if data is None:
-            # no file upload, try taking stuff from textarea
-            data = request.form.get('data_text')
-            if data is not None:
-                # there was a data_text field with (possibly empty) content
-                assert isinstance(data, unicode) # we get unicode from the form
-                data = self.data_form_to_internal(data)
-                data = self.data_internal_to_storage(data)
-                # we know it is text and utf-8 - XXX is there a way to get the charset of the form?
-                contenttype_guessed = u'text/plain;charset=utf-8'
-        # data might be None here, if we have a form with just the data_file field, no file was uploaded
-        # and no data_text field. this can happen if just metadata of a non-text item is edited.
-
-        meta_text = request.form.get('meta_text')
-        if meta_text is not None:
-            # there was a meta_text field with (possibly empty) content
-            # Note: if you get crashes here, please see the ValidJSON validator
-            # to catch invalid json issues early.
-            meta = self.meta_text_to_dict(meta_text)
-        if meta is None:
-            # no form metadata - reuse some stuff from previous metadata?
-            meta = {}
-
+    def modify(self, meta, data, comment=u'', contenttype_guessed=None, contenttype_qs=None):
         if contenttype_qs:
             # we use querystring param to FORCE content type
             meta[CONTENTTYPE] = contenttype_qs
 
-        comment = request.form.get('comment')
         return self._save(meta, data, contenttype_guessed=contenttype_guessed, comment=comment)
 
+    class _ModifyForm(BaseModifyForm):
+        """Base class for ModifyForm of Item subclasses."""
+        meta_form = BaseMetaForm
+        extra_meta_text = JSON.using(label=L_("Extra MetaData (JSON)")).with_properties(rows=ROWS_META, cols=COLS)
+        meta_template = 'modify_meta.html'
+
+        def _load(self, item):
+            meta = item.prepare_meta_for_modify(item.meta)
+            # Default value of `policy` argument of Flatland.Dict.set's is
+            # 'strict', which causes KeyError to be thrown when meta contains
+            # meta keys that are not present in self['meta_form']. Setting
+            # policy to 'duck' suppresses this behavior.
+            self['meta_form'].set(meta, policy='duck')
+            for k in self['meta_form'].field_schema_mapping.keys():
+                meta.pop(k, None)
+            self['extra_meta_text'].set(item.meta_dict_to_text(meta))
+            self['content_form']._load(item.content)
+
+        def _dump(self, item):
+            meta = self['meta_form'].value.copy()
+            meta.update(item.meta_text_to_dict(self['extra_meta_text'].value))
+            data, contenttype_guessed = self['content_form']._dump(item.content)
+            comment = self['comment'].value
+            return meta, data, contenttype_guessed, comment
+
+    def do_modify(self):
+        """
+        Handle +modify requests, both GET and POST.
+
+        This method should be overridden in subclasses, providing polymorphic
+        behavior for the +modify view.
+        """
+        raise NotImplementedError
+
     def _save(self, meta, data=None, name=None, action=u'SAVE', contenttype_guessed=None, comment=u'',
               overwrite=False, delete=False):
         backend = flaskg.storage
@@ -508,7 +436,7 @@
                 data = ''
 
         if isinstance(data, unicode):
-            data = data.encode(config.charset) # XXX wrong! if contenttype gives a coding, we MUST use THAT.
+            data = data.encode(charset) # XXX wrong! if contenttype gives a coding, we MUST use THAT.
 
         if isinstance(data, str):
             data = StringIO(data)
@@ -521,128 +449,125 @@
         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 """
-        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)
-        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 = []
-        last = self.name
-        for item in index:
-            name = item[0]
-
-            while not name.startswith(last):
-                last = last.rpartition('/')[0]
-
-            missing_layers = name.split('/')[last.count('/')+1:-1]
-
-            for layer in missing_layers:
-                last = '/'.join([last, layer])
-                new_index.append((last, last[len(self.name)+1:], u'application/x-nonexistent'))
-
-            last = item[0]
-            new_index.append(item)
-
-        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)
+    @property
+    def subitems_prefix(self):
+        return self.name + u'/' if self.name else u''
 
-        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
+    @timed()
+    def get_subitem_revs(self):
+        """
+        Create a list of subitems of this item.
 
-        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.
+        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:
+            query = And([query, Prefix(NAME_EXACT, self.subitems_prefix)])
+        revs = flaskg.storage.search(query, sortedby=NAME_EXACT, limit=None)
+        return revs
+
+    @timed()
+    def make_flat_index(self, subitems):
+        """
+        Create two IndexEntry lists - ``dirs`` and ``files`` - from a list of
+        subitems.
+
+        Direct subitems are added to the ``files`` list.
+
+        For indirect subitems, its ancestor which is a direct subitem is added
+        to the ``dirs`` list. Supposing current index root is 'foo' and when
+        'foo/bar/la' is encountered, 'foo/bar' is added to ``dirs``.
+
+        The direct subitem need not exist.
+
+        When both a subitem itself and some of its subitems are in the subitems
+        list, it appears in both ``files`` and ``dirs``.
+        """
+        prefix = self.subitems_prefix
+        prefixlen = len(prefix)
+        # IndexEntry instances of "file" subitems
+        files = []
+        # IndexEntry instances of "directory" subitems
+        dirs = []
+        added_dir_relnames = set()
+
+        for rev in subitems:
+            fullname = rev.meta[NAME]  # XXX BROKEN, this is a list of names now
+            relname = fullname[prefixlen:]
+            if '/' in relname:
+                # Find the *direct* subitem that is the ancestor of current
+                # (indirect) subitem. e.g. suppose when the index root is
+                # 'foo', and current item (`rev`) is 'foo/bar/lorem/ipsum',
+                # 'foo/bar' will be found.
+                direct_relname = relname.partition('/')[0]
+                if direct_relname not in added_dir_relnames:
+                    added_dir_relnames.add(direct_relname)
+                    direct_fullname = prefix + direct_relname
+                    direct_rev = get_storage_revision(direct_fullname)
+                    dirs.append(IndexEntry(direct_relname, direct_rev.meta))
             else:
-                index = [(fullname, relname, contenttype)
-                         for fullname, relname, contenttype in index
-                         if u'/' not in relname
-                         and relname.startswith(startswith)
-                         and (contenttype in selected_contenttypes)]
+                files.append(IndexEntry(relname, rev.meta))
 
-        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]
+        return dirs, files
 
+    @timed()
+    def filter_index(self, index, startswith=None, selected_groups=None):
+        """
+        Filter a list of IndexEntry.
+
+        :param startswith: if set, only items whose names start with startswith
+                           are selected.
+        :param selected_groups: if set, only items whose contentypes belong to
+                                the selected contenttype_groups are selected.
+        """
+        if startswith is not None:
+            index = [e for e in index
+                     if e.relname.startswith((startswith, startswith.swapcase()))]
+
+        def build_contenttypes(groups):
+            contenttypes = []
+            for g in groups:
+                entries = content_registry.groups.get(g, []) # .get is a temporary workaround for "unknown items" group
+                contenttypes.extend([e.content_type for e in entries])
+            return contenttypes
+
+        def contenttype_match(tested, cts):
+            for ct in cts:
+                if ct.issupertype(tested):
+                    return True
+            return False
+
+        if selected_groups is not None:
+            selected_contenttypes = build_contenttypes(selected_groups)
+            filtered_index = [e for e in index if contenttype_match(Type(e.meta[CONTENTTYPE]), selected_contenttypes)]
+
+            unknown_item_group = "unknown items"
+            if unknown_item_group in selected_groups:
+                all_contenttypes = build_contenttypes(content_registry.group_names)
+                filtered_index.extend([e for e in index
+                                       if not contenttype_match(Type(e.meta[CONTENTTYPE]), all_contenttypes)])
+
+            index = filtered_index
         return index
 
+    def get_index(self, startswith=None, selected_groups=None):
+        dirs, files = self.make_flat_index(self.get_subitem_revs())
+        return dirs, self.filter_index(files, startswith, selected_groups)
+
+    def get_mixed_index(self):
+        dirs, files = self.make_flat_index(self.get_subitem_revs())
+        dirs_dict = dict([(e.relname, MixedIndexEntry(*e, hassubitems=True)) for e in dirs])
+        index_dict = dict([(e.relname, MixedIndexEntry(*e, hassubitems=False)) for e in files])
+        index_dict.update(dirs_dict)
+        return sorted(index_dict.values())
+
     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]  # XXX BROKEN - this is a list of names now
         return initials
 
     delete_template = 'delete.html'
@@ -651,922 +576,155 @@
     rename_template = 'rename.html'
     revert_template = 'revert.html'
 
-class NonExistent(Item):
-    def do_get(self, force_attachment=False, mimetype=None):
-        abort(404)
-
-    def _convert(self, doc):
-        abort(404)
-
-    def do_modify(self, contenttype, template_name):
-        # First, check if the current user has the required privileges
-        if not flaskg.user.may.create(self.name):
-            abort(403)
 
-        # XXX think about and add item template support
-        return render_template('modify_show_type_selection.html',
-                               item_name=self.name,
-                               contenttype_groups=CONTENTTYPE_GROUPS,
-                              )
-
-item_registry.register(NonExistent._factory, Type('application/x-nonexistent'))
-
-class ValidJSON(Validator):
-    """Validator for JSON
+class Contentful(Item):
     """
-    invalid_json_msg = L_('Invalid JSON.')
-
-    def validate(self, element, state):
-        try:
-            json.loads(element.value)
-        except:
-            return self.note_error(element, state, 'invalid_json_msg')
-        return True
+    Base class for Item subclasses that have content.
+    """
+    @property
+    def ModifyForm(self):
+        class C(self._ModifyForm):
+            content_form = self.content.ModifyForm
+        C.__name__ = 'ModifyForm'
+        return C
 
 
-class Binary(Item):
-    """ An arbitrary binary item, fallback class for every item mimetype. """
-    modify_help = """\
-There is no help, you're doomed!
-"""
-
-    template = "modify_binary.html"
-
-    # XXX reads item rev data into memory!
-    def get_data(self):
-        if self.rev is not None:
-            return self.rev.data.read()
-        else:
-            return ''
-    data = property(fget=get_data)
-
-    def _render_meta(self):
-        return "<pre>{0}</pre>".format(escape(self.meta_dict_to_text(self.meta, use_filter=False)))
+@register
+class Default(Contentful):
+    """
+    A "conventional" wiki item.
+    """
+    itemtype = u'default'
+    display_name = L_('Default')
+    description = L_('Wiki item')
+    order = -10
 
-    def get_templates(self, contenttype=None):
-        """ create a list of templates (for some specific contenttype) """
-        terms = [Term(WIKINAME, app.cfg.interwikiname), Term(TAGS, u'template')]
-        if contenttype is not None:
-            terms.append(Term(CONTENTTYPE, contenttype))
-        query = And(terms)
-        revs = flaskg.storage.search(query, sortedby=NAME_EXACT, limit=None)
-        return [rev.name for rev in revs]
+    def _do_modify_show_templates(self):
+        # call this if the item is still empty
+        rev_ids = []
+        item_templates = self.content.get_templates(self.contenttype)
+        return render_template('modify_select_template.html',
+                               item_name=self.name,
+                               itemtype=self.itemtype,
+                               rev=self.rev,
+                               contenttype=self.contenttype,
+                               templates=item_templates,
+                               first_rev_id=rev_ids and rev_ids[0],
+                               last_rev_id=rev_ids and rev_ids[-1],
+                               meta_rendered='',
+                               data_rendered='',
+                               )
 
-    def do_modify(self, contenttype, template_name):
-        # XXX think about and add item template support
-        #if template_name is None and isinstance(self.rev, DummyRev):
-        #    return self._do_modify_show_templates()
-        from MoinMoin.apps.frontend.views import CommentForm
-        class ModifyForm(CommentForm):
-            meta_text = String.using(optional=False).with_properties(placeholder=L_("MetaData (JSON)")).validated_by(ValidJSON())
-            data_file = FileStorage.using(optional=True, label=L_('Upload file:'))
+    def do_show(self, revid):
+        show_revision = revid != CURRENT
+        show_navigation = False # TODO
+        first_rev = last_rev = None # TODO
+        return render_template(self.show_template,
+                               item=self, item_name=self.name,
+                               rev=self.rev,
+                               contenttype=self.contenttype,
+                               first_rev_id=first_rev,
+                               last_rev_id=last_rev,
+                               data_rendered=Markup(self.content._render_data()),
+                               show_revision=show_revision,
+                               show_navigation=show_navigation,
+                              )
 
-        if request.method == 'GET':
-            form = ModifyForm.from_defaults()
-            TextCha(form).amend_form()
-            form['meta_text'] = self.meta_dict_to_text(self.prepare_meta_for_modify(self.meta))
-        elif request.method == 'POST':
-            form = ModifyForm.from_flat(request.form.items() + request.files.items())
-            TextCha(form).amend_form()
+    def do_modify(self):
+        method = request.method
+        if method == 'GET':
+            if isinstance(self.content, NonExistentContent):
+                return render_template('modify_select_contenttype.html',
+                                       item_name=self.name,
+                                       itemtype=self.itemtype,
+                                       group_names=content_registry.group_names,
+                                       groups=content_registry.groups,
+                                      )
+            item = self
+            if isinstance(self.rev, DummyRev):
+                template_name = request.values.get('template')
+                if template_name is None:
+                    return self._do_modify_show_templates()
+                elif template_name:
+                    item = Item.create(template_name)
+            form = self.ModifyForm.from_item(item)
+        elif method == 'POST':
+            # XXX workaround for *Draw items
+            if isinstance(self.content, Draw):
+                try:
+                    self.content.handle_post()
+                except AccessDenied:
+                    abort(403)
+                else:
+                    # *Draw Applets POSTs more than once, redirecting would
+                    # break them
+                    return "OK"
+            form = self.ModifyForm.from_request(request)
             if form.validate():
+                meta, data, contenttype_guessed, comment = form._dump(self)
+                contenttype_qs = request.values.get('contenttype')
                 try:
-                    self.modify() # XXX
+                    self.modify(meta, data, comment, contenttype_guessed, contenttype_qs)
                 except AccessDenied:
                     abort(403)
                 else:
                     return redirect(url_for_item(self.name))
-        return render_template(self.template,
+        return render_template(self.modify_template,
                                item_name=self.name,
                                rows_meta=str(ROWS_META), cols=str(COLS),
-                               help=self.modify_help,
                                form=form,
                                search_form=None,
                               )
 
-    def _render_data_diff(self, oldrev, newrev):
-        hash_name = HASH_ALGORITHM
-        if oldrev.meta[hash_name] == newrev.meta[hash_name]:
-            return _("The items have the same data hash code (that means they very likely have the same data).")
-        else:
-            return _("The items have different data.")
-
-    _render_data_diff_text = _render_data_diff
-    _render_data_diff_raw = _render_data_diff
-
-    def _render_data_diff_atom(self, oldrev, newrev):
-        return render_template('atom.html',
-                               oldrev=oldrev, newrev=newrev, get='binary',
-                               content=Markup(self._render_data()))
-
-    def _convert(self, doc):
-        return _("Impossible to convert the data to the contenttype: %(contenttype)s",
-                 contenttype=request.values.get('contenttype'))
-
-    def do_get(self, force_attachment=False, mimetype=None):
-        hash = self.rev.meta.get(HASH_ALGORITHM)
-        if is_resource_modified(request.environ, hash): # use hash as etag
-            return self._do_get_modified(hash, force_attachment=force_attachment, mimetype=mimetype)
-        else:
-            return Response(status=304)
-
-    def _do_get_modified(self, hash, force_attachment=False, mimetype=None):
-        member = request.values.get('member')
-        return self._do_get(hash, member, force_attachment=force_attachment, mimetype=mimetype)
-
-    def _do_get(self, hash, member=None, force_attachment=False, mimetype=None):
-        if member: # content = file contained within a archive item revision
-            path, filename = os.path.split(member)
-            mt = MimeType(filename=filename)
-            content_length = None
-            file_to_send = self.get_member(member)
-        else: # content = item revision
-            rev = self.rev
-            filename = rev.item.name
-            try:
-                mimestr = rev.meta[CONTENTTYPE]
-            except KeyError:
-                mt = MimeType(filename=filename)
-            else:
-                mt = MimeType(mimestr=mimestr)
-            content_length = rev.meta[SIZE]
-            file_to_send = rev.data
-        if mimetype:
-            content_type = mimetype
-        else:
-            content_type = mt.content_type()
-        as_attachment = force_attachment or mt.as_attachment(app.cfg)
-        return send_file(file=file_to_send,
-                         mimetype=content_type,
-                         as_attachment=as_attachment, attachment_filename=filename,
-                         cache_timeout=10, # wiki data can change rapidly
-                         add_etags=True, etag=hash, conditional=True)
-
-item_registry.register(Binary._factory, Type('*/*'))
-
-
-class RenderableBinary(Binary):
-    """ Base class for some binary stuff that renders with a object tag. """
-
-
-class Application(Binary):
-    """ Base class for application/* """
-
-
-class TarMixin(object):
-    """
-    TarMixin offers additional functionality for tar-like items to list and
-    access member files and to create new revisions by multiple posts.
-    """
-    def list_members(self):
-        """
-        list tar file contents (member file names)
-        """
-        self.rev.data.seek(0)
-        tf = tarfile.open(fileobj=self.rev.data, mode='r')
-        return tf.getnames()
-
-    def get_member(self, name):
-        """
-        return a file-like object with the member file data
-
-        :param name: name of the data in the container file
-        """
-        self.rev.data.seek(0)
-        tf = tarfile.open(fileobj=self.rev.data, mode='r')
-        return tf.extractfile(name)
-
-    def put_member(self, name, content, content_length, expected_members):
-        """
-        puts a new member file into a temporary tar container.
-        If all expected members have been put, it saves the tar container
-        to a new item revision.
-
-        :param name: name of the data in the container file
-        :param content: the data to store into the tar file (str or file-like)
-        :param content_length: byte-length of content (for str, None can be given)
-        :param expected_members: set of expected member file names
-        """
-        if not name in expected_members:
-            raise StorageError("tried to add unexpected member {0!r} to container item {1!r}".format(name, self.name))
-        if isinstance(name, unicode):
-            name = name.encode('utf-8')
-        temp_fname = os.path.join(tempfile.gettempdir(), 'TarContainer_' +
-                                  cache_key(usage='TarContainer', name=self.name))
-        tf = tarfile.TarFile(temp_fname, mode='a')
-        ti = tarfile.TarInfo(name)
-        if isinstance(content, str):
-            if content_length is None:
-                content_length = len(content)
-            content = StringIO(content) # we need a file obj
-        elif not hasattr(content, 'read'):
-            logging.error("unsupported content object: {0!r}".format(content))
-            raise StorageError("unsupported content object: {0!r}".format(content))
-        assert content_length >= 0  # we don't want -1 interpreted as 4G-1
-        ti.size = content_length
-        tf.addfile(ti, content)
-        tf_members = set(tf.getnames())
-        tf.close()
-        if tf_members - expected_members:
-            msg = "found unexpected members in container item {0!r}".format(self.name)
-            logging.error(msg)
-            os.remove(temp_fname)
-            raise StorageError(msg)
-        if tf_members == expected_members:
-            # everything we expected has been added to the tar file, save the container as revision
-            meta = {CONTENTTYPE: self.contenttype}
-            data = open(temp_fname, 'rb')
-            self._save(meta, data, name=self.name, action=u'SAVE', comment='')
-            data.close()
-            os.remove(temp_fname)
-
-
-class ApplicationXTar(TarMixin, Application):
-    """
-    Tar items
-    """
-
-item_registry.register(ApplicationXTar._factory, Type('application/x-tar'))
-item_registry.register(ApplicationXTar._factory, Type('application/x-gtar'))
-
-
-class ZipMixin(object):
-    """
-    ZipMixin offers additional functionality for zip-like items to list and
-    access member files.
-    """
-    def list_members(self):
-        """
-        list zip file contents (member file names)
-        """
-        self.rev.data.seek(0)
-        zf = zipfile.ZipFile(self.rev.data, mode='r')
-        return zf.namelist()
-
-    def get_member(self, name):
-        """
-        return a file-like object with the member file data
-
-        :param name: name of the data in the zip file
-        """
-        self.rev.data.seek(0)
-        zf = zipfile.ZipFile(self.rev.data, mode='r')
-        return zf.open(name, mode='r')
-
-    def put_member(self, name, content, content_length, expected_members):
-        raise NotImplementedError
-
-
-class ApplicationZip(ZipMixin, Application):
-    """
-    Zip items
-    """
-
-item_registry.register(ApplicationZip._factory, Type('application/zip'))
-
-
-class PDF(Application):
-    """ PDF """
-
-item_registry.register(PDF._factory, Type('application/pdf'))
-
-
-class Video(Binary):
-    """ Base class for video/* """
-
-item_registry.register(Video._factory, Type('video/*'))
-
-
-class Audio(Binary):
-    """ Base class for audio/* """
-
-item_registry.register(Audio._factory, Type('audio/*'))
-
-
-class Image(Binary):
-    """ Base class for image/* """
-
-item_registry.register(Image._factory, Type('image/*'))
-
-
-class RenderableImage(RenderableBinary):
-    """ Base class for renderable Image mimetypes """
-
-
-class SvgImage(RenderableImage):
-    """ SVG images use <object> tag mechanism from RenderableBinary base class """
-
-item_registry.register(SvgImage._factory, Type('image/svg+xml'))
-
-
-class RenderableBitmapImage(RenderableImage):
-    """ PNG/JPEG/GIF images use <img> tag (better browser support than <object>) """
-    # if mimetype is also transformable, please register in TransformableImage ONLY!
-
-
-class TransformableBitmapImage(RenderableBitmapImage):
-    """ We can transform (resize, rotate, mirror) some image types """
-    def _transform(self, content_type, size=None, transpose_op=None):
-        """ resize to new size (optional), transpose according to exif infos,
-            result data should be content_type.
-        """
-        try:
-            from PIL import Image as PILImage
-        except ImportError:
-            # no PIL, we can't do anything, we just output the revision data as is
-            return content_type, self.rev.data.read()
-
-        if content_type == 'image/jpeg':
-            output_type = 'JPEG'
-        elif content_type == 'image/png':
-            output_type = 'PNG'
-        elif content_type == 'image/gif':
-            output_type = 'GIF'
-        else:
-            raise ValueError("content_type {0!r} not supported".format(content_type))
-
-        # revision obj has read() seek() tell(), thus this works:
-        image = PILImage.open(self.rev.data)
-        image.load()
-
-        try:
-            # if we have EXIF data, we can transpose (e.g. rotate left),
-            # so the rendered image is correctly oriented:
-            transpose_op = transpose_op or 1 # or self.exif['Orientation']
-        except KeyError:
-            transpose_op = 1 # no change
-
-        if size is not None:
-            image = image.copy() # create copy first as thumbnail works in-place
-            image.thumbnail(size, PILImage.ANTIALIAS)
-
-        transpose_func = {
-            1: lambda image: image,
-            2: lambda image: image.transpose(PILImage.FLIP_LEFT_RIGHT),
-            3: lambda image: image.transpose(PILImage.ROTATE_180),
-            4: lambda image: image.transpose(PILImage.FLIP_TOP_BOTTOM),
-            5: lambda image: image.transpose(PILImage.ROTATE_90).transpose(PILImage.FLIP_TOP_BOTTOM),
-            6: lambda image: image.transpose(PILImage.ROTATE_270),
-            7: lambda image: image.transpose(PILImage.ROTATE_90).transpose(PILImage.FLIP_LEFT_RIGHT),
-            8: lambda image: image.transpose(PILImage.ROTATE_90),
-        }
-        image = transpose_func[transpose_op](image)
-
-        outfile = StringIO()
-        image.save(outfile, output_type)
-        data = outfile.getvalue()
-        outfile.close()
-        return content_type, data
-
-    def _do_get_modified(self, hash, force_attachment=False, mimetype=None):
-        try:
-            width = int(request.values.get('w'))
-        except (TypeError, ValueError):
-            width = None
-        try:
-            height = int(request.values.get('h'))
-        except (TypeError, ValueError):
-            height = None
-        try:
-            transpose = int(request.values.get('t'))
-            assert 1 <= transpose <= 8
-        except (TypeError, ValueError, AssertionError):
-            transpose = 1
-        if width or height or transpose != 1:
-            # resize requested, XXX check ACL behaviour! XXX
-            hash_name = HASH_ALGORITHM
-            hash_hexdigest = self.rev.meta[hash_name]
-            cid = cache_key(usage="ImageTransform",
-                            hash_name=hash_name,
-                            hash_hexdigest=hash_hexdigest,
-                            width=width, height=height, transpose=transpose)
-            c = app.cache.get(cid)
-            if c is None:
-                if mimetype:
-                    content_type = mimetype
-                else:
-                    content_type = self.rev.meta[CONTENTTYPE]
-                size = (width or 99999, height or 99999)
-                content_type, data = self._transform(content_type, size=size, transpose_op=transpose)
-                headers = wikiutil.file_headers(content_type=content_type, content_length=len(data))
-                app.cache.set(cid, (headers, data))
-            else:
-                # XXX TODO check ACL behaviour
-                headers, data = c
-            return Response(data, headers=headers)
-        else:
-            return self._do_get(hash, force_attachment=force_attachment, mimetype=mimetype)
-
-    def _render_data_diff_atom(self, oldrev, newrev):
-        if PIL is None:
-            # no PIL, we can't do anything, we just call the base class method
-            return super(TransformableBitmapImage, self)._render_data_diff_atom(oldrev, newrev)
-        url = url_for('frontend.diffraw', _external=True, item_name=self.name, rev1=oldrev.revid, rev2=newrev.revid)
-        return render_template('atom.html',
-                               oldrev=oldrev, newrev=newrev, get='binary',
-                               content=Markup('<img src="{0}" />'.format(escape(url))))
-
-    def _render_data_diff(self, oldrev, newrev):
-        if PIL is None:
-            # no PIL, we can't do anything, we just call the base class method
-            return super(TransformableBitmapImage, self)._render_data_diff(oldrev, newrev)
-        url = url_for('frontend.diffraw', item_name=self.name, rev1=oldrev.revid, rev2=newrev.revid)
-        return Markup('<img src="{0}" />'.format(escape(url)))
-
-    def _render_data_diff_raw(self, oldrev, newrev):
-        hash_name = HASH_ALGORITHM
-        cid = cache_key(usage="ImageDiff",
-                        hash_name=hash_name,
-                        hash_old=oldrev.meta[hash_name],
-                        hash_new=newrev.meta[hash_name])
-        c = app.cache.get(cid)
-        if c is None:
-            if PIL is None:
-                abort(404) # TODO render user friendly error image
-
-            content_type = newrev.meta[CONTENTTYPE]
-            if content_type == 'image/jpeg':
-                output_type = 'JPEG'
-            elif content_type == 'image/png':
-                output_type = 'PNG'
-            elif content_type == 'image/gif':
-                output_type = 'GIF'
-            else:
-                raise ValueError("content_type {0!r} not supported".format(content_type))
-
-            try:
-                oldimage = PILImage.open(oldrev.data)
-                newimage = PILImage.open(newrev.data)
-                oldimage.load()
-                newimage.load()
-                diffimage = PILdiff(newimage, oldimage)
-                outfile = StringIO()
-                diffimage.save(outfile, output_type)
-                data = outfile.getvalue()
-                outfile.close()
-                headers = wikiutil.file_headers(content_type=content_type, content_length=len(data))
-                app.cache.set(cid, (headers, data))
-            except (IOError, ValueError) as err:
-                logging.exception("error during PILdiff: {0}".format(err.message))
-                abort(404) # TODO render user friendly error image
-        else:
-            # XXX TODO check ACL behaviour
-            headers, data = c
-        return Response(data, headers=headers)
-
-    def _render_data_diff_text(self, oldrev, newrev):
-        return super(TransformableBitmapImage, self)._render_data_diff_text(oldrev, newrev)
-
-item_registry.register(TransformableBitmapImage._factory, Type('image/png'))
-item_registry.register(TransformableBitmapImage._factory, Type('image/jpeg'))
-item_registry.register(TransformableBitmapImage._factory, Type('image/gif'))
+    show_template = 'show.html'
+    modify_template = 'modify.html'
 
 
-class Text(Binary):
-    """ Base class for text/* """
-    template = "modify_text.html"
-
-    # text/plain mandates crlf - but in memory, we want lf only
-    def data_internal_to_form(self, text):
-        """ convert data from memory format to form format """
-        return text.replace(u'\n', u'\r\n')
-
-    def data_form_to_internal(self, data):
-        """ convert data from form format to memory format """
-        return data.replace(u'\r\n', u'\n')
-
-    def data_internal_to_storage(self, text):
-        """ convert data from memory format to storage format """
-        return text.replace(u'\n', u'\r\n').encode(config.charset)
-
-    def data_storage_to_internal(self, data):
-        """ convert data from storage format to memory format """
-        return data.decode(config.charset).replace(u'\r\n', u'\n')
-
-    def _get_data_diff_html(self, oldrev, newrev, template):
-        from MoinMoin.util.diff_html import diff
-        old_text = self.data_storage_to_internal(oldrev.data.read())
-        new_text = self.data_storage_to_internal(newrev.data.read())
-        storage_item = flaskg.storage[self.name]
-        diffs = [(d[0], Markup(d[1]), d[2], Markup(d[3])) for d in diff(old_text, new_text)]
-        return render_template(template,
-                               item_name=self.name,
-                               oldrev=oldrev,
-                               newrev=newrev,
-                               diffs=diffs,
-                               )
-
-    def _render_data_diff_atom(self, oldrev, newrev):
-        """ renders diff in HTML for atom feed """
-        return self._get_data_diff_html(oldrev, newrev, 'diff_text_atom.html')
-
-    def _render_data_diff(self, oldrev, newrev):
-        return self._get_data_diff_html(oldrev, newrev, 'diff_text.html')
-
-    def _render_data_diff_text(self, oldrev, newrev):
-        from MoinMoin.util import diff_text
-        oldlines = self.data_storage_to_internal(oldrev.data.read()).split('\n')
-        newlines = self.data_storage_to_internal(newrev.data.read()).split('\n')
-        difflines = diff_text.diff(oldlines, newlines)
-        return '\n'.join(difflines)
-
-    _render_data_diff_raw = _render_data_diff
-
-    def _render_data_highlight(self):
-        from MoinMoin.converter import default_registry as reg
-        data_text = self.data_storage_to_internal(self.data)
-        # TODO: use registry as soon as it is in there
-        from MoinMoin.converter.pygments_in import Converter as PygmentsConverter
-        pygments_conv = PygmentsConverter(contenttype=self.contenttype)
-        doc = pygments_conv(data_text)
-        # TODO: Real output format
-        html_conv = reg.get(type_moin_document, Type('application/x-xhtml-moin-page'))
-        doc = html_conv(doc)
-        return conv_serialize(doc, {html.namespace: ''})
-
-    def do_modify(self, contenttype, template_name):
-        # XXX think about and add item template support
-        #if template_name is None and isinstance(self.rev, DummyRev):
-        #    return self._do_modify_show_templates()
-        from MoinMoin.apps.frontend.views import CommentForm
-        class ModifyForm(CommentForm):
-            meta_text = String.using(optional=False).with_properties(placeholder=L_("MetaData (JSON)")).validated_by(ValidJSON())
-            data_text = String.using(strip=False, optional=True).with_properties(placeholder=L_("Type your text here"))
-            data_file = FileStorage.using(optional=True, label=L_('Upload file:'))
-
-        if request.method == 'GET':
-            if template_name is None and isinstance(self.rev, DummyRev):
-                return self._do_modify_show_templates()
-            form = ModifyForm.from_defaults()
-            TextCha(form).amend_form()
-            if template_name:
-                item = Item.create(template_name)
-                form['data_text'] = self.data_storage_to_internal(item.data)
-            else:
-                form['data_text'] = self.data_storage_to_internal(self.data)
-            form['meta_text'] = self.meta_dict_to_text(self.prepare_meta_for_modify(self.meta))
-        elif request.method == 'POST':
-            form = ModifyForm.from_flat(request.form.items() + request.files.items())
-            TextCha(form).amend_form()
-            if form.validate():
-                try:
-                    self.modify() # XXX
-                except AccessDenied:
-                    abort(403)
-                else:
-                    return redirect(url_for_item(self.name))
-        return render_template(self.template,
-                               item_name=self.name,
-                               rows_data=str(ROWS_DATA), rows_meta=str(ROWS_META), cols=str(COLS),
-                               help=self.modify_help,
-                               form=form,
-                               search_form=None,
-                              )
-
-item_registry.register(Text._factory, Type('text/*'))
-
-
-class MarkupItem(Text):
+@register
+class Userprofile(Item):
     """
-    some kind of item with markup
-    (internal links and transcluded items)
-    """
-
-
-class MoinWiki(MarkupItem):
-    """ MoinMoin wiki markup """
-
-item_registry.register(MoinWiki._factory, Type('text/x.moin.wiki'))
-
-
-class CreoleWiki(MarkupItem):
-    """ Creole wiki markup """
-
-item_registry.register(CreoleWiki._factory, Type('text/x.moin.creole'))
-
-
-class MediaWiki(MarkupItem):
-    """ MediaWiki markup """
-
-item_registry.register(MediaWiki._factory, Type('text/x-mediawiki'))
-
-
-class ReST(MarkupItem):
-    """ ReStructured Text markup """
-
-item_registry.register(ReST._factory, Type('text/x-rst'))
-
-
-class HTML(Text):
-    """
-    HTML markup
-
-    Note: As we use html_in converter to convert this to DOM and later some
-          output converterter to produce output format (e.g. html_out for html
-          output), all(?) unsafe stuff will get lost.
-
-    Note: If raw revision data is accessed, unsafe stuff might be present!
+    Currently userprofile is implemented as a contenttype. This is a stub of an
+    itemtype implementation of userprofile.
     """
-    template = "modify_text_html.html"
-
-item_registry.register(HTML._factory, Type('text/html'))
-
-
-class DocBook(MarkupItem):
-    """ DocBook Document """
-    def _convert(self, doc):
-        from emeraldtree import ElementTree as ET
-        from MoinMoin.converter import default_registry as reg
-
-        doc = self._expand_document(doc)
-
-        # We convert the internal representation of the document
-        # into a DocBook document
-        conv = reg.get(type_moin_document, Type('application/docbook+xml'))
-
-        doc = conv(doc)
-
-        # We determine the different namespaces of the output form
-        output_namespaces = {
-             docbook.namespace: '',
-             xlink.namespace: 'xlink',
-         }
-
-        # We convert the result into a StringIO object
-        # With the appropriate namespace
-        # TODO: Some other operation should probably be done here too
-        # like adding a doctype
-        file_to_send = StringIO()
-        tree = ET.ElementTree(doc)
-        tree.write(file_to_send, namespaces=output_namespaces)
-
-        # We determine the different parameters for the reply
-        mt = MimeType(mimestr='application/docbook+xml;charset=utf-8')
-        content_type = mt.content_type()
-        as_attachment = mt.as_attachment(app.cfg)
-        # After creation of the StringIO, we are at the end of the file
-        # so position is the size the file.
-        # and then we should move it back at the beginning of the file
-        content_length = file_to_send.tell()
-        file_to_send.seek(0)
-        # Important: empty filename keeps flask from trying to autodetect filename,
-        # as this would not work for us, because our file's are not necessarily fs files.
-        return send_file(file=file_to_send,
-                         mimetype=content_type,
-                         as_attachment=as_attachment, attachment_filename=None,
-                         cache_timeout=10, # wiki data can change rapidly
-                         add_etags=False, etag=None, conditional=True)
-
-item_registry.register(DocBook._factory, Type('application/docbook+xml'))
+    itemtype = u'userprofile'
+    display_name = L_('User profile')
+    description = L_('User profile item (not implemented yet!)')
 
 
-class TWikiDraw(TarMixin, Image):
-    """
-    drawings by TWikiDraw applet. It creates three files which are stored as tar file.
+@register
+class NonExistent(Item):
     """
-    modify_help = ""
-    template = "modify_twikidraw.html"
-
-    def modify(self):
-        # called from modify UI/POST
-        file_upload = request.files.get('filepath')
-        filename = request.form['filename']
-        basepath, basename = os.path.split(filename)
-        basename, ext = os.path.splitext(basename)
-
-        filecontent = file_upload.stream
-        content_length = None
-        if ext == '.draw': # TWikiDraw POSTs this first
-            filecontent = filecontent.read() # read file completely into memory
-            filecontent = filecontent.replace("\r", "")
-        elif ext == '.map':
-            filecontent = filecontent.read() # read file completely into memory
-            filecontent = filecontent.strip()
-        elif ext == '.png':
-            #content_length = file_upload.content_length
-            # XXX gives -1 for wsgiref, gives 0 for werkzeug :(
-            # If this is fixed, we could use the file obj, without reading it into memory completely:
-            filecontent = filecontent.read()
-
-        self.put_member('drawing' + ext, filecontent, content_length,
-                        expected_members=set(['drawing.draw', 'drawing.map', 'drawing.png']))
-
-    def do_modify(self, contenttype, template_name):
-        # XXX think about and add item template support
-        #if template_name is None and isinstance(self.rev, DummyRev):
-        #    return self._do_modify_show_templates()
-        from MoinMoin.apps.frontend.views import CommentForm
-        class ModifyForm(CommentForm):
-            # XXX as the "saving" POSTs come from TWikiDraw (not the form), editing meta_text doesn't work
-            meta_text = String.using(optional=False).with_properties(placeholder=L_("MetaData (JSON)")).validated_by(ValidJSON())
-            data_file = FileStorage.using(optional=True, label=L_('Upload file:'))
+    A dummy Item for nonexistent items (when modifying, a nonexistent item with
+    undetermined itemtype)
+    """
+    itemtype = u'nonexistent'
+    shown = False
 
-        if request.method == 'GET':
-            form = ModifyForm.from_defaults()
-            TextCha(form).amend_form()
-            # XXX currently this is rather pointless, as the form does not get POSTed:
-            form['meta_text'] = self.meta_dict_to_text(self.prepare_meta_for_modify(self.meta))
-        elif request.method == 'POST':
-            # this POST comes directly from TWikiDraw (not from Browser), thus no validation
-            try:
-                self.modify() # XXX
-            except AccessDenied:
-                abort(403)
-            else:
-                # TWikiDraw POSTs more than once, redirecting would break them
-                return "OK"
-        return render_template(self.template,
-                               item_name=self.name,
-                               rows_meta=str(ROWS_META), cols=str(COLS),
-                               help=self.modify_help,
-                               form=form,
-                               search_form=None,
-                              )
+    def _convert(self, doc):
+        abort(404)
 
-    def _render_data(self):
-        # TODO: this could be a converter -> dom, then transcluding this kind
-        # of items and also rendering them with the code in base class could work
-        item_name = self.name
-        drawing_url = url_for('frontend.get_item', item_name=item_name, member='drawing.draw', rev=self.rev.revid)
-        png_url = url_for('frontend.get_item', item_name=item_name, member='drawing.png', rev=self.rev.revid)
-        title = _('Edit drawing %(filename)s (opens in new window)', filename=item_name)
+    def do_show(self, revid):
+        # First, check if the current user has the required privileges
+        if flaskg.user.may.create(self.name):
+            content = self._select_itemtype()
+        else:
+            content = render_template('show_nonexistent.html',
+                                      item_name=self.name,
+                                     )
+        return Response(content, 404)
 
-        mapfile = self.get_member('drawing.map')
-        try:
-            image_map = mapfile.read()
-            mapfile.close()
-        except (IOError, OSError):
-            image_map = ''
-        if image_map:
-            # we have a image map. inline it and add a map ref to the img tag
-            mapid = 'ImageMapOf' + item_name
-            image_map = image_map.replace('%MAPNAME%', mapid)
-            # add alt and title tags to areas
-            image_map = re.sub(r'href\s*=\s*"((?!%TWIKIDRAW%).+?)"', r'href="\1" alt="\1" title="\1"', image_map)
-            image_map = image_map.replace('%TWIKIDRAW%"', '{0}" alt="{1}" title="{2}"'.format((drawing_url, title, title)))
-            title = _('Clickable drawing: %(filename)s', filename=item_name)
+    def do_modify(self):
+        # First, check if the current user has the required privileges
+        if not flaskg.user.may.create(self.name):
+            abort(403)
+        return self._select_itemtype()
 
-            return Markup(image_map + '<img src="{0}" alt="{1}" usemap="#{2}" />'.format(png_url, title, mapid))
-        else:
-            return Markup('<img src="{0}" alt="{1}" />'.format(png_url, title))
-
-item_registry.register(TWikiDraw._factory, Type('application/x-twikidraw'))
+    def _select_itemtype(self):
+        return render_template('modify_select_itemtype.html',
+                               item_name=self.name,
+                               itemtypes=item_registry.shown_entries,
+                              )
 
 
-class AnyWikiDraw(TarMixin, Image):
-    """
-    drawings by AnyWikiDraw applet. It creates three files which are stored as tar file.
-    """
-    modify_help = ""
-    template = "modify_anywikidraw.html"
-
-    def modify(self):
-        # called from modify UI/POST
-        file_upload = request.files.get('filepath')
-        filename = request.form['filename']
-        basepath, basename = os.path.split(filename)
-        basename, ext = os.path.splitext(basename)
-        filecontent = file_upload.stream
-        content_length = None
-        if ext == '.svg':
-            filecontent = filecontent.read() # read file completely into memory
-            filecontent = filecontent.replace("\r", "")
-        elif ext == '.map':
-            filecontent = filecontent.read() # read file completely into memory
-            filecontent = filecontent.strip()
-        elif ext == '.png':
-            #content_length = file_upload.content_length
-            # XXX gives -1 for wsgiref, gives 0 for werkzeug :(
-            # If this is fixed, we could use the file obj, without reading it into memory completely:
-            filecontent = filecontent.read()
-        self.put_member('drawing' + ext, filecontent, content_length,
-                        expected_members=set(['drawing.svg', 'drawing.map', 'drawing.png']))
-
-    def do_modify(self, contenttype, template_name):
-        # XXX think about and add item template support
-        #if template_name is None and isinstance(self.rev, DummyRev):
-        #    return self._do_modify_show_templates()
-        from MoinMoin.apps.frontend.views import CommentForm
-        class ModifyForm(CommentForm):
-            # XXX as the "saving" POSTs come from AnyWikiDraw (not the form), editing meta_text doesn't work
-            meta_text = String.using(optional=False).with_properties(placeholder=L_("MetaData (JSON)")).validated_by(ValidJSON())
-            data_file = FileStorage.using(optional=True, label=L_('Upload file:'))
-
-        if request.method == 'GET':
-            form = ModifyForm.from_defaults()
-            TextCha(form).amend_form()
-            # XXX currently this is rather pointless, as the form does not get POSTed:
-            form['meta_text'] = self.meta_dict_to_text(self.prepare_meta_for_modify(self.meta))
-        elif request.method == 'POST':
-            # this POST comes directly from AnyWikiDraw (not from Browser), thus no validation
-            try:
-                self.modify() # XXX
-            except AccessDenied:
-                abort(403)
-            else:
-                # AnyWikiDraw POSTs more than once, redirecting would break them
-                return "OK"
-        try:
-            drawing_exists = 'drawing.svg' in self.list_members()
-        except:
-            drawing_exists = False
-        return render_template(self.template,
-                               item_name=self.name,
-                               rows_meta=str(ROWS_META), cols=str(COLS),
-                               help=self.modify_help,
-                               drawing_exists=drawing_exists,
-                               form=form,
-                               search_form=None,
-                              )
-
-    def _render_data(self):
-        # TODO: this could be a converter -> dom, then transcluding this kind
-        # of items and also rendering them with the code in base class could work
-        item_name = self.name
-        drawing_url = url_for('frontend.get_item', item_name=item_name, member='drawing.svg', rev=self.rev.revid)
-        png_url = url_for('frontend.get_item', item_name=item_name, member='drawing.png', rev=self.rev.revid)
-        title = _('Edit drawing %(filename)s (opens in new window)', filename=self.name)
-
-        mapfile = self.get_member('drawing.map')
-        try:
-            image_map = mapfile.read()
-            mapfile.close()
-        except (IOError, OSError):
-            image_map = ''
-        if image_map:
-            # ToDo mapid must become uniq
-            # we have a image map. inline it and add a map ref to the img tag
-            # we have also to set a unique ID
-            mapid = 'ImageMapOf' + self.name
-            image_map = image_map.replace(u'id="drawing.svg"', '')
-            image_map = image_map.replace(u'name="drawing.svg"', u'name="{0}"'.format(mapid))
-            # unxml, because 4.01 concrete will not validate />
-            image_map = image_map.replace(u'/>', u'>')
-            title = _('Clickable drawing: %(filename)s', filename=self.name)
-            return Markup(image_map + '<img src="{0}" alt="{1}" usemap="#{2}" />'.format(png_url, title, mapid))
-        else:
-            return Markup('<img src="{0}" alt="{1}" />'.format(png_url, title))
-
-item_registry.register(AnyWikiDraw._factory, Type('application/x-anywikidraw'))
-
-
-class SvgDraw(TarMixin, Image):
-    """ drawings by svg-edit. It creates two files (svg, png) which are stored as tar file. """
-    modify_help = ""
-    template = "modify_svg-edit.html"
-
-    def modify(self):
-        # called from modify UI/POST
-        png_upload = request.values.get('png_data')
-        svg_upload = request.values.get('filepath')
-        filename = request.form['filename']
-        png_content = png_upload.decode('base_64')
-        png_content = base64.urlsafe_b64decode(png_content.split(',')[1])
-        svg_content = svg_upload.decode('base_64')
-        content_length = None
-        self.put_member("drawing.svg", svg_content, content_length,
-                        expected_members=set(['drawing.svg', 'drawing.png']))
-        self.put_member("drawing.png", png_content, content_length,
-                        expected_members=set(['drawing.svg', 'drawing.png']))
-
-    def do_modify(self, contenttype, template_name):
-        # XXX think about and add item template support
-        #if template_name is None and isinstance(self.rev, DummyRev):
-        #    return self._do_modify_show_templates()
-        from MoinMoin.apps.frontend.views import CommentForm
-        class ModifyForm(CommentForm):
-            # XXX as the "saving" POSTs come from SvgDraw (not the form), editing meta_text doesn't work
-            meta_text = String.using(optional=False).with_properties(placeholder=L_("MetaData (JSON)")).validated_by(ValidJSON())
-            data_file = FileStorage.using(optional=True, label=L_('Upload file:'))
-
-        if request.method == 'GET':
-            form = ModifyForm.from_defaults()
-            TextCha(form).amend_form()
-            # XXX currently this is rather pointless, as the form does not get POSTed:
-            form['meta_text'] = self.meta_dict_to_text(self.prepare_meta_for_modify(self.meta))
-        elif request.method == 'POST':
-            # this POST comes directly from SvgDraw (not from Browser), thus no validation
-            try:
-                self.modify() # XXX
-            except AccessDenied:
-                abort(403)
-            else:
-                # SvgDraw POSTs more than once, redirecting would break them
-                return "OK"
-        return render_template(self.template,
-                               item_name=self.name,
-                               rows_meta=str(ROWS_META), cols=str(COLS),
-                               help=self.modify_help,
-                               form=form,
-                               search_form=None,
-                              )
-
-    def _render_data(self):
-        # TODO: this could be a converter -> dom, then transcluding this kind
-        # of items and also rendering them with the code in base class could work
-        item_name = self.name
-        drawing_url = url_for('frontend.get_item', item_name=item_name, member='drawing.svg', rev=self.rev.revid)
-        png_url = url_for('frontend.get_item', item_name=item_name, member='drawing.png', rev=self.rev.revid)
-        return Markup('<img src="{0}" alt="{1}" />'.format(png_url, drawing_url))
-
-item_registry.register(SvgDraw._factory, Type('application/x-svgdraw'))
-
+from ..util.pysupport import load_package_modules
+load_package_modules(__name__, __path__)