changeset 2937:456c68761e96

merge
author RogerHaase <haaserd@gmail.com>
date Tue, 28 Apr 2015 12:17:55 -0700
parents 4ab27780d078 (diff) cd48671a0488 (current diff)
children dde4eb0904f7
files MoinMoin/apps/frontend/views.py MoinMoin/templates/utils.html MoinMoin/themes/__init__.py
diffstat 71 files changed, 431 insertions(+), 247 deletions(-) [+]
line wrap: on
line diff
--- a/MoinMoin/__init__.py	Tue Mar 10 00:42:12 2015 +0530
+++ b/MoinMoin/__init__.py	Tue Apr 28 12:17:55 2015 -0700
@@ -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	Tue Mar 10 00:42:12 2015 +0530
+++ b/MoinMoin/_tests/ldap_testbase.py	Tue Apr 28 12:17:55 2015 -0700
@@ -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	Tue Mar 10 00:42:12 2015 +0530
+++ b/MoinMoin/app.py	Tue Apr 28 12:17:55 2015 -0700
@@ -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
@@ -208,7 +214,12 @@
     flaskg._login_messages = []
 
     # first try setting up from session
-    userobj = auth.setup_from_session()
+    try:
+        userobj = auth.setup_from_session()
+    except KeyError:
+        # error caused due to invalid cookie, recreating session
+        session.clear()
+        userobj = auth.setup_from_session()
 
     # then handle login/logout forms
     form = request.values.to_dict()
--- a/MoinMoin/apps/admin/views.py	Tue Mar 10 00:42:12 2015 +0530
+++ b/MoinMoin/apps/admin/views.py	Tue Apr 28 12:17:55 2015 -0700
@@ -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	Tue Mar 10 00:42:12 2015 +0530
+++ b/MoinMoin/apps/feed/views.py	Tue Apr 28 12:17:55 2015 -0700
@@ -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	Tue Mar 10 00:42:12 2015 +0530
+++ b/MoinMoin/apps/frontend/views.py	Tue Apr 28 12:17:55 2015 -0700
@@ -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():
@@ -1270,7 +1270,7 @@
     """a simple user registration form"""
     name = 'register'
 
-    username = RequiredText.using(label=L_('Name')).with_properties(placeholder=L_("The login name you want to use"))
+    username = RequiredText.using(label=L_('Username')).with_properties(placeholder=L_("The login username you want to use"))
     password1 = RequiredPassword.with_properties(placeholder=L_("The login password you want to use"))
     password2 = RequiredPassword.with_properties(placeholder=L_("Repeat the same password"))
     email = YourEmail
@@ -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')
 
