changeset 1451:c32ff2e07e7a

Introduce itemtype. itemtype is used to affect the overall frontend view of an item, and to affect the indexer by offering a set of metadata keys. The original items.Item hierarchy was mosted turned into the new Content hierarchy, leaving code affecting the overview in the new Item and Default (which is an Item descent) classes. Item instances now have a `content` property which is what used to be an Item instance (now a Content instance). This is the first itemtype changeset which just moves codes around ensure they are not broken. More changesets follow soon.
author Cheer Xiao <xiaqqaix@gmail.com>
date Sat, 28 Jul 2012 01:22:44 +0800
parents 90d02867e144
children 35024b2245c5
files MoinMoin/apps/frontend/views.py MoinMoin/constants/keys.py MoinMoin/converter/_tests/test_include.py MoinMoin/converter/include.py MoinMoin/items/__init__.py MoinMoin/items/_tests/test_Item.py MoinMoin/templates/modify.html MoinMoin/templates/modify_anywikidraw.html MoinMoin/templates/modify_applet.html MoinMoin/templates/modify_binary.html MoinMoin/templates/modify_show_contenttype_selection.html MoinMoin/templates/modify_show_itemtype_selection.html MoinMoin/templates/modify_show_template_selection.html MoinMoin/templates/modify_show_type_selection.html MoinMoin/templates/modify_svg-edit.html MoinMoin/templates/modify_text.html MoinMoin/templates/modify_text_html.html MoinMoin/templates/modify_twikidraw.html
diffstat 18 files changed, 827 insertions(+), 532 deletions(-) [+]
line wrap: on
line diff
--- a/MoinMoin/apps/frontend/views.py	Thu Jul 26 22:52:17 2012 +0200
+++ b/MoinMoin/apps/frontend/views.py	Sat Jul 28 01:22:44 2012 +0800
@@ -54,6 +54,7 @@
 from MoinMoin.forms import OptionalText, RequiredText, URL, YourOpenID, YourEmail, RequiredPassword, Checkbox, InlineCheckbox, Select, Tags, Natural, Submit, Hidden
 from MoinMoin.items import BaseChangeForm, Item, NonExistent
 from MoinMoin.items import ROWS_META, COLS, ROWS_DATA
+#from MoinMoin.items.content import ROWS_DATA
 from MoinMoin import config, user, util
 from MoinMoin.config import CONTENTTYPE_GROUPS
 from MoinMoin.constants.keys import *
@@ -321,7 +322,7 @@
                               contenttype=item.contenttype,
                               first_rev_id=first_rev,
                               last_rev_id=last_rev,
-                              data_rendered=Markup(item._render_data()),
+                              data_rendered=Markup(item.content._render_data()),
                               show_revision=show_revision,
                               show_navigation=show_navigation,
                              )
@@ -340,7 +341,7 @@
     else:
         status = 200
     content = render_template('dom.xml',
-                              data_xml=Markup(item._render_data_xml()),
+                              data_xml=Markup(item.content._render_data_xml()),
                              )
     return Response(content, status, mimetype='text/xml')
 
@@ -363,7 +364,7 @@
 def highlight_item(item):
     return render_template('highlight.html',
                            item=item, item_name=item.name,
-                           data_text=Markup(item._render_data_highlight()),
+                           data_text=Markup(item.content._render_data_highlight()),
                           )
 
 
@@ -408,17 +409,17 @@
         abort(404, item_name)
     return render_template('content.html',
                            item_name=item.name,
-                           data_rendered=Markup(item._render_data()),
+                           data_rendered=Markup(item.content._render_data()),
                            )
 
 @presenter('get')
 def get_item(item):
-    return item.do_get()
+    return item.content.do_get()
 
 @presenter('download')
 def download_item(item):
     mimetype = request.values.get("mimetype")
-    return item.do_get(force_attachment=True, mimetype=mimetype)
+    return item.content.do_get(force_attachment=True, mimetype=mimetype)
 
 @frontend.route('/+convert/<itemname:item_name>')
 def convert_item(item_name):
@@ -441,10 +442,11 @@
     # XXX Maybe use a random name to be sure it does not exist
     item_name_converted = item_name + 'converted'
     try:
-        converted_item = Item.create(item_name_converted, contenttype=contenttype)
+        # TODO implement Content.create and use it Content.create here
+        converted_item = Item.create(item_name_converted, itemtype=u'default', contenttype=contenttype)
     except AccessDenied:
         abort(403)
-    return converted_item._convert(item.internal_representation())
+    return converted_item.content._convert(item.content.internal_representation())
 
 
 @frontend.route('/+modify/<itemname:item_name>', methods=['GET', 'POST'])
