changeset 455:3ab8882c6da1

branch merged with main repo.
author Akash Sinha <akash2607@gmail.com>
date Tue, 31 May 2011 00:59:30 +0530
parents b07dfe4ceb8d (current diff) 727e03c59dbf (diff)
children 6177be92aa33
files MoinMoin/apps/frontend/views.py MoinMoin/themes/modernized/static/css/common.css MoinMoin/themes/modernized/static/img/white_clouds.png
diffstat 15 files changed, 303 insertions(+), 195 deletions(-) [+]
line wrap: on
line diff
--- a/MoinMoin/apps/frontend/views.py	Tue May 31 00:58:29 2011 +0530
+++ b/MoinMoin/apps/frontend/views.py	Tue May 31 00:59:30 2011 +0530
@@ -245,12 +245,13 @@
         abort(403)
     return converted_item._convert(item.internal_representation())
 
+
 @frontend.route('/+modify/<itemname:item_name>', methods=['GET', 'POST'])
 def modify_item(item_name):
     """Modify the wiki item item_name.
 
     On GET, displays a form.
-    On POST, saves the new page (unless there's an error in input, or cancelled).
+    On POST, saves the new page (unless there's an error in input).
     After successful POST, redirects to the page.
     """
     contenttype = request.values.get('contenttype')
@@ -259,42 +260,9 @@
         item = Item.create(item_name, contenttype=contenttype)
     except AccessDeniedError:
         abort(403)
-    if request.method == 'GET':
-        if not flaskg.user.may.write(item_name):
-            abort(403)
-        content = item.do_modify(template_name)
-        return content
-    elif request.method == 'POST':
-        cancelled = 'button_cancel' in request.form
-        if not cancelled:
-            form = TextChaizedForm.from_flat(request.form)
-            TextCha(form).amend_form()
-            valid = form.validate()
-            if not valid:
-                data_text = request.values.get('data_text')
-                meta_text = item.meta_dict_to_text(item.meta)
-                comment = request.values.get('comment')
-                return render_template(item.template,
-                                       item_name=item_name,
-                                       gen=make_generator(),
-                                       form=form,
-                                       data_text=data_text,
-                                       meta_text=meta_text,
-                                       comment=comment,
-                                       cols=COLS,
-                                       rows_data=ROWS_DATA,
-                                       rows_meta=ROWS_META,
-                                      )
-            try:
-                item.modify()
-                item_modified.send(app._get_current_object(),
-                                   item_name=item_name)
-                if contenttype in ('application/x-twikidraw', 'application/x-anywikidraw', 'application/x-svgdraw'):
-                    # TWikiDraw/AnyWikiDraw/SvgDraw POST more than once, redirecting would break them
-                    return "OK"
-            except AccessDeniedError:
-                abort(403)
-        return redirect(url_for('frontend.show_item', item_name=item_name))
+    if not flaskg.user.may.write(item_name):
+        abort(403)
+    return item.do_modify(contenttype, template_name)
 
 
 class CommentForm(TextChaizedForm):
@@ -332,8 +300,7 @@
     elif request.method == 'POST':
         form = RevertItemForm.from_flat(request.form)
         TextCha(form).amend_form()
