changeset 2932:4ab27780d078

merged
author Thomas Waldmann <tw AT waldmann-edv DOT de>
date Mon, 27 Apr 2015 22:07:21 +0200
parents 3862a0aede87 (current diff) a6bb853c740b (diff)
children 456c68761e96
files MoinMoin/app.py MoinMoin/apps/frontend/views.py MoinMoin/templates/usersettings_forms.html MoinMoin/themes/basic/templates/modify.html
diffstat 62 files changed, 380 insertions(+), 208 deletions(-) [+]
line wrap: on
line diff
--- a/MoinMoin/__init__.py	Mon Apr 27 22:02:22 2015 +0200
+++ b/MoinMoin/__init__.py	Mon Apr 27 22:07:21 2015 +0200
@@ -9,16 +9,17 @@
 
 from __future__ import absolute_import, division
 
-project = "MoinMoin"
-
 import sys
 import platform
 
+from MoinMoin.util.version import Version
+
+
+project = "MoinMoin"
+
 
 if sys.hexversion < 0x2070000 or sys.hexversion > 0x2999999:
     sys.exit("Error: %s requires Python 2.7.x., current version is %s\n" % (project, platform.python_version()))
 
 
-from MoinMoin.util.version import Version
-
 version = Version(2, 0, 0, 'a0')
--- a/MoinMoin/_tests/ldap_testbase.py	Mon Apr 27 22:02:22 2015 +0200
+++ b/MoinMoin/_tests/ldap_testbase.py	Mon Apr 27 22:07:21 2015 +0200
@@ -36,10 +36,6 @@
 """
 
 
-# filename of LDAP server executable - if it is not
-# in your PATH, you have to give full path/filename.
-SLAPD_EXECUTABLE = 'slapd'
-
 import os
 import shutil
 import tempfile
@@ -59,6 +55,11 @@
     ldap = None
 
 
+# filename of LDAP server executable - if it is not
+# in your PATH, you have to give full path/filename.
+SLAPD_EXECUTABLE = 'slapd'
+
+
 def check_environ():
     """ Check the system environment whether we are able to run.
         Either return some failure reason if we can't or None if everything
--- a/MoinMoin/app.py	Mon Apr 27 22:02:22 2015 +0200
+++ b/MoinMoin/app.py	Mon Apr 27 22:07:21 2015 +0200
@@ -20,21 +20,27 @@
 from flask import current_app as app
 from flask import g as flaskg
 
+# workaround Flask 0.10. incompatibility with openid - see #345, #515
+try:
+    from flask_oldsessions import OldSecureCookieSessionInterface
+except ImportError:
+    OldSecureCookieSessionInterface = None
+
 from flask.ext.cache import Cache
 from flask.ext.themes import setup_themes
 
 from jinja2 import ChoiceLoader, FileSystemLoader
 
-from MoinMoin import log
-logging = log.getLogger(__name__)
-
 from MoinMoin.constants.misc import ANON
 from MoinMoin.i18n import i18n_init
 from MoinMoin.i18n import _, L_, N_
-
 from MoinMoin.themes import setup_jinja_env, themed_error
+from MoinMoin.util.clock import Clock
+from MoinMoin.storage.middleware import protecting, indexing, routing
+from MoinMoin import auth, config, user
 
-from MoinMoin.util.clock import Clock
+from MoinMoin import log
+logging = log.getLogger(__name__)
 
 
 def create_app(config=None, create_index=False, create_storage=False):
@@ -68,6 +74,10 @@
     clock = Clock()
     clock.start('create_app total')
     app = Flask('MoinMoin')
+
+    if OldSecureCookieSessionInterface:
+        app.session_interface = OldSecureCookieSessionInterface()
+
     clock.start('create_app load config')
     if flask_config_file:
         app.config.from_pyfile(flask_config_file)
@@ -165,10 +175,6 @@
     deinit_backends(app)
 
 
-from MoinMoin.storage.middleware import protecting, indexing, routing
-from MoinMoin import auth, config, user
-
-
 def init_backends(app):
     """
     initialize the backends
--- a/MoinMoin/apps/admin/views.py	Mon Apr 27 22:02:22 2015 +0200
+++ b/MoinMoin/apps/admin/views.py	Mon Apr 27 22:07:21 2015 +0200
@@ -29,6 +29,7 @@
 from MoinMoin.datastruct.backends import GroupDoesNotExistError
 from MoinMoin.items import Item
 from MoinMoin.util.interwiki import split_fqname
+from MoinMoin.config import default as defaultconfig
 
 
 @admin.route('/superuser')
@@ -112,9 +113,6 @@
     return redirect(url_for('.userbrowser'))
 
 
-from MoinMoin.config import default as defaultconfig
-
-
 @admin.route('/wikiconfig', methods=['GET', ])
 @require_permission(SUPERUSER)
 def wikiconfig():
--- a/MoinMoin/apps/feed/views.py	Mon Apr 27 22:02:22 2015 +0200
+++ b/MoinMoin/apps/feed/views.py	Mon Apr 27 22:07:21 2015 +0200
@@ -20,9 +20,6 @@
 
 from whoosh.query import Term, And
 
-from MoinMoin import log
-logging = log.getLogger(__name__)
-
 from MoinMoin.i18n import _, L_, N_
 from MoinMoin.apps.feed import feed
 from MoinMoin.constants.keys import NAME, NAME_EXACT, WIKINAME, COMMENT, MTIME, REVID, ALL_REVS, PARENTID, LATEST_REVS
@@ -31,6 +28,9 @@
 from MoinMoin.util.crypto import cache_key
 from MoinMoin.util.interwiki import url_for_item
 
+from MoinMoin import log
+logging = log.getLogger(__name__)
+
 
 @feed.route('/atom/<itemname:item_name>')
 @feed.route('/atom', defaults=dict(item_name=''))
--- a/MoinMoin/apps/frontend/views.py	Mon Apr 27 22:02:22 2015 +0200
+++ b/MoinMoin/apps/frontend/views.py	Mon Apr 27 22:07:21 2015 +0200
@@ -43,9 +43,6 @@
 from whoosh.analysis import StandardAnalyzer
 from whoosh import sorting
 
-from MoinMoin import log
-logging = log.getLogger(__name__)
-
 from MoinMoin.i18n import _, L_, N_
 from MoinMoin.themes import render_template, contenttype_to_class
 from MoinMoin.apps.frontend import frontend
@@ -69,6 +66,9 @@
 from MoinMoin.signalling import item_displayed, item_modified
 from MoinMoin.storage.middleware.protecting import AccessDenied
 
+from MoinMoin import log
+logging = log.getLogger(__name__)
+
 
 @frontend.route('/+dispatch', methods=['GET', ])
 def dispatch():
@@ -1411,8 +1411,7 @@
     name_or_email_needed_msg = L_('Your user name or your email address is needed.')
 
     def validate(self, element, state):
-        if not(element['username'].valid and element['username'].value
-               or
+        if not(element['username'].valid and element['username'].value or
                element['email'].valid and element['email'].value):
             return self.note_error(element, state, 'name_or_email_needed_msg')
 
@@ -1677,11 +1676,50 @@
     submit_label = L_('Save')
 
 
+class ValidSubscriptions(Validator):
+    """Validator for a subscriptions change
+    """
+
+    def validate(self, element, state):
+        # TODO: is additional validation for namespaces, itemids, names, or name prefixes needed?
+        invalid_subscription_msg = L_('Invalid subscription syntax: ')
+        invalid_keyword = L_('Invalid keyword: ')
+        invalid_re_expression = L_('Invalid RE syntax: ')
+        errors = []
+        for subscription in element.value['subscriptions']:
+            try:
+                keyword, value = subscription.split(":", 1)
+            except ValueError:
+                errors.append(invalid_subscription_msg + subscription)
+                continue
+            if keyword == ITEMID:
+                continue
+            if keyword not in (NAME, NAMEPREFIX, TAGS, NAMERE, ):
+                errors.append(invalid_keyword + subscription)
+                continue
+            try:
+                namespace, pattern = value.split(":", 1)
+            except ValueError:
+                errors.append(invalid_subscription_msg + subscription)
+                continue
+            if keyword == NAMERE:
+                try:
+                    pattern = re.compile(pattern, re.U)
+                except re.error:
+                    errors.append(invalid_re_expression + subscription)
+                    continue
+        if errors:
+            return self.note_error(element, state, message=', '.join(errors))
+        return True
+
+
 class UserSettingsSubscriptionsForm(Form):
     name = 'usersettings_subscriptions'
     subscriptions = Subscriptions
     submit_label = L_('Save')
 
+    validators = [ValidSubscriptions()]
+
 
 @frontend.route('/+usersettings', methods=['GET', 'POST'])
 def usersettings():
@@ -1803,7 +1841,9 @@
                                                           "error"))
                         else:
                             flaskg.user.save()
-
+            else:
+                # validation failed
+                response['flash'].append((_("Nothing saved."), "error"))
             if not response['flash']:
                 # if no flash message was added until here, we add a generic success message
                 response['flash'].append((_("Your changes have been saved."), "info"))
--- a/MoinMoin/apps/serve/views.py	Mon Apr 27 22:02:22 2015 +0200
+++ b/MoinMoin/apps/serve/views.py	Mon Apr 27 22:07:21 2015 +0200
@@ -11,11 +11,11 @@
 
 from flask import current_app as app
 
+from MoinMoin.apps.serve import serve
+
 from MoinMoin import log
 logging = log.getLogger(__name__)
 
-from MoinMoin.apps.serve import serve
-
 
 @serve.route('/')
 def index():
--- a/MoinMoin/auth/_tests/test_log.py	Mon Apr 27 22:02:22 2015 +0200
+++ b/MoinMoin/auth/_tests/test_log.py	Mon Apr 27 22:07:21 2015 +0200
@@ -5,14 +5,15 @@
 Test for auth.log
 """
 
