You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@allura.apache.org by he...@apache.org on 2015/05/29 22:41:03 UTC

[41/45] allura git commit: [#7878] Used 2to3 to see what issues would come up

http://git-wip-us.apache.org/repos/asf/allura/blob/d52f8e2a/Allura/allura/scripts/__init__.py
----------------------------------------------------------------------
diff --git a/Allura/allura/scripts/__init__.py b/Allura/allura/scripts/__init__.py
index 35779cc..91068b1 100644
--- a/Allura/allura/scripts/__init__.py
+++ b/Allura/allura/scripts/__init__.py
@@ -1,3 +1,7 @@
+from __future__ import print_function
+from __future__ import division
+from __future__ import absolute_import
+from __future__ import unicode_literals
 #       Licensed to the Apache Software Foundation (ASF) under one
 #       or more contributor license agreements.  See the NOTICE file
 #       distributed with this work for additional information
@@ -15,4 +19,4 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
-from scripttask import ScriptTask
+from .scripttask import ScriptTask

http://git-wip-us.apache.org/repos/asf/allura/blob/d52f8e2a/Allura/allura/scripts/create_deleted_comments.py
----------------------------------------------------------------------
diff --git a/Allura/allura/scripts/create_deleted_comments.py b/Allura/allura/scripts/create_deleted_comments.py
deleted file mode 100644
index 509752c..0000000
--- a/Allura/allura/scripts/create_deleted_comments.py
+++ /dev/null
@@ -1,110 +0,0 @@
-#       Licensed to the Apache Software Foundation (ASF) under one
-#       or more contributor license agreements.  See the NOTICE file
-#       distributed with this work for additional information
-#       regarding copyright ownership.  The ASF licenses this file
-#       to you under the Apache License, Version 2.0 (the
-#       "License"); you may not use this file except in compliance
-#       with the License.  You may obtain a copy of the License at
-#
-#         http://www.apache.org/licenses/LICENSE-2.0
-#
-#       Unless required by applicable law or agreed to in writing,
-#       software distributed under the License is distributed on an
-#       "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-#       KIND, either express or implied.  See the License for the
-#       specific language governing permissions and limitations
-#       under the License.
-
-import sys
-import argparse
-import logging
-
-from ming.odm import session
-from pylons import tmpl_context as c
-
-from allura.scripts import ScriptTask
-from allura import model as M
-from allura.lib.utils import chunked_find
-from forgediscussion.model import ForumPost
-
-
-log = logging.getLogger(__name__)
-
-
-class CreateDeletedComments(ScriptTask):
-
-    @classmethod
-    def execute(cls, options):
-        models = [M.Post, ForumPost]
-        app_config_id = cls.get_tool_id(options.tool)
-        # Find all posts that have parent_id, but does not have actual parent
-        # and create fake parent for them
-        for model in models:
-            q = {'parent_id': {'$ne': None},
-                 'app_config_id': app_config_id}
-            for chunk in chunked_find(model, q):
-                for post in chunk:
-                    if not post.parent:
-                        log.info('Creating deleted parent for %s %s',
-                                 model.__mongometa__.name, post._id)
-                        c.project = post.app_config.project
-                        slug = post.slug.rsplit('/', 1)[0]
-                        full_slug = post.full_slug.rsplit('/', 1)[0]
-                        author = c.project.admins()[0]
-                        deleted_post = model(
-                            _id=post.parent_id,
-                            deleted=True,
-                            text="Automatically created in place of deleted post",
-                            app_id=post.app_id,
-                            app_config_id=post.app_config_id,
-                            discussion_id=post.discussion_id,
-                            thread_id=post.thread_id,
-                            author_id=author._id,
-                            slug=slug,
-                            full_slug=full_slug,
-                        )
-                        if options.dry_run:
-                            session(deleted_post).expunge(deleted_post)
-                        else:
-                            session(deleted_post).flush(deleted_post)
-
-    @classmethod
-    def get_tool_id(cls, tool):
-        _n, _p, _mount = tool.split('/')
-        n = M.Neighborhood.query.get(url_prefix='/{}/'.format(_n))
-        if not n:
-            log.error('Can not find neighborhood %s', _n)
-            sys.exit(1)
-        p = M.Project.query.get(neighborhood_id=n._id, shortname=_p)
-        if not p:
-            log.error('Can not find project %s', _p)
-            sys.exit(1)
-        t = p.app_instance(_mount)
-        if not t:
-            log.error('Can not find tool with mount point %s', _mount)
-            sys.exit(1)
-        return t.config._id
-
-    @classmethod
-    def parser(cls):
-        parser = argparse.ArgumentParser(
-            description='Create comments marked as deleted in place of '
-                        'actually deleted parent comments (ticket:#1731)'
-        )
-        parser.add_argument(
-            '--dry-run',
-            action='store_true',
-            dest='dry_run',
-            default=False,
-            help='Show comments that will be created, but do not actually '
-                 'create anything',
-        )
-        parser.add_argument(
-            '-t', '--tool',
-            required=True,
-            help='Create comments only in specified tool, e.g. p/test/wiki')
-        return parser
-
-
-if __name__ == '__main__':
-    CreateDeletedComments.main()

http://git-wip-us.apache.org/repos/asf/allura/blob/d52f8e2a/Allura/allura/scripts/refresh_last_commits.py
----------------------------------------------------------------------
diff --git a/Allura/allura/scripts/refresh_last_commits.py b/Allura/allura/scripts/refresh_last_commits.py
index 095fafb..d1b5632 100644
--- a/Allura/allura/scripts/refresh_last_commits.py
+++ b/Allura/allura/scripts/refresh_last_commits.py
@@ -1,3 +1,7 @@
+from __future__ import print_function
+from __future__ import division
+from __future__ import absolute_import
+from __future__ import unicode_literals
 #       Licensed to the Apache Software Foundation (ASF) under one
 #       or more contributor license agreements.  See the NOTICE file
 #       distributed with this work for additional information
@@ -142,11 +146,11 @@ class RefreshLastCommits(ScriptTask):
         )
         lcid_cache = {}
         timings = []
-        print 'Processing last commits'
+        print('Processing last commits')
         for i, commit_id in enumerate(commit_ids):
             commit = M.repository.Commit.query.get(_id=commit_id)
             if commit is None:
-                print "Commit missing, skipping: %s" % commit_id
+                print("Commit missing, skipping: %s" % commit_id)
                 continue
             commit.set_context(c.app.repo)
             with time(timings):
@@ -173,8 +177,8 @@ class RefreshLastCommits(ScriptTask):
         tt = sum(timings)
         at = tt / len(timings)
         mat = sum(timings[-debug_step:]) / debug_step
-        print '  Processed %d commits (max: %f, avg: %f, mavg: %f, tot: %f)' % (
-            processed, mt, at, mat, tt)
+        print('  Processed %d commits (max: %f, avg: %f, mavg: %f, tot: %f)' % (
+            processed, mt, at, mat, tt))
 
 
 @contextmanager

http://git-wip-us.apache.org/repos/asf/allura/blob/d52f8e2a/Allura/allura/scripts/refreshrepo.py
----------------------------------------------------------------------
diff --git a/Allura/allura/scripts/refreshrepo.py b/Allura/allura/scripts/refreshrepo.py
index a336b0d..15520a8 100644
--- a/Allura/allura/scripts/refreshrepo.py
+++ b/Allura/allura/scripts/refreshrepo.py
@@ -1,3 +1,7 @@
+from __future__ import print_function
+from __future__ import division
+from __future__ import absolute_import
+from __future__ import unicode_literals
 #       Licensed to the Apache Software Foundation (ASF) under one
 #       or more contributor license agreements.  See the NOTICE file
 #       distributed with this work for additional information

http://git-wip-us.apache.org/repos/asf/allura/blob/d52f8e2a/Allura/allura/scripts/reindex_projects.py
----------------------------------------------------------------------
diff --git a/Allura/allura/scripts/reindex_projects.py b/Allura/allura/scripts/reindex_projects.py
index 89c447e..2c51d43 100644
--- a/Allura/allura/scripts/reindex_projects.py
+++ b/Allura/allura/scripts/reindex_projects.py
@@ -1,3 +1,7 @@
+from __future__ import print_function
+from __future__ import division
+from __future__ import absolute_import
+from __future__ import unicode_literals
 #       Licensed to the Apache Software Foundation (ASF) under one
 #       or more contributor license agreements.  See the NOTICE file
 #       distributed with this work for additional information
@@ -61,7 +65,7 @@ class ReindexProjects(ScriptTask):
                         cls._post_add_projects(chunk)
                     else:
                         add_projects(chunk)
-            except CompoundError, err:
+            except CompoundError as err:
                 log.exception('Error indexing projects:\n%r', err)
                 log.error('%s', err.format_error())
             M.main_orm_session.flush()

http://git-wip-us.apache.org/repos/asf/allura/blob/d52f8e2a/Allura/allura/scripts/reindex_users.py
----------------------------------------------------------------------
diff --git a/Allura/allura/scripts/reindex_users.py b/Allura/allura/scripts/reindex_users.py
index 8a0762b..4dc03aa 100644
--- a/Allura/allura/scripts/reindex_users.py
+++ b/Allura/allura/scripts/reindex_users.py
@@ -1,3 +1,7 @@
+from __future__ import print_function
+from __future__ import division
+from __future__ import absolute_import
+from __future__ import unicode_literals
 #       Licensed to the Apache Software Foundation (ASF) under one
 #       or more contributor license agreements.  See the NOTICE file
 #       distributed with this work for additional information
@@ -47,7 +51,7 @@ class ReindexUsers(ScriptTask):
                         cls._post_add_users(chunk)
                     else:
                         add_users(chunk)
-            except CompoundError, err:
+            except CompoundError as err:
                 log.exception('Error indexing users:\n%r', err)
                 log.error('%s', err.format_error())
             M.main_orm_session.flush()

http://git-wip-us.apache.org/repos/asf/allura/blob/d52f8e2a/Allura/allura/scripts/remove_duplicate_troves.py
----------------------------------------------------------------------
diff --git a/Allura/allura/scripts/remove_duplicate_troves.py b/Allura/allura/scripts/remove_duplicate_troves.py
index 7866b2e..f61fc71 100644
--- a/Allura/allura/scripts/remove_duplicate_troves.py
+++ b/Allura/allura/scripts/remove_duplicate_troves.py
@@ -1,3 +1,7 @@
+from __future__ import print_function
+from __future__ import division
+from __future__ import absolute_import
+from __future__ import unicode_literals
 #       Licensed to the Apache Software Foundation (ASF) under one
 #       or more contributor license agreements.  See the NOTICE file
 #       distributed with this work for additional information
@@ -47,16 +51,16 @@ class RemoveDuplicateTroves(ScriptTask):
     @classmethod
     def execute(cls, options):
         duplicates = cls._find_duplicates()