-        valid = form.validate()
-        if valid:
+        if form.validate():
             item.revert()
             return redirect(url_for('frontend.show_item', item_name=item_name))
     return render_template(item.revert_template,
@@ -357,8 +324,7 @@
     elif request.method == 'POST':
         form = CopyItemForm.from_flat(request.form)
         TextCha(form).amend_form()
-        valid = form.validate()
-        if valid:
+        if form.validate():
             target = form['target'].value
             comment = form['comment'].value
             item.copy(target, comment)
@@ -383,8 +349,7 @@
     elif request.method == 'POST':
         form = RenameItemForm.from_flat(request.form)
         TextCha(form).amend_form()
-        valid = form.validate()
-        if valid:
+        if form.validate():
             target = form['target'].value
             comment = form['comment'].value
             item.rename(target, comment)
@@ -408,8 +373,7 @@
     elif request.method == 'POST':
         form = DeleteItemForm.from_flat(request.form)
         TextCha(form).amend_form()
-        valid = form.validate()
-        if valid:
+        if form.validate():
             comment = form['comment'].value
             item.delete(comment)
             return redirect(url_for('frontend.show_item', item_name=item_name))
@@ -440,8 +404,7 @@
     elif request.method == 'POST':
         form = DestroyItemForm.from_flat(request.form)
         TextCha(form).amend_form()
-        valid = form.validate()
-        if valid:
+        if form.validate():
             comment = form['comment'].value
             item.destroy(comment=comment, destroy_item=destroy_item)
             return redirect(url_for('frontend.show_item', item_name=item_name))
@@ -826,18 +789,17 @@
             form = OpenIDForm.from_flat(request.form)
             TextCha(form).amend_form()
 
-            valid = form.validate()
-            if valid:
-                    msg = user.create_user(username=form['username'].value,
-                                           password=form['password1'].value,
-                                           email=form['email'].value,
-                                           openid=form['openid'].value,
-                                          )
-                    if msg:
-                        flash(msg, "error")
-                    else:
-                        flash(_('Account created, please log in now.'), "info")
-                        return redirect(url_for('frontend.show_root'))
+            if form.validate():
+                msg = user.create_user(username=form['username'].value,
+                                       password=form['password1'].value,
+                                       email=form['email'].value,
+                                       openid=form['openid'].value,
+                                      )
+                if msg:
+                    flash(msg, "error")
+                else:
+                    flash(_('Account created, please log in now.'), "info")
+                    return redirect(url_for('frontend.show_root'))
 
     else:
         # not openid registration and no MoinAuth
@@ -853,8 +815,7 @@
             form = RegistrationForm.from_flat(request.form)
             TextCha(form).amend_form()
 
-            valid = form.validate()
-            if valid:
+            if form.validate():
                 msg = user.create_user(username=form['username'].value,
                                        password=form['password1'].value,
                                        email=form['email'].value,
@@ -910,8 +871,7 @@
         form = PasswordLostForm.from_defaults()
     elif request.method == 'POST':
         form = PasswordLostForm.from_flat(request.form)
-        valid = form.validate()
-        if valid:
+        if form.validate():
             u = None
             username = form['username'].value
             if username:
@@ -974,8 +934,7 @@
         form.update(request.values)
     elif request.method == 'POST':
         form = PasswordRecoveryForm.from_flat(request.form)
-        valid = form.validate()
-        if valid:
+        if form.validate():
             u = user.User(user.getUserId(form['username'].value))
             if u and u.valid and u.apply_recovery_token(form['token'].value, form['password1'].value):
                 flash(_("Your password has been changed, you can log in now."), "info")
@@ -1048,8 +1007,7 @@
                 flash(hint, "info")
     elif request.method == 'POST':
         form = LoginForm.from_flat(request.form)
-        valid = form.validate()
-        if valid:
+        if form.validate():
             # we have a logged-in, valid user
             return redirect(url_for('frontend.show_root'))
         # flash the error messages (if any)
@@ -1184,8 +1142,7 @@
         form['submit'].set_default() # XXX from_object() kills all values
     elif request.method == 'POST':
         form = FormClass.from_flat(request.form)
-        valid = form.validate()
-        if valid:
+        if form.validate():
             # successfully modified everything
             success = True
             if part == 'password':
--- a/MoinMoin/converter/_tests/test_moinwiki19_in.py	Tue May 31 00:58:29 2011 +0530
+++ b/MoinMoin/converter/_tests/test_moinwiki19_in.py	Tue May 31 00:59:30 2011 +0530
@@ -27,6 +27,13 @@
                 '<page><body><p><a xlink:href="wiki://Self/FrontPage">FrontPage</a></p></body></page>'),
             (u'http://moinmo.in/',
                 '<page><body><p><a xlink:href="http://moinmo.in/">http://moinmo.in/</a></p></body></page>'),
+            # email tests
+            (u'mailto:foo@bar.baz',
+                '<page><body><p><a xlink:href="mailto:foo@bar.baz">mailto:foo@bar.baz</a></p></body></page>'),
+            (u'foo@bar.baz',
+                '<page><body><p><a xlink:href="mailto:foo@bar.baz">foo@bar.baz</a></p></body></page>'),
+            (u'foo@bar', # 1.9 requires domain
+                '<page><body><p>foo@bar</p></body></page>'),
         ]
         for i in data:
             yield (self.do, ) + i