@@ -455,15 +457,15 @@
     On POST, saves the new page (unless there's an error in input).
     After successful POST, redirects to the page.
     """
+    itemtype = request.values.get('itemtype')
     contenttype = request.values.get('contenttype')
-    template_name = request.values.get('template')
     try:
-        item = Item.create(item_name, contenttype=contenttype)
+        item = Item.create(item_name, itemtype=itemtype, contenttype=contenttype)
     except AccessDenied:
         abort(403)
     if not flaskg.user.may.write(item_name):
         abort(403)
-    return item.do_modify(contenttype, template_name)
+    return item.do_modify()
 
 
 @frontend.route('/+blog/+<rev>/<itemname:item_name>', methods=['GET'])
@@ -1664,7 +1666,7 @@
     rev_ids = [CURRENT]  # XXX TODO we need a reverse sorted list
     return render_template(item.diff_template,
                            item=item, item_name=item.name,
-                           diff_html=Markup(item._render_data_diff(oldrev, newrev)),
+                           diff_html=Markup(item.content._render_data_diff(oldrev, newrev)),
                            rev=item.rev,
                            first_rev_id=rev_ids[0],
                            last_rev_id=rev_ids[-1],
@@ -1682,7 +1684,7 @@
         item = Item.create(item.name, contenttype=commonmt, rev_id=newrev.revid)
     except AccessDenied:
         abort(403)
-    return item._render_data_diff_raw(oldrev, newrev)
+    return item.content._render_data_diff_raw(oldrev, newrev)
 
 
 @frontend.route('/+similar_names/<itemname:item_name>')
--- a/MoinMoin/constants/keys.py	Thu Jul 26 22:52:17 2012 +0200
+++ b/MoinMoin/constants/keys.py	Sat Jul 28 01:22:44 2012 +0800
@@ -27,6 +27,7 @@
 SOMEDICT = "somedict"
 
 CONTENTTYPE = "contenttype"
+ITEMTYPE = u"itemtype"
 SIZE = "size"
 LANGUAGE = "language"
 EXTERNALLINKS = "externallinks"
--- a/MoinMoin/converter/_tests/test_include.py	Thu Jul 26 22:52:17 2012 +0200
+++ b/MoinMoin/converter/_tests/test_include.py	Sat Jul 28 01:22:44 2012 +0800
@@ -9,7 +9,7 @@
 import pytest
 
 from MoinMoin.converter.include import *
-from MoinMoin.items import MoinWiki
+from MoinMoin.items import Item
 from MoinMoin.config import CONTENTTYPE
 from MoinMoin._tests import wikiconfig, update_item
 
@@ -65,20 +65,20 @@
 
     def test_IncludeHandlesCircularRecursion(self):
         # issue #80
-        # we use MoinWiki items to make tests simple
+        # we use text/x.moin.wiki markup to make tests simple
         update_item(u'page1', {CONTENTTYPE: u'text/x.moin.wiki'}, u'{{page2}}')
         update_item(u'page2', {CONTENTTYPE: u'text/x.moin.wiki'}, u'{{page3}}')
         update_item(u'page3', {CONTENTTYPE: u'text/x.moin.wiki'}, u'{{page4}}')
         update_item(u'page4', {CONTENTTYPE: u'text/x.moin.wiki'}, u'{{page2}}')
 
-        page1 = MoinWiki.create(u'page1')
-        rendered = page1._render_data()
+        page1 = Item.create(u'page1')
+        rendered = page1.content._render_data()
         # an error message will follow strong tag
         assert '<strong class="moin-error">' in rendered
 
     def test_ExternalInclude(self):
         update_item(u'page1', {CONTENTTYPE: u'text/x.moin.wiki'}, u'{{http://moinmo.in}}')
-        rendered = MoinWiki.create(u'page1')._render_data()
+        rendered = Item.create(u'page1').content._render_data()
         assert '<object class="moin-http moin-transclusion" data="http://moinmo.in" data-href="http://moinmo.in">http://moinmo.in</object>' in rendered
 
     def test_InlineInclude(self):
@@ -86,32 +86,32 @@
         update_item(u'page1', {CONTENTTYPE: u'text/x.moin.wiki'}, u'Content of page2 is "{{page2}}".')
 
         update_item(u'page2', {CONTENTTYPE: u'text/x.moin.wiki'}, u'Single line')
-        rendered = MoinWiki.create(u'page1')._render_data()
+        rendered = Item.create(u'page1').content._render_data()
         assert '<p>Content of page2 is "<span class="moin-transclusion" data-href="/page2">Single line</span>".</p>' in rendered
 
         update_item(u'page2', {CONTENTTYPE: u'text/x.moin.wiki'}, u'Two\n\nParagraphs')
-        rendered = MoinWiki.create(u'page1')._render_data()
+        rendered = Item.create(u'page1').content._render_data()
         assert '<p>Content of page2 is "</p><div class="moin-transclusion" data-href="/page2"><p>Two</p><p>Paragraphs</p></div><p>".</p></div>' in rendered
 
         update_item(u'page2', {CONTENTTYPE: u'text/x.moin.wiki'}, u"this text contains ''italic'' string")
-        rendered = MoinWiki.create(u'page1')._render_data()
+        rendered = Item.create(u'page1').content._render_data()
         assert 'Content of page2 is "<span class="moin-transclusion" data-href="/page2">this text contains <em>italic</em>' in rendered
 
         update_item(u'page1', {CONTENTTYPE: u'text/x.moin.wiki'}, u'Content of page2 is\n\n{{page2}}')
         update_item(u'page2', {CONTENTTYPE: u'text/x.moin.wiki'}, u"Single Line")
-        rendered = MoinWiki.create(u'page1')._render_data()
+        rendered = Item.create(u'page1').content._render_data()
         assert '<p>Content of page2 is</p><p><span class="moin-transclusion" data-href="/page2">Single Line</span></p>' in rendered
 
         update_item(u'page1', {CONTENTTYPE: u'text/x.moin.wiki'}, u'Content of page2 is "{{page2}}"')
         update_item(u'page2', {CONTENTTYPE: u'text/x.moin.wiki'}, u"|| table || cell ||")
-        rendered = MoinWiki.create(u'page1')._render_data()
+        rendered = Item.create(u'page1').content._render_data()
         assert 'Content of page2 is "</p>' in rendered
         assert '<table>' in rendered
         assert rendered.count('<table>') == 1
 
         update_item(u'page1', {CONTENTTYPE: u'text/x.moin.wiki'}, u'Content of page2 is "{{page2}}"')
         update_item(u'page2', {CONTENTTYPE: u'text/x.moin.wiki'}, u"|| this || has ||\n|| two || rows ||")
-        rendered = MoinWiki.create(u'page1')._render_data()
+        rendered = Item.create(u'page1').content._render_data()
         assert 'Content of page2 is "</p>' in rendered
         assert '<table>' in rendered
         assert rendered.count('<table>') == 1
@@ -121,11 +121,11 @@
         update_item(u'logo', {CONTENTTYPE: u'image/png'}, u'')
 
         update_item(u'page1', {CONTENTTYPE: u'text/x.moin.wiki'}, u'{{logo}}')
-        rendered = MoinWiki.create(u'page1')._render_data()
+        rendered = Item.create(u'page1').content._render_data()
         assert '<img alt="logo" class="moin-transclusion"' in rendered
 
         # <p /> is not valid html5; should be <p></p>. to be valid.  Even better, there should be no empty p's.
         update_item(u'page1', {CONTENTTYPE: u'text/x.moin.wiki'}, u'{{logo}}{{logo}}')
-        rendered = MoinWiki.create(u'page1')._render_data()
+        rendered = Item.create(u'page1').content._render_data()
         assert '<p />' not in rendered
         assert '<p></p>' not in rendered
--- a/MoinMoin/converter/include.py	Thu Jul 26 22:52:17 2012 +0200
+++ b/MoinMoin/converter/include.py	Sat Jul 28 01:22:44 2012 +0800
@@ -249,7 +249,7 @@
                         elem_h = ET.Element(self.tag_h, attrib, children=(elem_a, ))
                         included_elements.append(elem_h)
 
-                    page_doc = page.internal_representation()
+                    page_doc = page.content.internal_representation()
                     # page_doc.tag = self.tag_div # XXX why did we have this?
 
                     self.recurse(page_doc, page_href)
--- a/MoinMoin/items/__init__.py	Thu Jul 26 22:52:17 2012 +0200
+++ b/MoinMoin/items/__init__.py	Sat Jul 28 01:22:44 2012 +0800
@@ -8,45 +8,31 @@
 # 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 re, time
 import itertools
 from StringIO import StringIO
-from array import array
 
-from flatland import Form, String, Integer, Boolean, Enum
-from flatland.validation import Validator, Present, IsEmail, ValueBetween, URLValidator, Converted
+from flatland import Form
+from flatland.validation import Validator
 
 from whoosh.query import Term, And, Prefix
 
-from MoinMoin.forms import RequiredText, OptionalText, File, Submit
+from MoinMoin.forms import RequiredText, OptionalText, OptionalMultilineText, Tags, Submit
 
-from MoinMoin.security.textcha import TextCha, TextChaizedForm, TextChaValid
+from MoinMoin.security.textcha import TextCha, TextChaizedForm
 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.util.mime import Type
 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__)
 
@@ -58,47 +44,44 @@
 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 flask import request, Response, redirect, abort, escape
 
 from werkzeug import is_resource_modified
-from jinja2 import Markup
 
-from MoinMoin.i18n import _, L_, N_
+from MoinMoin.i18n import L_
 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
+from MoinMoin.util.registry import RegistryBase
+from MoinMoin.constants.keys import (
+    NAME, NAME_OLD, NAME_EXACT, WIKINAME, MTIME, SYSITEM_VERSION, ITEMTYPE,
+    CONTENTTYPE, SIZE, TAGS, ACTION, ADDRESS, HOSTNAME, USERID, COMMENT,
+    HASH_ALGORITHM, ITEMID, REVID, DATAID, CURRENT, PARENTID
+    )
+from MoinMoin.constants.contenttypes import charset, CONTENTTYPE_GROUPS
+
+#from .content import content_registry
+
 
 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):
+        def __init__(self, factory, itemtype, priority):
             self.factory = factory
-            self.content_type = content_type
+            self.itemtype = itemtype
             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 __call__(self, name, itemtype, kw):
+            if self.itemtype == itemtype:
+                return self.factory(name, itemtype, **kw)
 
         def __eq__(self, other):
             if isinstance(other, self.__class__):
                 return (self.factory == other.factory and
-                        self.content_type == other.content_type and
+                        self.itemtype == other.itemtype and
                         self.priority == other.priority)
             return NotImplemented
 
@@ -106,44 +89,33 @@
             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}]>'.format(self.__class__.__name__,
-                    self.content_type,
+                    self.itemtype,
                     self.priority,
                     self.factory)
 
-    def get(self, name, content_type, **kw):
+    def get(self, name, itemtype, **kw):
         for entry in self._entries:
-            item = entry(name, content_type, kw)
+            item = entry(name, itemtype, kw)
             if item is not None:
                 return item
 
-    def register(self, factory, content_type, priority=RegistryBase.PRIORITY_MIDDLE):
+    def register(self, factory, itemtype, priority=RegistryBase.PRIORITY_MIDDLE):
         """
         Register a factory
 
         :param factory: Factory to register. Callable, must return an object.
         """
-        return self._register(self.Entry(factory, content_type, priority))
+        return self._register(self.Entry(factory, itemtype, priority))
 
 
 item_registry = RegistryItem()
 
 
-def conv_serialize(doc, namespaces, method='polyglot'):
-    out = array('u')
-    flaskg.clock.start('conv_serialize')
-    doc.write(out.fromunicode, namespaces=namespaces, method=method)
-    out = out.tounicode()
-    flaskg.clock.stop('conv_serialize')
-    return out
-
-
 class DummyRev(dict):
     """ if we have no stored Revision, we use this dummy """
     def __init__(self, item, contenttype):
@@ -163,17 +135,69 @@
         return True
 
 
+# XXX To code reviewers: Code chunks within {{{ }}} were moved verbatim from
+# somewhere else. Wherever I declare it to be "untouched", "untouched except
+# for ..." I double checked by isolating the same chunk in old and new
+# revisions and did a manual `diff` on that. FYI, the workflow is (in Vim):
+
+# 1. in the new revision select the chunk in visual line mode (V)
+# 2. :'<,'>w! /tmp/chunk.new
+# 3. do the same with the old revision, writing to /tmp/chunk.old
+# 4. :!diff /tmp/chunk.old /tmp/chunk.new
+
+# So trust me :-P
+
+
+# XXX Moved verbatim from below Item untouched {{{
+class ValidJSON(Validator):
+    """Validator for JSON
+    """
+    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
+
+
+class BaseChangeForm(TextChaizedForm):
+    comment = OptionalText.using(label=L_('Comment')).with_properties(placeholder=L_("Comment about your change"))
+    submit = Submit
+# }}}
+
+
 class Item(object):
-    """ Highlevel (not storage) Item """
+    """ Highlevel (not storage) Item, wraps around a storage Revision"""
     @classmethod
-    def _factory(cls, name=u'', contenttype=None, **kw):
-        return cls(name, contenttype=unicode(contenttype), **kw)
+    def _factory(cls, name=u'', itemtype=None, **kw):
+        return cls(name, **kw)
 
+    # TODO split Content creation to Content.create
     @classmethod
-    def create(cls, name=u'', contenttype=None, rev_id=CURRENT, item=None):
+    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.
+        """
         if contenttype is None:
             contenttype = u'application/x-nonexistent'
-
+        if itemtype is None:
+            itemtype = u'nonexistent'
         if 1: # try:
             if item is None:
                 item = flaskg.storage[name]
@@ -189,6 +213,7 @@
             try:
                 rev = item.get_revision(rev_id)
                 contenttype = u'application/octet-stream' # it exists
+                itemtype = u'default' # default itemtype to u'default' for compatibility
             except KeyError: # NoSuchRevisionError:
                 try:
                     rev = item.get_revision(CURRENT) # fall back to current revision
@@ -202,120 +227,37 @@
         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_registry.get(name, Type(contenttype))
+        logging.debug("Content class {0!r} handles {1!r}".format(content.__class__, contenttype))
+
+        itemtype = rev.meta.get(ITEMTYPE) or itemtype
+        logging.debug("Item {0!r}, got itemtype {1!r} from revision meta".format(name, itemtype))
+
+        item = item_registry.get(name, itemtype, 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
-        # TODO: Real output format
-        doc = self.internal_representation()
-        doc = self._expand_document(doc)
-        flaskg.clock.start('conv_dom_html')
-        html_conv = reg.get(type_moin_document, Type('application/x-xhtml-moin-page'))
-        doc = html_conv(doc)
-        flaskg.clock.stop('conv_dom_html')
-        rendered_data = conv_serialize(doc, {html.namespace: ''})
-        return rendered_data
-
-    def _render_data_xml(self):
-        doc = self.internal_representation()
-        return conv_serialize(doc,
-                              {moin_page.namespace: '',
-                               xlink.namespace: 'xlink',
-                               html.namespace: 'html',
-                              },
-                              'xml')
-
-    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 """
@@ -359,31 +301,11 @@
             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):
-        self._save(self.meta, self.data, name=name, action=action, comment=comment)
+        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.data, name='/'.join((name, child[1])), action=action, comment=comment)
+            item._save(item.meta, item.content.data, name='/'.join((name, child[1])), action=action, comment=comment)
 
     def rename(self, name, comment=u''):
         """
@@ -402,7 +324,7 @@
         return self._rename(trashname, comment, action=u'TRASH')
 
     def revert(self, comment=u''):
-        return self._save(self.meta, self.data, action=u'REVERT', comment=comment)
+        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
@@ -420,6 +342,42 @@
 
         return self._save(meta, data, contenttype_guessed=contenttype_guessed, comment=comment)
 
+    class _ModifyForm(BaseChangeForm):
+        """Base class for ModifyForm of Item subclasses."""
+        meta_text = OptionalMultilineText.using(label=L_("MetaData (JSON)")).with_properties(rows=ROWS_META, cols=COLS).validated_by(ValidJSON())
+
+        def _load(self, item):
+            self['meta_text'] = item.meta_dict_to_text(item.prepare_meta_for_modify(item.meta))
+            self['content_form']._load(item.content)
+
+        def _dump(self, item):
+            meta = item.meta_text_to_dict(self['meta_text'].value)
+            data, contenttype_guessed = self['content_form']._dump(item.content)
+            comment = self['comment'].value
+            return meta, data, contenttype_guessed, comment
+
+        @classmethod
+        def from_item(cls, item):
+            form = cls.from_defaults()
+            TextCha(form).amend_form()
+            form._load(item)
+            return form
+
+        @classmethod
+        def from_request(cls, request):
+            form = cls.from_flat(request.form.items() + request.files.items())
+            TextCha(form).amend_form()
+            return form
+
+    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):
         backend = flaskg.storage
         storage_item = backend[self.name]
@@ -459,7 +417,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)
@@ -599,122 +557,74 @@
     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)