-from MoinMoin import log
-logging = log.getLogger(__name__)
 
 from flask import g as flaskg
 
 from MoinMoin.auth.log import AuthLog
 from MoinMoin.constants.misc import ANON
 
+from MoinMoin import log
+logging = log.getLogger(__name__)
+
 
 class TestAuthLog(object):
     """ Test: TestAuthLog """
--- a/MoinMoin/auth/http.py	Mon Apr 27 22:02:22 2015 +0200
+++ b/MoinMoin/auth/http.py	Mon Apr 27 22:07:21 2015 +0200
@@ -17,15 +17,15 @@
 """
 
 
-from MoinMoin import log
-logging = log.getLogger(__name__)
-
 from flask import request
 
 from MoinMoin import user
 from MoinMoin.i18n import _, L_, N_
 from MoinMoin.auth import BaseAuth, GivenAuth
 
+from MoinMoin import log
+logging = log.getLogger(__name__)
+
 
 class HTTPAuthMoin(BaseAuth):
     """ authenticate via http (basic) auth """
--- a/MoinMoin/auth/ldap_login.py	Mon Apr 27 22:02:22 2015 +0200
+++ b/MoinMoin/auth/ldap_login.py	Mon Apr 27 22:07:21 2015 +0200
@@ -17,6 +17,7 @@
     TODO: allow more configuration (display name, ...) by using callables as parameters
 """
 
+
 from MoinMoin import log
 logging = log.getLogger(__name__)
 
--- a/MoinMoin/auth/log.py	Mon Apr 27 22:02:22 2015 +0200
+++ b/MoinMoin/auth/log.py	Mon Apr 27 22:07:21 2015 +0200
@@ -9,11 +9,11 @@
 """
 
 
+from MoinMoin.auth import BaseAuth, ContinueLogin
+
 from MoinMoin import log
 logging = log.getLogger(__name__)
 
-from MoinMoin.auth import BaseAuth, ContinueLogin
-
 
 class AuthLog(BaseAuth):
     """ just log the call, do nothing else """
--- a/MoinMoin/auth/openidrp.py	Mon Apr 27 22:02:22 2015 +0200
+++ b/MoinMoin/auth/openidrp.py	Mon Apr 27 22:07:21 2015 +0200
@@ -8,15 +8,12 @@
 """
 
 
-from MoinMoin import log
-logging = log.getLogger(__name__)
-
 from openid.store.memstore import MemoryStore
 from openid.consumer import consumer
 from openid.yadis.discover import DiscoveryFailure
 from openid.fetchers import HTTPFetchingError
 
-from flask import session, request, url_for
+from flask import session, request, url_for, flash
 from flask import current_app as app
 from MoinMoin.auth import BaseAuth, get_multistage_continuation_url
 from MoinMoin.auth import ContinueLogin, CancelLogin, MultistageFormLogin, MultistageRedirectLogin
@@ -24,6 +21,9 @@
 from MoinMoin import user
 from MoinMoin.i18n import _, L_, N_
 
+from MoinMoin import log
+logging = log.getLogger(__name__)
+
 
 class OpenIDAuth(BaseAuth):
     def __init__(self, trusted_providers=[], **kw):
@@ -83,13 +83,14 @@
                 # we get the user with this openid associated to him
                 identity = oid_info.identity_url
                 users = user.search_users(openid=identity)
-                user_obj = users and user.User(users[0][ITEMID], trusted=self.trusted)
+                user_obj = users and user.User(uid=users[0].item.itemid, trusted=self.trusted, auth_method=self.name)
 
                 # if the user actually exists
                 if user_obj:
                     # we get the authenticated user object
                     # success!
                     user_obj.auth_method = self.name
+                    flash(_('You have logged in with OpenID.'), "info")
                     return ContinueLogin(user_obj)
 
                 # there is no user with this openid
--- a/MoinMoin/auth/smb_mount.py	Mon Apr 27 22:02:22 2015 +0200
+++ b/MoinMoin/auth/smb_mount.py	Mon Apr 27 22:07:21 2015 +0200
@@ -11,11 +11,11 @@
 """
 
 
+from MoinMoin.auth import BaseAuth, CancelLogin, ContinueLogin
+
 from MoinMoin import log
 logging = log.getLogger(__name__)
 
-from MoinMoin.auth import BaseAuth, CancelLogin, ContinueLogin
-
 
 class SMBMount(BaseAuth):
     """ auth plugin for (un)mounting an smb share,
--- a/MoinMoin/config/default.py	Mon Apr 27 22:02:22 2015 +0200
+++ b/MoinMoin/config/default.py	Mon Apr 27 22:07:21 2015 +0200
@@ -16,9 +16,6 @@
 
 from babel import parse_locale
 
-from MoinMoin import log
-logging = log.getLogger(__name__)
-
 from MoinMoin.i18n import _, L_, N_
 from MoinMoin import error
 from MoinMoin.constants.rights import ACL_RIGHTS_CONTENTS, ACL_RIGHTS_FUNCTIONS
@@ -29,6 +26,9 @@
 from MoinMoin.util import plugins
 from MoinMoin.security import AccessControlList, DefaultSecurityPolicy
 
+from MoinMoin import log
+logging = log.getLogger(__name__)
+
 
 class CacheClass(object):
     """ just a container for stuff we cache """
--- a/MoinMoin/conftest.py	Mon Apr 27 22:02:22 2015 +0200
+++ b/MoinMoin/conftest.py	Mon Apr 27 22:07:21 2015 +0200
@@ -18,6 +18,16 @@
 
 from __future__ import absolute_import, division
 
+import pytest
+import py
+import MoinMoin.log
+import MoinMoin
+
+from MoinMoin.app import create_app_ext, destroy_app, before_wiki, teardown_wiki
+from MoinMoin._tests import wikiconfig
+from MoinMoin.storage import create_simple_mapping
+
+
 # exclude some directories from py.test test discovery, pathes relative to this file
 collect_ignore = [
     'static',  # same
@@ -25,20 +35,11 @@
     '../instance',  # tw likes to use this for wiki data (non-revisioned)
 ]
 
-import pytest
-import py
-import MoinMoin.log
-import MoinMoin
-
 # Logging for tests to avoid useless output like timing information on stderr on test failures
 Moindir = py.path.local(MoinMoin.__file__).dirname
 config_file = Moindir + '/_tests/test_logging.conf'
 MoinMoin.log.load_config(config_file)
 
-from MoinMoin.app import create_app_ext, destroy_app, before_wiki, teardown_wiki
-from MoinMoin._tests import wikiconfig
-from MoinMoin.storage import create_simple_mapping
-
 
 @pytest.fixture
 def cfg():