-        log.info('Found %s duplicate categories: %s', len(duplicates), duplicates.keys())
-        for name, dups in duplicates.iteritems():
+        log.info('Found %s duplicate categories: %s', len(duplicates), list(duplicates.keys()))
+        for name, dups in duplicates.items():
             projects_with_category = {}
             for dup in dups:
                 projects = cls._projects_with_category(dup._id)
                 projects_with_category[dup._id] = projects
             log.info('Following projects are using category %s:', name)
-            for _id, ps in projects_with_category.iteritems():
+            for _id, ps in projects_with_category.items():
                 log.info('  with id %s: %s', _id, [p.shortname for p in ps])
-            priority = [(i, len(ps)) for i, ps in projects_with_category.items()]
+            priority = [(i, len(ps)) for i, ps in list(projects_with_category.items())]
             priority = sorted(priority, key=itemgetter(1), reverse=True)
             priority = [p[0] for p in priority]
             live, kill = priority[0], priority[1:]

http://git-wip-us.apache.org/repos/asf/allura/blob/d52f8e2a/Allura/allura/scripts/scripttask.py
----------------------------------------------------------------------
diff --git a/Allura/allura/scripts/scripttask.py b/Allura/allura/scripts/scripttask.py
index a8494c9..f3d6e6e 100644
--- a/Allura/allura/scripts/scripttask.py
+++ b/Allura/allura/scripts/scripttask.py
@@ -43,6 +43,10 @@ To call as a task::
     MyScript.post('-p myproject --dry-run')
 
 """
+from __future__ import print_function
+from __future__ import division
+from __future__ import absolute_import
+from __future__ import unicode_literals
 
 import argparse
 import logging

http://git-wip-us.apache.org/repos/asf/allura/blob/d52f8e2a/Allura/allura/scripts/trac_export.py
----------------------------------------------------------------------
diff --git a/Allura/allura/scripts/trac_export.py b/Allura/allura/scripts/trac_export.py
index 8b6419d..488d65f 100644
--- a/Allura/allura/scripts/trac_export.py
+++ b/Allura/allura/scripts/trac_export.py
@@ -20,8 +20,8 @@
 import logging
 import sys
 import csv
-import urlparse
-import urllib2
+import urllib.parse
+import urllib.request, urllib.error, urllib.parse
 import json
 import time
 import re
@@ -39,7 +39,7 @@ except ImportError:
     try:
         from allura.lib.helpers import urlopen
     except ImportError:
-        from urllib2 import urlopen
+        from urllib.request import urlopen
 
 log = logging.getLogger(__name__)
 
@@ -101,7 +101,7 @@ class TracExport(object):
     def remap_fields(self, dict):
         "Remap fields to adhere to standard taxonomy."
         out = {}
-        for k, v in dict.iteritems():
+        for k, v in dict.items():
             key = self.match_pattern(r'\W*(\w+)\W*', k)
             out[self.FIELD_MAP.get(key, key)] = v
 
@@ -111,7 +111,7 @@ class TracExport(object):
         return out
 
     def full_url(self, suburl, type=None):
-        url = urlparse.urljoin(self.base_url, suburl)
+        url = urllib.parse.urljoin(self.base_url, suburl)
         if type is None:
             return url
         glue = '&' if '?' in suburl else '?'
@@ -120,7 +120,7 @@ class TracExport(object):
     def log_url(self, url):
         log.info(url)
         if self.verbose:
-            print >>sys.stderr, url
+            print(url, file=sys.stderr)
 
     @classmethod
     def trac2z_date(cls, s):
@@ -142,7 +142,7 @@ class TracExport(object):
         # telling that access denied. So, we'll emulate 403 ourselves.
         # TODO: currently, any non-csv result treated as 403.
         if not f.info()['Content-Type'].startswith('text/csv'):
-            raise urllib2.HTTPError(
+            raise urllib.error.HTTPError(
                 url, 403, 'Forbidden - emulated', f.info(), f)
         return f
 
@@ -151,7 +151,7 @@ class TracExport(object):
         url = self.full_url(self.TICKET_URL % id, 'csv')
         f = self.csvopen(url)
         reader = csv.DictReader(f)
-        ticket_fields = reader.next()
+        ticket_fields = next(reader)
         ticket_fields['class'] = 'ARTIFACT'
         ticket = self.remap_fields(ticket_fields)
 
@@ -172,7 +172,7 @@ class TracExport(object):
                 r'.* by ', '', comment.find('h3', 'change').text).strip()
             c['date'] = self.trac2z_date(
                 comment.find('a', 'timeline')['title'].replace(' in Timeline', ''))
-            changes = unicode(comment.find('ul', 'changes') or '')
+            changes = str(comment.find('ul', 'changes') or '')
             body = comment.find('div', 'comment')
             body = body.renderContents('utf8').decode('utf8') if body else ''
             c['comment'] = html2text.html2text(changes + body)
@@ -224,8 +224,8 @@ class TracExport(object):
         url = self.full_url(self.QUERY_MAX_ID_URL, 'csv')
         f = self.csvopen(url)
         reader = csv.DictReader(f)
-        fields = reader.next()
-        print fields
+        fields = next(reader)
+        print(fields)
         return int(fields['id'])
 
     def get_ticket(self, id, extra={}):
@@ -250,14 +250,14 @@ class TracExport(object):
         url = self.full_url(self.QUERY_BY_PAGE_URL % self.page, 'csv')
         try:
             f = self.csvopen(url)
-        except urllib2.HTTPError, e:
+        except urllib.error.HTTPError as e:
             if 'emulated' in e.msg:
                 body = e.fp.read()
                 if 'beyond the number of pages in the query' in body or 'Log in with a SourceForge account' in body:
                     raise StopIteration
             raise
         reader = csv.reader(f)
-        cols = reader.next()
+        cols = next(reader)
         for r in reader:
             if r and r[0].isdigit():
                 id = int(r[0])
@@ -274,7 +274,7 @@ class TracExport(object):
     def __iter__(self):
         return self
 
-    def next(self):
+    def __next__(self):
         while True:
             # queue empty, try to fetch more
             if len(self.ticket_queue) == 0 and not self.exhausted:

http://git-wip-us.apache.org/repos/asf/allura/blob/d52f8e2a/Allura/allura/scripts/trim_emails.py
----------------------------------------------------------------------
diff --git a/Allura/allura/scripts/trim_emails.py b/Allura/allura/scripts/trim_emails.py
index f847224..6e7f542 100644
--- a/Allura/allura/scripts/trim_emails.py
+++ b/Allura/allura/scripts/trim_emails.py
@@ -1,3 +1,7 @@
+from __future__ import print_function
+from __future__ import division
+from __future__ import absolute_import
+from __future__ import unicode_literals
 #       Licensed to the Apache Software Foundation (ASF) under one
 #       or more contributor license agreements.  See the NOTICE file
 #       distributed with this work for additional information

http://git-wip-us.apache.org/repos/asf/allura/blob/d52f8e2a/Allura/allura/scripts/update_checkout_url.py
----------------------------------------------------------------------
diff --git a/Allura/allura/scripts/update_checkout_url.py b/Allura/allura/scripts/update_checkout_url.py
index a1fc982..8bf115f 100644
--- a/Allura/allura/scripts/update_checkout_url.py
+++ b/Allura/allura/scripts/update_checkout_url.py
@@ -19,6 +19,10 @@
 Find repos with blank checkout url and top-level "trunk" dir,
 and point checkout_url at trunk.
 """
+from __future__ import print_function
+from __future__ import division
+from __future__ import absolute_import
+from __future__ import unicode_literals
 
 import logging
 

http://git-wip-us.apache.org/repos/asf/allura/blob/d52f8e2a/Allura/allura/tasks/activity_tasks.py
----------------------------------------------------------------------
diff --git a/Allura/allura/tasks/activity_tasks.py b/Allura/allura/tasks/activity_tasks.py
index 26ac02a..f25f69e 100644
--- a/Allura/allura/tasks/activity_tasks.py
+++ b/Allura/allura/tasks/activity_tasks.py
@@ -1,3 +1,7 @@
+from __future__ import print_function
+from __future__ import division
+from __future__ import absolute_import
+from __future__ import unicode_literals
 #       Licensed to the Apache Software Foundation (ASF) under one
 #       or more contributor license agreements.  See the NOTICE file
 #       distributed with this work for additional information

http://git-wip-us.apache.org/repos/asf/allura/blob/d52f8e2a/Allura/allura/tasks/admin_tasks.py
----------------------------------------------------------------------
diff --git a/Allura/allura/tasks/admin_tasks.py b/Allura/allura/tasks/admin_tasks.py
index 65f2775..7056c83 100644
--- a/Allura/allura/tasks/admin_tasks.py
+++ b/Allura/allura/tasks/admin_tasks.py
@@ -1,3 +1,7 @@
+from __future__ import print_function
+from __future__ import division
+from __future__ import absolute_import
+from __future__ import unicode_literals
 #       Licensed to the Apache Software Foundation (ASF) under one
 #       or more contributor license agreements.  See the NOTICE file
 #       distributed with this work for additional information

http://git-wip-us.apache.org/repos/asf/allura/blob/d52f8e2a/Allura/allura/tasks/event_tasks.py
----------------------------------------------------------------------
diff --git a/Allura/allura/tasks/event_tasks.py b/Allura/allura/tasks/event_tasks.py
index 8a2de35..43df95d 100644
--- a/Allura/allura/tasks/event_tasks.py
+++ b/Allura/allura/tasks/event_tasks.py
@@ -1,3 +1,7 @@
+from __future__ import print_function
+from __future__ import division
+from __future__ import absolute_import
+from __future__ import unicode_literals
 #       Licensed to the Apache Software Foundation (ASF) under one
 #       or more contributor license agreements.  See the NOTICE file
 #       distributed with this work for additional information
@@ -31,6 +35,6 @@ def event(event_type, *args, **kwargs):
             exceptions.append(sys.exc_info())
     if exceptions:
         if len(exceptions) == 1:
-            raise exceptions[0][0], exceptions[0][1], exceptions[0][2]
+            raise exceptions[0][0](exceptions[0][1]).with_traceback(exceptions[0][2])
         else:
             raise CompoundError(*exceptions)

http://git-wip-us.apache.org/repos/asf/allura/blob/d52f8e2a/Allura/allura/tasks/export_tasks.py
----------------------------------------------------------------------
diff --git a/Allura/allura/tasks/export_tasks.py b/Allura/allura/tasks/export_tasks.py
index eb9ff4d..0564685 100644
--- a/Allura/allura/tasks/export_tasks.py
+++ b/Allura/allura/tasks/export_tasks.py
@@ -1,3 +1,7 @@
+from __future__ import print_function
+from __future__ import division
+from __future__ import absolute_import
+from __future__ import unicode_literals
 #       Licensed to the Apache Software Foundation (ASF) under one
 #       or more contributor license agreements.  See the NOTICE file
 #       distributed with this work for additional information