-
-        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
-
-
-class BaseChangeForm(TextChaizedForm):
-    comment = OptionalText.using(label=L_('Comment')).with_properties(placeholder=L_("Comment about your change"))
-    submit = Submit
+    Base class for Item subclasses that have content.
+    """
+    @property
+    def ModifyForm(self):
+        class C(Item._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)))
-
-    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.meta[NAME] for rev in revs]
-
-    class ModifyForm(BaseChangeForm):
-        """Base class for ModifyForm of Binary's subclasses."""
-        meta_text = RequiredText.with_properties(placeholder=L_("MetaData (JSON)")).validated_by(ValidJSON())
-        data_file = File.using(optional=True, label=L_('Upload file:'))
+# TODO better name and clearer definition
+class Default(Contentful):
+    """
+    A "conventional" wiki item.
+    """
+    # XXX this method was moved from Item, untouched except for the addition
+    # of itemtype keyword argument plus the comment above it.
+    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_show_template_selection.html',
+                               item_name=self.name,
+                               # XXX avoid the magic string
+                               itemtype=u'default',
+                               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 _load(self, item):
-            self['meta_text'] = item.meta_dict_to_text(item.prepare_meta_for_modify(item.meta))
-
-        def _dump(self, item):
-            data = meta = contenttype_guessed = None
-            data_file = self['data_file'].value
-            if data_file:
-                data = data_file.stream
-                # this is likely a guess by the browser, based on the filename
-                contenttype_guessed = data_file.content_type # comes from form multipart data
-            meta = item.meta_text_to_dict(self['meta_text'].value)
-            comment = self['comment'].value
-            return meta, data, contenttype_guessed, comment
-
-        extra_template_args = {}
-
-        @classmethod
-        def from_item(cls, item):
-            form = cls.from_defaults()
-            TextCha(form).amend_form()
-            form._load(item)
-            return form
-
-        @classmethod
-        def from_request(cls, request):
-            form = cls.from_flat(request.form.items() + request.files.items())
-            TextCha(form).amend_form()
-            return form
-
-    def do_modify(self, contenttype, template_name):
-        """
-        Handle +modify requests, both GET and POST.
-
-        This method can be overridden in subclasses, providing polymorphic
-        behavior for the +modify view.
-        """
+    # To code reviewers: this method was mostly merged from Item.do_modify and
+    # Draw, with modifications.
+    def do_modify(self):
         method = request.method
         if method == 'GET':
+            if isinstance(self.content, NonExistentContent):
+                return render_template('modify_show_contenttype_selection.html',
+                                       item_name=self.name,
+                                       # XXX avoid the magic string
+                                       itemtype=u'default',
+                                       contenttype_groups=CONTENTTYPE_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)
@@ -725,15 +635,358 @@
                     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,
-                               **form.extra_template_args
                               )
 