--- a/MoinMoin/converter/_tests/test_docbook_in.py	Mon Apr 27 22:02:22 2015 +0200
+++ b/MoinMoin/converter/_tests/test_docbook_in.py	Mon Apr 27 22:07:21 2015 +0200
@@ -16,11 +16,11 @@
 
 from emeraldtree.tree import *
 
+from MoinMoin.converter.docbook_in import *
+
 from MoinMoin import log
 logging = log.getLogger(__name__)
 
-from MoinMoin.converter.docbook_in import *
-
 
 class Base(object):
     input_namespaces = ns_all = u'xmlns="{0}" xmlns:xlink="{1}"'.format(docbook.namespace, xlink.namespace)
--- a/MoinMoin/converter/_tests/test_docbook_out.py	Mon Apr 27 22:02:22 2015 +0200
+++ b/MoinMoin/converter/_tests/test_docbook_out.py	Mon Apr 27 22:07:21 2015 +0200
@@ -15,11 +15,11 @@
 
 from emeraldtree.tree import *
 
+from MoinMoin.converter.docbook_out import *
+
 from MoinMoin import log
 logging = log.getLogger(__name__)
 
-from MoinMoin.converter.docbook_out import *
-
 
 class Base(object):
     input_namespaces = ns_all = 'xmlns="{0}" xmlns:page="{1}" xmlns:html="{2}" xmlns:xlink="{3}" xmlns:xml="{4}"'.format(moin_page.namespace, moin_page.namespace, html.namespace, xlink.namespace, xml.namespace)
--- a/MoinMoin/converter/_tests/test_html_in.py	Mon Apr 27 22:02:22 2015 +0200
+++ b/MoinMoin/converter/_tests/test_html_in.py	Mon Apr 27 22:07:21 2015 +0200
@@ -15,9 +15,10 @@
 
 from emeraldtree.tree import *
 
+from MoinMoin.converter.html_in import *
+
 from MoinMoin import log
 logging = log.getLogger(__name__)
-from MoinMoin.converter.html_in import *
 
 
 class Base(object):
--- a/MoinMoin/converter/_tests/test_html_in_out.py	Mon Apr 27 22:02:22 2015 +0200
+++ b/MoinMoin/converter/_tests/test_html_in_out.py	Mon Apr 27 22:07:21 2015 +0200
@@ -16,13 +16,13 @@
 
 etree = pytest.importorskip('lxml.etree')
 
-from MoinMoin import log
-logging = log.getLogger(__name__)
-
 from MoinMoin.converter.html_in import Converter as HTML_IN
 from MoinMoin.converter.html_out import Converter as HTML_OUT
 from MoinMoin.util.tree import html, moin_page, xlink
 
+from MoinMoin import log
+logging = log.getLogger(__name__)
+
 
 class Base(object):
 
--- a/MoinMoin/converter/_tests/test_html_out.py	Mon Apr 27 22:02:22 2015 +0200
+++ b/MoinMoin/converter/_tests/test_html_out.py	Mon Apr 27 22:07:21 2015 +0200
@@ -16,11 +16,11 @@
 
 from emeraldtree.tree import *
 
+from MoinMoin.converter.html_out import *
+
 from MoinMoin import log
 logging = log.getLogger(__name__)
 
-from MoinMoin.converter.html_out import *
-
 
 class Base(object):
     input_namespaces = ns_all = 'xmlns="{0}" xmlns:page="{1}" xmlns:html="{2}" xmlns:xlink="{3}" xmlns:xml="{4}"'.format(moin_page.namespace, moin_page.namespace, html.namespace, xlink.namespace, xml.namespace)
--- a/MoinMoin/converter/_wiki_macro.py	Mon Apr 27 22:02:22 2015 +0200
+++ b/MoinMoin/converter/_wiki_macro.py	Mon Apr 27 22:07:21 2015 +0200
@@ -7,8 +7,6 @@
 Base class for wiki parser with macro support.
 """
 
-from MoinMoin import log
-logging = log.getLogger(__name__)
 
 from emeraldtree import ElementTree as ET
 
@@ -16,6 +14,9 @@
 from MoinMoin.util.mime import Type
 from MoinMoin.util.tree import moin_page, xinclude
 
+from MoinMoin import log
+logging = log.getLogger(__name__)
+
 
 class ConverterMacro(object):
     def _BR_repl(self, args, text, context_block):
--- a/MoinMoin/converter/archive_in.py	Mon Apr 27 22:02:22 2015 +0200
+++ b/MoinMoin/converter/archive_in.py	Mon Apr 27 22:07:21 2015 +0200
@@ -14,13 +14,13 @@
 
 from ._table import TableMixin
 
-from MoinMoin import log
-logging = log.getLogger(__name__)
-
 from MoinMoin.i18n import _, L_, N_
 from MoinMoin.util.iri import Iri
 from MoinMoin.util.tree import moin_page, xlink
 
+from MoinMoin import log
+logging = log.getLogger(__name__)
+
 
 class ArchiveException(Exception):
     """
--- a/MoinMoin/converter/docbook_in.py	Mon Apr 27 22:02:22 2015 +0200
+++ b/MoinMoin/converter/docbook_in.py	Mon Apr 27 22:07:21 2015 +0200
@@ -25,14 +25,13 @@
     # in case converters become an independent package
     flaskg = None
 
-from MoinMoin import log
-logging = log.getLogger(__name__)
-
 from MoinMoin.util.tree import moin_page, xlink, docbook, xml, html
 from MoinMoin.converter.html_out import mark_item_as_transclusion
 
 from ._wiki_macro import ConverterMacro
 from ._util import allowed_uri_scheme, decode_data, normalize_split_text
+from MoinMoin import log
+logging = log.getLogger(__name__)
 
 
 class NameSpaceError(Exception):
--- a/MoinMoin/converter/docbook_out.py	Mon Apr 27 22:02:22 2015 +0200
+++ b/MoinMoin/converter/docbook_out.py	Mon Apr 27 22:07:21 2015 +0200
@@ -13,12 +13,12 @@
 
 from emeraldtree import ElementTree as ET
 
+from MoinMoin.util.tree import html, moin_page, xlink, docbook, xml
+from MoinMoin.constants.contenttypes import CONTENTTYPE_NONEXISTENT
+
 from MoinMoin import log
 logging = log.getLogger(__name__)
 
-from MoinMoin.util.tree import html, moin_page, xlink, docbook, xml
-from MoinMoin.constants.contenttypes import CONTENTTYPE_NONEXISTENT
-
 
 class Converter(object):
     """
--- a/MoinMoin/converter/html_in.py	Mon Apr 27 22:02:22 2015 +0200
+++ b/MoinMoin/converter/html_in.py	Mon Apr 27 22:07:21 2015 +0200
@@ -17,14 +17,14 @@
 from emeraldtree import ElementTree as ET
 from emeraldtree.html import HTML
 
-from MoinMoin import log
-logging = log.getLogger(__name__)
-
 from MoinMoin.util.tree import html, moin_page, xlink, xml
 
 from ._wiki_macro import ConverterMacro
 from ._util import allowed_uri_scheme, decode_data, normalize_split_text
 
+from MoinMoin import log
+logging = log.getLogger(__name__)
+
 
 class Converter(object):
     """
@@ -96,8 +96,7 @@
         # We create an element tree from the HTML content
         # The content is a list of string, line per line
         # We can concatenate all in one string
-        html_str = ''
-        html_str = html_str.join(content)
+        html_str = u'\n'.join(content)
         html_tree = HTML(html_str)
 
         # We should have a root element, which will be converted as <page>
--- a/MoinMoin/converter/html_out.py	Mon Apr 27 22:02:22 2015 +0200
+++ b/MoinMoin/converter/html_out.py	Mon Apr 27 22:07:21 2015 +0200
@@ -22,6 +22,9 @@
 from MoinMoin.util.tree import html, moin_page, xlink, xml, Name, xinclude
 from MoinMoin.constants.contenttypes import CONTENTTYPE_NONEXISTENT
 from MoinMoin.util.iri import Iri
+from MoinMoin.util.mime import Type, type_moin_document
+
+from . import default_registry
 
 from MoinMoin import log
 logging = log.getLogger(__name__)