@@ -1556,7 +1555,7 @@
     """
     name = 'login'
 
-    username = RequiredText.using(label=L_('Name'), optional=False).with_properties(autofocus=True)
+    username = RequiredText.using(label=L_('Username'), optional=False).with_properties(autofocus=True)
     password = RequiredPassword
     openid = YourOpenID.using(optional=True)
     # This field results in a login_submit field in the POST form, which is in
@@ -1569,6 +1568,10 @@
 
 @frontend.route('/+login', methods=['GET', 'POST'])
 def login():
+    if flaskg.user.valid:
+        return redirect(url_for('.show_root'))
+
+
     # TODO use ?next=next_location check if target is in the wiki and not outside domain
     title_name = _(u'Login')
 
@@ -1618,6 +1621,8 @@
     password_problem_msg = L_('New password is unacceptable, could not get processed.')
 
     def validate(self, element, state):
+        password_not_accepted_msg = L_('New password not acceptable: ')
+
         if not (element['password_current'].valid and element['password1'].valid and element['password2'].valid):
             return False
 
@@ -1628,6 +1633,11 @@
             return self.note_error(element, state, 'passwords_mismatch_msg')
 
         password = element['password1'].value
+        pw_checker = app.cfg.password_checker
+        if pw_checker:
+            pw_error = pw_checker(flaskg.user.name[0], password)
+            if pw_error:
+                return self.note_error(element, state, message=password_not_accepted_msg + pw_error)
         try:
             app.cfg.cache.pwd_context.encrypt(password)
         except (ValueError, TypeError) as err:
@@ -1672,11 +1682,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():
@@ -1686,7 +1735,7 @@
     # these forms can't be global because we need app object, which is only available within a request:
     class UserSettingsPersonalForm(Form):
         name = 'usersettings_personal'  # "name" is duplicate
-        name = Names.using(label=L_('Names')).with_properties(placeholder=L_("The login names you want to use"))
+        name = Names.using(label=L_('Usernames')).with_properties(placeholder=L_("The login usernames you want to use"))
         display_name = OptionalText.using(label=L_('Display-Name')).with_properties(
             placeholder=L_("Your display name (informational)"))
         openid = YourOpenID.using(optional=True)
@@ -1798,7 +1847,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	Tue Mar 10 00:42:12 2015 +0530
+++ b/MoinMoin/apps/serve/views.py	Tue Apr 28 12:17:55 2015 -0700
@@ -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	Tue Mar 10 00:42:12 2015 +0530
+++ b/MoinMoin/auth/_tests/test_log.py	Tue Apr 28 12:17:55 2015 -0700
@@ -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	Tue Mar 10 00:42:12 2015 +0530
+++ b/MoinMoin/auth/http.py	Tue Apr 28 12:17:55 2015 -0700
@@ -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	Tue Mar 10 00:42:12 2015 +0530
+++ b/MoinMoin/auth/ldap_login.py	Tue Apr 28 12:17:55 2015 -0700
@@ -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	Tue Mar 10 00:42:12 2015 +0530
+++ b/MoinMoin/auth/log.py	Tue Apr 28 12:17:55 2015 -0700
@@ -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	Tue Mar 10 00:42:12 2015 +0530
+++ b/MoinMoin/auth/openidrp.py	Tue Apr 28 12:17:55 2015 -0700
@@ -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	Tue Mar 10 00:42:12 2015 +0530
+++ b/MoinMoin/auth/smb_mount.py	Tue Apr 28 12:17:55 2015 -0700
@@ -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	Tue Mar 10 00:42:12 2015 +0530
+++ b/MoinMoin/config/default.py	Tue Apr 28 12:17:55 2015 -0700
@@ -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	Tue Mar 10 00:42:12 2015 +0530
+++ b/MoinMoin/conftest.py	Tue Apr 28 12:17:55 2015 -0700
@@ -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	Tue Mar 10 00:42:12 2015 +0530
+++ b/MoinMoin/converter/_tests/test_docbook_in.py	Tue Apr 28 12:17:55 2015 -0700
@@ -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	Tue Mar 10 00:42:12 2015 +0530
+++ b/MoinMoin/converter/_tests/test_docbook_out.py	Tue Apr 28 12:17:55 2015 -0700
@@ -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	Tue Mar 10 00:42:12 2015 +0530
+++ b/MoinMoin/converter/_tests/test_html_in.py	Tue Apr 28 12:17:55 2015 -0700
@@ -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	Tue Mar 10 00:42:12 2015 +0530
+++ b/MoinMoin/converter/_tests/test_html_in_out.py	Tue Apr 28 12:17:55 2015 -0700
@@ -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	Tue Mar 10 00:42:12 2015 +0530
+++ b/MoinMoin/converter/_tests/test_html_out.py	Tue Apr 28 12:17:55 2015 -0700
@@ -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	Tue Mar 10 00:42:12 2015 +0530
+++ b/MoinMoin/converter/_wiki_macro.py	Tue Apr 28 12:17:55 2015 -0700
@@ -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	Tue Mar 10 00:42:12 2015 +0530
+++ b/MoinMoin/converter/archive_in.py	Tue Apr 28 12:17:55 2015 -0700
@@ -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	Tue Mar 10 00:42:12 2015 +0530
+++ b/MoinMoin/converter/docbook_in.py	Tue Apr 28 12:17:55 2015 -0700
@@ -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	Tue Mar 10 00:42:12 2015 +0530
+++ b/MoinMoin/converter/docbook_out.py	Tue Apr 28 12:17:55 2015 -0700
@@ -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	Tue Mar 10 00:42:12 2015 +0530
+++ b/MoinMoin/converter/html_in.py	Tue Apr 28 12:17:55 2015 -0700
@@ -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	Tue Mar 10 00:42:12 2015 +0530
+++ b/MoinMoin/converter/html_out.py	Tue Apr 28 12:17:55 2015 -0700
@@ -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	Tue Mar 10 00:42:12 2015 +0530
+++ b/MoinMoin/converter/include.py	Tue Apr 28 12:17:55 2015 -0700
@@ -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	Tue Mar 10 00:42:12 2015 +0530
+++ b/MoinMoin/converter/markdown_in.py	Tue Apr 28 12:17:55 2015 -0700
@@ -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	Tue Mar 10 00:42:12 2015 +0530
+++ b/MoinMoin/converter/mediawiki_in.py	Tue Apr 28 12:17:55 2015 -0700
@@ -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	Tue Mar 10 00:42:12 2015 +0530
+++ b/MoinMoin/converter/moinwiki19_in.py	Tue Apr 28 12:17:55 2015 -0700
@@ -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	Tue Mar 10 00:42:12 2015 +0530
+++ b/MoinMoin/converter/moinwiki_in.py	Tue Apr 28 12:17:55 2015 -0700
@@ -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	Tue Mar 10 00:42:12 2015 +0530
+++ b/MoinMoin/converter/opendocument_in.py	Tue Apr 28 12:17:55 2015 -0700
@@ -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	Tue Mar 10 00:42:12 2015 +0530
+++ b/MoinMoin/converter/pdf_in.py	Tue Apr 28 12:17:55 2015 -0700
@@ -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	Tue Mar 10 00:42:12 2015 +0530
+++ b/MoinMoin/converter/pygments_in.py	Tue Apr 28 12:17:55 2015 -0700
@@ -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	Tue Mar 10 00:42:12 2015 +0530
+++ b/MoinMoin/converter/rst_in.py	Tue Apr 28 12:17:55 2015 -0700
@@ -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	Tue Mar 10 00:42:12 2015 +0530
+++ b/MoinMoin/converter/rst_out.py	Tue Apr 28 12:17:55 2015 -0700
@@ -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	Tue Mar 10 00:42:12 2015 +0530
+++ b/MoinMoin/converter/text_csv_in.py	Tue Apr 28 12:17:55 2015 -0700
@@ -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	Tue Mar 10 00:42:12 2015 +0530
+++ b/MoinMoin/converter/xml_in.py	Tue Apr 28 12:17:55 2015 -0700
@@ -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	Tue Mar 10 00:42:12 2015 +0530
+++ b/MoinMoin/items/__init__.py	Tue Apr 28 12:17:55 2015 -0700
@@ -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	Tue Mar 10 00:42:12 2015 +0530
+++ b/MoinMoin/items/content.py	Tue Apr 28 12:17:55 2015 -0700
@@ -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	Tue Mar 10 00:42:12 2015 +0530
+++ b/MoinMoin/mail/sendmail.py	Tue Apr 28 12:17:55 2015 -0700
@@ -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	Tue Mar 10 00:42:12 2015 +0530
+++ b/MoinMoin/script/maint/index.py	Tue Apr 28 12:17:55 2015 -0700
@@ -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	Tue Mar 10 00:42:12 2015 +0530
+++ b/MoinMoin/script/migration/moin19/import19.py	Tue Apr 28 12:17:55 2015 -0700
@@ -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	Tue Mar 10 00:42:12 2015 +0530
+++ b/MoinMoin/security/__init__.py	Tue Apr 28 12:17:55 2015 -0700
@@ -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	Tue Mar 10 00:42:12 2015 +0530
+++ b/MoinMoin/security/textcha.py	Tue Apr 28 12:17:55 2015 -0700
@@ -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	Tue Mar 10 00:42:12 2015 +0530
+++ b/MoinMoin/security/ticket.py	Tue Apr 28 12:17:55 2015 -0700
@@ -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	Tue Mar 10 00:42:12 2015 +0530
+++ b/MoinMoin/static/css/common.css	Tue Apr 28 12:17:55 2015 -0700
@@ -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/static/js/common.js	Tue Mar 10 00:42:12 2015 +0530
+++ b/MoinMoin/static/js/common.js	Tue Apr 28 12:17:55 2015 -0700
@@ -242,22 +242,6 @@
 };
 
 
-
-// OnMouseOver show the fqname of the item else only show the value/id.
-function togglefqname() {
-    "use strict";
-    var fullname, value;
-    $(".moin-fqname").hover(function () {
-        fullname = $(this).attr('data-fqname');
-        value = $(this).html();
-        $(this).html(fullname);
-    }, function () {
-        $(this).html(value);
-    });
-}
-$(document).ready(togglefqname);
-
-
 // Executed when user clicks insert-name button defined in modify.html.
 // When a page with subitems is modified, a subitems sidebar is present. User may
 // position caret in textarea and click button to insert name into textarea.
@@ -394,8 +378,8 @@
             newform.data('initialForm', newform.serialize());
             // replace the old form with the new one
             form.replaceWith(newform);
-            if (ev.currentTarget.name === 'usersettings_ui') {
-                // theme has changed, show user the new theme
+            if (ev.currentTarget.name === 'usersettings_ui' ||  ev.currentTarget.id === 'usersettings_personal') {
+                // theme or language may have changed, show user the new theme/language
                 location.reload(true);
             }
         }, 'json');
--- a/MoinMoin/storage/backends/_tests/test_stores.py	Tue Mar 10 00:42:12 2015 +0530
+++ b/MoinMoin/storage/backends/_tests/test_stores.py	Tue Apr 28 12:17:55 2015 -0700
@@ -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	Tue Mar 10 00:42:12 2015 +0530
+++ b/MoinMoin/storage/middleware/indexing.py	Tue Apr 28 12:17:55 2015 -0700
@@ -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	Tue Mar 10 00:42:12 2015 +0530
+++ b/MoinMoin/storage/middleware/protecting.py	Tue Apr 28 12:17:55 2015 -0700
@@ -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/index.html	Tue Mar 10 00:42:12 2015 +0530
+++ b/MoinMoin/templates/index.html	Tue Apr 28 12:17:55 2015 -0700
@@ -68,10 +68,6 @@
         {{ self.action_bars_inner() }}
 {% endblock %}
 
-{% macro entry_anchor(type, e) -%}
-    index-{{ type }}-{{ e.meta['itemid'] }}
-{%- endmacro %}
-
 {% set maxchars = 16 %}
 
 {% macro render_file_entry(e) %}
@@ -88,7 +84,7 @@
            {{ e.relname|truncate(maxchars, true, '..') }}
         </a>
         {% if e in dirs %}
-            <a href="#{{ entry_anchor('dir', e) }}"
+            <a href="{{ url_for('.index', item_name=e.fullname) }}"
                title="{{ _("This item also has subitems that match your filter.") }}">↓
             </a>
         {% endif %}
--- a/MoinMoin/templates/link_list_item_panel.html	Tue Mar 10 00:42:12 2015 +0530
+++ b/MoinMoin/templates/link_list_item_panel.html	Tue Apr 28 12:17:55 2015 -0700
@@ -1,4 +1,5 @@
 {% extends theme("show.html") %}
+{% from "utils.html" import item_panel %}
 {% block content %}
     {% if headline %}
         <h1>{{ headline }}</h1>
@@ -7,7 +8,7 @@
         Total: {{ fq_names|count }}
         <ul class="moin-link-list">
             {% for fq_name in fq_names|sort(attribute='value') %}
-                <li><a class="moin-fqname" href="{{ url_for('frontend.show_item', item_name=fq_name) }}" data-fqname="{{ fq_name }}">{{ fq_name.value }}</a></li>
+                {{ item_panel(fq_name) }}
             {% endfor %}
         </ul>
     {% endif %}
--- a/MoinMoin/templates/link_list_no_item_panel.html	Tue Mar 10 00:42:12 2015 +0530
+++ b/MoinMoin/templates/link_list_no_item_panel.html	Tue Apr 28 12:17:55 2015 -0700
@@ -1,4 +1,5 @@
 {% extends theme("layout.html") %}
+{% from "utils.html" import item_panel %}
 {% block content %}
     {% if headline %}
         <h1>{{ headline }}</h1>
@@ -7,13 +8,7 @@
         <h2>Total: {{ fq_names|count }}</h2>
         <ul class="moin-link-list">
             {% for fq_name in fq_names|sort(attribute='value') %}
-                <li>
-                    <a class="moin-fqname"
-                       href="{{ url_for('frontend.show_item', item_name=fq_name) }}"
-                       data-fqname="{{ fq_name }}">
-                           {{ fq_name.value }}
-                    </a>
-                </li>
+                {{ item_panel(fq_name) }}
             {% endfor %}
         </ul>
     {% endif %}
--- a/MoinMoin/templates/modify.html	Tue Mar 10 00:42:12 2015 +0530
+++ b/MoinMoin/templates/modify.html	Tue Apr 28 12:17:55 2015 -0700
@@ -31,6 +31,9 @@
 {% block content %}
     <h1>{{ title }}</h1>
     <div id="moin-modify" class="moin-form">
+        <a class="btn btn-success" style="float: right" href="{{ url_for('.download_item', item_name=item_name, mimetype='application/x.moin.download') }}">
+        <span class="fa fa-download"></span> {{ _('Download') }}
+        </a>
         {{ gen.form.open(form, method='post', enctype='multipart/form-data') }}
             {{ forms.render_errors(form) }}
             {#
--- a/MoinMoin/templates/mychanges.html	Tue Mar 10 00:42:12 2015 +0530
+++ b/MoinMoin/templates/mychanges.html	Tue Apr 28 12:17:55 2015 -0700
@@ -55,5 +55,7 @@
                 </tbody>
             </table>
         </div>
+    {% else %}
+        <p class='moin-flash moin-flash-javascript moin-flash-error'>No changes detected</p>
     {% endif %}
 {% endblock %}
--- a/MoinMoin/templates/openid_register.html	Tue Mar 10 00:42:12 2015 +0530
+++ b/MoinMoin/templates/openid_register.html	Tue Apr 28 12:17:55 2015 -0700
@@ -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/sitemap.html	Tue Mar 10 00:42:12 2015 +0530
+++ b/MoinMoin/templates/sitemap.html	Tue Apr 28 12:17:55 2015 -0700
@@ -20,7 +20,7 @@
                     <a href="{{ url_for('frontend.sitemap', item_name=entry) }}">
                         <span class="fa fa-sitemap"></span>
                     </a>
-                    <a class="moin-fqname" href="{{ url_for('frontend.show_item', item_name=entry) }}" data-fqname="{{ entry }}">
+                    <a class="moin-fqname" href="{{ url_for('frontend.show_item', item_name=entry) }}">
                         {{ entry.value }}
                     </a>
                 </li>
--- a/MoinMoin/templates/usersettings_forms.html	Tue Mar 10 00:42:12 2015 +0530
+++ b/MoinMoin/templates/usersettings_forms.html	Tue Apr 28 12:17:55 2015 -0700
@@ -1,7 +1,7 @@
 {% import "forms.html" as forms %}
 
 {% macro personal(form) %}
-    {{ gen.form.open(form, method="post", action=url_for('frontend.usersettings')) }}
+    {{ gen.form.open(form, id="usersettings_personal", method="post", action=url_for('frontend.usersettings')) }}
         {{ forms.render_errors(form) }}
         <dl>
             {{ forms.render(form['name']) }}
@@ -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/templates/utils.html	Tue Mar 10 00:42:12 2015 +0530
+++ b/MoinMoin/templates/utils.html	Tue Apr 28 12:17:55 2015 -0700
@@ -263,6 +263,18 @@
     </ul>
 {% endmacro %}
 
+{% macro item_panel(fq_name) %}
+    <li>
+        <a class="moin-fqname"
+           href="{{ url_for('frontend.show_item', item_name=fq_name) }}"
+           {% if fq_name.value != fq_name|string %}
+               title="{{ fq_name }}"
+           {% endif %}>
+           {{ fq_name.value }}
+       </a>
+    </li>
+{% endmacro %}
+
 {% macro show_meta(rev) %}
     <div>
         <span class="moin-diff-info-caption">
--- a/MoinMoin/themes/__init__.py	Tue Mar 10 00:42:12 2015 +0530
+++ b/MoinMoin/themes/__init__.py	Tue Apr 28 12:17:55 2015 -0700
@@ -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	Tue Mar 10 00:42:12 2015 +0530
+++ b/MoinMoin/themes/basic/templates/modify.html	Tue Apr 28 12:17:55 2015 -0700
@@ -26,6 +26,9 @@
 {% block content %}
     <h1>{{ title }}</h1>
     <div id="moin-modify" class="moin-form">
+        <a class="btn btn-success" style="float: right" href="{{ url_for('.download_item', item_name=item_name, mimetype='application/x.moin.download') }}">
+        <span class="fa fa-download"></span> {{ _('Download') }}
+        </a>
         {{ gen.form.open(form, method='post', enctype='multipart/form-data') }}
             {{ forms.render_errors(form) }}
             {{ forms.render_errors(form['meta_form']['acl']) }}
@@ -99,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	Tue Mar 10 00:42:12 2015 +0530
+++ b/MoinMoin/user.py	Tue Apr 28 12:17:55 2015 -0700
@@ -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	Tue Mar 10 00:42:12 2015 +0530
+++ b/MoinMoin/util/clock.py	Tue Apr 28 12:17:55 2015 -0700
@@ -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	Tue Mar 10 00:42:12 2015 +0530
+++ b/MoinMoin/util/subscriptions.py	Tue Apr 28 12:17:55 2015 -0700
@@ -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	Tue Mar 10 00:42:12 2015 +0530
+++ b/MoinMoin/wikiutil.py	Tue Apr 28 12:17:55 2015 -0700
@@ -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/README.txt	Tue Mar 10 00:42:12 2015 +0530
+++ b/README.txt	Tue Apr 28 12:17:55 2015 -0700
@@ -14,13 +14,13 @@
 NOTE: moin2 is not released yet, so much information you find on the wiki
 (and elsewhere) will be about moin 1.x (and NOT applicable to moin2).
 
-There is one wiki page collecting all moin2 specific links and infos:
+There is one wiki page collecting all moin2 specific links and info:
 
 http://moinmo.in/MoinMoin2.0 < READ THIS!
 
 
 Project homepage is at http://moinmo.in/ - there are also links to support
-resources and informations about MoinMoin development status and plans.
+resources and information about MoinMoin development status and plans.
 
 In general, please make sure that documentation you read on the wiki or
 somewhere else on the web is written for the moin version you are using.
--- a/docs/devel/development.rst	Tue Mar 10 00:42:12 2015 +0530
+++ b/docs/devel/development.rst	Tue Apr 28 12:17:55 2015 -0700
@@ -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	Tue Mar 10 00:42:12 2015 +0530
+++ b/make.py	Tue Apr 28 12:17:55 2015 -0700
@@ -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	Tue Mar 10 00:42:12 2015 +0530
+++ b/setup.cfg	Tue Apr 28 12:17:55 2015 -0700
@@ -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	Tue Mar 10 00:42:12 2015 +0530
+++ b/setup.py	Tue Apr 28 12:17:55 2015 -0700
@@ -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",