+    modify_template = 'modify.html'
+
+item_registry.register(Default._factory, u'default')
+
+
+class Ticket(Contentful):
+    """
+    Stub for ticket item class.
+    """
+
+item_registry.register(Ticket._factory, u'ticket')
+
+
+class Userprofile(Item):
+    """
+    Currently userprofile is implemented as a contenttype. This is a stub of an
+    itemtype implementation of userprofile.
+    """
+
+item_registry.register(Userprofile._factory, u'userprofile')
+
+
+class NonExistent(Item):
+    def _convert(self, doc):
+        abort(404)
+
+    def do_modify(self):
+        # First, check if the current user has the required privileges
+        if not flaskg.user.may.create(self.name):
+            abort(403)
+
+        # 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'),
+        ]
+
+        return render_template('modify_show_itemtype_selection.html',
+                               item_name=self.name,
+                               itemtypes=ITEMTYPES,
+                              )
+
+item_registry.register(NonExistent._factory, u'nonexistent')
+
+
+# This should be a separate module items/content.py. Included here to
+# faciliate codereview.
+"""
+    MoinMoin - item contents
+
+    Classes handling the content part of items (ie. minus metadata). The
+    content part is sometimes called the "data" part in other places, but is
+    always called content in this module to avoid confusion.
+
+    Each class in this module corresponds to a contenttype value.
+"""
+
+import os, re, base64
+import tarfile
+import zipfile
+import tempfile
+from StringIO import StringIO
+from array import array
+
+from flatland import Form, String
+
+from whoosh.query import Term, And
+
+from MoinMoin.forms import File
+
+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__)
+
+from flask import current_app as app
+from flask import g as flaskg
+
+from flask import request, url_for, Response, abort, escape
+
+from jinja2 import Markup
+
+from MoinMoin.i18n import _, L_
+from MoinMoin.themes import render_template
+from MoinMoin import wikiutil, config
+from MoinMoin.util.send_file import send_file
+from MoinMoin.util.interwiki import url_for_item
+from MoinMoin.storage.error import StorageError
+from MoinMoin.util.registry import RegistryBase
+from MoinMoin.constants.keys import (
+    NAME, NAME_EXACT, WIKINAME, CONTENTTYPE, SIZE, TAGS, HASH_ALGORITHM
+    )
+
+
+COLS = 80
+ROWS_DATA = 20
+
+
+# XXX Too much boilerplate in Entry implementation. Maybe use namedtuple
+# as a starting point?
+# Renamed from old RegistryItem, whole class untouched
+class RegistryContent(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
+
+        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 NotImplemented
+
+        def __repr__(self):
+            return '<{0}: {1}, prio {2} [{3!r}]>'.format(self.__class__.__name__,
+                    self.content_type,
+                    self.priority,
+                    self.factory)
+
+    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):
+        """
+        Register a factory
+
+        :param factory: Factory to register. Callable, must return an object.
+        """
+        return self._register(self.Entry(factory, content_type, priority))
+
+
+content_registry = RegistryContent()
+
+
+def conv_serialize(doc, namespaces, method='polyglot'):
+    out = array('u')
+    flaskg.clock.start('conv_serialize')
+    doc.write(out.fromunicode, namespaces=namespaces, method=method)
+    out = out.tounicode()
+    flaskg.clock.stop('conv_serialize')
+    return out
+
+
+class Content(object):
+    """
+    Base for content classes defining some helpers, agnostic about content
+    data.
+    """
+    @classmethod
+    def _factory(cls, name=u'', contenttype=None, **kw):
+        return cls(name, contenttype=unicode(contenttype), **kw)
+
+    def __init__(self, item, contenttype=None):
+        self.item = item
+        # TODO gradually remove self.contenttype as theoretically there is
+        # one-to-one correspondance of contenttype and Content type
+        # (pun intended :)
+        self.contenttype = contenttype
+
+    # XXX For backward-compatibility (so code can be moved from Item
+    # untouched), remove soon
+    @property
+    def rev(self):
+        return self.item.rev
+
+    @property
+    def name(self):
+        return self.item.name
+
+    def get_data(self):
+        return '' # TODO create a better method for binary stuff
+    data = property(fget=get_data)
+
+    # Moved from Item, untouched {{{
+    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
+
+    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
+        # TODO: Real output format
+        doc = self.internal_representation()
+        doc = self._expand_document(doc)
+        flaskg.clock.start('conv_dom_html')
+        html_conv = reg.get(type_moin_document, Type('application/x-xhtml-moin-page'))
+        doc = html_conv(doc)
+        flaskg.clock.stop('conv_dom_html')
+        rendered_data = conv_serialize(doc, {html.namespace: ''})
+        return rendered_data
+
+    def _render_data_xml(self):
+        doc = self.internal_representation()
+        return conv_serialize(doc,
+                              {moin_page.namespace: '',
+                               xlink.namespace: 'xlink',
+                               html.namespace: 'html',
+                              },
+                              'xml')
+
+    def _render_data_highlight(self):
+        # override this in child classes
+        return ''
+    # }}}
+
+    # Moved from Binary, untouched {{{
+    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.meta[NAME] for rev in revs]
+    # }}}
+
+
+class NonExistentContent(Content):
+    """Dummy Content to use with NonExistent."""
+    def do_get(self, force_attachment=False, mimetype=None):
+        abort(404)
+
+    def _convert(self, doc):
+        abort(404)
+
+
+content_registry.register(NonExistentContent._factory, Type('application/x-nonexistent'))
+
+
+class Binary(Content):
+    """ An arbitrary binary item, fallback class for every item mimetype. """
+
+    # 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)
+
+    class ModifyForm(Form):
+        template = 'modify_binary.html'
+        help = """\
+There is no help, you're doomed!
+"""
+        data_file = File.using(optional=True, label=L_('Upload file:'))
+
+        def _load(self, item):
+            pass
+
+        def _dump(self, item):
+            data_file = self['data_file'].value
+            if data_file:
+                data = data_file.stream
+                # this is likely a guess by the browser, based on the filename
+                contenttype_guessed = data_file.content_type # comes from form multipart data
+                return data, contenttype_guessed
+            else:
+                return None, None
+
     def _render_data_diff(self, oldrev, newrev):
         hash_name = HASH_ALGORITHM
         if oldrev.meta[hash_name] == newrev.meta[hash_name]:
@@ -792,7 +1045,7 @@
                          cache_timeout=10, # wiki data can change rapidly
                          add_etags=True, etag=hash, conditional=True)
 
-item_registry.register(Binary._factory, Type('*/*'))
+content_registry.register(Binary._factory, Type('*/*'))
 
 
 class RenderableBinary(Binary):
@@ -866,7 +1119,7 @@
             # 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='')
+            self.item._save(meta, data, name=self.name, action=u'SAVE', comment='')
             data.close()
             os.remove(temp_fname)
 
@@ -876,8 +1129,8 @@
     Tar items
     """
 
-item_registry.register(ApplicationXTar._factory, Type('application/x-tar'))
-item_registry.register(ApplicationXTar._factory, Type('application/x-gtar'))
+content_registry.register(ApplicationXTar._factory, Type('application/x-tar'))
+content_registry.register(ApplicationXTar._factory, Type('application/x-gtar'))
 
 
 class ZipMixin(object):
@@ -912,31 +1165,31 @@
     Zip items
     """
 
-item_registry.register(ApplicationZip._factory, Type('application/zip'))
+content_registry.register(ApplicationZip._factory, Type('application/zip'))
 
 
 class PDF(Application):
     """ PDF """
 
-item_registry.register(PDF._factory, Type('application/pdf'))
+content_registry.register(PDF._factory, Type('application/pdf'))
 
 
 class Video(Binary):
     """ Base class for video/* """
 
-item_registry.register(Video._factory, Type('video/*'))
+content_registry.register(Video._factory, Type('video/*'))
 
 
 class Audio(Binary):
     """ Base class for audio/* """
 
-item_registry.register(Audio._factory, Type('audio/*'))
+content_registry.register(Audio._factory, Type('audio/*'))
 
 
 class Image(Binary):
     """ Base class for image/* """
 
-item_registry.register(Image._factory, Type('image/*'))
+content_registry.register(Image._factory, Type('image/*'))
 
 
 class RenderableImage(RenderableBinary):
@@ -946,7 +1199,7 @@
 class SvgImage(RenderableImage):
     """ SVG images use <object> tag mechanism from RenderableBinary base class """
 
-item_registry.register(SvgImage._factory, Type('image/svg+xml'))
+content_registry.register(SvgImage._factory, Type('image/svg+xml'))
 
 
 class RenderableBitmapImage(RenderableImage):
@@ -1107,17 +1360,19 @@
     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'))
+content_registry.register(TransformableBitmapImage._factory, Type('image/png'))
+content_registry.register(TransformableBitmapImage._factory, Type('image/jpeg'))
+content_registry.register(TransformableBitmapImage._factory, Type('image/gif'))
 
 
 class Text(Binary):
     """ Base class for text/* """
-    template = "modify_text.html"
 
     class ModifyForm(Binary.ModifyForm):
+        template = 'modify_text.html'
         data_text = String.using(strip=False, optional=True).with_properties(placeholder=L_("Type your text here"))
+        rows = ROWS_DATA
+        cols = COLS
 
         def _load(self, item):
             super(Text.ModifyForm, self)._load(item)
@@ -1127,16 +1382,14 @@
             self['data_text'] = data
 
         def _dump(self, item):
-            meta, data, contenttype_guessed, comment = super(Text.ModifyForm, self)._dump(item)
+            data, contenttype_guessed = super(Text.ModifyForm, self)._dump(item)
             if data is None:
                 data = self['data_text'].value
                 data = item.data_form_to_internal(data)
                 data = item.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'
-            return meta, data, contenttype_guessed, comment
-
-        extra_template_args = {'rows_data': str(ROWS_DATA)}
+            return data, contenttype_guessed
 
     # text/plain mandates crlf - but in memory, we want lf only
     def data_internal_to_form(self, text):