@@ -817,6 +820,4 @@
     """
 
 
-from . import default_registry
-from MoinMoin.util.mime import Type, type_moin_document
 default_registry.register(ConverterPage._factory, type_moin_document, Type('application/x-xhtml-moin-page'))
--- a/MoinMoin/converter/include.py	Mon Apr 27 22:02:22 2015 +0200
+++ b/MoinMoin/converter/include.py	Mon Apr 27 22:07:21 2015 +0200
@@ -89,9 +89,6 @@
 import types
 import copy
 
-from MoinMoin import log
-logging = log.getLogger(__name__)
-
 from flask import current_app as app
 from flask import g as flaskg
 
@@ -107,6 +104,10 @@
 
 from ._args import Arguments
 
+from MoinMoin import log
+logging = log.getLogger(__name__)
+
+
 # elements generated by moin wiki markup that cannot have block children
 NO_BLOCK_CHILDREN = [
     'p',
--- a/MoinMoin/converter/markdown_in.py	Mon Apr 27 22:02:22 2015 +0200
+++ b/MoinMoin/converter/markdown_in.py	Mon Apr 27 22:07:21 2015 +0200
@@ -18,9 +18,7 @@
 from MoinMoin.util.tree import moin_page, xml, html, xlink, xinclude
 from ._util import allowed_uri_scheme, decode_data
 from MoinMoin.util.iri import Iri
-
-from MoinMoin import log
-logging = log.getLogger(__name__)
+from MoinMoin.converter.html_in import Converter as HTML_IN_Converter
 
 from emeraldtree import ElementTree as ET
 try:
@@ -31,6 +29,13 @@
 
 from markdown import Markdown
 import markdown.util as md_util
+from MoinMoin import log
+logging = log.getLogger(__name__)
+
+
+html_in_converter = HTML_IN_Converter()
+block_elements = 'p h blockcode ol ul pre address blockquote dl div fieldset form hr noscript table'.split()
+BLOCK_ELEMENTS = {moin_page(x) for x in block_elements}
 
 
 def postproc_text(markdown, text):
@@ -382,7 +387,7 @@
 
     def do_children(self, element, add_lineno=False):
         new = []
-        # markdown parser surrounds child nodes with u"\n" children, we ignore them here to avoid the issue in html_out.py
+        # markdown parser surrounds child nodes with unwanted u"\n" children, here we remove leading \n
         if hasattr(element, "text") and element.text is not None and element.text != u'\n':
             new.append(postproc_text(self.markdown, element.text))
 
@@ -395,6 +400,7 @@
                     r.attrib[html.data_lineno] = self.line_numbers.popleft()
                 r = (r, )
             new.extend(r)
+            # markdown parser surrounds child nodes with unwanted u"\n" children, here we drop trailing \n
             if hasattr(child, "tail") and child.tail is not None and child.tail != u'\n':
                 new.append(postproc_text(self.markdown, child.tail))
         return new
@@ -415,6 +421,7 @@
 
             * blank lines within lists create separate blocks
             * omitting a blank line after a heading combines 2 elements into one block
+            * using more than one blank lines between blocks
 
         The net result is we either have too few or too many line numbers in the generated list which
         will cause the double-click-to-edit autoscroll textarea to sometimes be off by several lines.
@@ -451,6 +458,61 @@
                 lineno += line_count + 2
         self.line_numbers = line_numbers
 
+    def embedded_markup(self, text):
+        """
+        Per http://meta.stackexchange.com/questions/1777/what-html-tags-are-allowed-on-stack-exchange-sites
+        markdown markup allows users to specify several "safe" HTML tags within a document. These tags include:
+
+            a b blockquote code del dd dl dt em h1 h2 h3 i img kbd li ol p pre s sup sub strong strike ul br hr
+
+        In addition, some markdown extensions output raw HTML tags (e.g. fenced outputs "<pre><code>...").
+        To prevent the <, > characters from being escaped, the embedded tags are converted to nodes by using
+        the converter in html_in.py.
+        """
+        try:
+            # work around a possible bug - there is a traceback if HTML document has no tags
+            p_text = html_in_converter(u'<p>%s</p>' % text)
+        except AssertionError:
+            # html_in converter (EmeraldTree) throws exceptions on markup style links: "Some text <http://moinmo.in> more text"
+            p_text = text
+
+        if not isinstance(p_text, unicode) and p_text.tag == moin_page.page and p_text[0].tag == moin_page.body and p_text[0][0].tag == moin_page.p:
+            # will fix possible problem of P node having block children later
+            return p_text[0][0]
+        return p_text
+
+    def convert_embedded_markup(self, node):
+        """
+        Recurse through tree looking for embedded markup.
+
+        :param node: a tree node
+        """
+        for idx, child in enumerate(node):
+            if isinstance(child, unicode):
+                if u'<' in child:
+                    node[idx] = self.embedded_markup(child)  # child is immutable string, so must do node[idx]
+            else:
+                # do not convert markup within a <pre> tag
+                if not child.tag == moin_page.blockcode:
+                    self.convert_embedded_markup(child)
+
+    def convert_invalid_p_nodes(self, node):
+        """
+        Processing embedded HTML tags within markup or output from extensions with embedded markup can
+        result in invalid HTML output caused by <p> tags enclosing a block element.
+
+        The solution is to search for these occurances and change the <p> tag to a <div>.
+
+        :param node: a tree node
+        """
+        for child in node:
+            if not isinstance(child, unicode):
+                if child.tag == moin_page.p and len(child):
+                    for grandchild in child:
+                        if not isinstance(grandchild, unicode) and grandchild.tag in BLOCK_ELEMENTS:
+                            child.tag = moin_page.div
+                self.convert_invalid_p_nodes(child)
+
     def __init__(self):
         self.markdown = Markdown(extensions=['extra', 'toc', ])
 
@@ -499,6 +561,8 @@
         converted = self.do_children(md_root, add_lineno=add_lineno)
         body = moin_page.body(children=converted)
         root = moin_page.page(children=[body])
+        self.convert_embedded_markup(root)
+        self.convert_invalid_p_nodes(root)
 
         return root
 
--- a/MoinMoin/converter/mediawiki_in.py	Mon Apr 27 22:02:22 2015 +0200
+++ b/MoinMoin/converter/mediawiki_in.py	Mon Apr 27 22:07:21 2015 +0200
@@ -17,9 +17,6 @@
 
 from werkzeug import url_encode
 
-from MoinMoin import log
-logging = log.getLogger(__name__)
-
 from MoinMoin.constants.contenttypes import CHARSET
 from MoinMoin.constants.misc import URI_SCHEMES
 from MoinMoin.util.iri import Iri
@@ -29,6 +26,9 @@
 from ._wiki_macro import ConverterMacro
 from ._util import decode_data, normalize_split_text, _Iter, _Stack
 
+from MoinMoin import log
+logging = log.getLogger(__name__)
+
 
 class _TableArguments(object):
     rules = r'''
--- a/MoinMoin/converter/moinwiki19_in.py	Mon Apr 27 22:02:22 2015 +0200
+++ b/MoinMoin/converter/moinwiki19_in.py	Mon Apr 27 22:07:21 2015 +0200
@@ -13,9 +13,6 @@
 
 import re
 
-from MoinMoin import log
-logging = log.getLogger(__name__)
-
 from MoinMoin import wikiutil
 from MoinMoin.constants.misc import URI_SCHEMES
 from MoinMoin.constants.chartypes import CHARS_LOWER, CHARS_UPPER
@@ -25,6 +22,9 @@
 
 from .moinwiki_in import Converter
 
+from MoinMoin import log
+logging = log.getLogger(__name__)
+
 
 class ConverterFormat19(Converter):
     @classmethod
--- a/MoinMoin/converter/moinwiki_in.py	Mon Apr 27 22:02:22 2015 +0200
+++ b/MoinMoin/converter/moinwiki_in.py	Mon Apr 27 22:07:21 2015 +0200
@@ -15,9 +15,6 @@
 
 from werkzeug import url_encode
 
-from MoinMoin import log
-logging = log.getLogger(__name__)
-
 from MoinMoin.constants.contenttypes import CHARSET
 from MoinMoin.constants.misc import URI_SCHEMES
 from MoinMoin.util.iri import Iri
@@ -30,6 +27,9 @@
 from ._wiki_macro import ConverterMacro
 from ._util import decode_data, normalize_split_text, _Iter, _Stack
 
+from MoinMoin import log
+logging = log.getLogger(__name__)
+
 
 class _TableArguments(object):
     rules = r'''
--- a/MoinMoin/converter/opendocument_in.py	Mon Apr 27 22:02:22 2015 +0200
+++ b/MoinMoin/converter/opendocument_in.py	Mon Apr 27 22:07:21 2015 +0200
@@ -12,11 +12,11 @@
 
 import zipfile
 
+from .xml_in import strip_xml
+
 from MoinMoin import log
 logging = log.getLogger(__name__)
 
-from .xml_in import strip_xml
-
 
 class OpenDocumentIndexingConverter(object):
     @classmethod
--- a/MoinMoin/converter/pdf_in.py	Mon Apr 27 22:02:22 2015 +0200
+++ b/MoinMoin/converter/pdf_in.py	Mon Apr 27 22:07:21 2015 +0200
@@ -8,9 +8,6 @@
 
 from __future__ import absolute_import, division
 
-from MoinMoin import log
-logging = log.getLogger(__name__)
-
 from pdfminer.pdfparser import PDFDocument, PDFParser
 from pdfminer.pdfinterp import PDFResourceManager, PDFPageInterpreter, process_pdf
 from pdfminer.pdfdevice import PDFDevice
@@ -18,6 +15,9 @@
 from pdfminer.cmapdb import CMapDB
 from pdfminer.layout import LAParams
 
+from MoinMoin import log
+logging = log.getLogger(__name__)
+
 
 LAPARAMS = LAParams(
     # value is specified not as an actual length, but as a proportion of the length to the
--- a/MoinMoin/converter/pygments_in.py	Mon Apr 27 22:02:22 2015 +0200
+++ b/MoinMoin/converter/pygments_in.py	Mon Apr 27 22:07:21 2015 +0200
@@ -17,14 +17,13 @@
 except ImportError:
     pygments = None
 
+from MoinMoin.util.mime import Type, type_moin_document
+from MoinMoin.util.tree import moin_page
+from ._util import decode_data, normalize_split_text
 
 from MoinMoin import log
 logging = log.getLogger(__name__)
 
-from MoinMoin.util.mime import Type, type_moin_document
-from MoinMoin.util.tree import moin_page
-from ._util import decode_data, normalize_split_text
-
 
 if pygments:
     class TreeFormatter(pygments.formatter.Formatter):
--- a/MoinMoin/converter/rst_in.py	Mon Apr 27 22:02:22 2015 +0200
+++ b/MoinMoin/converter/rst_in.py	Mon Apr 27 22:07:21 2015 +0200
@@ -22,9 +22,6 @@
 
 from werkzeug import url_encode, url_decode
 
-from MoinMoin import log
-logging = log.getLogger(__name__)
-
 try:
     from flask import g as flaskg
 except ImportError:
@@ -47,6 +44,9 @@
 from docutils.parsers.rst import directives, roles
 # ####
 
+from MoinMoin import log
+logging = log.getLogger(__name__)
+
 
 class NodeVisitor(object):
     """