--- a/MoinMoin/converter/_tests/test_moinwiki_in.py	Tue May 31 00:58:29 2011 +0530
+++ b/MoinMoin/converter/_tests/test_moinwiki_in.py	Tue May 31 00:59:30 2011 +0530
@@ -274,6 +274,35 @@
         ]
         for i in data:
             yield (self.do, ) + i
+    def test_interwiki(self):
+        data = [
+            (u'[[MoinMoin:RecentChanges]]',
+                '<page><body><p><a xlink:href="wiki://MoinMoin/RecentChanges">RecentChanges</a></p></body></page>'),
+            (u'[[MoinMoin:RecentChanges|changes]]',
+                '<page><body><p><a xlink:href="wiki://MoinMoin/RecentChanges">changes</a></p></body></page>'),
+            (u'[[MoinMoin:Foo/Bar.Baz]]',
+                '<page><body><p><a xlink:href="wiki://MoinMoin/Foo/Bar.Baz">Foo/Bar.Baz</a></p></body></page>'),
+            (u'[[MoinMoin:Blank In Page Name|blank in page name]]',
+                '<page><body><p><a xlink:href="wiki://MoinMoin/Blank%20In%20Page%20Name">blank in page name</a></p></body></page>'),
+            (u'[[InvalidWikiName:RecentChanges]]',
+                '<page><body><p><a xlink:href="wiki.local:InvalidWikiName:RecentChanges">InvalidWikiName:RecentChanges</a></p></body></page>'),
+        ]
+        for i in data:
+            yield (self.do, ) + i
+
+    def test_email(self):
+        data = [
+            (u'[[mailto:root]]',
+                '<page><body><p><a xlink:href="mailto:root">mailto:root</a></p></body></page>'),
+            (u'[[mailto:foo@bar.baz]]',
+                '<page><body><p><a xlink:href="mailto:foo@bar.baz">mailto:foo@bar.baz</a></p></body></page>'),
+            (u'[[mailto:foo@bar.baz|write me]]',
+                '<page><body><p><a xlink:href="mailto:foo@bar.baz">write me</a></p></body></page>'),
+            (u'[[mailto:foo.bar_baz@bar.baz]]', # . and _ are special characters commonly allowed by email systems
+                '<page><body><p><a xlink:href="mailto:foo.bar_baz@bar.baz">mailto:foo.bar_baz@bar.baz</a></p></body></page>'),
+        ]
+        for i in data:
+            yield (self.do, ) + i
 
     def serialize(self, elem, **options):
         from StringIO import StringIO
--- a/MoinMoin/converter/link.py	Tue May 31 00:58:29 2011 +0530
+++ b/MoinMoin/converter/link.py	Tue May 31 00:59:30 2011 +0530
@@ -141,7 +141,7 @@
             # interwiki link
             wikitag, wikiurl, wikitail, err = resolve_interwiki(unicode(input.authority.host), unicode(input.path[1:]))
             if not err:
-                elem.set(html.class_, 'interwiki')
+                elem.set(html.class_, 'moin-interwiki')
                 if do is not None:
                     # this will only work for wikis with compatible URL design
                     # for other wikis, don't use do=... in your interwiki links
--- a/MoinMoin/converter/moinwiki19_in.py	Tue May 31 00:58:29 2011 +0530
+++ b/MoinMoin/converter/moinwiki19_in.py	Tue May 31 00:59:30 2011 +0530
@@ -130,8 +130,7 @@
                 )
             )
             (?P<url_target>
-                # TODO: config.url_schemas
-                (http|https|ftp|nntp|news|mailto|telnet|file|irc):
+                (%(url_schemas)s):
                 \S+?
             )
             (
@@ -145,7 +144,7 @@
                 )
             )
         )