@@ -1196,7 +1449,7 @@
         doc = html_conv(doc)
         return conv_serialize(doc, {html.namespace: ''})
 
-item_registry.register(Text._factory, Type('text/*'))
+content_registry.register(Text._factory, Type('text/*'))
 
 
 class MarkupItem(Text):
@@ -1209,25 +1462,25 @@
 class MoinWiki(MarkupItem):
     """ MoinMoin wiki markup """
 
-item_registry.register(MoinWiki._factory, Type('text/x.moin.wiki'))
+content_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'))
+content_registry.register(CreoleWiki._factory, Type('text/x.moin.creole'))
 
 
 class MediaWiki(MarkupItem):
     """ MediaWiki markup """
 
-item_registry.register(MediaWiki._factory, Type('text/x-mediawiki'))
+content_registry.register(MediaWiki._factory, Type('text/x-mediawiki'))
 
 
 class ReST(MarkupItem):
     """ ReStructured Text markup """
 
-item_registry.register(ReST._factory, Type('text/x-rst'))
+content_registry.register(ReST._factory, Type('text/x-rst'))
 
 
 class HTML(Text):
@@ -1240,9 +1493,10 @@
 
     Note: If raw revision data is accessed, unsafe stuff might be present!
     """
-    template = "modify_text_html.html"
+    class ModifyForm(Text.ModifyForm):
+        template = "modify_text_html.html"
 
-item_registry.register(HTML._factory, Type('text/html'))
+content_registry.register(HTML._factory, Type('text/html'))
 
 
 class DocBook(MarkupItem):
@@ -1290,7 +1544,7 @@
                          cache_timeout=10, # wiki data can change rapidly
                          add_etags=False, etag=None, conditional=True)
 
-item_registry.register(DocBook._factory, Type('application/docbook+xml'))
+content_registry.register(DocBook._factory, Type('application/docbook+xml'))
 
 
 class Draw(TarMixin, Image):
@@ -1298,33 +1552,21 @@
     Base class for *Draw that use special Java/Javascript applets to modify and store data in a tar file.
     """
     class ModifyForm(Binary.ModifyForm):
-        pass
+        # Set the workaround flag respected in modify.html
+        is_draw = True
 
     def handle_post():
         raise NotImplementedError
 
-    def do_modify(self, contenttype, template_name):
-        # XXX as the "saving" POSTs come from *Draw applets (not the form),
-        # they need to be handled specially for each applet. Besides, editing
-        # meta_text doesn't work
-        if request.method == 'POST':
-            try:
-                self.handle_post()
-            except AccessDenied:
-                abort(403)
-            else:
-                # *Draw Applets POSTs more than once, redirecting would break them
-                return "OK"
-        else:
-            return super(Draw, self).do_modify(contenttype, template_name)
-
 
 class TWikiDraw(Draw):
     """
     drawings by TWikiDraw applet. It creates three files which are stored as tar file.
     """
-    modify_help = ""
-    template = "modify_twikidraw.html"
+
+    class ModifyForm(Draw.ModifyForm):
+        template = "modify_twikidraw.html"
+        help = ""
 
     def handle_post(self):
         # called from modify UI/POST
@@ -1377,24 +1619,24 @@
         else:
             return Markup(u'<img src="{0}" alt="{1}" />'.format(png_url, title))
 
-item_registry.register(TWikiDraw._factory, Type('application/x-twikidraw'))
+content_registry.register(TWikiDraw._factory, Type('application/x-twikidraw'))
 
 
 class AnyWikiDraw(Draw):
     """
     drawings by AnyWikiDraw applet. It creates three files which are stored as tar file.
     """
-    modify_help = ""
-    template = "modify_anywikidraw.html"
 
     class ModifyForm(Draw.ModifyForm):
+        template = "modify_anywikidraw.html"
+        help = ""
         def _load(self, item):
             super(AnyWikiDraw.ModifyForm, self)._load(item)
             try:
                 drawing_exists = 'drawing.svg' in item.list_members()
             except tarfile.TarError: # item doesn't exist yet
                 drawing_exists = False
-            self.extra_template_args = {'drawing_exists': drawing_exists}
+            self.drawing_exists = drawing_exists
 
     def handle_post(self):
         # called from modify UI/POST
@@ -1446,13 +1688,15 @@
         else:
             return Markup(u'<img src="{0}" alt="{1}" />'.format(png_url, title))
 
-item_registry.register(AnyWikiDraw._factory, Type('application/x-anywikidraw'))
+content_registry.register(AnyWikiDraw._factory, Type('application/x-anywikidraw'))
 
 
 class SvgDraw(Draw):
     """ drawings by svg-edit. It creates two files (svg, png) which are stored as tar file. """
-    modify_help = ""
-    template = "modify_svg-edit.html"
+
+    class ModifyForm(Draw.ModifyForm):
+        template = "modify_svg-edit.html"
+        help = ""
 
     def handle_post(self):
         # called from modify UI/POST
@@ -1476,4 +1720,4 @@
         png_url = url_for('frontend.get_item', item_name=item_name, member='drawing.png', rev=self.rev.revid)
         return Markup(u'<img src="{0}" alt="{1}" />'.format(png_url, drawing_url))
 
-item_registry.register(SvgDraw._factory, Type('application/x-svgdraw'))
+content_registry.register(SvgDraw._factory, Type('application/x-svgdraw'))
--- a/MoinMoin/items/_tests/test_Item.py	Thu Jul 26 22:52:17 2012 +0200
+++ b/MoinMoin/items/_tests/test_Item.py	Sat Jul 28 01:22:44 2012 +0800
@@ -5,7 +5,9 @@
     MoinMoin - MoinMoin.items Tests
 """
 
-# TODO: split the tests into multiple files after the items/__init__.py is split.
+# TODO: Separate Content tests from here after separating Content from
+# items/__init__.py. Also split tests for Content subclasses after the
+# subclasses themselves are split
 
 import pytest
 
@@ -25,7 +27,7 @@
     def testNonExistent(self):
         item = Item.create(u'DoesNotExist')
         assert isinstance(item, NonExistent)
-        meta, data = item.meta, item.data
+        meta, data = item.meta, item.content.data
         assert meta == {CONTENTTYPE: u'application/x-nonexistent'}
         assert data == ''
 
@@ -38,7 +40,7 @@
                 (u'image/png', TransformableBitmapImage),
             ]:
             item = Item.create(u'foo', contenttype=contenttype)
-            assert isinstance(item, ExpectedClass)
+            assert isinstance(item.content, ExpectedClass)
 
     def testCRUD(self):
         name = u'NewItem'
@@ -52,7 +54,7 @@
         item._save(meta, data, comment=comment)
         # check save result
         item = Item.create(name)
-        saved_meta, saved_data = item.meta, item.data
+        saved_meta, saved_data = item.meta, item.content.data
         assert saved_meta[CONTENTTYPE] == contenttype
         assert saved_meta[COMMENT] == comment
         assert saved_data == data
@@ -63,7 +65,7 @@
         item._save(meta, data, comment=comment)
         # check save result
         item = Item.create(name)
-        saved_meta, saved_data = dict(item.meta), item.data
+        saved_meta, saved_data = dict(item.meta), item.content.data
         assert saved_meta[CONTENTTYPE] == contenttype
         assert saved_meta[COMMENT] == comment
         assert saved_data == data
@@ -74,7 +76,7 @@
         item._save(meta, data, comment=comment)
         # check save result
         item = Item.create(name)
-        saved_meta, saved_data = dict(item.meta), item.data
+        saved_meta, saved_data = dict(item.meta), item.content.data
         assert saved_meta[CONTENTTYPE] == contenttype
         assert saved_meta[COMMENT] == comment
         assert saved_data == data
@@ -206,7 +208,7 @@
         assert item.name == u'Test_new_Item'
         assert item.meta['name_old'] == u'Test_Item'
         assert item.meta['comment'] == u'renamed'
-        assert item.data == u'test_data'
+        assert item.content.data == u'test_data'
 
     def test_rename_recursion(self):
         update_item(u'Page', {CONTENTTYPE: u'text/x.moin.wiki'}, u'Page 1')
@@ -232,19 +234,19 @@
         assert item.name == u'Renamed_Page'
         assert item.meta['name_old'] == u'Page'
         assert item.meta['comment'] == u'renamed'
-        assert item.data == u'Page 1'
+        assert item.content.data == u'Page 1'
 
         item = Item.create(u'Renamed_Page/Child')
         assert item.name == u'Renamed_Page/Child'
         assert item.meta['name_old'] == u'Page/Child'
         assert item.meta['comment'] == u'renamed'
-        assert item.data == u'this is child'
+        assert item.content.data == u'this is child'
 
         item = Item.create(u'Renamed_Page/Child/Another')
         assert item.name == u'Renamed_Page/Child/Another'
         assert item.meta['name_old'] == u'Page/Child/Another'
         assert item.meta['comment'] == u'renamed'
-        assert item.data == u'another child'
+        assert item.content.data == u'another child'
 
     def test_delete(self):
         name = u'Test_Item2'
@@ -298,38 +300,44 @@
         with pytest.raises(KeyError):
             item.meta['test_key']
         assert item.meta['another_test_key'] == another_meta['another_test_key']
-        assert item.data == another_data
+        assert item.content.data == another_data
 
 
-class TestBinary(object):
-    """ Test for arbitrary binary items """
+# TODO: When testing Content and derived classes, instead of creating a full
+# item with Item.create and test against item.content, implement
+# Content.create and test for returned Content instance directly. Item.create
+# will still be needed when it is required to create new items in the storage
+# to set up the test environment, but its use should be restricted to that
+# only.
+class TestContent(object):
+    """ Test for arbitrary content """
 
     def test_get_templates(self):
         item_name1 = u'Template_Item1'
-        item1 = Binary.create(item_name1)
+        item1 = Item.create(item_name1)
         contenttype1 = u'text/plain'
         meta = {CONTENTTYPE: contenttype1, 'tags': ['template']}
         item1._save(meta)
-        item1 = Binary.create(item_name1)
+        item1 = Item.create(item_name1)
 
         item_name2 = u'Template_Item2'
-        item2 = Binary.create(item_name2)
+        item2 = Item.create(item_name2)
         contenttype1 = u'text/plain'
         meta = {CONTENTTYPE: contenttype1, 'tags': ['template']}
         item2._save(meta)
-        item2 = Binary.create(item_name2)
+        item2 = Item.create(item_name2)
 
         item_name3 = u'Template_Item3'
-        item3 = Binary.create(item_name3)
+        item3 = Item.create(item_name3)
         contenttype2 = u'image/png'
         meta = {CONTENTTYPE: contenttype2, 'tags': ['template']}
         item3._save(meta)
-        item3 = Binary.create(item_name3)
+        item3 = Item.create(item_name3)
         # two items of same content type
-        result1 = item1.get_templates(contenttype1)
+        result1 = item1.content.get_templates(contenttype1)
         assert result1 == [item_name1, item_name2]
         # third of different content type
-        result2 = item1.get_templates(contenttype2)
+        result2 = item1.content.get_templates(contenttype2)
         assert result2 == [item_name3]
 
 class TestTarItems(object):
@@ -342,74 +350,74 @@
         creates a container and tests the content saved to the container
         """
         item_name = u'ContainerItem1'