--- a/MoinMoin/converter/rst_out.py	Mon Apr 27 22:02:22 2015 +0200
+++ b/MoinMoin/converter/rst_out.py	Mon Apr 27 22:07:21 2015 +0200
@@ -338,9 +338,7 @@
                             u' ' * (len(u''.join(self.list_item_labels)) + len(self.list_item_labels)), m.group(1)), child)
                     if self.last_closed == "p":
                         childrens_output.append(
-                            u'\n' + u' '
-                            * (len(''.join(self.list_item_labels)) +
-                               len(self.list_item_labels)))
+                            u'\n' + u' ' * (len(''.join(self.list_item_labels)) + len(self.list_item_labels)))
                 elif self.status[-1] == "text":
                     if self.last_closed == "p":
                         childrens_output.append(self.define_references())
@@ -395,8 +393,8 @@
     def open_moinpage_blockcode(self, elem):
         text = u''.join(elem.itertext())
         max_subpage_lvl = 3
-        text = text.replace(u'\n', u'\n  '
-                            + u' ' * (len(u''.join(self.list_item_labels)) + len(self.list_item_labels)))
+        text = text.replace(u'\n', u'\n  ' +
+                            u' ' * (len(u''.join(self.list_item_labels)) + len(self.list_item_labels)))
 
         if self.list_level >= 0:
             self.delete_newlines = True
@@ -436,10 +434,7 @@
 
     def open_moinpage_line_break(self, elem):
         if self.status[-1] == "list":
-            return (ReST.linebreak
-                    + u' '
-                      * (len(u''.join(self.list_item_labels))
-                         + len(self.list_item_labels)))
+            return (ReST.linebreak + u' ' * (len(u''.join(self.list_item_labels)) + len(self.list_item_labels)))
         if self.last_closed == 'p':
             return u'\n\n'
         return ReST.linebreak
@@ -473,9 +468,7 @@
         if self.list_item_labels[-1] == u'' or self.list_item_labels[-1] == u' ':
             self.list_item_labels[-1] = u' '
             self.list_item_label = self.list_item_labels[-1] + u' '
-            ret = (u' '
-                   * (len(u''.join(self.list_item_labels[:-1]))
-                      + len(self.list_item_labels[:-1])))
+            ret = (u' ' * (len(u''.join(self.list_item_labels[:-1])) + len(self.list_item_labels[:-1])))
             if self.last_closed and self.last_closed != 'list':
                 ret = u'\n{0}'.format(ret)
             return ret + self.open_children(elem)
@@ -485,9 +478,8 @@
         ret = u''
         if self.last_closed:
             ret = u'\n'
-        ret += (u' ' * (len(u''.join(self.list_item_labels[:-1]))
-                        + len(self.list_item_labels[:-1]))
-                + self.list_item_label)
+        ret += (u' ' * (len(u''.join(self.list_item_labels[:-1])) +
+                        len(self.list_item_labels[:-1])) + self.list_item_label)
         if self.list_item_labels[-1] in [u'1.', u'i.', u'I.', u'a.', u'A.']:
             self.list_item_labels[-1] = u'#.'
 
@@ -558,13 +550,12 @@
                 and self.last_closed != 'list_item_header' \
                 and self.last_closed != 'list_item_footer' \
                 and self.last_closed != 'p':
-                ret = (ReST.linebreak + u' '
-                                        * (len(u''.join(self.list_item_labels))
-                                           + len(self.list_item_labels)) + self.open_children(elem))
+                ret = (ReST.linebreak + u' ' * (len(u''.join(self.list_item_labels)) +
+                                                len(self.list_item_labels)) + self.open_children(elem))
             elif self.last_closed and self.last_closed == 'p':
                 # return ReST.p +\
-                ret = (u"\n" + u' ' * (len(u''.join(self.list_item_labels))
-                                       + len(self.list_item_labels)) + self.open_children(elem))
+                ret = (u"\n" + u' ' * (len(u''.join(self.list_item_labels)) +
+                                       len(self.list_item_labels)) + self.open_children(elem))
             else:
                 ret = self.open_children(elem)
             if not self.delete_newlines:
@@ -747,10 +738,10 @@
         """
         ret = u''
         self.all_used_references.extend(self.used_references)
-        definitions = [u" " * (len(u''.join(self.list_item_labels)) + len(self.list_item_labels))
-                       + u".. _{0}: {1}".format(t, h) for t, h in self.used_references]
-        definitions.extend(u" " * (len(u''.join(self.list_item_labels)) + len(self.list_item_labels))
-                           + link for link in self.objects)
+        definitions = [u" " * (len(u''.join(self.list_item_labels)) + len(self.list_item_labels)) +
+                       u".. _{0}: {1}".format(t, h) for t, h in self.used_references]
+        definitions.extend(u" " * (len(u''.join(self.list_item_labels)) + len(self.list_item_labels)) +
+                           link for link in self.objects)
         definition_block = u"\n\n".join(definitions)
 
         if definitions:
--- a/MoinMoin/converter/text_csv_in.py	Mon Apr 27 22:02:22 2015 +0200
+++ b/MoinMoin/converter/text_csv_in.py	Mon Apr 27 22:07:21 2015 +0200
@@ -15,6 +15,9 @@
 from MoinMoin.util.tree import moin_page
 from MoinMoin.i18n import _, L_, N_
 
+from . import default_registry
+from MoinMoin.util.mime import Type, type_moin_document
+
 
 class Converter(TableMixin):
     """