-    """
+    """ % dict(url_schemas='|'.join(config.url_schemas))
 
     def inline_url_repl(self, stack, url, url_target):
         url = Iri(url_target)
@@ -165,3 +164,4 @@
 default_registry.register(ConverterFormat19.factory, Type('text/x.moin.wiki;format=1.9'), type_moin_document)
 default_registry.register(ConverterFormat19.factory, Type('x-moin/format;name=wiki;format=1.9'), type_moin_document)
 
+
--- a/MoinMoin/converter/moinwiki_in.py	Tue May 31 00:58:29 2011 +0530
+++ b/MoinMoin/converter/moinwiki_in.py	Tue May 31 00:59:30 2011 +0530
@@ -21,7 +21,7 @@
 from MoinMoin import config
 from MoinMoin.util.iri import Iri
 from MoinMoin.util.tree import html, moin_page, xlink, xinclude
-
+from MoinMoin.util.interwiki import resolve_interwiki
 from ._args import Arguments
 from ._args_wiki import parse as parse_arguments
 from ._wiki_macro import ConverterMacro
@@ -748,11 +748,16 @@
             \s*
             (
                 (?P<link_url>
-                    [a-zA-Z0-9+.-]+
-                    ://
+                    (%(url_schemas)s):
                     [^|]+?
                 )
                 |
+                (
+                    (?P<link_interwiki_site>[A-Z][a-zA-Z]+)
+                    :
+                    (?P<link_interwiki_item>[^|]+) # accept any item name; will verify link_interwiki_site below
+                )
+                |
                 (?P<link_item> [^|]+? )
             )
             \s*
@@ -770,11 +775,29 @@
             )?
             \]\]
         )
-    """
+    """ % dict(url_schemas='|'.join(config.url_schemas))
 
     def inline_link_repl(self, stack, link, link_url=None, link_item=None,
-            link_text=None, link_args=None):
+            link_text=None, link_args=None,
+            link_interwiki_site=None, link_interwiki_item=None):
         """Handle all kinds of links."""
+        if link_interwiki_site:
+            err = resolve_interwiki(link_interwiki_site, link_interwiki_item)[3]
+            if not err:
+                link = Iri(scheme='wiki',
+                        authority=link_interwiki_site,
+                        path='/' + link_interwiki_item)
+                element = moin_page.a(attrib={xlink.href: link})
+                stack.push(element)
+                if link_text:
+                    self.parse_inline(link_text, stack, self.inlinedesc_re)
+                else:
+                    stack.top_append(link_interwiki_item)
+                stack.pop()
+                return
+            else:
+                # assume local language uses ":" inside of words, set link_item and continue
+                link_item = '%s:%s' % (link_interwiki_site, link_interwiki_item)
         if link_args:
             link_args = parse_arguments(link_args) # XXX needs different parsing
             query = url_encode(link_args.keyword, charset=config.charset, encode_keys=True)
@@ -1052,6 +1075,7 @@
         inline_macro,
         inline_nowiki,
         inline_emphstrong,
+        inline_object,
     )
     inlinedesc_re = re.compile('|'.join(inlinedesc), re.X | re.U)
 
@@ -1112,3 +1136,4 @@
 default_registry.register(Converter.factory, type_moin_wiki, type_moin_document)
 default_registry.register(Converter.factory, Type('x-moin/format;name=wiki'), type_moin_document)
 
+
--- a/MoinMoin/items/__init__.py	Tue May 31 00:58:29 2011 +0530
+++ b/MoinMoin/items/__init__.py	Tue May 31 00:59:30 2011 +0530
@@ -22,7 +22,12 @@
 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 MoinMoin.util.forms import FileStorage
+
 from MoinMoin.security.textcha import TextCha, TextChaizedForm, TextChaValid