-        item = Item.create(item_name, contenttype=u'application/x-tar')
+        item = Item.create(item_name, itemtype=u'default', contenttype=u'application/x-tar')
         filecontent = 'abcdefghij'
         content_length = len(filecontent)
         members = set(['example1.txt', 'example2.txt'])
-        item.put_member('example1.txt', filecontent, content_length, expected_members=members)
-        item.put_member('example2.txt', filecontent, content_length, expected_members=members)
+        item.content.put_member('example1.txt', filecontent, content_length, expected_members=members)
+        item.content.put_member('example2.txt', filecontent, content_length, expected_members=members)
 
-        item = Item.create(item_name, contenttype=u'application/x-tar')
-        tf_names = set(item.list_members())
+        item = Item.create(item_name, itemtype=u'default', contenttype=u'application/x-tar')
+        tf_names = set(item.content.list_members())
         assert tf_names == members
-        assert item.get_member('example1.txt').read() == filecontent
+        assert item.content.get_member('example1.txt').read() == filecontent
 
     def testRevisionUpdate(self):
         """
         creates two revisions of a container item
         """
         item_name = u'ContainerItem2'
-        item = Item.create(item_name, contenttype=u'application/x-tar')
+        item = Item.create(item_name, itemtype=u'default', contenttype=u'application/x-tar')
         filecontent = 'abcdefghij'
         content_length = len(filecontent)
         members = set(['example1.txt'])
-        item.put_member('example1.txt', filecontent, content_length, expected_members=members)
+        item.content.put_member('example1.txt', filecontent, content_length, expected_members=members)
         filecontent = 'AAAABBBB'
         content_length = len(filecontent)
-        item.put_member('example1.txt', filecontent, content_length, expected_members=members)
+        item.content.put_member('example1.txt', filecontent, content_length, expected_members=members)
 
         item = Item.create(item_name, contenttype=u'application/x-tar')
-        assert item.get_member('example1.txt').read() == filecontent
+        assert item.content.get_member('example1.txt').read() == filecontent
 
 class TestZipMixin(object):
     """ Test for zip-like items """
 
     def test_put_member(self):
         item_name = u'Zip_file'
-        item = Item.create(item_name, contenttype='application/zip')
+        item = Item.create(item_name, itemtype=u'default', contenttype='application/zip')
         filecontent = 'test_contents'
         content_length = len(filecontent)
         members = set(['example1.txt', 'example2.txt'])
         with pytest.raises(NotImplementedError):
-            item.put_member('example1.txt', filecontent, content_length, expected_members=members)
+            item.content.put_member('example1.txt', filecontent, content_length, expected_members=members)
 
 class TestTransformableBitmapImage(object):
 
     def test__transform(self):
         item_name = u'image_Item'
-        item = Binary.create(item_name)
+        item = Item.create(item_name)
         contenttype = u'image/jpeg'
         meta = {CONTENTTYPE: contenttype}
         item._save(meta)
-        item = Binary.create(item_name)
+        item = Item.create(item_name)
         try:
             from PIL import Image as PILImage
             with pytest.raises(ValueError):
-                result = TransformableBitmapImage._transform(item, 'text/plain')
+                result = TransformableBitmapImage._transform(item.content, 'text/plain')
         except ImportError:
-            result = TransformableBitmapImage._transform(item, contenttype)
+            result = TransformableBitmapImage._transform(item.content, contenttype)
             assert result == (u'image/jpeg', '')
 
     def test__render_data_diff(self):
         item_name = u'image_Item'
-        item = Binary.create(item_name)
+        item = Item.create(item_name)
         contenttype = u'image/jpeg'
         meta = {CONTENTTYPE: contenttype}
         item._save(meta)
-        item1 = Binary.create(item_name)
+        item1 = Item.create(item_name)
         try:
             from PIL import Image as PILImage
-            result = Markup(TransformableBitmapImage._render_data_diff(item1, item.rev, item1.rev))
+            result = Markup(TransformableBitmapImage._render_data_diff(item1.content, item.rev, item1.rev))
             # On Werkzeug 0.8.2+, urls with '+' are automatically encoded to '%2B'
             # The assert statement works with both older and newer versions of Werkzeug
             # Probably not an intentional change on the werkzeug side, see issue:
@@ -422,18 +430,18 @@
 
     def test__render_data_diff_text(self):
         item_name = u'image_Item'
-        item = Binary.create(item_name)
+        item = Item.create(item_name)
         contenttype = u'image/jpeg'
         meta = {CONTENTTYPE: contenttype}
         item._save(meta)
-        item1 = Binary.create(item_name)
+        item1 = Item.create(item_name)
         data = 'test_data'
         comment = u'next revision'
         item1._save(meta, data, comment=comment)
-        item2 = Binary.create(item_name)
+        item2 = Item.create(item_name)
         try:
             from PIL import Image as PILImage
-            result = TransformableBitmapImage._render_data_diff_text(item1, item1.rev, item2.rev)
+            result = TransformableBitmapImage._render_data_diff_text(item1.content, item1.rev, item2.rev)
             expected = u'The items have different data.'
             assert result == expected
         except ImportError:
@@ -443,24 +451,24 @@
 
     def test_data_conversion(self):
         item_name = u'Text_Item'
-        item = Text.create(item_name, u'text/plain')
+        item = Item.create(item_name, u'default', u'text/plain')
         test_text = u'This \n is \n a \n Test'
         # test for data_internal_to_form
-        result = Text.data_internal_to_form(item, test_text)
+        result = Text.data_internal_to_form(item.content, test_text)
         expected = u'This \r\n is \r\n a \r\n Test'
         assert result == expected
         # test for data_form_to_internal
         test_form = u'This \r\n is \r\n a \r\n Test'
-        result = Text.data_form_to_internal(item, test_text)
+        result = Text.data_form_to_internal(item.content, test_text)
         expected = test_text
         assert result == expected
         # test for data_internal_to_storage