@@ -85,12 +89,12 @@ class BulkExport(object):
         }
 
         email = {
-            'toaddr': unicode(user._id),
-            'fromaddr': unicode(tg.config['forgemail.return_path']),
-            'sender': unicode(tg.config['forgemail.return_path']),
-            'reply_to': unicode(tg.config['forgemail.return_path']),
+            'toaddr': str(user._id),
+            'fromaddr': str(tg.config['forgemail.return_path']),
+            'sender': str(tg.config['forgemail.return_path']),
+            'reply_to': str(tg.config['forgemail.return_path']),
             'message_id': h.gen_message_id(),
-            'subject': u'Bulk export for project %s completed' % project.shortname,
+            'subject': 'Bulk export for project %s completed' % project.shortname,
             'text': tmpl.render(tmpl_context)
         }
 

http://git-wip-us.apache.org/repos/asf/allura/blob/d52f8e2a/Allura/allura/tasks/index_tasks.py
----------------------------------------------------------------------
diff --git a/Allura/allura/tasks/index_tasks.py b/Allura/allura/tasks/index_tasks.py
index c9e853d..d239ac9 100644
--- a/Allura/allura/tasks/index_tasks.py
+++ b/Allura/allura/tasks/index_tasks.py
@@ -1,3 +1,7 @@
+from __future__ import print_function
+from __future__ import division
+from __future__ import absolute_import
+from __future__ import unicode_literals
 #       Licensed to the Apache Software Foundation (ASF) under one
 #       or more contributor license agreements.  See the NOTICE file
 #       distributed with this work for additional information
@@ -106,7 +110,7 @@ def add_artifacts(ref_ids, update_solr=True, update_refs=True, solr_hosts=None):
         __get_solr(solr_hosts).add(solr_updates)
 
     if len(exceptions) == 1:
-        raise exceptions[0][0], exceptions[0][1], exceptions[0][2]
+        raise exceptions[0][0](exceptions[0][1]).with_traceback(exceptions[0][2])
     if exceptions:
         raise CompoundError(*exceptions)
 

http://git-wip-us.apache.org/repos/asf/allura/blob/d52f8e2a/Allura/allura/tasks/mail_tasks.py
----------------------------------------------------------------------
diff --git a/Allura/allura/tasks/mail_tasks.py b/Allura/allura/tasks/mail_tasks.py
index 2547291..a771f03 100644
--- a/Allura/allura/tasks/mail_tasks.py
+++ b/Allura/allura/tasks/mail_tasks.py
@@ -1,3 +1,7 @@
+from __future__ import print_function
+from __future__ import division
+from __future__ import absolute_import
+from __future__ import unicode_literals
 #       Licensed to the Apache Software Foundation (ASF) under one
 #       or more contributor license agreements.  See the NOTICE file
 #       distributed with this work for additional information
@@ -16,7 +20,7 @@
 #       under the License.
 
 import logging
-import HTMLParser
+import html.parser
 
 from pylons import tmpl_context as c, app_globals as g
 from bson import ObjectId