+from MoinMoin.signalling import item_modified
 from MoinMoin.util.forms import make_generator
 from MoinMoin.util.mimetype import MimeType
 from MoinMoin.util.mime import Type, type_moin_document
@@ -48,7 +53,8 @@
 from flask import current_app as app
 from flask import g as flaskg
 
-from flask import request, url_for, Response, abort, escape
+from flask import request, url_for, flash, Response, redirect, abort, escape
+
 from werkzeug import is_resource_modified
 from jinja2 import Markup
 
@@ -436,11 +442,9 @@
         meta_text = request.form.get('meta_text')
         if meta_text is not None:
             # there was a meta_text field with (possibly empty) content
-            try:
-                meta = self.meta_text_to_dict(meta_text)
-            except ValueError:
-                # XXX maybe rather validate and reject invalid json
-                pass
+            # 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 = {}
@@ -465,6 +469,7 @@
             rev_no = currentrev.revno
             contenttype_current = currentrev.get(CONTENTTYPE)
         except NoSuchRevisionError:
+            currentrev = None
             rev_no = -1
             contenttype_current = None
         new_rev_no = rev_no + 1
@@ -482,9 +487,12 @@
         newrev[NAME] = name
 
         if data is None:
-            # we don't have (new) data, just copy the old one.
-            # a valid usecase of this is to just edit metadata.
-            data = currentrev
+            if currentrev is not None:
+                # we don't have (new) data, just copy the old one.
+                # a valid usecase of this is to just edit metadata.
+                data = currentrev
+            else:
+                data = ''
         size = self._write_stream(data, newrev)
 
         # XXX if meta is from old revision, and user did not give a non-empty
@@ -500,7 +508,7 @@
         newrev[ACTION] = unicode(action)
         self.before_revision_commit(newrev, data)
         storage_item.commit()
-        # XXX Event ?
+        item_modified.send(app._get_current_object(), item_name=name)
         return new_rev_no, size
 
     def before_revision_commit(self, newrev, data):
@@ -632,7 +640,7 @@
     def _convert(self):
         abort(404)
 