@@ -56,6 +59,4 @@
         return moin_page.page(children=(body, ))
 
 
-from . import default_registry
-from MoinMoin.util.mime import Type, type_moin_document
 default_registry.register(Converter._factory, Type('text/csv'), type_moin_document)
--- a/MoinMoin/converter/xml_in.py	Mon Apr 27 22:02:22 2015 +0200
+++ b/MoinMoin/converter/xml_in.py	Mon Apr 27 22:07:21 2015 +0200
@@ -9,10 +9,11 @@
 
 import re
 
+from ._util import decode_data
+
 from MoinMoin import log
 logging = log.getLogger(__name__)
 
-from ._util import decode_data
 
 RX_STRIPXML = re.compile(u"<[^>]*?>", re.U | re.DOTALL | re.MULTILINE)
 
--- a/MoinMoin/items/__init__.py	Mon Apr 27 22:02:22 2015 +0200
+++ b/MoinMoin/items/__init__.py	Mon Apr 27 22:07:21 2015 +0200
@@ -35,9 +35,6 @@
 
 from MoinMoin.constants.contenttypes import CONTENTTYPES_HELP_DOCS
 
-from MoinMoin import log
-logging = log.getLogger(__name__)
-
 from MoinMoin.security.textcha import TextCha, TextChaizedForm
 from MoinMoin.signalling import item_modified
 from MoinMoin.storage.middleware.protecting import AccessDenied
@@ -63,6 +60,10 @@
 from MoinMoin.util.notifications import DESTROY_REV, DESTROY_ALL
 
 from .content import content_registry, Content, NonExistentContent, Draw
+from ..util.pysupport import load_package_modules
+
+from MoinMoin import log
+logging = log.getLogger(__name__)
 
 
 COLS = 80
@@ -965,5 +966,4 @@
         pass
 
 
-from ..util.pysupport import load_package_modules
 load_package_modules(__name__, __path__)
--- a/MoinMoin/items/content.py	Mon Apr 27 22:02:22 2015 +0200
+++ b/MoinMoin/items/content.py	Mon Apr 27 22:07:21 2015 +0200
@@ -47,9 +47,6 @@
 except ImportError:
     PIL = PILImage = PILdiff = None
 
-from MoinMoin import log
-logging = log.getLogger(__name__)
-
 from MoinMoin import wikiutil
 from MoinMoin.i18n import _, L_
 from MoinMoin.themes import render_template
@@ -73,6 +70,9 @@
 from MoinMoin.constants.keys import (NAME_EXACT, WIKINAME, CONTENTTYPE, SIZE, TAGS,
                                      HASH_ALGORITHM, ACTION_SAVE)
 
+from MoinMoin import log
+logging = log.getLogger(__name__)
+
 
 COLS = 80
 ROWS_DATA = 20
--- a/MoinMoin/mail/sendmail.py	Mon Apr 27 22:02:22 2015 +0200
+++ b/MoinMoin/mail/sendmail.py	Mon Apr 27 22:07:21 2015 +0200
@@ -12,14 +12,15 @@
 import re
 from email.header import Header
 
-from MoinMoin import log
-logging = log.getLogger(__name__)
-
 from flask import current_app as app
 
 from MoinMoin.constants.contenttypes import CHARSET
 from MoinMoin.i18n import _, L_, N_
 
+from MoinMoin import log
+logging = log.getLogger(__name__)
+
+
 _transdict = {"AT": "@", "DOT": ".", "DASH": "-"}
 
 
--- a/MoinMoin/script/maint/index.py	Mon Apr 27 22:02:22 2015 +0200
+++ b/MoinMoin/script/maint/index.py	Mon Apr 27 22:07:21 2015 +0200
@@ -10,11 +10,11 @@
 from flask.ext.script import Command, Option
 from flask import g as flaskg
 
+from MoinMoin.constants.keys import LATEST_REVS, ALL_REVS
+
 from MoinMoin import log
 logging = log.getLogger(__name__)
 
-from MoinMoin.constants.keys import LATEST_REVS, ALL_REVS
-
 
 class IndexCreate(Command):
     description = 'Create empty indexes.'
--- a/MoinMoin/script/migration/moin19/import19.py	Mon Apr 27 22:02:22 2015 +0200
+++ b/MoinMoin/script/migration/moin19/import19.py	Mon Apr 27 22:07:21 2015 +0200
@@ -23,9 +23,6 @@
 from flask import current_app as app
 from flask.ext.script import Command, Option
 
-from MoinMoin import log
-logging = log.getLogger(__name__)
-
 from ._utils19 import quoteWikinameFS, unquoteWikiname, split_body
 from ._logfile19 import LogFile
 
@@ -41,6 +38,9 @@
 from MoinMoin.util.crypto import make_uuid
 from MoinMoin import security
 
+from MoinMoin import log
+logging = log.getLogger(__name__)
+
 
 CHARSET = 'utf-8'
 
--- a/MoinMoin/security/__init__.py	Mon Apr 27 22:02:22 2015 +0200
+++ b/MoinMoin/security/__init__.py	Mon Apr 27 22:07:21 2015 +0200
@@ -74,8 +74,7 @@
            * READ - gives permission to read, unconditionally
            * PUBREAD - gives permission to read, when published
         """
-        return (flaskg.storage.may(itemname, rights.READ, usernames=self.names)
-                or
+        return (flaskg.storage.may(itemname, rights.READ, usernames=self.names) or
                 flaskg.storage.may(itemname, rights.PUBREAD, usernames=self.names))
 
     def __getattr__(self, attr):
--- a/MoinMoin/security/textcha.py	Mon Apr 27 22:02:22 2015 +0200
+++ b/MoinMoin/security/textcha.py	Mon Apr 27 22:07:21 2015 +0200
@@ -24,9 +24,6 @@
 import re
 import random
 
-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
@@ -41,6 +38,10 @@
 
 from MoinMoin.i18n import _, L_, N_
 
+from MoinMoin import log
+logging = log.getLogger(__name__)
+
+
 SHA1_LEN = 40  # length of hexdigest
 TIMESTAMP_LEN = 10  # length of timestamp
 
--- a/MoinMoin/security/ticket.py	Mon Apr 27 22:02:22 2015 +0200
+++ b/MoinMoin/security/ticket.py	Mon Apr 27 22:07:21 2015 +0200
@@ -13,12 +13,12 @@
 import hmac
 import hashlib
 
+from flask import current_app as app
+from flask import g as flaskg
+
 from MoinMoin import log
 logging = log.getLogger(__name__)
 
-from flask import current_app as app
-from flask import g as flaskg
-
 
 def createTicket(tm=None, **kw):
     """ Create a ticket using a configured secret
--- a/MoinMoin/static/css/common.css	Mon Apr 27 22:02:22 2015 +0200
+++ b/MoinMoin/static/css/common.css	Mon Apr 27 22:07:21 2015 +0200
@@ -365,4 +365,5 @@
 
 #options dd { width: 10%; }
 #options dt { width: 60%; max-width: 40em; }
-#subscriptions textarea { width: 80%; max-width: 40em; }
+#subscriptions textarea { width: 80%; max-width: 40em; margin-bottom: 1em; }
+#subscriptions div.tip { width: 80%; max-width: 40em; margin: auto; }
--- a/MoinMoin/storage/backends/_tests/test_stores.py	Mon Apr 27 22:02:22 2015 +0200
+++ b/MoinMoin/storage/backends/_tests/test_stores.py	Mon Apr 27 22:07:21 2015 +0200
@@ -11,11 +11,16 @@
 
 from __future__ import absolute_import, division
 
+import os
+import tempfile
+
 from ..stores import MutableBackend
 from . import MutableBackendTestBase
 
 from MoinMoin.storage.stores.memory import BytesStore as MemoryBytesStore
 from MoinMoin.storage.stores.memory import FileStore as MemoryFileStore
+from MoinMoin.storage.stores.fs import BytesStore as FSBytesStore
+from MoinMoin.storage.stores.fs import FileStore as FSFileStore
 
 
 class TestMemoryBackend(MutableBackendTestBase):
@@ -26,12 +31,6 @@
         self.be.create()
         self.be.open()
 