@@ -78,7 +82,7 @@ def route_email(
                                 c.app.handle_message(userpart, msg)
                         else:
                             c.app.handle_message(userpart, msg)
-            except exc.MailError, e:
+            except exc.MailError as e:
                 log.error('Error routing email to %s: %s', addr, e)
             except:
                 log.exception('Error routing mail to %s', addr)
@@ -100,7 +104,7 @@ def sendmail(fromaddr, destinations, text, reply_to, subject,
     addrs_multi = []
     if fromaddr is None:
         fromaddr = g.noreply
-    elif not isinstance(fromaddr, basestring) or '@' not in fromaddr:
+    elif not isinstance(fromaddr, str) or '@' not in fromaddr:
         log.warning('Looking up user with fromaddr: %s', fromaddr)
         user = M.User.query.get(_id=ObjectId(fromaddr), disabled=False, pending=False)
         if not user:
@@ -138,7 +142,7 @@ def sendmail(fromaddr, destinations, text, reply_to, subject,
                 addrs_html.append(addr)
             else:
                 addrs_multi.append(addr)
-    htmlparser = HTMLParser.HTMLParser()
+    htmlparser = html.parser.HTMLParser()
     plain_msg = mail_util.encode_email_part(htmlparser.unescape(text), 'plain')
     html_text = g.forge_markdown(email=True).convert(text)
     html_msg = mail_util.encode_email_part(html_text, 'html')
@@ -177,7 +181,7 @@ def sendsimplemail(
     from allura import model as M
     if fromaddr is None:
         fromaddr = g.noreply
-    elif not isinstance(fromaddr, basestring) or '@' not in fromaddr:
+    elif not isinstance(fromaddr, str) or '@' not in fromaddr:
         log.warning('Looking up user with fromaddr: %s', fromaddr)
         user = M.User.query.get(_id=ObjectId(fromaddr), disabled=False, pending=False)
         if not user:
@@ -186,7 +190,7 @@ def sendsimplemail(
         else:
             fromaddr = user.email_address_header()
 
-    if not isinstance(toaddr, basestring) or '@' not in toaddr:
+    if not isinstance(toaddr, str) or '@' not in toaddr:
         log.warning('Looking up user with toaddr: %s', toaddr)
         user = M.User.query.get(_id=ObjectId(toaddr), disabled=False, pending=False)
         if not user:
@@ -195,7 +199,7 @@ def sendsimplemail(
         else:
             toaddr = user.email_address_header()
 
-    htmlparser = HTMLParser.HTMLParser()
+    htmlparser = html.parser.HTMLParser()
     plain_msg = mail_util.encode_email_part(htmlparser.unescape(text), 'plain')
     html_text = g.forge_markdown(email=True).convert(text)
     html_msg = mail_util.encode_email_part(html_text, 'html')

http://git-wip-us.apache.org/repos/asf/allura/blob/d52f8e2a/Allura/allura/tasks/notification_tasks.py
----------------------------------------------------------------------
diff --git a/Allura/allura/tasks/notification_tasks.py b/Allura/allura/tasks/notification_tasks.py
index a6b7564..2244b46 100644
--- a/Allura/allura/tasks/notification_tasks.py
+++ b/Allura/allura/tasks/notification_tasks.py
@@ -1,3 +1,7 @@
+from __future__ import print_function
+from __future__ import division
+from __future__ import absolute_import
+from __future__ import unicode_literals
 #       Licensed to the Apache Software Foundation (ASF) under one
 #       or more contributor license agreements.  See the NOTICE file
 #       distributed with this work for additional information

http://git-wip-us.apache.org/repos/asf/allura/blob/d52f8e2a/Allura/allura/tasks/repo_tasks.py
----------------------------------------------------------------------
diff --git a/Allura/allura/tasks/repo_tasks.py b/Allura/allura/tasks/repo_tasks.py
index e44ea94..d747867 100644
--- a/Allura/allura/tasks/repo_tasks.py
+++ b/Allura/allura/tasks/repo_tasks.py
@@ -1,3 +1,7 @@
+from __future__ import print_function
+from __future__ import division
+from __future__ import absolute_import
+from __future__ import unicode_literals
 #       Licensed to the Apache Software Foundation (ASF) under one
 #       or more contributor license agreements.  See the NOTICE file
 #       distributed with this work for additional information

http://git-wip-us.apache.org/repos/asf/allura/blob/d52f8e2a/Allura/allura/templates/__init__.py
----------------------------------------------------------------------
diff --git a/Allura/allura/templates/__init__.py b/Allura/allura/templates/__init__.py
index 8109045..4c0b4ac 100644
--- a/Allura/allura/templates/__init__.py
+++ b/Allura/allura/templates/__init__.py
@@ -18,3 +18,7 @@
 #       under the License.
 
 """Templates package for the application."""
+from __future__ import print_function
+from __future__ import division
+from __future__ import absolute_import
+from __future__ import unicode_literals

http://git-wip-us.apache.org/repos/asf/allura/blob/d52f8e2a/Allura/allura/templates/mail/commits.md
----------------------------------------------------------------------
diff --git a/Allura/allura/templates/mail/commits.md b/Allura/allura/templates/mail/commits.md
new file mode 100644
index 0000000..8a0d934
--- /dev/null
+++ b/Allura/allura/templates/mail/commits.md
@@ -0,0 +1,36 @@
+{# <!---
+       Licensed to the Apache Software Foundation (ASF) under one
+       or more contributor license agreements.  See the NOTICE file
+       distributed with this work for additional information
+       regarding copyright ownership.  The ASF licenses this file
+       to you under the Apache License, Version 2.0 (the
+       "License"); you may not use this file except in compliance
+       with the License.  You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+       Unless required by applicable law or agreed to in writing,
+       software distributed under the License is distributed on an
+       "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+       KIND, either express or implied.  See the License for the
+       specific language governing permissions and limitations
+       under the License.
+--> #}
+
+-----------
+
+[![{{ author }}][2]][1]
+[New commit to **{{ project }}:{{ label }}**][3]
+ by [{{ author }}][1] on {{ short_date }}.
+**Summary**: ```{{ summary }}```
+{% if text_branches %}**Branch:** {% for b in text_branches %} *{{ b }}* {% endfor%}{% endif %}
+#### [**View Changes**][3]
+
+  [1]: {{ author_url }}
+  [2]: {{author_gravatar}} ({{ author }})
+  [3]: {{ commit_url }}
+
+
+
+  
+  
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/allura/blob/d52f8e2a/Allura/allura/templates/neighborhood_add_project.html
----------------------------------------------------------------------
diff --git a/Allura/allura/templates/neighborhood_add_project.html b/Allura/allura/templates/neighborhood_add_project.html
index 6aebc2c..cd1f79c 100644
--- a/Allura/allura/templates/neighborhood_add_project.html
+++ b/Allura/allura/templates/neighborhood_add_project.html
@@ -24,21 +24,5 @@
 {% block header %}Create a Project{% endblock %}
 
 {% block content %}
-  {% if c.show_phone_verification_overlay %}
-    {% do g.register_js('js/jquery.lightbox_me.js') %}
-    <div id="phone_verification_overlay" class="ui-widget-content">
-      <h2 class="dark title">Phone Verification Required</h2>
-      <iframe src="{{neighborhood.url_prefix}}phone_verification_fragment"></iframe>
-    </div>
-    <script type="text/javascript">
-      $(function () {
-        $('#phone_verification_overlay').draggable().lightbox_me({
-          closeClick: false,
-          closeEsc: false,
-          centered: true
-        });
-      });
-    </script>
-  {% endif %}
-  {{c.add_project.display(action=neighborhood.url()+'register',value=form_data,neighborhood=neighborhood)}}
+          {{c.add_project.display(action=neighborhood.url()+'register',value=form_data,neighborhood=neighborhood)}}
 {% endblock %}

http://git-wip-us.apache.org/repos/asf/allura/blob/d52f8e2a/Allura/allura/templates/phone_verification_fragment.html
----------------------------------------------------------------------
diff --git a/Allura/allura/templates/phone_verification_fragment.html b/Allura/allura/templates/phone_verification_fragment.html
deleted file mode 100644
index faa0009..0000000
--- a/Allura/allura/templates/phone_verification_fragment.html
+++ /dev/null
@@ -1,73 +0,0 @@
-{#-
-       Licensed to the Apache Software Foundation (ASF) under one
-       or more contributor license agreements.  See the NOTICE file
-       distributed with this work for additional information
-       regarding copyright ownership.  The ASF licenses this file
-       to you under the Apache License, Version 2.0 (the
-       "License"); you may not use this file except in compliance
-       with the License.  You may obtain a copy of the License at
-
-         http://www.apache.org/licenses/LICENSE-2.0
-
-       Unless required by applicable law or agreed to in writing,
-       software distributed under the License is distributed on an
-       "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-       KIND, either express or implied.  See the License for the
-       specific language governing permissions and limitations
-       under the License.
--#}
-<!DOCTYPE html>
-<!-- Server: {{g.server_name}} -->
-{% import 'allura:templates/jinja_master/lib.html' as lib with context %}
-{% if g.theme.jinja_macros %}
-  {% import g.theme.jinja_macros as theme_macros with context %}
-{% endif %}
-{% do g.register_forge_js('js/react.min.js', location='head_js') %}
-{% do g.register_forge_js('js/jquery-base.js', location='head_js') %}
-{% do g.register_forge_js('js/phone-verification.js', location='body_js_tail') %}
-{% do g.theme.require() %}
-{% do g.resource_manager.register_widgets(c) %}
-{# paulirish.com/2008/conditional-stylesheets-vs-css-hacks-answer-neither/ #}
-<!--[if lt IE 7 ]> <html lang="en" class="no-js ie6"> <![endif]-->
-<!--[if IE 7 ]>    <html lang="en" class="no-js ie7"> <![endif]-->
-<!--[if IE 8 ]>    <html lang="en" class="no-js ie8"> <![endif]-->
-<!--[if IE 9 ]>    <html lang="en" class="no-js ie9"> <![endif]-->
-<!--[if (gt IE 9)|!(IE)]>--> <html lang="en" class="no-js"> <!--<![endif]-->
-    <head>
-        {{theme_macros.extra_header(g.theme_href(''))}}
-        {% for blob in g.resource_manager.emit('head_css') %}
-          {{ blob }}
-        {% endfor %}
-        {% for blob in g.resource_manager.emit('head_js') %}
-          {{ blob }}
-        {% endfor %}
-        <style type="text/css">
-            html { overflow: hidden; }
-            body { padding-top: 1em; }
-            .error-text {
-              color: red;
-              margin-top: .5em;
-              margin-bottom: .5em;
-            }
-        </style>
-    </head>
-    <body>
-        <div class="grid-9">
-          {% block phone_validation_message %}
-          <p>
-            In order to register a project you need to perform phone validation
-            (just this once).  Please, enter your phone number below. You'll
-            receive an SMS message with a PIN you need to provide on the next
-            step.  Your phone number will not be stored.
-          </p>
-          {% endblock phone_validation_message %}
-          <div id="phone-verification-form">
-            {# Phone verification form will be mounted here (see phone-verification.js) #}
-          </div>
-        </div>
-
-        {% for blob in g.resource_manager.emit('body_js_tail') %}
-          {{ blob }}
-        {% endfor %}
-    </body>
-</html>

http://git-wip-us.apache.org/repos/asf/allura/blob/d52f8e2a/Allura/allura/templates/widgets/neighborhood_add_project.html
----------------------------------------------------------------------
diff --git a/Allura/allura/templates/widgets/neighborhood_add_project.html b/Allura/allura/templates/widgets/neighborhood_add_project.html
index a84f9b5..ca21381 100644
--- a/Allura/allura/templates/widgets/neighborhood_add_project.html
+++ b/Allura/allura/templates/widgets/neighborhood_add_project.html
@@ -50,15 +50,13 @@
     {% if not neighborhood.project_template %}
     {% for opt in widget.fields.tools.options %}
         {% set tool = g.entry_points["tool"][opt.html_value] %}
-        {% set _ctx = widget.context_for(widget.fields.tools) %}
         {% if tool.installable %}
         <div class="tool">
             <img src="{{ g.theme.app_icon_url(tool, 48) }}" alt="{{ opt.label }} icon">
-            <input type="checkbox" value="{{ opt.html_value }}"
-                   {% if _ctx['value'] and opt.html_value in _ctx['value'] %}checked{% endif %}
-                   name="{{ _ctx['rendered_name'] }}"
-                   id="{{ _ctx['rendered_name'] }}_{{ opt.html_value }}">
-            <h1><label for="{{ _ctx['rendered_name'] }}_{{ opt.html_value }}">{{ opt.label }}</label></h1>
+            <input checked type="checkbox" value="{{ opt.html_value }}"
+                   name="{{ widget.context_for(widget.fields.tools)['rendered_name'] }}"
+                   id="{{ widget.context_for(widget.fields.tools)['rendered_name'] }}_{{ opt.html_value }}">
+            <h1><label for="{{ widget.context_for(widget.fields.tools)['rendered_name'] }}_{{ opt.html_value }}">{{ opt.label }}</label></h1>
             <p>{{ tool.tool_description }}</p>
         </div>
         {% endif %}

http://git-wip-us.apache.org/repos/asf/allura/blob/d52f8e2a/Allura/allura/tests/__init__.py
----------------------------------------------------------------------
diff --git a/Allura/allura/tests/__init__.py b/Allura/allura/tests/__init__.py
index 8047c6b..14e03bf 100644
--- a/Allura/allura/tests/__init__.py
+++ b/Allura/allura/tests/__init__.py
@@ -18,6 +18,10 @@
 #       under the License.
 
 """Unit and functional test suite for allura."""
+from __future__ import print_function
+from __future__ import division
+from __future__ import absolute_import
+from __future__ import unicode_literals
 
 import alluratest.controller
 

http://git-wip-us.apache.org/repos/asf/allura/blob/d52f8e2a/Allura/allura/tests/decorators.py
----------------------------------------------------------------------
diff --git a/Allura/allura/tests/decorators.py b/Allura/allura/tests/decorators.py
index 46369d1..635e03a 100644
--- a/Allura/allura/tests/decorators.py
+++ b/Allura/allura/tests/decorators.py
@@ -1,3 +1,7 @@
+from __future__ import print_function
+from __future__ import division
+from __future__ import absolute_import
+from __future__ import unicode_literals
 #       Licensed to the Apache Software Foundation (ASF) under one
 #       or more contributor license agreements.  See the NOTICE file
 #       distributed with this work for additional information

http://git-wip-us.apache.org/repos/asf/allura/blob/d52f8e2a/Allura/allura/tests/functional/__init__.py
----------------------------------------------------------------------
diff --git a/Allura/allura/tests/functional/__init__.py b/Allura/allura/tests/functional/__init__.py
index 40a4394..ba896c1 100644
--- a/Allura/allura/tests/functional/__init__.py
+++ b/Allura/allura/tests/functional/__init__.py
@@ -18,3 +18,7 @@
 #       under the License.
 
 """Functional test suite for the controllers of the application."""
+from __future__ import print_function
+from __future__ import division
+from __future__ import absolute_import
+from __future__ import unicode_literals

http://git-wip-us.apache.org/repos/asf/allura/blob/d52f8e2a/Allura/allura/tests/functional/test_admin.py
----------------------------------------------------------------------
diff --git a/Allura/allura/tests/functional/test_admin.py b/Allura/allura/tests/functional/test_admin.py
index 819a92a..88ddbdd 100644
--- a/Allura/allura/tests/functional/test_admin.py
+++ b/Allura/allura/tests/functional/test_admin.py
@@ -1,3 +1,7 @@
+from __future__ import print_function
+from __future__ import division
+from __future__ import absolute_import
+from __future__ import unicode_literals
 #       Licensed to the Apache Software Foundation (ASF) under one
 #       or more contributor license agreements.  See the NOTICE file
 #       distributed with this work for additional information
@@ -19,7 +23,7 @@ import re
 import os
 import allura
 import pkg_resources
-import StringIO
+import io
 from contextlib import contextmanager
 import logging
 
@@ -60,12 +64,12 @@ class TestProjectAdmin(TestController):
         with audits(
                 'change summary to Milkshakes are for crazy monkeys',
                 'change project name to My Test Project',
-                u'change short description to (\u00bf A Test Project \?){45}'):
+                'change short description to (\u00bf A Test Project \?){45}'):
             self.app.post('/admin/update', params=dict(
                 name='My Test Project',
                 shortname='test',
                 summary='Milkshakes are for crazy monkeys',
-                short_description=u'\u00bf A Test Project ?'.encode(
+                short_description='\u00bf A Test Project ?'.encode(
                         'utf-8') * 45,
                 labels='aaa,bbb'))
         r = self.app.get('/admin/overview')
@@ -389,7 +393,7 @@ class TestProjectAdmin(TestController):
                 short_description='A Test Project'),
                 upload_files=[upload])
         r = self.app.get('/p/test/icon')
-        image = PIL.Image.open(StringIO.StringIO(r.body))
+        image = PIL.Image.open(io.StringIO(r.body))
         assert image.size == (48, 48)
 
         r = self.app.get('/p/test/icon?foo=bar')
@@ -412,10 +416,10 @@ class TestProjectAdmin(TestController):
         filename = project.get_screenshots()[0].filename
         r = self.app.get('/p/test/screenshot/' + filename)
         uploaded = PIL.Image.open(file_path)
-        screenshot = PIL.Image.open(StringIO.StringIO(r.body))
+        screenshot = PIL.Image.open(io.StringIO(r.body))
         assert uploaded.size == screenshot.size
         r = self.app.get('/p/test/screenshot/' + filename + '/thumb')
-        thumb = PIL.Image.open(StringIO.StringIO(r.body))
+        thumb = PIL.Image.open(io.StringIO(r.body))
         assert thumb.size == (150, 150)
         # FIX: home pages don't currently support screenshots (now that they're a wiki);
         # reinstate this code (or appropriate) when we have a macro for that
@@ -467,10 +471,10 @@ class TestProjectAdmin(TestController):
                 'new.mount_label': 'sub-del-undel'})
         r = self.app.get('/p/test/admin/overview')
         assert 'This project has been deleted and is not visible to non-admin users' not in r
-        assert r.html.find(
-            'input', {'name': 'removal', 'value': ''}).has_key('checked')
-        assert not r.html.find(
-            'input', {'name': 'removal', 'value': 'deleted'}).has_key('checked')
+        assert 'checked' in r.html.find(
+            'input', {'name': 'removal', 'value': ''})
+        assert 'checked' not in r.html.find(
+            'input', {'name': 'removal', 'value': 'deleted'})
         with audits('delete project'):
             self.app.post('/admin/update', params=dict(
                 name='Test Project',
@@ -480,10 +484,10 @@ class TestProjectAdmin(TestController):
                 delete='on'))
         r = self.app.get('/p/test/admin/overview')
         assert 'This project has been deleted and is not visible to non-admin users' in r
-        assert not r.html.find(
-            'input', {'name': 'removal', 'value': ''}).has_key('checked')
-        assert r.html.find(
-            'input', {'name': 'removal', 'value': 'deleted'}).has_key('checked')
+        assert 'checked' not in r.html.find(
+            'input', {'name': 'removal', 'value': ''})
+        assert 'checked' in r.html.find(
+            'input', {'name': 'removal', 'value': 'deleted'})
         # make sure subprojects get deleted too
         r = self.app.get('/p/test/sub-del-undel/admin/overview')
         assert 'This project has been deleted and is not visible to non-admin users' in r
@@ -496,10 +500,10 @@ class TestProjectAdmin(TestController):
                 undelete='on'))
         r = self.app.get('/p/test/admin/overview')
         assert 'This project has been deleted and is not visible to non-admin users' not in r
-        assert r.html.find(
-            'input', {'name': 'removal', 'value': ''}).has_key('checked')
-        assert not r.html.find(
-            'input', {'name': 'removal', 'value': 'deleted'}).has_key('checked')
+        assert 'checked' in r.html.find(
+            'input', {'name': 'removal', 'value': ''})
+        assert 'checked' not in r.html.find(
+            'input', {'name': 'removal', 'value': 'deleted'})
 
     def test_project_delete_not_allowed(self):
         # turn off project delete option
@@ -544,8 +548,8 @@ class TestProjectAdmin(TestController):
                     delete='on'))
             r = self.app.get('/p/test/sub-no-del/admin/overview')
             assert 'This project has been deleted and is not visible to non-admin users' in r
-            assert r.html.find(
-                'input', {'name': 'removal', 'value': 'deleted'}).has_key('checked')
+            assert 'checked' in r.html.find(
+                'input', {'name': 'removal', 'value': 'deleted'})
         finally:
             if old_allow_project_delete == ():
                 del config['allow_project_delete']
@@ -925,43 +929,43 @@ class TestProjectAdmin(TestController):
             'role_id': anon_id,
             'permission': 'create',
             'allow': 'true'})