-    def do_modify(self, template_name):
+    def do_modify(self, contenttype, template_name):
         # XXX think about and add item template support
         return render_template('modify_show_type_selection.html',
                                item_name=self.name,
@@ -641,6 +649,18 @@
 
 item_registry.register(NonExistent._factory, Type('application/x-nonexistent'))
 
+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 Binary(Item):
     """ An arbitrary binary item, fallback class for every item mimetype. """
@@ -671,17 +691,34 @@
         items = [item.name for item in item_iterator]
         return sorted(items)
 
-    def do_modify(self, template_name):
+    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()
-        form = TextChaizedForm.from_defaults()
-        TextCha(form).amend_form()
+        from MoinMoin.apps.frontend.views import CommentForm
+        class ModifyForm(CommentForm):
+            rev = Integer.using(optional=False)
+            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()
+            form['meta_text'] = self.meta_dict_to_text(self.meta)
+            form['rev'] = self.rev.revno if self.rev.revno is not None else -1
+        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 AccessDeniedError:
+                    abort(403)
+                else:
+                    return redirect(url_for('frontend.show_item', item_name=self.name))
         return render_template(self.template,
                                item_name=self.name,
-                               rows_meta=ROWS_META, cols=COLS,
-                               revno=0,
-                               meta_text=self.meta_dict_to_text(self.meta),
+                               rows_meta=str(ROWS_META), cols=str(COLS),
                                help=self.modify_help,
                                form=form,
                                gen=make_generator(),
@@ -1111,24 +1148,42 @@
         doc = html_conv(doc)
         return conv_serialize(doc, {html.namespace: ''})
 
-    def do_modify(self, template_name):
-        form = TextChaizedForm.from_defaults()
-        TextCha(form).amend_form()
-        if template_name is None and isinstance(self.rev, DummyRev):
-            return self._do_modify_show_templates()
-        if template_name:
-            item = Item.create(template_name)
-            data_text = self.data_storage_to_internal(item.data)
-        else:
-            data_text = self.data_storage_to_internal(self.data)
-        meta_text = self.meta_dict_to_text(self.meta)
+    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):
+            rev = Integer.using(optional=False)
+            meta_text = String.using(optional=False).with_properties(placeholder=L_("MetaData (JSON)")).validated_by(ValidJSON())
+            data_text = String.using(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.meta)
+            form['rev'] = self.rev.revno if self.rev.revno is not None else -1
+        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 AccessDeniedError:
+                    abort(403)
+                else:
+                    return redirect(url_for('frontend.show_item', item_name=self.name))
         return render_template(self.template,
                                item_name=self.name,
-                               rows_data=ROWS_DATA, rows_meta=ROWS_META, cols=COLS,
-                               revno=0,
-                               data_text=data_text,
-                               meta_text=meta_text,
-                               lang='en', direction='ltr',
+                               rows_data=str(ROWS_DATA), rows_meta=str(ROWS_META), cols=str(COLS),
                                help=self.modify_help,
                                form=form,
                                gen=make_generator(),
@@ -1208,29 +1263,6 @@
     """
     template = "modify_text_html.html"
 
-    def do_modify(self, template_name):
-        form = TextChaizedForm.from_defaults()
-        TextCha(form).amend_form()
-        if template_name is None and isinstance(self.rev, DummyRev):
-            return self._do_modify_show_templates()
-        if template_name:
-            item = Item.create(template_name)
-            data_text = self.data_storage_to_internal(item.data)
-        else:
-            data_text = self.data_storage_to_internal(self.data)
-        meta_text = self.meta_dict_to_text(self.meta)
-        return render_template(self.template,
-                               item_name=self.name,
-                               rows_data=ROWS_DATA, rows_meta=ROWS_META, cols=COLS,
-                               revno=0,
-                               data_text=data_text,
-                               meta_text=meta_text,
-                               lang='en', direction='ltr',
-                               help=self.modify_help,
-                               form=form,
-                               gen=make_generator(),
-                              )
-
 item_registry.register(HTML._factory, Type('text/html'))
 
 
@@ -1313,18 +1345,35 @@
         self.put_member('drawing' + ext, filecontent, content_length,
                         expected_members=set(['drawing.draw', 'drawing.map', 'drawing.png']))
 
-    def do_modify(self, template_name):
-        """
-        Fills params into the template for initialzing of the the java applet.
-        The applet is called for doing modifications.
-        """
-        form = TextChaizedForm.from_defaults()
-        TextCha(form).amend_form()
+    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):
+            rev = Integer.using(optional=False)
+            # 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:'))
+
+        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.meta)
+            form['rev'] = self.rev.revno if self.rev.revno is not None else -1
+        elif request.method == 'POST':
+            # this POST comes directly from TWikiDraw (not from Browser), thus no validation
+            try:
+                self.modify() # XXX
+            except AccessDeniedError:
+                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=ROWS_META, cols=COLS,
-                               revno=0,
-                               meta_text=self.meta_dict_to_text(self.meta),
+                               rows_meta=str(ROWS_META), cols=str(COLS),
                                help=self.modify_help,
                                form=form,
                                gen=make_generator(),
@@ -1389,19 +1438,39 @@
         self.put_member('drawing' + ext, filecontent, content_length,
                         expected_members=set(['drawing.svg', 'drawing.map', 'drawing.png']))
 
-    def do_modify(self, template_name):
-        """
-        Fills params into the template for initialzing of the the java applet.
-        The applet is called for doing modifications.
-        """
-        form = TextChaizedForm.from_defaults()
-        TextCha(form).amend_form()
-        drawing_exists = 'drawing.svg' in self.list_members()
+    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):
+            rev = Integer.using(optional=False)
+            # 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.meta)
+            form['rev'] = self.rev.revno if self.rev.revno is not None else -1
+        elif request.method == 'POST':
+            # this POST comes directly from AnyWikiDraw (not from Browser), thus no validation
+            try:
+                self.modify() # XXX
+            except AccessDeniedError:
+                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=ROWS_META, cols=COLS,
-                               revno=0,
-                               meta_text=self.meta_dict_to_text(self.meta),
+                               rows_meta=str(ROWS_META), cols=str(COLS),
                                help=self.modify_help,
                                drawing_exists=drawing_exists,
                                form=form,
@@ -1458,17 +1527,35 @@
         self.put_member(filename, filecontent, content_length,
                         expected_members=set(['drawing.svg', 'drawing.png']))
 
-    def do_modify(self, template_name):
-        """
-        Fills params into the template for initializing of the applet.
-        """
-        form = TextChaizedForm.from_defaults()
-        TextCha(form).amend_form()
+    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):
+            rev = Integer.using(optional=False)
+            # 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.meta)
+            form['rev'] = self.rev.revno if self.rev.revno is not None else -1
+        elif request.method == 'POST':
+            # this POST comes directly from SvgDraw (not from Browser), thus no validation
+            try:
+                self.modify() # XXX
+            except AccessDeniedError:
+                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=ROWS_META, cols=COLS,
-                               revno=0,
-                               meta_text=self.meta_dict_to_text(self.meta),
+                               rows_meta=str(ROWS_META), cols=str(COLS),
                                help=self.modify_help,
                                form=form,
                                gen=make_generator(),
--- a/MoinMoin/templates/modify_applet.html	Tue May 31 00:58:29 2011 +0530
+++ b/MoinMoin/templates/modify_applet.html	Tue May 31 00:59:30 2011 +0530
@@ -1,22 +1,18 @@
+{% import "forms.html" as forms %}
 {% extends theme("layout.html") %}
 {% block content %}
 <h1>{{ _("Modifying %(item_name)s", item_name=item_name) }}</h1>
 <div class="moin-form">
-<form action="" method="POST" enctype="multipart/form-data">
-<input type="hidden" name="action" value="modify" />
-<input type="hidden" name="rev" value="{{ revno }}" />
+{{ gen.form.open(form, method='post', action='', enctype='multipart/form-data') }}
+{{ forms.render_errors(form) }}
+{{ gen.input(form['rev'], type='hidden') }}
 {% block extra_form %}{% endblock %}
 {% block data_editor %}{% endblock %}
-    <dl>
-        <dt>
-            <label for="data_file">{{ _("Upload file:") }}</label>
-         </dt>
-         <dd>
-            <input type="file" id="data_file" name="data_file" />
-        </dd>
-    </dl>
+{{ forms.render_field(gen, form['data_file'], 'file') }}
 <pre>{{ help }}</pre>
-<textarea name="meta_text" lang="en" dir="ltr" rows="{{ rows_meta }}" cols="{{ cols }}">{{ meta_text }}</textarea>
-</form>
+{{ 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	Tue May 31 00:58:29 2011 +0530
+++ b/MoinMoin/templates/modify_binary.html	Tue May 31 00:59:30 2011 +0530
@@ -1,10 +1,7 @@
 {% import "forms.html" as forms %}
 {% extends "modify_applet.html" %}
 {% block extra_form %}
-<input class="button" type="submit" name="button_save" value="{{ _("Save") }}" />
-<input class="button" type="submit" name="button_cancel" value="{{ _("Cancel") }}" />
+{{ gen.input(form['submit'], class='button', type='submit') }}
 {{ forms.render_textcha(gen, form) }}
-<br />
-<label for="comment">{{ _("Comment:") }}</label><input type="text" id="comment" name="comment" size="80" maxlength="200" value="{{ comment }}" />
-<br />
+{{ forms.render_field(gen, form['comment'], 'text') }}
 {% endblock %}
--- a/MoinMoin/templates/modify_text.html	Tue May 31 00:58:29 2011 +0530
+++ b/MoinMoin/templates/modify_text.html	Tue May 31 00:59:30 2011 +0530
@@ -1,5 +1,5 @@
 {% extends "modify_binary.html" %}
 {% block data_editor %}
-<textarea name="data_text" lang="{{ lang }}" dir="{{ direction }}" rows="{{ rows_data }}" cols="{{ cols }}">{{ data_text }}</textarea>
+{{ gen.textarea(form['data_text'], lang=lang, dir=direction, rows=rows_data, cols=cols) }}
 <br />
 {% endblock %}
--- a/MoinMoin/templates/modify_text_html.html	Tue May 31 00:58:29 2011 +0530
+++ b/MoinMoin/templates/modify_text_html.html	Tue May 31 00:59:30 2011 +0530
@@ -8,8 +8,6 @@
 
 {% block data_editor %}
 <p>
-    <textarea class="ckeditor" id="data_text" name="data_text" rows="{{ rows_data }}" cols="{{ cols }}">{{
-        data_text
-    }}</textarea>
+{{ gen.textarea(form['data_text'], class='ckeditor', lang=lang, dir=direction, rows=rows_data, cols=cols) }}
 </p>
 {% endblock %}
--- a/MoinMoin/themes/modernized/static/css/common.css	Tue May 31 00:58:29 2011 +0530
+++ b/MoinMoin/themes/modernized/static/css/common.css	Tue May 31 00:59:30 2011 +0530
@@ -69,9 +69,7 @@
 
 /* dead links */
 a.moin-nonexistent:visited,
-a.moin-nonexistent,
-a.moin-badinterwiki:visited,
-a.moin-badinterwiki { color: #444; }
+a.moin-nonexistent { color: #444; }
 
 /* lists */
 ol, ul { margin-left: 3em; }
@@ -357,7 +355,6 @@
 a.moin-ircs:before { content: url(../img/moin-telnet.png); margin: 0 0.2em; }
 a.moin-mailto:before { content: url(../img/moin-email.png); margin: 0 0.2em; }
 a.moin-attachment:before { content: url(../img/moin-attach.png); margin: 0 0.2em; }
-a.moin-badinterwiki:before { content: url(../img/moin-inter.png); margin: 0 0.2em; }
 a.moin-interwiki:before { content: url(../img/moin-inter.png); margin: 0 0.2em; }
 a.moin-action:before { content: url(../img/moin-action.png); margin: 0 0.2em; }
 
@@ -474,7 +471,7 @@
 
 /* moin-header */
 #moin-header { margin: 0px; padding: 0px;
-            background: url(../img/white_clouds.png); /* background: #E6EAF0; */
+            background: url(../img/white-clouds.jpg); /* background: #E6EAF0; */
             line-height: 1.12em; }
 
 /* moin-header searchform */
@@ -573,11 +570,11 @@
 html { font-family: Times, serif; font-size: 12pt; width: 100%; }
 body, #moin-page, #moin-page, #moin-content-data { margin: 0; padding: 0; }
 
-a, a:visited, a.nonexistent,
-a.badinterwiki { color: black !important; text-decoration: none !important; }
+a, a:visited,
+a.nonexistent { color: black !important; text-decoration: none !important; }
 
-a.interwiki:before, a.badinterwiki:before { content: attr(title) ":"; }
-a.interwiki img, a.badinterwiki img { display: none; }
+a.interwiki:before { content: attr(title) ":"; }
+a.interwiki img { display: none; }
 
 pre { font-size: 10pt; }
 .footnotes div { width: 5em; border-top: 1pt solid gray; }
Binary file MoinMoin/themes/modernized/static/img/white-clouds.jpg has changed
Binary file MoinMoin/themes/modernized/static/img/white_clouds.png has changed
--- a/MoinMoin/util/forms.py	Tue May 31 00:58:29 2011 +0530
+++ b/MoinMoin/util/forms.py	Tue May 31 00:59:30 2011 +0530
@@ -85,3 +85,18 @@
                               error_filter,
                               required_filter, placeholder_filter, autofocus_filter])
 
+
+# other flatland stuff
+
+from flatland import AdaptationError, Scalar
+import werkzeug
+
+
+class FileStorage(Scalar):
+    """Schema element for Werkzeug FileStorage instances."""
+
+    def adapt(self, value):
+        if not isinstance(value, (type(None), werkzeug.FileStorage)):
+            raise AdaptationError
+        return value
+