-import os
-import tempfile
-
-from MoinMoin.storage.stores.fs import BytesStore as FSBytesStore
-from MoinMoin.storage.stores.fs import FileStore as FSFileStore
-
 
 class TestFSBackend(MutableBackendTestBase):
     def setup_method(self, method):
--- a/MoinMoin/storage/middleware/indexing.py	Mon Apr 27 22:02:22 2015 +0200
+++ b/MoinMoin/storage/middleware/indexing.py	Mon Apr 27 22:07:21 2015 +0200
@@ -55,8 +55,7 @@
 import shutil
 import datetime
 
-from MoinMoin import log
-logging = log.getLogger(__name__)
+from collections import Mapping
 
 from flask import request
 from flask import g as flaskg
@@ -80,6 +79,15 @@
 from MoinMoin.storage.error import NoSuchItemError, ItemAlreadyExistsError
 from MoinMoin.util.interwiki import split_fqname, CompositeName
 
+from MoinMoin.util.mime import Type, type_moin_document
+from MoinMoin.util.tree import moin_page
+from MoinMoin.converter import default_registry
+from MoinMoin.util.iri import Iri
+
+from MoinMoin import log
+logging = log.getLogger(__name__)
+
+
 WHOOSH_FILESTORAGE = 'FileStorage'
 INDEXES = [LATEST_REVS, ALL_REVS, ]
 
@@ -166,12 +174,6 @@
     return subscription_ids, subscription_patterns
 
 
-from MoinMoin.util.mime import Type, type_moin_document
-from MoinMoin.util.tree import moin_page
-from MoinMoin.converter import default_registry
-from MoinMoin.util.iri import Iri
-
-
 def convert_to_indexable(meta, data, item_name=None, is_new=False):
     """
     Convert revision data to a indexable content.
@@ -1226,9 +1228,6 @@
         return cmp(self.meta, other.meta)
 
 
-from collections import Mapping
-
-
 class Meta(Mapping):
     def __init__(self, revision, doc, meta=None):
         self.revision = revision
--- a/MoinMoin/storage/middleware/protecting.py	Mon Apr 27 22:02:22 2015 +0200
+++ b/MoinMoin/storage/middleware/protecting.py	Mon Apr 27 22:07:21 2015 +0200
@@ -16,9 +16,6 @@
 
 import time
 
-from MoinMoin import log
-logging = log.getLogger(__name__)
-
 from whoosh.util.cache import lru_cache
 
 from MoinMoin.constants.rights import (CREATE, READ, PUBREAD, WRITE, ADMIN, DESTROY, ACL_RIGHTS_CONTENTS)
@@ -28,6 +25,10 @@
 
 from MoinMoin.util.interwiki import split_fqname
 
+from MoinMoin import log
+logging = log.getLogger(__name__)
+
+
 # max sizes of some lru caches:
 LOOKUP_CACHE = 100  # ACL lookup for some itemname
 PARSE_CACHE = 100  # ACL string -> ACL object parsing
--- a/MoinMoin/templates/openid_register.html	Mon Apr 27 22:02:22 2015 +0200
+++ b/MoinMoin/templates/openid_register.html	Mon Apr 27 22:07:21 2015 +0200
@@ -17,6 +17,7 @@
                 {{ forms.render(form['email']) }}
                 {{ forms.render_textcha(gen, form) }}
             </dl>
+            <input type="hidden" name="openid_submit" value="1" >
             {{ forms.render_submit(form) }}
         {{ gen.form.close() }}
     </div>
--- a/MoinMoin/templates/usersettings_forms.html	Mon Apr 27 22:02:22 2015 +0200
+++ b/MoinMoin/templates/usersettings_forms.html	Mon Apr 27 22:07:21 2015 +0200
@@ -77,11 +77,26 @@
 {% endmacro %}
 
 {% macro subscriptions(form) %}
+    {% set myitemname =  _('MyItemName')  %}
+    {% set mytagname =  _('MyTagName')  %}
+    {% set mynamespace =  _('MyNameSpace')  %}
+    {% set my =  _('My')  %}
+    {% set itemid =  _('item id')  %}
     {{ gen.form.open(form, method="post", action=url_for('frontend.usersettings')) }}
         {{ forms.render_errors(form) }}
         <dl>
             {{ forms.render(form['subscriptions']) }}
         </dl>
+        <div class="tip">
+            {{ _('Formats for entering subscriptions:') }}
+            <ul>
+                <li>name::{{ myitemname }} or name:{{ mynamespace }}:{{ myitemname }}</li>
+                <li>nameprefix::{{ my }}</li>
+                <li>tags::{{ mytagname }}</li>
+                <li>namere::.*</li>
+                <li>itemid:&lt;{{ itemid }}&gt; {{ _('is created/removed by clicking a Subscribe/Unsubscribe link') }}</li>
+            </ul>
+        </div>
         {{ forms.render_submit(form, 'part', 'subscriptions') }}
     {{ gen.form.close() }}
 {% endmacro %}
--- a/MoinMoin/themes/__init__.py	Mon Apr 27 22:02:22 2015 +0200
+++ b/MoinMoin/themes/__init__.py	Mon Apr 27 22:07:21 2015 +0200
@@ -17,9 +17,6 @@
 from flask import url_for, request
 from flask.ext.themes import get_theme, render_theme_template
 
-from MoinMoin import log
-logging = log.getLogger(__name__)
-
 from MoinMoin.i18n import _, L_, N_
 from MoinMoin import wikiutil, user
 from MoinMoin.constants.keys import USERID, ADDRESS, HOSTNAME, REVID, ITEMID, NAME_EXACT, ASSIGNED_TO
@@ -32,6 +29,9 @@
 from MoinMoin.util.clock import timed
 from MoinMoin.util.mime import Type
 
+from MoinMoin import log
+logging = log.getLogger(__name__)
+
 
 def get_current_theme():
     # this might be called at a time when flaskg.user is not setup yet:
--- a/MoinMoin/themes/basic/templates/modify.html	Mon Apr 27 22:02:22 2015 +0200
+++ b/MoinMoin/themes/basic/templates/modify.html	Mon Apr 27 22:07:21 2015 +0200
@@ -102,3 +102,9 @@
         {{ gen.form.close() }}
     </div>
 {% endblock %}
+
+{% block options_for_javascript %}
+    {%- if user.scroll_page_after_edit -%}
+        <br id="moin-scroll-page-after-edit" />
+    {%- endif %}
+{% endblock %}
--- a/MoinMoin/user.py	Mon Apr 27 22:02:22 2015 +0200
+++ b/MoinMoin/user.py	Mon Apr 27 22:07:21 2015 +0200
@@ -32,9 +32,6 @@
 from flask import session, request, url_for, render_template
 from jinja2.runtime import Undefined
 
-from MoinMoin import log
-logging = log.getLogger(__name__)
-
 from MoinMoin import wikiutil
 from MoinMoin.constants.contenttypes import CONTENTTYPE_USER
 from MoinMoin.constants.namespaces import NAMESPACE_USERPROFILES
@@ -47,6 +44,9 @@
 from MoinMoin.util.subscriptions import get_matched_subscription_patterns
 from MoinMoin.storage.error import NoSuchItemError, ItemAlreadyExistsError, NoSuchRevisionError
 
+from MoinMoin import log
+logging = log.getLogger(__name__)
+
 
 def create_user(username, password, email, validate=True, is_encrypted=False, **meta):
     """
--- a/MoinMoin/util/clock.py	Mon Apr 27 22:02:22 2015 +0200
+++ b/MoinMoin/util/clock.py	Mon Apr 27 22:07:21 2015 +0200
@@ -10,11 +10,11 @@
 import time
 from functools import wraps, partial
 
+from flask import g as flaskg
+
 from MoinMoin import log
 logging = log.getLogger(__name__)
 