-        assert {u'text': u'Inherited permission create from Anonymous',
-                u'has': u'inherit', u'name': u'create'} in r.json[admin_id]
-        assert {u'text': u'Inherited permission create from Anonymous',
-                u'has': u'inherit', u'name': u'create'} in r.json[mem_id]
-        assert {u'text': u'Has permission create', u'has':
-                u'yes', u'name': u'create'} in r.json[anon_id]
+        assert {'text': 'Inherited permission create from Anonymous',
+                'has': 'inherit', 'name': 'create'} in r.json[admin_id]
+        assert {'text': 'Inherited permission create from Anonymous',
+                'has': 'inherit', 'name': 'create'} in r.json[mem_id]
+        assert {'text': 'Has permission create', 'has':
+                'yes', 'name': 'create'} in r.json[anon_id]
         r = self.app.post('/admin/groups/change_perm', params={
             'role_id': anon_id,
             'permission': 'create',
             'allow': 'false'})
-        assert {u'text': u'Does not have permission create',
-                u'has': u'no', u'name': u'create'} in r.json[admin_id]
-        assert {u'text': u'Does not have permission create',
-                u'has': u'no', u'name': u'create'} in r.json[mem_id]
-        assert {u'text': u'Does not have permission create',
-                u'has': u'no', u'name': u'create'} in r.json[anon_id]
+        assert {'text': 'Does not have permission create',
+                'has': 'no', 'name': 'create'} in r.json[admin_id]
+        assert {'text': 'Does not have permission create',
+                'has': 'no', 'name': 'create'} in r.json[mem_id]
+        assert {'text': 'Does not have permission create',
+                'has': 'no', 'name': 'create'} in r.json[anon_id]
         # updates to Member inherit up
         r = self.app.post('/admin/groups/change_perm', params={
             'role_id': mem_id,
             'permission': 'create',
             'allow': 'true'})
-        assert {u'text': u'Inherited permission create from Member',
-                u'has': u'inherit', u'name': u'create'} in r.json[admin_id]
-        assert {u'text': u'Has permission create', u'has':
-                u'yes', u'name': u'create'} in r.json[mem_id]
-        assert {u'text': u'Does not have permission create',
-                u'has': u'no', u'name': u'create'} in r.json[anon_id]
+        assert {'text': 'Inherited permission create from Member',
+                'has': 'inherit', 'name': 'create'} in r.json[admin_id]
+        assert {'text': 'Has permission create', 'has':
+                'yes', 'name': 'create'} in r.json[mem_id]
+        assert {'text': 'Does not have permission create',
+                'has': 'no', 'name': 'create'} in r.json[anon_id]
         r = self.app.post('/admin/groups/change_perm', params={
             'role_id': mem_id,
             'permission': 'create',
             'allow': 'false'})
-        assert {u'text': u'Does not have permission create',
-                u'has': u'no', u'name': u'create'} in r.json[admin_id]
-        assert {u'text': u'Does not have permission create',
-                u'has': u'no', u'name': u'create'} in r.json[mem_id]
-        assert {u'text': u'Does not have permission create',
-                u'has': u'no', u'name': u'create'} in r.json[anon_id]
+        assert {'text': 'Does not have permission create',
+                'has': 'no', 'name': 'create'} in r.json[admin_id]
+        assert {'text': 'Does not have permission create',
+                'has': 'no', 'name': 'create'} in r.json[mem_id]
+        assert {'text': 'Does not have permission create',
+                'has': 'no', 'name': 'create'} in r.json[anon_id]
 
     def test_admin_extension_sidebar(self):
 
@@ -1014,7 +1018,7 @@ class TestExport(TestController):
         exportable_tools = AdminApp.exportable_tools_for(project)
         exportable_mount_points = [
             t.options.mount_point for t in exportable_tools]
-        assert_equals(exportable_mount_points, [u'admin', u'wiki', u'wiki2'])
+        assert_equals(exportable_mount_points, ['admin', 'wiki', 'wiki2'])
 
     def test_access(self):
         r = self.app.get('/admin/export',
@@ -1055,29 +1059,29 @@ class TestExport(TestController):
             exportable_mount_points = [
                 t.options.mount_point for t in exportable_tools]
             assert_equals(exportable_mount_points,
-                          [u'admin', u'search', u'wiki', u'wiki2'])
+                          ['admin', 'search', 'wiki', 'wiki2'])
 
     def test_tools_not_selected(self):
         r = self.app.post('/admin/export')
         assert_in('error', self.webflash(r))
 
     def test_bad_tool(self):
-        r = self.app.post('/admin/export', {'tools': u'search'})
+        r = self.app.post('/admin/export', {'tools': 'search'})
         assert_in('error', self.webflash(r))
 
     @mock.patch('allura.ext.admin.admin_main.export_tasks')
     def test_selected_one_tool(self, export_tasks):
-        r = self.app.post('/admin/export', {'tools': u'wiki'})
+        r = self.app.post('/admin/export', {'tools': 'wiki'})
         assert_in('ok', self.webflash(r))
         export_tasks.bulk_export.post.assert_called_once_with(
-            [u'wiki'], 'test.zip', send_email=True)
+            ['wiki'], 'test.zip', send_email=True)
 
     @mock.patch('allura.ext.admin.admin_main.export_tasks')
     def test_selected_multiple_tools(self, export_tasks):
-        r = self.app.post('/admin/export', {'tools': [u'wiki', u'wiki2']})
+        r = self.app.post('/admin/export', {'tools': ['wiki', 'wiki2']})
         assert_in('ok', self.webflash(r))
         export_tasks.bulk_export.post.assert_called_once_with(
-            [u'wiki', u'wiki2'], 'test.zip', send_email=True)
+            ['wiki', 'wiki2'], 'test.zip', send_email=True)
 
     def test_export_in_progress(self):
         from allura.tasks import export_tasks

http://git-wip-us.apache.org/repos/asf/allura/blob/d52f8e2a/Allura/allura/tests/functional/test_auth.py
----------------------------------------------------------------------
diff --git a/Allura/allura/tests/functional/test_auth.py b/Allura/allura/tests/functional/test_auth.py
index 0f9a8ee..79325cc 100644
--- a/Allura/allura/tests/functional/test_auth.py
+++ b/Allura/allura/tests/functional/test_auth.py
@@ -1,3 +1,7 @@
+from __future__ import print_function
+from __future__ import division
+from __future__ import absolute_import
+from __future__ import unicode_literals
 # Licensed to the Apache Software Foundation (ASF) under one
 #       or more contributor license agreements.  See the NOTICE file
 #       distributed with this work for additional information
@@ -18,8 +22,8 @@
 import calendar
 from datetime import datetime, time, timedelta
 import json
-from urlparse import urlparse, parse_qs
-from urllib import urlencode
+from urllib.parse import urlparse, parse_qs
+from urllib.parse import urlencode
 from bson import ObjectId
 
 import re
@@ -196,7 +200,7 @@ class TestAuth(TestController):
 
         assert sendsimplemail.post.call_count == 1
         assert kwargs['toaddr'] == email_address
-        assert kwargs['subject'] == u'%s - Email address claim attempt' % config['site_name']
+        assert kwargs['subject'] == '%s - Email address claim attempt' % config['site_name']
         assert "You tried to add %s to your Allura account, " \
                "but it is already claimed by your %s account." % (email_address, user.username) in kwargs['text']
 
@@ -294,7 +298,7 @@ class TestAuth(TestController):
         args, kwargs = sendsimplemail.post.call_args
         assert sendsimplemail.post.call_count == 1
         assert kwargs['toaddr'] == email_address
-        assert kwargs['subject'] == u'%s - Email address claim attempt' % config['site_name']
+        assert kwargs['subject'] == '%s - Email address claim attempt' % config['site_name']
         assert "You tried to add %s to your Allura account, " \
                "but it is already claimed by your %s account." % (email_address, user.username) in kwargs['text']
 
@@ -382,7 +386,7 @@ class TestAuth(TestController):
                       params=change_params)
 
         u = M.User.by_username('test-admin')