-        result = Text.data_internal_to_storage(item, test_text)
+        result = Text.data_internal_to_storage(item.content, test_text)
         expected = 'This \r\n is \r\n a \r\n Test'
         assert result == expected
         # test for data_storage_to_internal
         data_storage = 'This \r\n is \r\n a \r\n Test'
-        result = Text.data_storage_to_internal(item, data_storage)
+        result = Text.data_storage_to_internal(item.content, data_storage)
         expected = test_text
         assert result == expected
 
@@ -469,23 +477,23 @@
         empty_html = u'<span></span>'
         html = u'<span>\ud55c</span>'
         meta = {CONTENTTYPE: u'text/html;charset=utf-8'}
-        item = Text.create(item_name)
+        item = Item.create(item_name)
         item._save(meta, empty_html)
-        item = Text.create(item_name)
+        item = Item.create(item_name)
         # Unicode test, html escaping
         rev1 = update_item(item_name, meta, html)
         rev2 = update_item(item_name, {}, u'     ')
-        result = Text._render_data_diff(item, rev1, rev2)
+        result = Text._render_data_diff(item.content, rev1, rev2)
         assert escape(html) in result
         # Unicode test, whitespace
         rev1 = update_item(item_name, {}, u'\n\n')
         rev2 = update_item(item_name, {}, u'\n     \n')
-        result = Text._render_data_diff(item, rev1, rev2)
+        result = Text._render_data_diff(item.content, rev1, rev2)
         assert '<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span>' in result
         # If fairly similar diffs are correctly spanned or not, also check indent
         rev1 = update_item(item_name, {}, u'One Two Three Four\nSix\n\ud55c')
         rev2 = update_item(item_name, {}, u'Two Three Seven Four\nSix\n\ud55c')
-        result = Text._render_data_diff(item, rev1, rev2)
+        result = Text._render_data_diff(item.content, rev1, rev2)
         assert '<span>One </span>Two Three Four' in result
         assert 'Two Three <span>Seven </span>Four' in result
         # Check for diff_html.diff return types
@@ -493,33 +501,33 @@
 
     def test__render_data_diff_text(self):
         item_name = u'Text_Item'
-        item = Text.create(item_name)
+        item = Item.create(item_name)
         contenttype = u'text/plain'
         meta = {CONTENTTYPE: contenttype}
         item._save(meta)
-        item1 = Text.create(item_name)
+        item1 = Item.create(item_name)
         data = 'test_data'
         comment = u'next revision'
         item1._save(meta, data, comment=comment)
-        item2 = Text.create(item_name)
-        result = Text._render_data_diff_text(item1, item1.rev, item2.rev)
+        item2 = Item.create(item_name)
+        result = Text._render_data_diff_text(item1.content, item1.rev, item2.rev)
         expected = u'- \n+ test_data'
         assert result == expected
-        assert item2.data == ''
+        assert item2.content.data == ''
 
     def test__render_data_highlight(self):
         item_name = u'Text_Item'
-        item = Text.create(item_name)
+        item = Item.create(item_name)
         contenttype = u'text/plain'
         meta = {CONTENTTYPE: contenttype}
         item._save(meta)
-        item1 = Text.create(item_name)
+        item1 = Item.create(item_name)
         data = 'test_data\nnext line'
         comment = u'next revision'
         item1._save(meta, data, comment=comment)
-        item2 = Text.create(item_name)
-        result = Text._render_data_highlight(item2)
+        item2 = Item.create(item_name)
+        result = Text._render_data_highlight(item2.content)
         assert u'<pre class="highlight">test_data\n' in result