-from flask import g as flaskg
-
 
 class Clock(object):
     """
--- a/MoinMoin/util/subscriptions.py	Mon Apr 27 22:02:22 2015 +0200
+++ b/MoinMoin/util/subscriptions.py	Mon Apr 27 22:07:21 2015 +0200
@@ -16,9 +16,11 @@
 from MoinMoin.constants.keys import (DEFAULT_LOCALE, EMAIL, EMAIL_UNVALIDATED, ITEMID,
                                      LATEST_REVS, LOCALE, NAME, NAMERE, NAMEPREFIX,
                                      NAMESPACE, SUBSCRIPTION_IDS, SUBSCRIPTION_PATTERNS, TAGS)
+
+from MoinMoin.util.interwiki import CompositeName
+
 from MoinMoin import log
 logging = log.getLogger(__name__)
-from MoinMoin.util.interwiki import CompositeName
 
 
 Subscriber = namedtuple('Subscriber', [ITEMID, NAME, EMAIL, LOCALE])
@@ -75,9 +77,17 @@
     item_namespace = meta.get(NAMESPACE)
     matched_subscriptions = []
     for subscription in subscription_patterns:
-        keyword, value = subscription.split(":", 1)
+        try:
+            keyword, value = subscription.split(":", 1)
+        except ValueError:
+            logging.exception("User {0} has invalid subscription entry: {1}".format(flaskg.user.name[0], subscription))
+            continue
         if keyword in (NAMEPREFIX, NAMERE, ) and item_namespace is not None and item_names:
-            namespace, pattern = value.split(":", 1)
+            try:
+                namespace, pattern = value.split(":", 1)
+            except ValueError:
+                logging.exception("User {0} has invalid subscription entry: {1}".format(flaskg.user.name[0], subscription))
+                continue
             if item_namespace == namespace:
                 if keyword == NAMEPREFIX:
                     if any(name.startswith(pattern) for name in item_names):
--- a/MoinMoin/wikiutil.py	Mon Apr 27 22:02:22 2015 +0200
+++ b/MoinMoin/wikiutil.py	Mon Apr 27 22:07:21 2015 +0200
@@ -15,13 +15,12 @@
 
 import os
 
-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
 
+import werkzeug
+
 from MoinMoin.constants.contenttypes import CHARSET
 from MoinMoin.constants.keys import CURRENT
 from MoinMoin.constants.misc import URI_SCHEMES, CLEAN_INPUT_TRANSLATION_MAP, ITEM_INVALID_CHARS_REGEX
@@ -31,7 +30,9 @@
 from MoinMoin.util.mimetype import MimeType
 from MoinMoin.storage.error import NoSuchItemError, NoSuchRevisionError
 
-import werkzeug
+from MoinMoin import log
+logging = log.getLogger(__name__)
+
 
 # constants for page names
 PARENT_PREFIX = "../"
--- a/docs/devel/development.rst	Mon Apr 27 22:02:22 2015 +0200
+++ b/docs/devel/development.rst	Mon Apr 27 22:07:21 2015 +0200
@@ -103,7 +103,7 @@
 
     ./m css  # Windows: m css
     hg diff  # verify nothing changed
-* check for coding errors (tabs, trailing spaces, line endings)::
+* check for coding errors (tabs, trailing spaces, line endings, template indentation and spacing)::
 
     ./m coding-std  # Windows: m coding-std
     hg diff  # verify nothing changed
@@ -140,7 +140,13 @@
   channel
 * to avoid duplicate work, add a comment on the issue tracker that you are
   working on that issue
-* just before you start to code changes, update your local repo: "hg pull -u"
+* just before you start to code changes, bring your repo up to date::
+
+    hg pull -u      # pull all recent changes
+    ./m coding-std  # just in case someone else forgot to do it
+    ./m css         # just in case
+    hg diff         # expect no changes
+    ./m tests       # note existing errors
 
 develop a testing strategy
 --------------------------
@@ -151,7 +157,6 @@
   implement it
 * make a plan for using a browser to test your changes; which wiki pages are
   effected, how many browsers must be tested
-* run "./m tests" to determine if there are any existing test failures before you make changes
 
 develop a working solution
 --------------------------
@@ -201,9 +206,13 @@
 * commit your changes to your local repo, use a concise commit comment
   describing the change
 
-  * if your change fixes a bitbucket issue, include the number as "fixes #nnn" in your commit comment
-* pull any changes made by others from the main repo on Bitbucket, then
-  merge and commit the merge
+  * while a commit message may have multiple lines, many tools show only 80 characters of the first line
+  * stuff as much info as possible into those first 80 characters::
+
+        <concise description of your change>, fixes #123
+
+* pull any changes made by others from the main repo on Bitbucket,
+  merge, then commit the merge
 * push the changeset to your public bitbucket repo
 * create a pull request so your changes will get pulled into the
   main repository
@@ -437,3 +446,11 @@
 the local HTML docs that are linked to under the User tab. To generate local docs::
 
     ./m docs  # Windows: m docs
+
+Moin Shell
+==========
+
+While the make.py utility provides a menu of the most frequently used commands, there may be an occasional need to access the moin shell directly::
+
+    source <path-to-venv>/bin/activate  # or ". activate"  windows: "activate"
+    moin -h                             # show help
--- a/make.py	Mon Apr 27 22:02:22 2015 +0200
+++ b/make.py	Mon Apr 27 22:07:21 2015 +0200
@@ -191,6 +191,12 @@
     return subprocess.check_output(command, shell=True)
 
 
+def get_sitepackages_location():
+    """Return the location of the virtualenv site-packages directory."""
+    command = ACTIVATE + 'python -c "from distutils.sysconfig import get_python_lib; print get_python_lib()"'
+    return subprocess.check_output(command, shell=True).strip()
+
+
 class Commands(object):
     """Each cmd_ method processes a choice on the menu."""
     def __init__(self):
@@ -222,7 +228,12 @@
 
     def cmd_extras(self, *args):
         """install optional packages: OpenID, Pillow, pymongo, sqlalchemy, ldap; and upload.py"""
+        sp_dir = get_sitepackages_location()
         upload = '{0} MoinMoin/script/win/wget.py https://codereview.appspot.com/static/upload.py upload.py'.format(sys.executable)
+        # TODO oldsessions is short term fix for obsolete OpenID 2.0, see #515
+        # http://pythonhosted.org//Flask-OldSessions/ docs are broken see https://github.com/mitsuhiko/flask-oldsessions/issues/1
+        # we do wget of flask_oldsessions.py to site-packages as another workaround
+        oldsessions = '{0} MoinMoin/script/win/wget.py https://raw.githubusercontent.com/mitsuhiko/flask-oldsessions/master/flask_oldsessions.py {1}/flask_oldsessions.py'.format(sys.executable, sp_dir)
         packages = ['python-openid', 'pillow', 'pymongo', 'sqlalchemy', ]
         if WINDOWS_OS:
             installer = 'easy_install --upgrade '
@@ -233,7 +244,7 @@
         else:
             installer = 'pip install --upgrade '
             packages.append('python-ldap')
-        command = ACTIVATE + installer + (SEP + installer).join(packages) + SEP + upload
+        command = ACTIVATE + installer + (SEP + installer).join(packages) + SEP + upload + SEP + oldsessions
         print 'Installing {0}, upload.py... output messages written to {1}.'.format(', '.join(packages), EXTRAS)
         with open(EXTRAS, 'w') as messages:
             subprocess.call(command, shell=True, stderr=messages, stdout=messages)
--- a/setup.cfg	Mon Apr 27 22:02:22 2015 +0200
+++ b/setup.cfg	Mon Apr 27 22:07:21 2015 +0200
@@ -48,7 +48,9 @@
 pep8ignore =
  *.py E124  # closing bracket does not match visual indentation (behaves strange!?)
  *.py E125  # continuation line does not distinguish itself from next logical line (difficult to avoid!)
+ *.py E402  # module level import not at top of file
  *.py E501  # maximum line length (see also pep8maxlinelength)
+ */util/*.py E731  # do not assign a lambda expression, use a def
  MoinMoin/config/default.py E501  # maximum line length (long lines expected there)
  MoinMoin/config/default.py E122  # continuation line missing indentation or outdented
  MoinMoin/constants/chartypes.py E501  # auto-generated, long lines
--- a/setup.py	Mon Apr 27 22:02:22 2015 +0200
+++ b/setup.py	Mon Apr 27 22:07:21 2015 +0200
@@ -4,6 +4,8 @@
 # License: GNU GPL v2 (or any later version), see LICENSE.txt for details.
 
 import os
+from setuptools import setup, find_packages
+
 import MoinMoin  # validate python version
 
 
@@ -11,8 +13,6 @@
 with open(os.path.join(basedir, 'README.txt')) as f:
     long_description = f.read()
 
-from setuptools import setup, find_packages
-
 
 setup_args = dict(
     name="moin",