-        print(u.get_tool_data('AuthPasswordReset', 'hash'))
+        print((u.get_tool_data('AuthPasswordReset', 'hash')))
         assert_equal(u.get_tool_data('AuthPasswordReset', 'hash'), '')
         assert_equal(u.get_tool_data('AuthPasswordReset', 'hash_expiry'), '')
 
@@ -561,7 +565,7 @@ class TestAuth(TestController):
 
     def _find_subscriptions_form(self, r):
         form = None
-        for f in r.forms.itervalues():
+        for f in r.forms.values():
             if f.action == 'update_subscriptions':
                 form = f
                 break
@@ -570,7 +574,7 @@ class TestAuth(TestController):
 
     def _find_subscriptions_field(self, form, subscribed=False):
         field_name = None
-        for k, v in form.fields.iteritems():
+        for k, v in form.fields.items():
             if subscribed:
                 check = c and v[0].value == 'on'
             else:

http://git-wip-us.apache.org/repos/asf/allura/blob/d52f8e2a/Allura/allura/tests/functional/test_discuss.py
----------------------------------------------------------------------
diff --git a/Allura/allura/tests/functional/test_discuss.py b/Allura/allura/tests/functional/test_discuss.py
index 0063cb8..8092595 100644
--- a/Allura/allura/tests/functional/test_discuss.py
+++ b/Allura/allura/tests/functional/test_discuss.py
@@ -1,3 +1,7 @@
+from __future__ import print_function
+from __future__ import division
+from __future__ import absolute_import
+from __future__ import unicode_literals
 #       Licensed to the Apache Software Foundation (ASF) under one
 #       or more contributor license agreements.  See the NOTICE file
 #       distributed with this work for additional information
@@ -15,12 +19,9 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
-import os
 from mock import patch
 from nose.tools import assert_in, assert_not_in, assert_equal
 
-from ming.odm import session
-
 from allura.tests import TestController
 from allura import model as M
 
@@ -66,9 +67,8 @@ class TestDiscuss(TestController):
         params = dict()
         inputs = f.findAll('input')
         for field in inputs:
-            if field.has_key('name'):
-                params[field['name']] = field.has_key(
-                    'value') and field['value'] or ''
+            if 'name' in field:
+                params[field['name']] = 'value' in field and field['value'] or ''
         params[f.find('textarea')['name']] = text
         r = self.app.post(f['action'].encode('utf-8'), params=params,
                           headers={'Referer': thread_link.encode("utf-8")},
@@ -91,9 +91,8 @@ class TestDiscuss(TestController):
         params = dict()
         inputs = post_form.findAll('input')
         for field in inputs:
-            if field.has_key('name'):
-                params[field['name']] = field.has_key(
-                    'value') and field['value'] or ''
+            if 'name' in field:
+                params[field['name']] = 'value' in field and field['value'] or ''
         params[post_form.find('textarea')['name']] = 'This is a new post'
         r = self.app.post(post_link,
                           params=params,
@@ -106,9 +105,8 @@ class TestDiscuss(TestController):
         params = dict()
         inputs = post_form.findAll('input')
         for field in inputs:
-            if field.has_key('name'):
-                params[field['name']] = field.has_key(
-                    'value') and field['value'] or ''
+            if 'name' in field:
+                params[field['name']] = 'value' in field and field['value'] or ''
         params[post_form.find('textarea')['name']] = 'Tis a reply'
         r = self.app.post(post_link + 'reply',
                           params=params,
@@ -174,8 +172,7 @@ class TestDiscuss(TestController):
         post = M.Post.query.find().first()
         assert post.status == 'ok'
         self.app.post(post_link + 'moderate', params=dict(delete='delete'))
-        assert M.Post.query.find().count() == 1
-        assert M.Post.query.find({'deleted': False}).count() == 0
+        assert M.Post.query.find().count() == 0
 
     @patch.object(M.Thread, 'is_spam')
     def test_feed_does_not_include_comments_held_for_moderation(self, is_spam):
@@ -218,9 +215,8 @@ class TestDiscuss(TestController):
         params = dict()
         inputs = reply_form.findAll('input')
         for field in inputs:
-            if field.has_key('name'):
-                params[field['name']] = field.has_key(
-                    'value') and field['value'] or ''
+            if 'name' in field:
+                params[field['name']] = 'value' in field and field['value'] or ''
         params[reply_form.find('textarea')['name']] = 'zzz'
         self.app.post(post_link, params)
         assert create_activity.call_count == 1, create_activity.call_count
@@ -230,18 +226,6 @@ class TestDiscuss(TestController):
         assert 'Last edit: Test Admin less than 1 minute ago' in str(
             r.html.find('div', {'class': 'display_post'}))
 
-    def test_deleted_post(self):
-        r = self._make_post('This is a post')
-        reply_form = r.html.find(
-            'div', {'class': 'edit_post_form reply'}).find('form')
-        post_link = str(reply_form['action']).rstrip('/')
-        _, slug = post_link.rsplit('/', 1)
-        r = self.app.get(post_link, status=200)
-        post = M.Post.query.get(slug=slug)
-        post.deleted = True
-        session(post).flush(post)
-        r = self.app.get(post_link, status=404)
-
 
 class TestAttachment(TestController):
 
@@ -259,9 +243,8 @@ class TestAttachment(TestController):
         params = dict()
         inputs = f.findAll('input')
         for field in inputs:
-            if field.has_key('name'):
-                params[field['name']] = field.has_key(
-                    'value') and field['value'] or ''
+            if 'name' in field:
+                params[field['name']] = 'value' in field and field['value'] or ''
         params[f.find('textarea')['name']] = 'Test Post'
         r = self.app.post(f['action'].encode('utf-8'), params=params,
                           headers={'Referer': self.thread_link})
@@ -269,21 +252,17 @@ class TestAttachment(TestController):
         self.post_link = str(
             r.html.find('div', {'class': 'edit_post_form reply'}).find('form')['action'])
 
-    def attach_link(self):
+    def test_attach(self):
+        r = self.app.post(self.post_link + 'attach',
+                          upload_files=[('file_info', 'test.txt', 'HiThere!')])
         r = self.app.get(self.thread_link)
         for alink in r.html.findAll('a'):
             if 'attachment' in alink['href']:
                 alink = str(alink['href'])
-                return alink
+                break
         else:
             assert False, 'attachment link not found'
-
-    def test_attach(self):
-        r = self.app.post(self.post_link + 'attach',
-                          upload_files=[('file_info', 'test.txt', 'HiThere!')])
-        r = self.app.get(self.thread_link)
         assert '<div class="attachment_thumb">' in r
-        alink = self.attach_link()
         r = self.app.get(alink)
         assert r.content_disposition == 'attachment;filename="test.txt"', 'Attachments should force download'
         r = self.app.post(self.post_link + 'attach',
@@ -299,31 +278,11 @@ class TestAttachment(TestController):
         inputs = post_form.findAll('input')
 
         for field in inputs:
-            if field.has_key('name') and (field['name'] != 'file_info'):
-                params[field['name']] = field.has_key(
-                    'value') and field['value'] or ''
+            if 'name' in field and (field['name'] != 'file_info'):
+                params[field['name']] = 'value' in field and field['value'] or ''
         params[post_form.find('textarea')['name']] = 'Reply'
         r = self.app.post(self.post_link + 'reply',
                           params=params,
                           upload_files=[('file_info', 'test.txt', 'HiThere!')])
         r = self.app.get(self.thread_link)
         assert "test.txt" in r
-
-    def test_deleted_post_attachment(self):
-        f = os.path.join(os.path.dirname(__file__), '..', 'data', 'user.png')
-        with open(f) as f:
-            pic = f.read()
-        self.app.post(
-            self.post_link + 'attach',
-            upload_files=[('file_info', 'user.png', pic)])
-        alink = self.attach_link()
-        thumblink = alink + '/thumb'
-        self.app.get(alink, status=200)
-        self.app.get(thumblink, status=200)
-        _, slug = self.post_link.rstrip('/').rsplit('/', 1)
-        post = M.Post.query.get(slug=slug)
-        assert post, 'Could not find post for {} {}'.format(slug, self.post_link)
-        post.deleted = True
-        session(post).flush(post)
-        self.app.get(alink, status=404)
-        self.app.get(thumblink, status=404)

http://git-wip-us.apache.org/repos/asf/allura/blob/d52f8e2a/Allura/allura/tests/functional/test_feeds.py
----------------------------------------------------------------------
diff --git a/Allura/allura/tests/functional/test_feeds.py b/Allura/allura/tests/functional/test_feeds.py
index 54416a5..d17252a 100644
--- a/Allura/allura/tests/functional/test_feeds.py
+++ b/Allura/allura/tests/functional/test_feeds.py
@@ -1,3 +1,7 @@
+from __future__ import print_function
+from __future__ import division
+from __future__ import absolute_import
+from __future__ import unicode_literals
 #       Licensed to the Apache Software Foundation (ASF) under one
 #       or more contributor license agreements.  See the NOTICE file
 #       distributed with this work for additional information
@@ -44,7 +48,7 @@ class TestFeeds(TestController):
                     status='open',
                     description='This is a description'))),
             status=302)