-        assert item2.data == ''
+        assert item2.content.data == ''
 
 coverage_modules = ['MoinMoin.items']
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/MoinMoin/templates/modify.html	Sat Jul 28 01:22:44 2012 +0800
@@ -0,0 +1,65 @@
+{% import "forms.html" as forms %}
+{% import "utils.html" as utils %}
+{% extends theme("layout.html") %}
+
+{# Import macros data_editor and extra_head from content_form's template.
+   extra_head is optional, so instead of a simple "import from" we need to do
+   this manually #}
+{% import form['content_form'].template as content_template %}
+{% set extra_head = content_template.extra_head %}
+{% set data_editor = content_template.data_editor %}
+
+{% set title = _("Modifying %(item_name)s", item_name=item_name) %}
+
+{% block head %}
+    {{ super() }}
+    {% if extra_head %}
+        {{ extra_head() }}
+    {% endif %}
+{% endblock %}
+
+{% block subitem_navigation %}
+    {% call(fullname, shortname, contenttype, has_children) utils.render_subitem_navigation(item_name, True) %}
+        {% set shortname = shortname|json_dumps %}
+        {% set fullname = fullname|json_dumps %}
+        <button class="link-action" onclick='linkSubitem({{ shortname }}, {{ fullname }})'
+            title="{{ _('Link to Subitem') }}">{{ _('Link') }}</button>
+        <button class="transclude-action"
+            onclick='transcludeSubitem({{ shortname }}, {{ fullname }})'
+            title="{{ _('Transclude Subitem') }}">{{ _('Transclude') }}</button>
+    {% endcall %}
+{% endblock %}
+
+
+{% block content %}
+<h1>{{ title }}</h1>
+<div class="moin-form">
+    {{ gen.form.open(form, method='post', enctype='multipart/form-data') }}
+    {{ forms.render_errors(form) }}
+    {#
+       Workaround:
+       For *Draw content, hide form['submit'] and form['comment'], since *Draw
+       POSTs originate from their respective applets.
+    #}
+    {% if not form['content_form'].is_draw %}
+        {{ forms.render(form['submit']) }}
+        <dl>
+            {{ forms.render_textcha(gen, form) }}
+            {{ forms.render(form['comment']) }}
+        </dl>
+    {% endif %}
+    {{ data_editor(form['content_form']) }}
+    {% if form['content_form'].help %}
+        <pre id="moin-editor-help">{{ form['content_form'].help }}</pre>
+    {% endif %}
+    <dl>
+        {{ forms.render(form['meta_text']) }}
+    </dl>
+    {#
+    {{ gen.textarea(form['meta_text'], lang='en', dir='ltr', rows=rows_meta, cols=cols) }}
+    <br />
+    {{ forms.render_errors(form['meta_text']) }}
+    #}
+    {{ gen.form.close() }}
+</div>
+{% endblock %}
--- a/MoinMoin/templates/modify_anywikidraw.html	Thu Jul 26 22:52:17 2012 +0200
+++ b/MoinMoin/templates/modify_anywikidraw.html	Sat Jul 28 01:22:44 2012 +0800
@@ -1,11 +1,10 @@
-{% extends "modify_applet.html" %}
-{% block data_editor %}
+{% macro data_editor(form) %}
 <p>
 <applet code="org.anywikidraw.moinmoin.MoinMoinDrawingApplet.class" codebase="."
         archive="{{ url_for('serve.files', name='anywikidraw', filename='AnyWikiDrawForMoinMoin.jar') }}"
         width="800" height="620">
 <param name="DrawingName" value="drawing.svg" />
-{% if drawing_exists %}
+{% if form.drawing_exists %}
 <param name="DrawingURL" value="{{ url_for('frontend.get_item', item_name=item_name, member='drawing.svg') }}" />
 {% endif %}
 <param name="PageURL" value="{{ url_for('frontend.show_item', item_name=item_name) }}" />
@@ -19,4 +18,4 @@
 </applet>
 </p>
 <br />
-{% endblock %}
+{% endmacro %}
--- a/MoinMoin/templates/modify_applet.html	Thu Jul 26 22:52:17 2012 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,22 +0,0 @@
-{% import "forms.html" as forms %}
-{% extends theme("layout.html") %}
-
-{% set title = _("Modifying %(item_name)s", item_name=item_name) %}
-
-{% block content %}
-<h1>{{ title }}</h1>
-<div class="moin-form">
-    {{ gen.form.open(form, method='post', enctype='multipart/form-data') }}
-    {{ forms.render_errors(form) }}
-    {% block extra_form %}{% endblock %}
-    {% block data_editor %}{% endblock %}
-    <dl>
-        {{ forms.render(form['data_file']) }}
-    </dl>
-    <pre id="moin-editor-help">{{ help }}</pre>
-    {{ gen.textarea(form['meta_text'], lang='en', dir='ltr', rows=rows_meta, cols=cols) }}
-    <br />
-    {{ forms.render_errors(form['meta_text']) }}
-    {{ gen.form.close() }}
-</div>
-{% endblock %}
--- a/MoinMoin/templates/modify_binary.html	Thu Jul 26 22:52:17 2012 +0200
+++ b/MoinMoin/templates/modify_binary.html	Sat Jul 28 01:22:44 2012 +0800
@@ -1,9 +1,7 @@
 {% import "forms.html" as forms %}
-{% extends "modify_applet.html" %}
-{% block extra_form %}
-{{ forms.render(form['submit']) }}
-<dl>
-    {{ forms.render_textcha(gen, form) }}
-    {{ forms.render(form['comment']) }}
-</dl>
-{% endblock %}
+
+{% macro data_editor(form) %}
+    <dl>
+        {{ forms.render(form['data_file']) }}
+    </dl>
+{% endmacro %}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/MoinMoin/templates/modify_show_contenttype_selection.html	Sat Jul 28 01:22:44 2012 +0800
@@ -0,0 +1,22 @@
+{% extends theme("layout.html") %}
+{% block content %}
+<h1>{{ _("Create new item?") }}</h1>
+<p>
+{# XXX should show itemtype's display name #}
+{{ _("Please select the contenttype of the new %(itemtype)s item.", itemtype=itemtype) }}
+</p>
+<table class="zebra">
+    {% for gname, contenttypes in contenttype_groups %}
+    <tr>
+        <th>{{ gname }}</th>
+    </tr>
+    <tr>
+        <td>
+        {% for ctname, ctlabel in contenttypes %}
+            <a href="{{ url_for('frontend.modify_item', item_name=item_name, itemtype=itemtype, contenttype=ctname) }}">{{ ctlabel }}</a> -
+        {% endfor %}
+        </td>
+    </tr>
+    {% endfor %}
+</table>
+{% endblock %}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/MoinMoin/templates/modify_show_itemtype_selection.html	Sat Jul 28 01:22:44 2012 +0800
@@ -0,0 +1,16 @@
+{% extends theme("layout.html") %}
+{% block content %}
+<h1>{{ _("Create new item?") }}</h1>
+<p>
+{{ _("This item does not exist (yet), but you can try creating it now. Please select the type of the item you want to create.") }}
+</p>
+<table class="zebra">
+    {% for itname, itlabel, itdesc in itemtypes %}
+    <tr>
+        <td>
+            <a href="{{ url_for('frontend.modify_item', item_name=item_name, itemtype=itname) }}">{{ itlabel }}</a> - {{ itdesc }}
+        </td>
+    </tr>
+    {% endfor %}
+</table>
+{% endblock %}
--- a/MoinMoin/templates/modify_show_template_selection.html	Thu Jul 26 22:52:17 2012 +0200
+++ b/MoinMoin/templates/modify_show_template_selection.html	Sat Jul 28 01:22:44 2012 +0800
@@ -3,13 +3,13 @@
 <h1>{{ _("Create new item?") }}</h1>
 <p>
 {{ _("You can either <a href='%(modifyhref)s'>create the item from scratch</a> or select a template.",
-modifyhref=url_for('frontend.modify_item', item_name=item_name, contenttype=contenttype, template='') ) }}
+modifyhref=url_for('frontend.modify_item', item_name=item_name, itemtype=itemtype, contenttype=contenttype, template='') ) }}
 </p>
 <h2>{{ _("Available template items") }}</h2>
 <ul>
     {% for template in templates %}
     <li>
-    <a href="{{ url_for('frontend.modify_item', item_name=item_name, contenttype=contenttype, template=template) }}">{{ template }}</a>
+    <a href="{{ url_for('frontend.modify_item', item_name=item_name, itemtype=itemtype, contenttype=contenttype, template=template) }}">{{ template }}</a>
     </li>
     {% endfor %}
 </ul>
--- a/MoinMoin/templates/modify_show_type_selection.html	Thu Jul 26 22:52:17 2012 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,21 +0,0 @@
-{% extends theme("layout.html") %}
-{% block content %}
-<h1>{{ _("Create new item?") }}</h1>
-<p>
-{{ _("This item does not exist (yet), but you can try creating it now. Please select the type of the item you want to create.") }}
-</p>
-<table class="zebra">
-    {% for gname, contenttypes in contenttype_groups %}
-    <tr>
-        <th>{{ gname }}</th>
-    </tr>
-    <tr>
-        <td>
-        {% for ctname, ctlabel in contenttypes %}
-            <a href="{{ url_for('frontend.modify_item', item_name=item_name, contenttype=ctname) }}">{{ ctlabel }}</a> -
-        {% endfor %}
-        </td>
-    </tr>
-    {% endfor %}
-</table>
-{% endblock %}
--- a/MoinMoin/templates/modify_svg-edit.html	Thu Jul 26 22:52:17 2012 +0200
+++ b/MoinMoin/templates/modify_svg-edit.html	Sat Jul 28 01:22:44 2012 +0800
@@ -1,8 +1,7 @@
-{% extends "modify_applet.html" %}
-{% block data_editor %}
+{% macro data_editor(form) %}
 <p>
 <object data="{{ url_for('serve.files', name='svgedit_moin', filename='editor/svg-editor.html') }}?paramurl={{ url_for('frontend.get_item', item_name=item_name, member='drawing.svg') }}" width="100%" height="600">
 </object>
 </p>
 <br />
-{% endblock %}
+{% endmacro %}
--- a/MoinMoin/templates/modify_text.html	Thu Jul 26 22:52:17 2012 +0200
+++ b/MoinMoin/templates/modify_text.html	Sat Jul 28 01:22:44 2012 +0800
@@ -1,23 +1,11 @@
-{% extends "modify_binary.html" %}
-{% import "utils.html" as utils %}
+{% from "modify_binary.html" import data_editor as base_editor %}
 
-{% block subitem_navigation %}
-        {% call(fullname, shortname, contenttype, has_children) utils.render_subitem_navigation(item_name, True) %}
-            {% set shortname = shortname|json_dumps %}
-            {% set fullname = fullname|json_dumps %}
-            <button class="link-action" onclick='linkSubitem({{ shortname }}, {{ fullname }})'
-                title="{{ _('Link to Subitem') }}">{{ _('Link') }}</button>
-            <button class="transclude-action"
-                onclick='transcludeSubitem({{ shortname }}, {{ fullname }})'
-                title="{{ _('Transclude Subitem') }}">{{ _('Transclude') }}</button>
-        {% endcall %}
-{% endblock %}
-
-{% block data_editor %}
+{% macro data_editor(form) %}
+{{ base_editor(form) }}
 {% if direction %}
-    {{ gen.textarea(form['data_text'], lang=lang, dir=direction, rows=rows_data, cols=cols) }}
+    {{ gen.textarea(form['data_text'], lang=lang, dir=direction, rows=form.rows|string, cols=form.cols|string) }}
 {% else %}
-    {{ gen.textarea(form['data_text'], lang=lang, rows=rows_data, cols=cols) }}
+    {{ gen.textarea(form['data_text'], lang=lang, rows=form.rows|string, cols=form.cols|string) }}
 {% endif %}
 <br />
-{% endblock %}
+{% endmacro %}
--- a/MoinMoin/templates/modify_text_html.html	Thu Jul 26 22:52:17 2012 +0200
+++ b/MoinMoin/templates/modify_text_html.html	Sat Jul 28 01:22:44 2012 +0800
@@ -1,13 +1,10 @@
-{% extends "modify_binary.html" %}
+{% from 'modify_text.html' import data_editor as base_editor %}
 
-{% block head %}
-{{ super() }}
+{% macro extra_head() %}
 <script type="text/javascript" src="{{ url_for('serve.files', name='ckeditor', filename='ckeditor.js') }}"></script>
 <link rel="stylesheet" href="{{ url_for('serve.files', name='ckeditor', filename='contents.css') }}" />
-{% endblock %}
+{% endmacro %}
 
-{% block data_editor %}
-<p>
-{{ gen.textarea(form['data_text'], class='ckeditor', lang=lang, dir=direction, rows=rows_data, cols=cols) }}
-</p>
-{% endblock %}
+{% macro data_editor(form) %}
+{{ gen.textarea(form['data_text'], class='ckeditor', lang=lang, dir=direction, rows=form.rows|string, cols=form.cols|string) }}
+{% endmacro %}
--- a/MoinMoin/templates/modify_twikidraw.html	Thu Jul 26 22:52:17 2012 +0200
+++ b/MoinMoin/templates/modify_twikidraw.html	Sat Jul 28 01:22:44 2012 +0800
@@ -1,5 +1,4 @@
-{% extends "modify_applet.html" %}
-{% block data_editor %}
+{% macro data_editor(form) %}
 <p>
 <applet code="CH.ifa.draw.twiki.TWikiDraw.class"
         archive="{{ url_for('serve.files', name='twikidraw_moin', filename='twikidraw_moin.jar') }}"
@@ -14,4 +13,4 @@
 </applet>
 </p>
 <br />
-{% endblock %}
+{% endmacro %}