-        title = u'Descri\xe7\xe3o e Arquitetura'.encode('utf-8')
+        title = 'Descri\xe7\xe3o e Arquitetura'.encode('utf-8')
         self.app.post(
             '/wiki/%s/update' % title,
             params=dict(

http://git-wip-us.apache.org/repos/asf/allura/blob/d52f8e2a/Allura/allura/tests/functional/test_gravatar.py
----------------------------------------------------------------------
diff --git a/Allura/allura/tests/functional/test_gravatar.py b/Allura/allura/tests/functional/test_gravatar.py
index bea43f3..2d2d597 100644
--- a/Allura/allura/tests/functional/test_gravatar.py
+++ b/Allura/allura/tests/functional/test_gravatar.py
@@ -1,3 +1,7 @@
+from __future__ import print_function
+from __future__ import division
+from __future__ import absolute_import
+from __future__ import unicode_literals
 #       Licensed to the Apache Software Foundation (ASF) under one
 #       or more contributor license agreements.  See the NOTICE file
 #       distributed with this work for additional information
@@ -15,7 +19,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
-from urlparse import urlparse, parse_qs
+from urllib.parse import urlparse, parse_qs
 
 from allura.tests import TestController
 import allura.lib.gravatar as gravatar
@@ -30,7 +34,7 @@ class TestGravatar(TestController):
         assert expected_id == actual_id
 
     def test_unicode_id(self):
-        email = u'Vin\u00EDcius@example.com'
+        email = 'Vin\u00EDcius@example.com'
         expected_id = 'e00968255d68523b034a6a39c522efdb'
         actual_id = gravatar.id(email)
         assert expected_id == actual_id, 'Expected gravitar ID %s, got %s' % (

http://git-wip-us.apache.org/repos/asf/allura/blob/d52f8e2a/Allura/allura/tests/functional/test_home.py
----------------------------------------------------------------------
diff --git a/Allura/allura/tests/functional/test_home.py b/Allura/allura/tests/functional/test_home.py
index 58f1d61..e02ef01 100644
--- a/Allura/allura/tests/functional/test_home.py
+++ b/Allura/allura/tests/functional/test_home.py
@@ -1,3 +1,7 @@
+from __future__ import print_function
+from __future__ import division
+from __future__ import absolute_import
+from __future__ import unicode_literals
 #       Licensed to the Apache Software Foundation (ASF) under one
 #       or more contributor license agreements.  See the NOTICE file
 #       distributed with this work for additional information
@@ -53,10 +57,10 @@ class TestProjectHome(TestController):
         menu = response.json['menu']
         wikis = menu[-2]['children']
         assert_equal(len(wikis), 2)
-        assert {u'url': u'/p/test/wiki/', u'name': u'Wiki', u'icon':
-                u'tool-wiki', 'tool_name': 'wiki'} in wikis, wikis
-        assert {u'url': u'/p/test/wiki2/', u'name': u'wiki2', u'icon':
-                u'tool-wiki', 'tool_name': 'wiki'} in wikis, wikis
+        assert {'url': '/p/test/wiki/', 'name': 'Wiki', 'icon':
+                'tool-wiki', 'tool_name': 'wiki'} in wikis, wikis
+        assert {'url': '/p/test/wiki2/', 'name': 'wiki2', 'icon':
+                'tool-wiki', 'tool_name': 'wiki'} in wikis, wikis
 
     def test_sitemap_limit_per_tool(self):
         """Test that sitemap is limited to max of 10 items per tool type."""
@@ -85,8 +89,8 @@ class TestProjectHome(TestController):
         menu = response.json['menu']
         wiki_menu = [m for m in menu if m['tool_name'] == 'wiki'][0]
         assert_equal(len(wiki_menu['children']), 10)
-        assert {u'url': u'/p/test/_list/wiki', u'name': u'More...',
-                u'icon': u'tool-wiki', 'tool_name': 'wiki'} in wiki_menu['children']
+        assert {'url': '/p/test/_list/wiki', 'name': 'More...',
+                'icon': 'tool-wiki', 'tool_name': 'wiki'} in wiki_menu['children']
 
     @td.with_wiki
     def test_neighborhood_home(self):
@@ -127,8 +131,8 @@ class TestProjectHome(TestController):
         r = self.app.get('/p/test/users', status=200)
         j = json.loads(r.body)
         expected = [{
-            'value': u'test-admin',
-            'label': u'Test Admin (test-admin)'
+            'value': 'test-admin',
+            'label': 'Test Admin (test-admin)'
         }]
         assert_equal(j['options'], expected)
 

http://git-wip-us.apache.org/repos/asf/allura/blob/d52f8e2a/Allura/allura/tests/functional/test_neighborhood.py
----------------------------------------------------------------------
diff --git a/Allura/allura/tests/functional/test_neighborhood.py b/Allura/allura/tests/functional/test_neighborhood.py
index dde6647..494ca2c 100644
--- a/Allura/allura/tests/functional/test_neighborhood.py
+++ b/Allura/allura/tests/functional/test_neighborhood.py
@@ -1,3 +1,7 @@
+from __future__ import print_function
+from __future__ import division
+from __future__ import absolute_import
+from __future__ import unicode_literals
 #       Licensed to the Apache Software Foundation (ASF) under one
 #       or more contributor license agreements.  See the NOTICE file
 #       distributed with this work for additional information
@@ -17,16 +21,14 @@
 
 import json
 import os
-from cStringIO import StringIO
-import urllib2
+from io import StringIO
+import urllib.request, urllib.error, urllib.parse
 
 import PIL
-from mock import patch
 from tg import config
-from nose.tools import assert_equal, assert_in, assert_not_equal
+from nose.tools import assert_equal, assert_in
 from ming.orm.ormsession import ThreadLocalORMSession
 from paste.httpexceptions import HTTPFound
-from pylons import app_globals as g
 
 import allura
 from allura import model as M
@@ -586,7 +588,7 @@ class TestNeighborhood(TestController):
             antispam=True,
             extra_environ=dict(username='root'))
         cookies = r.headers.getall('Set-Cookie')
-        flash_msg_cookies = map(urllib2.unquote, cookies)
+        flash_msg_cookies = list(map(urllib.parse.unquote, cookies))
 
         assert any('Internal Error' in cookie for cookie in flash_msg_cookies)
 
@@ -956,98 +958,3 @@ class TestNeighborhood(TestController):
             'div', {'class': 'neighborhood_title_link'}).find('a')
         assert 'View More Projects' in str(link)
         assert link['href'] == '/adobe/'
-
-
-class TestPhoneVerificationOnProjectRegistration(TestController):
-
-    def test_phone_verification_fragment_renders(self):
-        self.app.get('/p/phone_verification_fragment', status=200)
-        self.app.get('/adobe/phone_verification_fragment', status=200)
-
-    def test_verify_phone_no_params(self):
-        with h.push_config(config, **{'project.verify_phone': 'true'}):
-            self.app.get('/p/verify_phone', status=404)
-
-    def test_verify_phone_error(self):
-        with h.push_config(config, **{'project.verify_phone': 'true'}):
-            r = self.app.get('/p/verify_phone', {'number': '1234567890'})
-            expected = {'status': 'error',
-                        'error': 'Phone service is not configured'}
-            assert_equal(r.json, expected)
-            rid = r.session.get('phone_verification.request_id')
-            hash = r.session.get('phone_verification.number_hash')
-            assert_equal(rid, None)
-            assert_equal(hash, None)
-
-    @patch.object(g, 'phone_service', autospec=True)
-    def test_verify_phone(self, phone_service):
-        with h.push_config(config, **{'project.verify_phone': 'true'}):
-            phone_service.verify.return_value = {
-                'request_id': 'request-id', 'status': 'ok'}
-            r = self.app.get('/p/verify_phone', {'number': '1234567890'})
-            phone_service.verify.assert_called_once_with('1234567890')
-            assert_equal(r.json, {'status': 'ok'})
-            rid = r.session.get('phone_verification.request_id')
-            hash = r.session.get('phone_verification.number_hash')
-            assert_equal(rid, 'request-id')
-            assert_equal(hash, '01b307acba4f54f55aafc33bb06bbbf6ca803e9a')
-
-    def test_check_phone_verification_no_params(self):
-        with h.push_config(config, **{'project.verify_phone': 'true'}):
-            self.app.get('/p/check_phone_verification', status=404)
-
-    @patch.object(g, 'phone_service', autospec=True)
-    def test_check_phone_verification_error(self, phone_service):
-        with h.push_config(config, **{'project.verify_phone': 'true'}):
-            phone_service.check.return_value = {'status': 'error'}
-            req_id = 'request-id'
-
-            # make request to verify first to initialize session
-            phone_service.verify.return_value = {
-                'request_id': req_id, 'status': 'ok'}
-            r = self.app.get('/p/verify_phone', {'number': '1234567890'})
-
-            r = self.app.get('/p/check_phone_verification', {'pin': '1234'})
-            assert_equal(r.json, {'status': 'error'})
-            phone_service.check.assert_called_once_with(req_id, '1234')
-
-            user = M.User.by_username('test-admin')
-            hash = user.get_tool_data('phone_verification', 'number_hash')
-            assert_equal(hash, None)
-
-    @patch.object(g, 'phone_service', autospec=True)
-    def test_check_phone_verification_ok(self, phone_service):
-        with h.push_config(config, **{'project.verify_phone': 'true'}):
-            phone_service.check.return_value = {'status': 'ok'}
-            req_id = 'request-id'
-
-            # make request to verify first to initialize session
-            phone_service.verify.return_value = {
-                'request_id': req_id, 'status': 'ok'}
-            r = self.app.get('/p/verify_phone', {'number': '1234567890'})
-
-            r = self.app.get('/p/check_phone_verification', {'pin': '1234'})
-            assert_equal(r.json, {'status': 'ok'})
-            phone_service.check.assert_called_once_with(req_id, '1234')
-
-            user = M.User.by_username('test-admin')
-            hash = user.get_tool_data('phone_verification', 'number_hash')
-            assert_equal(hash, '01b307acba4f54f55aafc33bb06bbbf6ca803e9a')
-
-    def test_register_phone_not_verified(self):
-        with h.push_config(config, **{'project.verify_phone': 'true'}):
-            r = self.app.post(
-                '/p/register',
-                params=dict(
-                    project_unixname='phonetest',
-                    project_name='Phone Test',
-                    project_description='',
-                    neighborhood='Projects'),
-                extra_environ=dict(username='test-user'),
-                antispam=True)
-            overlay = r.html.find('div', {'id': 'phone_verification_overlay'})
-            assert_not_equal(overlay, None)
-            header = overlay.find('h2')
-            iframe = overlay.find('iframe')
-            assert_equal(header.getText(), 'Phone Verification Required')
-            assert_equal(iframe.get('src'), '/p/phone_verification_fragment')

http://git-wip-us.apache.org/repos/asf/allura/blob/d52f8e2a/Allura/allura/tests/functional/test_rest.py
----------------------------------------------------------------------
diff --git a/Allura/allura/tests/functional/test_rest.py b/Allura/allura/tests/functional/test_rest.py
index 43a92d1..41efaaa 100644
--- a/Allura/allura/tests/functional/test_rest.py
+++ b/Allura/allura/tests/functional/test_rest.py
@@ -17,6 +17,10 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import print_function
+from __future__ import division
+from __future__ import absolute_import
+from __future__ import unicode_literals
 from pylons import app_globals as g
 import mock
 from nose.tools import assert_equal, assert_in, assert_not_in
@@ -295,7 +299,7 @@ class TestDoap(TestRestApiBase):
         user = maintainers[0].find(self.foaf + 'Person')
         assert_equal(user.find(self.foaf + 'name').text, 'Test Admin')
         assert_equal(user.find(self.foaf + 'nick').text, 'test-admin')
-        assert_equal(user.find(self.foaf + 'homepage').items()[0][1],
+        assert_equal(list(user.find(self.foaf + 'homepage').items())[0][1],
                      'http://localhost/u/test-admin/')
 
     @td.with_tool('test', 'Tickets', 'bugs')
@@ -314,7 +318,7 @@ class TestDoap(TestRestApiBase):
         p = r.xml.find(self.ns + 'Project')
         tools = p.findall(self.ns_sf + 'feature')
         tools = [(t.find(self.ns_sf + 'Feature').find(self.ns + 'name').text,
-                  t.find(self.ns_sf + 'Feature').find(self.foaf + 'page').items()[0][1])
+                  list(t.find(self.ns_sf + 'Feature').find(self.foaf + 'page').items())[0][1])
                  for t in tools]
         assert_in(('Tickets', 'http://localhost/p/test/bugs/'), tools)
         assert_in(('Tickets', 'http://localhost/p/test/private-bugs/'), tools)
@@ -325,7 +329,7 @@ class TestDoap(TestRestApiBase):
         p = r.xml.find(self.ns + 'Project')
         tools = p.findall(self.ns_sf + 'feature')
         tools = [(t.find(self.ns_sf + 'Feature').find(self.ns + 'name').text,
-                  t.find(self.ns_sf + 'Feature').find(self.foaf + 'page').items()[0][1])
+                  list(t.find(self.ns_sf + 'Feature').find(self.foaf + 'page').items())[0][1])
                  for t in tools]
         assert_in(('Tickets', 'http://localhost/p/test/bugs/'), tools)
         assert_not_in(('Tickets', 'http://localhost/p/test/private-bugs/'), tools)

http://git-wip-us.apache.org/repos/asf/allura/blob/d52f8e2a/Allura/allura/tests/functional/test_root.py
----------------------------------------------------------------------
diff --git a/Allura/allura/tests/functional/test_root.py b/Allura/allura/tests/functional/test_root.py
index 8f29b31..e2aac1f 100644
--- a/Allura/allura/tests/functional/test_root.py
+++ b/Allura/allura/tests/functional/test_root.py
@@ -28,6 +28,10 @@ functional tests exercise the whole application and its WSGI stack.
 Please read http://pythonpaste.org/webtest/ for more information.
 
 """
+from __future__ import print_function
+from __future__ import division
+from __future__ import absolute_import
+from __future__ import unicode_literals
 from pylons import tmpl_context as c
 from nose.tools import assert_equal
 from ming.orm.ormsession import ThreadLocalORMSession

http://git-wip-us.apache.org/repos/asf/allura/blob/d52f8e2a/Allura/allura/tests/functional/test_search.py
----------------------------------------------------------------------
diff --git a/Allura/allura/tests/functional/test_search.py b/Allura/allura/tests/functional/test_search.py
index 374d5f3..50c0cfb 100644
--- a/Allura/allura/tests/functional/test_search.py
+++ b/Allura/allura/tests/functional/test_search.py
@@ -1,3 +1,7 @@
+from __future__ import print_function
+from __future__ import division
+from __future__ import absolute_import
+from __future__ import unicode_literals
 #       Licensed to the Apache Software Foundation (ASF) under one
 #       or more contributor license agreements.  See the NOTICE file
 #       distributed with this work for additional information

http://git-wip-us.apache.org/repos/asf/allura/blob/d52f8e2a/Allura/allura/tests/functional/test_site_admin.py
----------------------------------------------------------------------
diff --git a/Allura/allura/tests/functional/test_site_admin.py b/Allura/allura/tests/functional/test_site_admin.py
index e89b9fd..70436e3 100644
--- a/Allura/allura/tests/functional/test_site_admin.py
+++ b/Allura/allura/tests/functional/test_site_admin.py
@@ -16,6 +16,10 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import print_function
+from __future__ import division
+from __future__ import absolute_import
+from __future__ import unicode_literals
 import json
 import datetime as dt
 
@@ -156,7 +160,7 @@ class TestSiteAdmin(TestController):
             user='root',
             path='/p/test/admin',
         ), status=302)
-        task = M.MonQTask.query.find({}).sort('_id', -1).next()
+        task = next(M.MonQTask.query.find({}).sort('_id', -1))
         assert str(task._id) in r.location
         assert task.context['project_id'] == project._id
         assert task.context['app_config_id'] == app.config._id
@@ -338,14 +342,14 @@ class TestUserDetails(TestController):
 
     def test_add_comment(self):
         r = self.app.get('/nf/admin/user/test-user')
-        assert_not_in(u'Comment by test-admin: I was hêre!', r)
+        assert_not_in('Comment by test-admin: I was hêre!', r)
         form = r.forms[4]
         assert_equal(form['username'].value, 'test-user')
-        form['comment'] = u'I was hêre!'
+        form['comment'] = 'I was hêre!'
         r = form.submit()
-        assert_in(u'Comment added', self.webflash(r))
+        assert_in('Comment added', self.webflash(r))
         r = self.app.get('/nf/admin/user/test-user')
-        assert_in(u'Comment by test-admin: I was hêre!', r)
+        assert_in('Comment by test-admin: I was hêre!', r)
 
     def test_disable_user(self):
         # user was not pending
@@ -359,7 +363,7 @@ class TestUserDetails(TestController):
         with td.audits('Account disabled', user=True):
             r = form.submit()
             assert_equal(M.AuditLog.query.find().count(), 1)
-        assert_in(u'User disabled', self.webflash(r))
+        assert_in('User disabled', self.webflash(r))
         assert_equal(M.User.by_username('test-user-3').disabled, True)
         assert_equal(M.User.by_username('test-user-3').pending, False)
 
@@ -378,7 +382,7 @@ class TestUserDetails(TestController):
         with td.audits('Account disabled', user=True):
             r = form.submit()
             assert_equal(M.AuditLog.query.find().count(), 1)
-        assert_in(u'User disabled', self.webflash(r))
+        assert_in('User disabled', self.webflash(r))
         assert_equal(M.User.by_username('test-user-3').disabled, True)
         assert_equal(M.User.by_username('test-user-3').pending, True)
 
@@ -397,7 +401,7 @@ class TestUserDetails(TestController):
         with td.audits('Account enabled', user=True):
             r = form.submit()
             assert_equal(M.AuditLog.query.find().count(), 1)
-        assert_in(u'User enabled', self.webflash(r))
+        assert_in('User enabled', self.webflash(r))
         assert_equal(M.User.by_username('test-user-3').disabled, False)
         assert_equal(M.User.by_username('test-user-3').pending, False)
 
@@ -416,7 +420,7 @@ class TestUserDetails(TestController):
         with td.audits('Account enabled', user=True):
             r = form.submit()
             assert_equal(M.AuditLog.query.find().count(), 1)
-        assert_in(u'User enabled', self.webflash(r))
+        assert_in('User enabled', self.webflash(r))
         assert_equal(M.User.by_username('test-user-3').disabled, False)
         assert_equal(M.User.by_username('test-user-3').pending, False)
 
@@ -435,7 +439,7 @@ class TestUserDetails(TestController):
         with td.audits('Account enabled', user=True):
             r = form.submit()
             assert_equal(M.AuditLog.query.find().count(), 1)
-        assert_in(u'User enabled', self.webflash(r))
+        assert_in('User enabled', self.webflash(r))
         assert_equal(M.User.by_username('test-user-3').disabled, False)
         assert_equal(M.User.by_username('test-user-3').pending, False)
 
@@ -454,7 +458,7 @@ class TestUserDetails(TestController):
         with td.audits('Account changed to pending', user=True):
             r = form.submit()
             assert_equal(M.AuditLog.query.find().count(), 1)
-        assert_in(u'Set user status to pending', self.webflash(r))
+        assert_in('Set user status to pending', self.webflash(r))
         assert_equal(M.User.by_username('test-user-3').disabled, False)
         assert_equal(M.User.by_username('test-user-3').pending, True)
 
@@ -473,7 +477,7 @@ class TestUserDetails(TestController):
         with td.audits('Account changed to pending', user=True):
             r = form.submit()
             assert_equal(M.AuditLog.query.find().count(), 1)
-        assert_in(u'Set user status to pending', self.webflash(r))
+        assert_in('Set user status to pending', self.webflash(r))
         assert_equal(M.User.by_username('test-user-3').disabled, False)
         assert_equal(M.User.by_username('test-user-3').pending, True)
 

http://git-wip-us.apache.org/repos/asf/allura/blob/d52f8e2a/Allura/allura/tests/functional/test_static.py
----------------------------------------------------------------------
diff --git a/Allura/allura/tests/functional/test_static.py b/Allura/allura/tests/functional/test_static.py
index 49e9295..5a69189 100644
--- a/Allura/allura/tests/functional/test_static.py
+++ b/Allura/allura/tests/functional/test_static.py
@@ -1,3 +1,7 @@
+from __future__ import print_function
+from __future__ import division
+from __future__ import absolute_import
+from __future__ import unicode_literals
 #       Licensed to the Apache Software Foundation (ASF) under one
 #       or more contributor license agreements.  See the NOTICE file
 #       distributed with this work for additional information

http://git-wip-us.apache.org/repos/asf/allura/blob/d52f8e2a/Allura/allura/tests/functional/test_subscriber.py
----------------------------------------------------------------------
diff --git a/Allura/allura/tests/functional/test_subscriber.py b/Allura/allura/tests/functional/test_subscriber.py
index 93b822e..67c2e39 100644
--- a/Allura/allura/tests/functional/test_subscriber.py
+++ b/Allura/allura/tests/functional/test_subscriber.py
@@ -1,3 +1,7 @@
+from __future__ import print_function
+from __future__ import division
+from __future__ import absolute_import
+from __future__ import unicode_literals
 #       Licensed to the Apache Software Foundation (ASF) under one
 #       or more contributor license agreements.  See the NOTICE file
 #       distributed with this work for additional information

http://git-wip-us.apache.org/repos/asf/allura/blob/d52f8e2a/Allura/allura/tests/functional/test_tool_list.py
----------------------------------------------------------------------
diff --git a/Allura/allura/tests/functional/test_tool_list.py b/Allura/allura/tests/functional/test_tool_list.py
index 08607cf..44e83e5 100644
--- a/Allura/allura/tests/functional/test_tool_list.py
+++ b/Allura/allura/tests/functional/test_tool_list.py
@@ -1,3 +1,7 @@
+from __future__ import print_function
+from __future__ import division
+from __future__ import absolute_import
+from __future__ import unicode_literals
 #       Licensed to the Apache Software Foundation (ASF) under one
 #       or more contributor license agreements.  See the NOTICE file
 #       distributed with this work for additional information

http://git-wip-us.apache.org/repos/asf/allura/blob/d52f8e2a/Allura/allura/tests/functional/test_trovecategory.py
----------------------------------------------------------------------
diff --git a/Allura/allura/tests/functional/test_trovecategory.py b/Allura/allura/tests/functional/test_trovecategory.py
index 64992a7..3284711 100644
--- a/Allura/allura/tests/functional/test_trovecategory.py
+++ b/Allura/allura/tests/functional/test_trovecategory.py
@@ -1,3 +1,7 @@
+from __future__ import print_function
+from __future__ import division
+from __future__ import absolute_import
+from __future__ import unicode_literals
 #       Licensed to the Apache Software Foundation (ASF) under one
 #       or more contributor license agreements.  See the NOTICE file
 #       distributed with this work for additional information