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:06 UTC

[44/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/controllers/project.py
----------------------------------------------------------------------
diff --git a/Allura/allura/controllers/project.py b/Allura/allura/controllers/project.py
index 197f859..40d0d07 100644
--- a/Allura/allura/controllers/project.py
+++ b/Allura/allura/controllers/project.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,11 +22,10 @@
 import re
 import logging
 from datetime import datetime, timedelta
-from urllib import unquote
-from hashlib import sha1
+from urllib.parse import unquote
 
 from bson import ObjectId
-from tg import expose, flash, redirect, validate, request, config, session
+from tg import expose, flash, redirect, validate, request, config
 from tg.decorators import with_trailing_slash, without_trailing_slash
 from pylons import tmpl_context as c, app_globals as g
 from paste.deploy.converters import asbool
@@ -126,10 +129,10 @@ class NeighborhoodController(object):
                          pname, remainder)
                 project.configure_project(is_user_project=True)
             else:
-                raise exc.HTTPNotFound, pname
+                raise exc.HTTPNotFound(pname)
         c.project = project
         if project is None or (project.deleted and not has_access(c.project, 'update')()):
-            raise exc.HTTPNotFound, pname
+            raise exc.HTTPNotFound(pname)
         return ProjectController(), remainder
 
     @expose('jinja:allura:templates/neighborhood_project_list.html')
@@ -185,37 +188,12 @@ class NeighborhoodController(object):
     @without_trailing_slash
     def add_project(self, **form_data):
         require_access(self.neighborhood, 'register')
-        verify = c.form_errors == {'_the_form': u'phone-verification'}
-        c.show_phone_verification_overlay = verify
         c.add_project = W.add_project
-        form_data.setdefault('tools', W.add_project.default_tools)
+        form_data.setdefault(
+            'tools', ['Wiki', 'Git', 'Tickets', 'Discussion'])
         form_data['neighborhood'] = self.neighborhood.name
         return dict(neighborhood=self.neighborhood, form_data=form_data)
 
-    @expose('jinja:allura:templates/phone_verification_fragment.html')
-    def phone_verification_fragment(self, *args, **kw):
-        return {}
-
-    @expose('json:')
-    def verify_phone(self, number):
-        p = plugin.ProjectRegistrationProvider.get()
-        result = p.verify_phone(c.user, number)
-        request_id = result.pop('request_id', None)
-        if request_id:
-            session['phone_verification.request_id'] = request_id
-            number_hash = utils.phone_number_hash(number)
-            session['phone_verification.number_hash'] = number_hash
-            session.save()
-        return result
-
-    @expose('json:')
-    def check_phone_verification(self, pin):
-        p = plugin.ProjectRegistrationProvider.get()
-        request_id = session.get('phone_verification.request_id')
-        number_hash = session.get('phone_verification.number_hash')
-        res = p.check_phone_verification(c.user, request_id, pin, number_hash)
-        return res
-
     @expose('json:')
     def suggest_name(self, project_name=''):
         provider = plugin.ProjectRegistrationProvider.get()
@@ -256,9 +234,6 @@ class NeighborhoodController(object):
             flash(
                 "Project creation rate limit exceeded.  Please try again later.", 'error')
             redirect('add_project')
-        except exceptions.ProjectPhoneVerificationError:
-            flash('You must pass phone verification', 'error')
-            redirect('add_project')
         except Exception as e:
             log.error('error registering project: %s',
                       project_unixname, exc_info=True)
@@ -272,7 +247,7 @@ class NeighborhoodController(object):
             anchored_tools = neighborhood.get_anchored_tools()
             install_params = []
             for i, tool in enumerate(tools):
-                if (tool.lower() not in anchored_tools.keys()) and (c.project.app_instance(tool) is None):
+                if (tool.lower() not in list(anchored_tools.keys())) and (c.project.app_instance(tool) is None):
                     install_params.append(dict(ep_name=tool, ordinal=i + offset))
             c.project.install_apps(install_params)
         flash('Welcome to the %s Project System! '
@@ -395,10 +370,10 @@ class ProjectController(FeedController):
             return ProjectController(), remainder
         app = c.project.app_instance(name)
         if app is None:
-            raise exc.HTTPNotFound, name
+            raise exc.HTTPNotFound(name)
         c.app = app
         if not app.root:
-            raise exc.HTTPNotFound, name
+            raise exc.HTTPNotFound(name)
 
         return app.root, remainder
 
@@ -690,7 +665,7 @@ class NeighborhoodAdminController(object):
                       anchored_tools, 'error')
                 result = False
 
-        for tool in validate_tools.keys():
+        for tool in list(validate_tools.keys()):
             if tool not in g.entry_points['tool']:
                 flash('Anchored tools "%s" is invalid' %
                       anchored_tools, 'error')

http://git-wip-us.apache.org/repos/asf/allura/blob/d52f8e2a/Allura/allura/controllers/repository.py
----------------------------------------------------------------------
diff --git a/Allura/allura/controllers/repository.py b/Allura/allura/controllers/repository.py
index 8fd2047..bb0dbd4 100644
--- a/Allura/allura/controllers/repository.py
+++ b/Allura/allura/controllers/repository.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,7 +22,7 @@
 import os
 import logging
 import difflib
-from urllib import quote, unquote
+from urllib.parse import quote, unquote
 from collections import defaultdict, OrderedDict
 from itertools import islice
 
@@ -148,7 +152,7 @@ class RepoRootController(BaseController, FeedController):
                     redirect(to_project.url() + mount_point + '/')
                 except exc.HTTPRedirection:
                     raise
-                except Exception, ex:
+                except Exception as ex:
                     flash(str(ex), 'error')
                     redirect(request.referer)
 
@@ -233,7 +237,7 @@ class RepoRootController(BaseController, FeedController):
         parents = {}
         children = defaultdict(list)
         dates = {}
-        for row, (oid, ci) in enumerate(commits_by_id.iteritems()):
+        for row, (oid, ci) in enumerate(commits_by_id.items()):
             parents[oid] = list(ci.parent_ids)
             dates[oid] = ci.committed.date
             for p_oid in ci.parent_ids:
@@ -757,8 +761,8 @@ class FileBrowser(BaseController):
 
         la = list(a)
         lb = list(b)
-        adesc = (u'a' + h.really_unicode(apath)).encode('utf-8')
-        bdesc = (u'b' + h.really_unicode(b.path())).encode('utf-8')
+        adesc = ('a' + h.really_unicode(apath)).encode('utf-8')
+        bdesc = ('b' + h.really_unicode(b.path())).encode('utf-8')
 
         if not fmt:
             fmt = web_session.get('diformat', '')

http://git-wip-us.apache.org/repos/asf/allura/blob/d52f8e2a/Allura/allura/controllers/rest.py
----------------------------------------------------------------------
diff --git a/Allura/allura/controllers/rest.py b/Allura/allura/controllers/rest.py
index 8eafa30..6627d66 100644
--- a/Allura/allura/controllers/rest.py
+++ b/Allura/allura/controllers/rest.py
@@ -18,6 +18,10 @@
 #       under the License.
 
 """REST Controller"""
+from __future__ import print_function
+from __future__ import division
+from __future__ import absolute_import
+from __future__ import unicode_literals
 import logging
 
 import oauth2 as oauth
@@ -80,7 +84,7 @@ class RestController(object):
         """
         summary = dict()
         stats = dict()
-        for stat, provider in g.entry_points['site_stats'].iteritems():
+        for stat, provider in g.entry_points['site_stats'].items():
             stats[stat] = provider()
         if stats:
             summary['site_stats'] = stats
@@ -93,7 +97,7 @@ class RestController(object):
             c.user = c.api_token.user
         neighborhood = M.Neighborhood.query.get(url_prefix='/' + name + '/')
         if not neighborhood:
-            raise exc.HTTPNotFound, name
+            raise exc.HTTPNotFound(name)
         return NeighborhoodRestController(neighborhood), remainder
 
 
@@ -263,12 +267,12 @@ class NeighborhoodRestController(object):
             provider.shortname_validator.to_python(
                 name, check_allowed=False, neighborhood=self._neighborhood)
         except Invalid:
-            raise exc.HTTPNotFound, name
+            raise exc.HTTPNotFound(name)
         name = self._neighborhood.shortname_prefix + name
         project = M.Project.query.get(
             shortname=name, neighborhood_id=self._neighborhood._id, deleted=False)
         if not project:
-            raise exc.HTTPNotFound, name
+            raise exc.HTTPNotFound(name)
 
         if project and name and name.startswith('u/'):
             # make sure user-projects are associated with an enabled user
@@ -296,10 +300,10 @@ class ProjectRestController(object):
             return ProjectRestController(), remainder
         app = c.project.app_instance(name)
         if app is None:
-            raise exc.HTTPNotFound, name
+            raise exc.HTTPNotFound(name)
         c.app = app
         if app.api_root is None:
-            raise exc.HTTPNotFound, name
+            raise exc.HTTPNotFound(name)
         action_logger.info('', extra=dict(
             api_key=request.params.get('api_key')))
         return app.api_root, remainder

http://git-wip-us.apache.org/repos/asf/allura/blob/d52f8e2a/Allura/allura/controllers/root.py
----------------------------------------------------------------------
diff --git a/Allura/allura/controllers/root.py b/Allura/allura/controllers/root.py
index a0418d0..9389c91 100644
--- a/Allura/allura/controllers/root.py
+++ b/Allura/allura/controllers/root.py
@@ -18,6 +18,10 @@
 #       under the License.
 
 """Main Controller"""
+from __future__ import print_function
+from __future__ import division
+from __future__ import absolute_import
+from __future__ import unicode_literals
 import logging
 
 from tg import expose, request, config, session

http://git-wip-us.apache.org/repos/asf/allura/blob/d52f8e2a/Allura/allura/controllers/search.py
----------------------------------------------------------------------
diff --git a/Allura/allura/controllers/search.py b/Allura/allura/controllers/search.py
index eaf8446..9de8c7c 100644
--- a/Allura/allura/controllers/search.py
+++ b/Allura/allura/controllers/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
@@ -66,7 +70,7 @@ class ProjectBrowseController(BaseController):
             self.category = M.ProjectCategory.query.find(
                 dict(name=category_name, parent_id=parent_id)).first()
             if not self.category:
-                raise exc.HTTPNotFound, request.path
+                raise exc.HTTPNotFound(request.path)
         else:
             self.category = None
 

http://git-wip-us.apache.org/repos/asf/allura/blob/d52f8e2a/Allura/allura/controllers/secure.py
----------------------------------------------------------------------
diff --git a/Allura/allura/controllers/secure.py b/Allura/allura/controllers/secure.py
index 899ad42..8defdb6 100644
--- a/Allura/allura/controllers/secure.py
+++ b/Allura/allura/controllers/secure.py
@@ -18,6 +18,10 @@
 #       under the License.
 
 """Sample controller with all its actions protected."""
+from __future__ import print_function
+from __future__ import division
+from __future__ import absolute_import
+from __future__ import unicode_literals
 
 # This controller is only used when you activate auth. You can safely remove
 # this file from your project.

http://git-wip-us.apache.org/repos/asf/allura/blob/d52f8e2a/Allura/allura/controllers/site_admin.py
----------------------------------------------------------------------
diff --git a/Allura/allura/controllers/site_admin.py b/Allura/allura/controllers/site_admin.py
index ee47274..9eba323 100644
--- a/Allura/allura/controllers/site_admin.py
+++ b/Allura/allura/controllers/site_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
@@ -44,7 +48,7 @@ from allura import model as M
 from allura.command.show_models import dfs, build_model_inheritance_graph
 import allura
 
-from urlparse import urlparse
+from urllib.parse import urlparse
 
 
 log = logging.getLogger(__name__)
@@ -76,7 +80,7 @@ class SiteAdminController(object):
             controller = admin_extension().controllers.get(name)
             if controller:
                 return controller(), remainder
-        raise HTTPNotFound, name
+        raise HTTPNotFound(name)
 
     def sidebar_menu(self):
         base_url = '/nf/admin/'
@@ -280,7 +284,7 @@ class SiteAdminController(object):
         def convert_fields(obj):
             # throw the type away (e.g. '_s' from 'url_s')
             result = {}
-            for k,val in obj.iteritems():
+            for k,val in obj.items():
                 name = k.rsplit('_', 1)
                 if len(name) == 2:
                     name = name[0]
@@ -292,7 +296,7 @@ class SiteAdminController(object):
         return {
             'q': q,
             'f': f,
-            'objects': map(convert_fields, objects),
+            'objects': list(map(convert_fields, objects)),
             'count': count,
             'page': page,
             'limit': limit,

http://git-wip-us.apache.org/repos/asf/allura/blob/d52f8e2a/Allura/allura/controllers/static.py
----------------------------------------------------------------------
diff --git a/Allura/allura/controllers/static.py b/Allura/allura/controllers/static.py
index 313a877..ae6d2d1 100644
--- a/Allura/allura/controllers/static.py
+++ b/Allura/allura/controllers/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
@@ -15,7 +19,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
-from cStringIO import StringIO
+from io import StringIO
 
 from tg import expose
 from tg.decorators import without_trailing_slash

http://git-wip-us.apache.org/repos/asf/allura/blob/d52f8e2a/Allura/allura/controllers/task.py
----------------------------------------------------------------------
diff --git a/Allura/allura/controllers/task.py b/Allura/allura/controllers/task.py
index cb666ee..2f8de2e 100644
--- a/Allura/allura/controllers/task.py
+++ b/Allura/allura/controllers/task.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/controllers/template.py
----------------------------------------------------------------------
diff --git a/Allura/allura/controllers/template.py b/Allura/allura/controllers/template.py
index 89d4b11..17c3f2d 100644
--- a/Allura/allura/controllers/template.py
+++ b/Allura/allura/controllers/template.py
@@ -18,6 +18,10 @@
 #       under the License.
 
 """Fallback controller."""
+from __future__ import print_function
+from __future__ import division
+from __future__ import absolute_import
+from __future__ import unicode_literals
 
 from pylons.controllers.util import abort
 

http://git-wip-us.apache.org/repos/asf/allura/blob/d52f8e2a/Allura/allura/controllers/trovecategories.py
----------------------------------------------------------------------
diff --git a/Allura/allura/controllers/trovecategories.py b/Allura/allura/controllers/trovecategories.py
index b57e517..258b04e 100644
--- a/Allura/allura/controllers/trovecategories.py
+++ b/Allura/allura/controllers/trovecategories.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
@@ -88,7 +92,7 @@ class TroveCategoryController(BaseController):
             (self.generate_category(child) for child in category.subcategories)
         }
 
-        return category.fullname, OrderedDict(sorted(children.iteritems()))
+        return category.fullname, OrderedDict(sorted(children.items()))
 
     @without_trailing_slash
     @expose('jinja:allura:templates/browse_trove_categories.html')
@@ -99,7 +103,7 @@ class TroveCategoryController(BaseController):
             for (key, value) in
             (self.generate_category(child) for child in parent_categories)
         }
-        return dict(tree=OrderedDict(sorted(tree.iteritems())))
+        return dict(tree=OrderedDict(sorted(tree.items())))
 
     @expose()
     @require_post()

http://git-wip-us.apache.org/repos/asf/allura/blob/d52f8e2a/Allura/allura/eventslistener.py
----------------------------------------------------------------------
diff --git a/Allura/allura/eventslistener.py b/Allura/allura/eventslistener.py
index f9f7431..28ac2cc 100644
--- a/Allura/allura/eventslistener.py
+++ b/Allura/allura/eventslistener.py
@@ -19,6 +19,10 @@
 a specific entity (e.g. user, project, ...). To do so, the new classes should
 overwrite the methods defined here, which will be called when the related
 event happens, so that the statistics for the given entity are updated.'''
+from __future__ import print_function
+from __future__ import division
+from __future__ import absolute_import
+from __future__ import unicode_literals
 
 
 class EventsListener:

http://git-wip-us.apache.org/repos/asf/allura/blob/d52f8e2a/Allura/allura/ext/admin/__init__.py
----------------------------------------------------------------------
diff --git a/Allura/allura/ext/admin/__init__.py b/Allura/allura/ext/admin/__init__.py
index 2a46058..c045bd3 100644
--- a/Allura/allura/ext/admin/__init__.py
+++ b/Allura/allura/ext/admin/__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

http://git-wip-us.apache.org/repos/asf/allura/blob/d52f8e2a/Allura/allura/ext/admin/admin_main.py
----------------------------------------------------------------------
diff --git a/Allura/allura/ext/admin/admin_main.py b/Allura/allura/ext/admin/admin_main.py
index b51c816..83797b9 100644
--- a/Allura/allura/ext/admin/admin_main.py
+++ b/Allura/allura/ext/admin/admin_main.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,7 +21,7 @@
 
 import logging
 from datetime import datetime
-from urlparse import urlparse
+from urllib.parse import urlparse
 import json
 from operator import itemgetter
 import pkg_resources
@@ -102,7 +106,7 @@ class AdminApp(Application):
     @staticmethod
     def installable_tools_for(project):
         tools = []
-        for name, App in g.entry_points['tool'].iteritems():
+        for name, App in g.entry_points['tool'].items():
             cfg = M.AppConfig(project_id=project._id, tool_name=name)
             app = App(project, cfg)
             if app.installable:
@@ -189,7 +193,7 @@ class AdminExtensionLookup(object):
             controller = admin_extension().project_admin_controllers.get(name)
             if controller:
                 return controller(), remainder
-        raise exc.HTTPNotFound, name
+        raise exc.HTTPNotFound(name)
 
 
 class ProjectAdminController(BaseController):
@@ -339,7 +343,7 @@ class ProjectAdminController(BaseController):
     def _lookup(self, name, *remainder):
         app = c.project.app_instance(name)
         if app is None:
-            raise exc.HTTPNotFound, name
+            raise exc.HTTPNotFound(name)
         return app.admin, remainder
 
     @expose()
@@ -691,7 +695,7 @@ class ProjectAdminController(BaseController):
             if new_app:
                 # force redir to last page of tools, where new app will be
                 page = ''
-        except forge_exc.ForgeError, exc:
+        except forge_exc.ForgeError as exc:
             flash('%s: %s' % (exc.__class__.__name__, exc.args[0]),
                   'error')
         redirect('tools?limit=%s&page=%s' % (limit, page))
@@ -838,7 +842,7 @@ class ProjectAdminRestController(BaseController):
                    'type': ac.tool_name.lower()}
                   for ac in c.project.app_configs]
         subs = {p.shortname: p for p in M.Project.query.find({'parent_id': c.project._id})}
-        for sub in subs.values():
+        for sub in list(subs.values()):
             mounts.append({'ordinal': sub.ordinal,
                            'mount': sub.shortname,
                            'type': 'sub-project'})
@@ -887,11 +891,11 @@ class ProjectAdminRestController(BaseController):
     @expose()
     def _lookup(self, *args):
         if len(args) == 0:
-            raise exc.HTTPNotFound, args
+            raise exc.HTTPNotFound(args)
         name, remainder = args[0], args[1:]
         app = c.project.app_instance(name)
         if app is None or app.admin_api_root is None:
-            raise exc.HTTPNotFound, name
+            raise exc.HTTPNotFound(name)
         return app.admin_api_root, remainder
 
 
@@ -916,9 +920,9 @@ class PermissionsController(BaseController):
             perm = args['id']
             new_group_ids = args.get('new', [])
             group_ids = args.get('value', [])
-            if isinstance(new_group_ids, basestring):
+            if isinstance(new_group_ids, str):
                 new_group_ids = [new_group_ids]
-            if isinstance(group_ids, basestring):
+            if isinstance(group_ids, str):
                 group_ids = [group_ids]
             # make sure the admin group has the admin permission
             if perm == 'admin':
@@ -933,10 +937,10 @@ class PermissionsController(BaseController):
                         'You cannot remove the admin group from the admin permission.', 'warning')
                     group_ids.append(admin_group_id)
             permissions[perm] = []
-            role_ids = map(ObjectId, group_ids + new_group_ids)
+            role_ids = list(map(ObjectId, group_ids + new_group_ids))
             permissions[perm] = role_ids
         c.project.acl = []
-        for perm, role_ids in permissions.iteritems():
+        for perm, role_ids in permissions.items():
             role_names = lambda ids: ','.join(sorted(
                 pr.name for pr in M.ProjectRole.query.find(dict(_id={'$in': ids}))))
             old_role_ids = old_permissions.get(perm, [])
@@ -1103,9 +1107,9 @@ class GroupsController(BaseController):
             assert group.project == c.project, 'Security violation'
             user_ids = pr.get('value', [])
             new_users = pr.get('new', [])
-            if isinstance(user_ids, basestring):
+            if isinstance(user_ids, str):
                 user_ids = [user_ids]
-            if isinstance(new_users, basestring):
+            if isinstance(new_users, str):
                 new_users = [new_users]
             # Handle new users in groups
             user_added = False
@@ -1121,7 +1125,7 @@ class GroupsController(BaseController):
                     user, upsert=True).roles.append(group._id)
                 user_added = True
             # Make sure we aren't removing all users from the Admin group
-            if group.name == u'Admin' and not (user_ids or user_added):
+            if group.name == 'Admin' and not (user_ids or user_added):
                 flash('You must have at least one user with the Admin role.',
                       'warning')
                 redirect('.')

http://git-wip-us.apache.org/repos/asf/allura/blob/d52f8e2a/Allura/allura/ext/admin/widgets.py
----------------------------------------------------------------------
diff --git a/Allura/allura/ext/admin/widgets.py b/Allura/allura/ext/admin/widgets.py
index 5fb5bbd..068e203 100644
--- a/Allura/allura/ext/admin/widgets.py
+++ b/Allura/allura/ext/admin/widgets.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/ext/project_home/__init__.py
----------------------------------------------------------------------
diff --git a/Allura/allura/ext/project_home/__init__.py b/Allura/allura/ext/project_home/__init__.py
index c3be50e..d523d65 100644
--- a/Allura/allura/ext/project_home/__init__.py
+++ b/Allura/allura/ext/project_home/__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

http://git-wip-us.apache.org/repos/asf/allura/blob/d52f8e2a/Allura/allura/ext/project_home/project_main.py
----------------------------------------------------------------------
diff --git a/Allura/allura/ext/project_home/project_main.py b/Allura/allura/ext/project_home/project_main.py
index a02b807..cb28555 100644
--- a/Allura/allura/ext/project_home/project_main.py
+++ b/Allura/allura/ext/project_home/project_main.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/ext/search/__init__.py
----------------------------------------------------------------------
diff --git a/Allura/allura/ext/search/__init__.py b/Allura/allura/ext/search/__init__.py
index a0bc037..2c6e656 100644
--- a/Allura/allura/ext/search/__init__.py
+++ b/Allura/allura/ext/search/__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

http://git-wip-us.apache.org/repos/asf/allura/blob/d52f8e2a/Allura/allura/ext/search/search_main.py
----------------------------------------------------------------------
diff --git a/Allura/allura/ext/search/search_main.py b/Allura/allura/ext/search/search_main.py
index 99c5dfc..7678128 100644
--- a/Allura/allura/ext/search/search_main.py
+++ b/Allura/allura/ext/search/search_main.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/ext/user_profile/__init__.py
----------------------------------------------------------------------
diff --git a/Allura/allura/ext/user_profile/__init__.py b/Allura/allura/ext/user_profile/__init__.py
index 5b323d6..9569e6c 100644
--- a/Allura/allura/ext/user_profile/__init__.py
+++ b/Allura/allura/ext/user_profile/__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

http://git-wip-us.apache.org/repos/asf/allura/blob/d52f8e2a/Allura/allura/ext/user_profile/user_main.py
----------------------------------------------------------------------
diff --git a/Allura/allura/ext/user_profile/user_main.py b/Allura/allura/ext/user_profile/user_main.py
index 2f71deb..bad3136 100644
--- a/Allura/allura/ext/user_profile/user_main.py
+++ b/Allura/allura/ext/user_profile/user_main.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
@@ -122,7 +126,7 @@ class UserProfileApp(Application):
         for section in re.split(r'\s*,\s*', section_ordering):
             if section in sections:
                 ordered_sections.append(sections.pop(section))
-        UserProfileApp._sections = ordered_sections + sections.values()
+        UserProfileApp._sections = ordered_sections + list(sections.values())
         return UserProfileApp._sections
 
 

http://git-wip-us.apache.org/repos/asf/allura/blob/d52f8e2a/Allura/allura/lib/AsciiDammit.py
----------------------------------------------------------------------
diff --git a/Allura/allura/lib/AsciiDammit.py b/Allura/allura/lib/AsciiDammit.py
index d86c26d..afbd460 100644
--- a/Allura/allura/lib/AsciiDammit.py
+++ b/Allura/allura/lib/AsciiDammit.py
@@ -16,6 +16,10 @@ To the extent that this statement does not divest the copyright,
 the copyright holder hereby grants irrevocably to every recipient
 all rights in this work otherwise reserved under copyright.
 """
+from __future__ import print_function
+from __future__ import division
+from __future__ import absolute_import
+from __future__ import unicode_literals
 
 __author__ = "Leonard Richardson (leonardr@segfault.org)"
 __version__ = "$Revision: 1.3 $"
@@ -173,7 +177,7 @@ def _repl(match, html=0):
     "Replace the matched character with its HTML or ASCII equivalent."
     g = match.group(0)
     a = CHARS.get(g, g)
-    if type(a) == types.TupleType:
+    if type(a) == tuple:
         a = a[html]
         if html:
             a = '&' + a + ';'
@@ -213,11 +217,11 @@ def demoronise(t):
 if __name__ == '__main__':
 
     french = '\x93Sacr\xe9 bleu!\x93'
-    print "First we mangle some French."
-    print asciiDammit(french)
-    print htmlDammit(french)
-
-    print
-    print "And now we fix the MS-quotes but leave the French alone."
-    print demoronise(french)
-    print htmlDammit(french, 1)
+    print("First we mangle some French.")
+    print(asciiDammit(french))
+    print(htmlDammit(french))
+
+    print()
+    print("And now we fix the MS-quotes but leave the French alone.")
+    print(demoronise(french))
+    print(htmlDammit(french, 1))

http://git-wip-us.apache.org/repos/asf/allura/blob/d52f8e2a/Allura/allura/lib/app_globals.py
----------------------------------------------------------------------
diff --git a/Allura/allura/lib/app_globals.py b/Allura/allura/lib/app_globals.py
index 05fc254..a87ce24 100644
--- a/Allura/allura/lib/app_globals.py
+++ b/Allura/allura/lib/app_globals.py
@@ -19,6 +19,10 @@
 
 
 """The application's Globals object"""
+from __future__ import print_function
+from __future__ import division
+from __future__ import absolute_import
+from __future__ import unicode_literals
 
 __all__ = ['Globals']
 import logging
@@ -26,7 +30,7 @@ import cgi
 import hashlib
 import json
 import datetime
-from urllib import urlencode
+from urllib.parse import urlencode
 from subprocess import Popen, PIPE
 import os
 import time
@@ -76,7 +80,7 @@ class ForgeMarkdown(markdown.Markdown):
             # so we return it as a plain text
             log.info('Text is too big. Skipping markdown processing')
             escaped = cgi.escape(h.really_unicode(source))
-            return h.html.literal(u'<pre>%s</pre>' % escaped)
+            return h.html.literal('<pre>%s</pre>' % escaped)
         try:
             return markdown.Markdown.convert(self, source)
         except Exception:
@@ -84,7 +88,7 @@ class ForgeMarkdown(markdown.Markdown):
                      ''.join(traceback.format_stack()), exc_info=True)
             escaped = h.really_unicode(source)
             escaped = cgi.escape(escaped)
-            return h.html.literal(u"""<p><strong>ERROR!</strong> The markdown supplied could not be parsed correctly.
+            return h.html.literal("""<p><strong>ERROR!</strong> The markdown supplied could not be parsed correctly.
             Did you forget to surround a code snippet with "~~~~"?</p><pre>%s</pre>""" % escaped)
 
     def cached_convert(self, artifact, field_name):
@@ -284,7 +288,6 @@ class Globals(object):
             theme=_cache_eps('allura.theme'),
             user_prefs=_cache_eps('allura.user_prefs'),
             spam=_cache_eps('allura.spam'),
-            phone=_cache_eps('allura.phone'),
             stats=_cache_eps('allura.stats'),
             site_stats=_cache_eps('allura.site_stats'),
             admin=_cache_eps('allura.admin'),
@@ -304,7 +307,7 @@ class Globals(object):
 
         # Set listeners to update stats
         statslisteners = []
-        for name, ep in self.entry_points['stats'].iteritems():
+        for name, ep in self.entry_points['stats'].items():
             statslisteners.append(ep())
         self.statsUpdater = PostEvent(statslisteners)
 
@@ -318,12 +321,6 @@ class Globals(object):
         return spam.SpamFilter.get(config, self.entry_points['spam'])
 
     @LazyProperty
-    def phone_service(self):
-        """Return a :class:`allura.lib.phone.PhoneService` implementation"""
-        from allura.lib import phone
-        return phone.PhoneService.get(config, self.entry_points['phone'])
-
-    @LazyProperty
     def director(self):
         """Return activitystream director"""
         if asbool(config.get('activitystream.recording.enabled', False)):
@@ -394,7 +391,7 @@ class Globals(object):
                 self._zarkov = ZarkovClient(
                     config.get('zarkov.host', 'tcp://127.0.0.1:6543'))
             self._zarkov.event(event_type, context, extra)
-        except Exception, ex:
+        except Exception as ex:
             self._zarkov = None
             log.error('Error sending zarkov event(%r): %r', ex, dict(
                 type=event_type, context=context, extra=extra))
@@ -458,7 +455,7 @@ class Globals(object):
                 # a <pre>
                 text = h.really_unicode(text)
                 text = cgi.escape(text)
-                return h.html.literal(u'<pre>' + text + u'</pre>')
+                return h.html.literal('<pre>' + text + '</pre>')
         else:
             lexer = pygments.lexers.get_lexer_by_name(
                 lexer, encoding='chardet')
@@ -592,7 +589,7 @@ class Globals(object):
         'h.set_context() is preferred over this method'
         if isinstance(pid_or_project, M.Project):
             c.project = pid_or_project
-        elif isinstance(pid_or_project, basestring):
+        elif isinstance(pid_or_project, str):
             raise TypeError('need a Project instance, got %r' % pid_or_project)
         elif pid_or_project is None:
             c.project = None
@@ -621,7 +618,7 @@ class Globals(object):
 
     @LazyProperty
     def noreply(self):
-        return unicode(config.get('noreply', 'noreply@%s' % config['domain']))
+        return str(config.get('noreply', 'noreply@%s' % config['domain']))
 
     @property
     def build_key(self):

http://git-wip-us.apache.org/repos/asf/allura/blob/d52f8e2a/Allura/allura/lib/base.py
----------------------------------------------------------------------
diff --git a/Allura/allura/lib/base.py b/Allura/allura/lib/base.py
index b0c9050..4a89127 100644
--- a/Allura/allura/lib/base.py
+++ b/Allura/allura/lib/base.py
@@ -18,6 +18,10 @@
 #       under the License.
 
 """The base Controller API."""
+from __future__ import print_function
+from __future__ import division
+from __future__ import absolute_import
+from __future__ import unicode_literals
 from webob import exc
 import pylons
 from tg import TGController
@@ -37,10 +41,10 @@ class WsgiDispatchController(TGController):
 
     def _setup_request(self):
         '''Responsible for setting all the values we need to be set on pylons.tmpl_context'''
-        raise NotImplementedError, '_setup_request'
+        raise NotImplementedError('_setup_request')
 
     def _cleanup_request(self):
-        raise NotImplementedError, '_cleanup_request'
+        raise NotImplementedError('_cleanup_request')
 
     def __call__(self, environ, start_response):
         try:
@@ -48,7 +52,7 @@ class WsgiDispatchController(TGController):
             response = super(WsgiDispatchController, self).__call__(
                 environ, start_response)
             return self.cleanup_iterator(response)
-        except exc.HTTPException, err:
+        except exc.HTTPException as err:
             return err(environ, start_response)
 
     def cleanup_iterator(self, response):

http://git-wip-us.apache.org/repos/asf/allura/blob/d52f8e2a/Allura/allura/lib/custom_middleware.py
----------------------------------------------------------------------
diff --git a/Allura/allura/lib/custom_middleware.py b/Allura/allura/lib/custom_middleware.py
index 8212cd2..be78e85 100644
--- a/Allura/allura/lib/custom_middleware.py
+++ b/Allura/allura/lib/custom_middleware.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
@@ -202,7 +206,7 @@ class AlluraTimerMiddleware(TimerMiddleware):
         import ming
         import pymongo
         import socket
-        import urllib2
+        import urllib.request, urllib.error, urllib.parse
         import activitystream
 
         timers = self.entry_point_timers() + [

http://git-wip-us.apache.org/repos/asf/allura/blob/d52f8e2a/Allura/allura/lib/decorators.py
----------------------------------------------------------------------
diff --git a/Allura/allura/lib/decorators.py b/Allura/allura/lib/decorators.py
index cb5cfc5..0c47362 100644
--- a/Allura/allura/lib/decorators.py
+++ b/Allura/allura/lib/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
@@ -20,7 +24,7 @@ import sys
 import json
 import logging
 from collections import defaultdict
-from urllib import unquote
+from urllib.parse import unquote
 
 from decorator import decorator
 from tg.decorators import before_validate
@@ -30,6 +34,7 @@ from pylons import tmpl_context as c
 
 from allura.lib import helpers as h
 from allura.lib import utils
+import collections
 
 
 def task(*args, **kw):
@@ -64,7 +69,7 @@ def task(*args, **kw):
         # or it gets a spurious cls argument
         func.post = staticmethod(post) if inspect.isclass(func) else post
         return func
-    if len(args) == 1 and callable(args[0]):
+    if len(args) == 1 and isinstance(args[0], collections.Callable):
         return task_(args[0])
     return task_
 
@@ -137,7 +142,7 @@ class log_action(object):  # pragma no cover
                 result = self._func(*args, **kwargs)
             except exc.HTTPServerError:
                 raise
-            except exc.HTTPException, e:
+            except exc.HTTPException as e:
                 result = e
             args = self._args
             kwargs = self._kwargs

http://git-wip-us.apache.org/repos/asf/allura/blob/d52f8e2a/Allura/allura/lib/diff.py
----------------------------------------------------------------------
diff --git a/Allura/allura/lib/diff.py b/Allura/allura/lib/diff.py
index 2691b65..dfae26e 100644
--- a/Allura/allura/lib/diff.py
+++ b/Allura/allura/lib/diff.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/lib/exceptions.py
----------------------------------------------------------------------
diff --git a/Allura/allura/lib/exceptions.py b/Allura/allura/lib/exceptions.py
index f796d99..fd7205f 100644
--- a/Allura/allura/lib/exceptions.py
+++ b/Allura/allura/lib/exceptions.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
@@ -43,10 +47,6 @@ class ProjectRatelimitError(ForgeError):
     pass
 
 
-class ProjectPhoneVerificationError(ForgeError):
-    pass
-
-
 class ToolError(ForgeError):
     pass
 

http://git-wip-us.apache.org/repos/asf/allura/blob/d52f8e2a/Allura/allura/lib/gravatar.py
----------------------------------------------------------------------
diff --git a/Allura/allura/lib/gravatar.py b/Allura/allura/lib/gravatar.py
index 0e03be2..274c141 100644
--- a/Allura/allura/lib/gravatar.py
+++ b/Allura/allura/lib/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
@@ -16,7 +20,7 @@
 #       under the License.
 
 import re
-import urllib
+import urllib.request, urllib.parse, urllib.error
 import hashlib
 
 _wrapped_email = re.compile(r'.*<(.+)>')
@@ -79,7 +83,7 @@ def url(email=None, gravatar_id=None, **kw):
         gravatar_id = id(email)
     if 'r' not in kw and 'rating' not in kw:
         kw['r'] = 'pg'
-    return ('https://secure.gravatar.com/avatar/%s?%s' % (gravatar_id, urllib.urlencode(kw)))
+    return ('https://secure.gravatar.com/avatar/%s?%s' % (gravatar_id, urllib.parse.urlencode(kw)))
 
 
 def for_user(user):

http://git-wip-us.apache.org/repos/asf/allura/blob/d52f8e2a/Allura/allura/lib/helpers.py
----------------------------------------------------------------------
diff --git a/Allura/allura/lib/helpers.py b/Allura/allura/lib/helpers.py
index 36932a3..4668a1e 100644
--- a/Allura/allura/lib/helpers.py
+++ b/Allura/allura/lib/helpers.py
@@ -17,18 +17,22 @@
 #       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 sys
 import os
 import os.path
 import difflib
-import urllib
-import urllib2
+import urllib.request, urllib.parse, urllib.error
+import urllib.request, urllib.error, urllib.parse
 import re
 import json
 import logging
 import string
 import random
-import cPickle as pickle
+import pickle as pickle
 from hashlib import sha1
 from datetime import datetime, timedelta
 from collections import defaultdict
@@ -134,7 +138,7 @@ def make_safe_path_portion(ustr, relaxed=True):
     return s
 
 def escape_json(data):
-    return json.dumps(data).replace('<', '\u003C')
+    return json.dumps(data).replace('<', '\\u003C')
 
 def monkeypatch(*objs):
     def patchem(func):
@@ -145,31 +149,31 @@ def monkeypatch(*objs):
 
 def urlquote(url, safe="/"):
     try:
-        return urllib.quote(str(url), safe=safe)
+        return urllib.parse.quote(str(url), safe=safe)
     except UnicodeEncodeError:
-        return urllib.quote(url.encode('utf-8'), safe=safe)
+        return urllib.parse.quote(url.encode('utf-8'), safe=safe)
 
 
 def urlquoteplus(url, safe=""):
     try:
-        return urllib.quote_plus(str(url), safe=safe)
+        return urllib.parse.quote_plus(str(url), safe=safe)
     except UnicodeEncodeError:
-        return urllib.quote_plus(url.encode('utf-8'), safe=safe)
+        return urllib.parse.quote_plus(url.encode('utf-8'), safe=safe)
 
 
 def _attempt_encodings(s, encodings):
     if s is None:
-        return u''
+        return ''
     for enc in encodings:
         try:
             if enc is None:
-                return unicode(s)  # try default encoding
+                return str(s)  # try default encoding
             else:
-                return unicode(s, enc)
+                return str(s, enc)
         except (UnicodeDecodeError, LookupError):
             pass
     # Return the repr of the str -- should always be safe
-    return unicode(repr(str(s)))[1:-1]
+    return str(repr(str(s)))[1:-1]
 
 
 def really_unicode(s):
@@ -248,7 +252,7 @@ def make_app_admin_only(app):
 def push_config(obj, **kw):
     saved_attrs = {}
     new_attrs = []
-    for k, v in kw.iteritems():
+    for k, v in kw.items():
         try:
             saved_attrs[k] = getattr(obj, k)
         except AttributeError:
@@ -257,7 +261,7 @@ def push_config(obj, **kw):
     try:
         yield obj
     finally:
-        for k, v in saved_attrs.iteritems():
+        for k, v in saved_attrs.items():
             setattr(obj, k, v)
         for k in new_attrs:
             delattr(obj, k)
@@ -304,7 +308,7 @@ def set_context(project_shortname_or_id, mount_point=None, app_config_id=None, n
     if app_config_id is None:
         c.app = p.app_instance(mount_point)
     else:
-        if isinstance(app_config_id, basestring):
+        if isinstance(app_config_id, str):
             app_config_id = ObjectId(app_config_id)
         app_config = model.AppConfig.query.get(_id=app_config_id)
         c.app = p.app_instance(app_config)
@@ -333,13 +337,13 @@ def encode_keys(d):
     a valid kwargs argument'''
     return dict(
         (k.encode('utf-8'), v)
-        for k, v in d.iteritems())
+        for k, v in d.items())
 
 
 def vardec(fun):
     def vardec_hook(remainder, params):
         new_params = variable_decode(dict(
-            (k, v) for k, v in params.items()
+            (k, v) for k, v in list(params.items())
             if re_clean_vardec_key.match(k)))
         params.update(new_params)
     before_validate(vardec_hook)(fun)
@@ -470,7 +474,7 @@ def gen_message_id(_id=None):
 class ProxiedAttrMeta(type):
 
     def __init__(cls, name, bases, dct):
-        for v in dct.itervalues():
+        for v in dct.values():
             if isinstance(v, attrproxy):
                 v.cls = cls
 
@@ -571,7 +575,7 @@ def config_with_prefix(d, prefix):
     with the prefix stripped
     '''
     plen = len(prefix)
-    return dict((k[plen:], v) for k, v in d.iteritems()
+    return dict((k[plen:], v) for k, v in d.items()
                 if k.startswith(prefix))
 
 
@@ -621,7 +625,7 @@ class log_action(object):
         extra = kwargs.setdefault('extra', {})
         meta = kwargs.pop('meta', {})
         kwpairs = extra.setdefault('kwpairs', {})
-        for k, v in meta.iteritems():
+        for k, v in meta.items():
             kwpairs['meta_%s' % k] = v
         extra.update(self._make_extra())
         self._logger.log(level, self._action + ': ' + message, *args, **kwargs)
@@ -701,7 +705,7 @@ def _add_table_line_numbers_to_text(text):
 
     def _len_to_str_column(l, start=1):
         max_num = l + start
-        return '\n'.join(map(_prepend_whitespaces, range(start, max_num), [max_num] * l))
+        return '\n'.join(map(_prepend_whitespaces, list(range(start, max_num)), [max_num] * l))
 
     lines = text.splitlines(True)
     linenumbers = '<td class="linenos"><div class="linenodiv"><pre>' + \
@@ -926,7 +930,7 @@ def ming_config(**conf):
         yield
     finally:
         Session._datastores = datastores
-        for name, session in Session._registry.iteritems():
+        for name, session in Session._registry.items():
             session.bind = datastores.get(name, None)
             session._name = name
 
@@ -951,7 +955,7 @@ def split_select_field_options(field_options):
         # it's better to pass properly encoded byte-string
         field_options = shlex.split(field_options.encode('utf-8'))
         # convert splitted string back to unicode
-        field_options = map(really_unicode, field_options)
+        field_options = list(map(really_unicode, field_options))
     except ValueError:
         field_options = field_options.split()
         # After regular split field_options might contain a " characters,
@@ -1022,8 +1026,8 @@ def urlopen(url, retries=3, codes=(408,), timeout=None):
     attempts = 0
     while True:
         try:
-            return urllib2.urlopen(url, timeout=timeout)
-        except (urllib2.HTTPError, socket.timeout) as e:
+            return urllib.request.urlopen(url, timeout=timeout)
+        except (urllib.error.HTTPError, socket.timeout) as e:
             if attempts < retries and (isinstance(e, socket.timeout) or
                                        e.code in codes):
                 attempts += 1
@@ -1089,7 +1093,7 @@ def iter_entry_points(group, *a, **kw):
         by_name = defaultdict(list)
         for ep in entry_points:
             by_name[ep.name].append(ep)
-        for name, eps in by_name.iteritems():
+        for name, eps in by_name.items():
             ep_count = len(eps)
             if ep_count == 1:
                 yield eps[0]
@@ -1098,8 +1102,8 @@ def iter_entry_points(group, *a, **kw):
 
     def subclass(entry_points):
         loaded = dict((ep, ep.load()) for ep in entry_points)
-        for ep, cls in loaded.iteritems():
-            others = loaded.values()[:]
+        for ep, cls in loaded.items():
+            others = list(loaded.values())[:]
             others.remove(cls)
             if all([issubclass(cls, other) for other in others]):
                 return ep

http://git-wip-us.apache.org/repos/asf/allura/blob/d52f8e2a/Allura/allura/lib/import_api.py
----------------------------------------------------------------------
diff --git a/Allura/allura/lib/import_api.py b/Allura/allura/lib/import_api.py
index 60754a6..d674425 100644
--- a/Allura/allura/lib/import_api.py
+++ b/Allura/allura/lib/import_api.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,9 +19,9 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
-import urllib
-import urllib2
-import urlparse
+import urllib.request, urllib.parse, urllib.error
+import urllib.request, urllib.error, urllib.parse
+import urllib.parse
 import hmac
 import hashlib
 import json
@@ -40,24 +44,24 @@ class AlluraImportApiClient(object):
         return params
 
     def call(self, url, **params):
-        url = urlparse.urljoin(self.base_url, url)
+        url = urllib.parse.urljoin(self.base_url, url)
         if self.verbose:
             log.info("Import API URL: %s", url)
 
-        params = self.sign(urlparse.urlparse(url).path, params.items())
+        params = self.sign(urllib.parse.urlparse(url).path, list(params.items()))
 
         while True:
             try:
-                result = urllib2.urlopen(url, urllib.urlencode(params))
+                result = urllib.request.urlopen(url, urllib.parse.urlencode(params))
                 resp = result.read()
                 return json.loads(resp)
-            except urllib2.HTTPError, e:
+            except urllib.error.HTTPError as e:
                 e.msg += ' ({0})'.format(url)
                 if self.verbose:
                     error_content = e.read()
                     e.msg += '. Error response:\n' + error_content
                 raise e
-            except (urllib2.URLError, IOError):
+            except (urllib.error.URLError, IOError):
                 if self.retry:
                     log.exception('Error making API request, will retry')
                     continue

http://git-wip-us.apache.org/repos/asf/allura/blob/d52f8e2a/Allura/allura/lib/macro.py
----------------------------------------------------------------------
diff --git a/Allura/allura/lib/macro.py b/Allura/allura/lib/macro.py
index fd0ef58..04d9e66 100644
--- a/Allura/allura/lib/macro.py
+++ b/Allura/allura/lib/macro.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
@@ -23,7 +27,7 @@ import traceback
 import oembed
 import jinja2
 from operator import attrgetter
-from urlparse import urlparse, urlunparse
+from urllib.parse import urlparse, urlunparse
 
 import pymongo
 from pylons import tmpl_context as c, app_globals as g
@@ -59,7 +63,7 @@ class parse(object):
             if s.startswith('quote '):
                 return '[[' + s[len('quote '):] + ']]'
             try:
-                parts = [unicode(x, 'utf-8')
+                parts = [str(x, 'utf-8')
                          for x in shlex.split(s.encode('utf-8'))]
                 if not parts:
                     return '[[' + s + ']]'
@@ -76,9 +80,9 @@ class parse(object):
                 log.warn('macro error.  Upwards stack is %s',
                          ''.join(traceback.format_stack()),
                          exc_info=True)
-                msg = cgi.escape(u'[[%s]] (%s)' % (s, repr(ex)))
+                msg = cgi.escape('[[%s]] (%s)' % (s, repr(ex)))
                 return '\n<div class="error"><pre><code>%s</code></pre></div>' % msg
-        except Exception, ex:
+        except Exception as ex:
             raise
             return '[[Error parsing %s: %s]]' % (s, ex)
 
@@ -400,7 +404,7 @@ def include(ref=None, repo=None, **kw):
 
 @macro()
 def img(src=None, **kw):
-    attrs = ('%s="%s"' % t for t in kw.iteritems())
+    attrs = ('%s="%s"' % t for t in kw.items())
     included = request.environ.setdefault('allura.macro.att_embedded', set())
     included.add(src)
     if '://' in src:

http://git-wip-us.apache.org/repos/asf/allura/blob/d52f8e2a/Allura/allura/lib/mail_util.py
----------------------------------------------------------------------
diff --git a/Allura/allura/lib/mail_util.py b/Allura/allura/lib/mail_util.py
index 68a9649..a71c8af 100644
--- a/Allura/allura/lib/mail_util.py
+++ b/Allura/allura/lib/mail_util.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
@@ -49,11 +53,11 @@ def Header(text, *more_text):
     # email.header.Header handles str vs unicode differently
     # see
     # http://docs.python.org/library/email.header.html#email.header.Header.append
-    if type(text) != unicode:
+    if type(text) != str:
         raise TypeError('This must be unicode: %r' % text)
     head = header.Header(text)
     for m in more_text:
-        if type(m) != unicode:
+        if type(m) != str:
             raise TypeError('This must be unicode: %r' % text)
         head.append(m)
     return head
@@ -65,7 +69,7 @@ def AddrHeader(fromaddr):
         foo@bar.com
         "Foo Bar" <fo...@bar.com>
     '''
-    if isinstance(fromaddr, basestring) and ' <' in fromaddr:
+    if isinstance(fromaddr, str) and ' <' in fromaddr:
         name, addr = fromaddr.rsplit(' <', 1)
         addr = '<' + addr  # restore the char we just split off
         addrheader = Header(name, addr)
@@ -101,18 +105,18 @@ def parse_address(addr):
     userpart, domain = addr.split('@')
     # remove common domain suffix
     if not domain.endswith(config.common_suffix):
-        raise exc.AddressException, 'Unknown domain: ' + domain
+        raise exc.AddressException('Unknown domain: ' + domain)
     domain = domain[:-len(config.common_suffix)]
     path = '/'.join(reversed(domain.split('.')))
     project, mount_point = h.find_project('/' + path)
     if project is None:
-        raise exc.AddressException, 'Unknown project: ' + domain
+        raise exc.AddressException('Unknown project: ' + domain)
     if len(mount_point) != 1:
-        raise exc.AddressException, 'Unknown tool: ' + domain
+        raise exc.AddressException('Unknown tool: ' + domain)
     with h.push_config(c, project=project):
         app = project.app_instance(mount_point[0])
         if not app:
-            raise exc.AddressException, 'Unknown tool: ' + domain
+            raise exc.AddressException('Unknown tool: ' + domain)
     return userpart, project, app
 
 
@@ -231,27 +235,27 @@ class SMTPClient(object):
         message['From'] = AddrHeader(fromaddr)
         message['Reply-To'] = AddrHeader(reply_to)
         message['Subject'] = Header(subject)
-        message['Message-ID'] = Header('<' + message_id + u'>')
+        message['Message-ID'] = Header('<' + message_id + '>')
         if sender:
             message['Sender'] = AddrHeader(sender)
         if cc:
             message['CC'] = AddrHeader(cc)
             addrs.append(cc)
         if in_reply_to:
-            if not isinstance(in_reply_to, basestring):
+            if not isinstance(in_reply_to, str):
                 raise TypeError('Only strings are supported now, not lists')
-            message['In-Reply-To'] = Header(u'<%s>' % in_reply_to)
+            message['In-Reply-To'] = Header('<%s>' % in_reply_to)
             if not references:
                 message['References'] = message['In-Reply-To']
         if references:
-            references = [u'<%s>' % r for r in aslist(references)]
+            references = ['<%s>' % r for r in aslist(references)]
             message['References'] = Header(*references)
         content = message.as_string()
-        smtp_addrs = map(_parse_smtp_addr, addrs)
+        smtp_addrs = list(map(_parse_smtp_addr, addrs))
         smtp_addrs = [a for a in smtp_addrs if isvalid(a)]
         if not smtp_addrs:
             log.warning('No valid addrs in %s, so not sending mail',
-                        map(unicode, addrs))
+                        list(map(str, addrs)))
             return
         try:
             self._client.sendmail(

http://git-wip-us.apache.org/repos/asf/allura/blob/d52f8e2a/Allura/allura/lib/markdown_extensions.py
----------------------------------------------------------------------
diff --git a/Allura/allura/lib/markdown_extensions.py b/Allura/allura/lib/markdown_extensions.py
index 9a68e1a..c20b9ed 100644
--- a/Allura/allura/lib/markdown_extensions.py
+++ b/Allura/allura/lib/markdown_extensions.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,7 +21,7 @@
 
 import re
 import logging
-from urlparse import urljoin
+from urllib.parse import urljoin
 
 from tg import config
 from BeautifulSoup import BeautifulSoup
@@ -178,8 +182,7 @@ class TracRef2(Pattern):
         comments = ticket.discussion_thread.post_class().query.find(dict(
             discussion_id=ticket.discussion_thread.discussion_id,
             thread_id=ticket.discussion_thread._id,
-            status={'$in': ['ok', 'pending']},
-            deleted=False)).sort('timestamp')
+            status={'$in': ['ok', 'pending']})).sort('timestamp')
 
         if comment_num <= comments.count():
             return comments.all()[comment_num - 1].slug

http://git-wip-us.apache.org/repos/asf/allura/blob/d52f8e2a/Allura/allura/lib/package_path_loader.py
----------------------------------------------------------------------
diff --git a/Allura/allura/lib/package_path_loader.py b/Allura/allura/lib/package_path_loader.py
index be28fed..d96800c 100644
--- a/Allura/allura/lib/package_path_loader.py
+++ b/Allura/allura/lib/package_path_loader.py
@@ -122,6 +122,10 @@ The positioners are:
 **TODO:** Support multiple partial themes
 
 """
+from __future__ import print_function
+from __future__ import division
+from __future__ import absolute_import
+from __future__ import unicode_literals
 import pkg_resources
 import os
 
@@ -238,7 +242,7 @@ class PackagePathLoader(jinja2.BaseLoader):
         This mutates paths.
         """
         p_idx = lambda n: [e[0] for e in paths].index(n)
-        for target, replacement in rules.items():
+        for target, replacement in list(rules.items()):
             try:
                 removed = paths.pop(p_idx(replacement))
                 paths[p_idx(target)][1] = removed[1]

http://git-wip-us.apache.org/repos/asf/allura/blob/d52f8e2a/Allura/allura/lib/patches.py
----------------------------------------------------------------------
diff --git a/Allura/allura/lib/patches.py b/Allura/allura/lib/patches.py
index 2c363ea..63225e7 100644
--- a/Allura/allura/lib/patches.py
+++ b/Allura/allura/lib/patches.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
@@ -58,14 +62,14 @@ def apply():
         else:
             return
 
-        for content_type, content_engine in engines.iteritems():
+        for content_type, content_engine in engines.items():
             template = template.split(':', 1)
             template.extend(content_engine[2:])
             try:
                 override_mapping = request._override_mapping
             except AttributeError:
                 override_mapping = request._override_mapping = {}
-            override_mapping[controller.im_func] = {content_type: template}
+            override_mapping[controller.__func__] = {content_type: template}
 
     @h.monkeypatch(tg, tg.decorators)
     @decorator

http://git-wip-us.apache.org/repos/asf/allura/blob/d52f8e2a/Allura/allura/lib/phone/__init__.py
----------------------------------------------------------------------
diff --git a/Allura/allura/lib/phone/__init__.py b/Allura/allura/lib/phone/__init__.py
deleted file mode 100644
index 1b14575..0000000
--- a/Allura/allura/lib/phone/__init__.py
+++ /dev/null
@@ -1,74 +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 logging
-
-log = logging.getLogger(__name__)
-
-
-class PhoneService(object):
-    """
-    Defines the phone verification service interface and provides a default
-    no-op implementation.
-    """
-
-    def __init__(self, config):
-        pass
-
-    def verify(self, number):
-        """
-        Generate PIN and send it to phone number :param number:
-
-        Returns dict with following keys: status, request_id, error. status is
-        either 'ok' or 'error'.
-
-        If status is 'ok' then request_id is present (used later to verify PIN)
-        otherwise 'error' is an error message.
-        """
-        log.info('Phone service is not configured')
-        return {
-            'status': 'error',
-            'error': 'Phone service is not configured',
-        }
-
-    def check(self, request_id, pin):
-        """
-        Given the :param pin: code user entered and :param request_id:
-        (obtained by verify), verify that :param pin: is valid.
-
-        Returns dict with following keys: status, error. status is either 'ok'
-        or 'error'.
-
-        If status is 'ok' then verification was successful otherwise 'error' is
-        an error message.
-        """
-        log.info('Phone service is not configured')
-        return {
-            'status': 'error',
-            'error': 'Phone service is not configured',
-        }
-
-    @classmethod
-    def get(cls, config, entry_points):
-        """
-        Return an instance of PhoneService implementation based on ``config``.
-        """
-        method = config.get('phone.method')
-        if not method:
-            return cls(config)
-        service = entry_points[method]
-        return service(config)

http://git-wip-us.apache.org/repos/asf/allura/blob/d52f8e2a/Allura/allura/lib/phone/nexmo.py
----------------------------------------------------------------------
diff --git a/Allura/allura/lib/phone/nexmo.py b/Allura/allura/lib/phone/nexmo.py
deleted file mode 100644
index 0ee4269..0000000
--- a/Allura/allura/lib/phone/nexmo.py
+++ /dev/null
@@ -1,89 +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 logging
-from urlparse import urljoin
-
-import json
-import requests
-from allura.lib.phone import PhoneService
-from allura.lib.utils import phone_number_hash
-
-log = logging.getLogger(__name__)
-
-
-class NexmoPhoneService(PhoneService):
-    """
-    Implementation of :class:`allura.lib.phone.PhoneService` interface
-    for Nexmo Verify
-    """
-
-    BASE_URL = 'https://api.nexmo.com/'
-
-    def __init__(self, config):
-        self.config = config
-        self.api_key = config.get('phone.api_key')
-        self.api_secret = config.get('phone.api_secret')
-
-    def add_common_params(self, params):
-        common = {
-            'api_key': self.api_key,
-            'api_secret': self.api_secret,
-        }
-        return dict(params, **common)
-
-    def error(self, msg):
-        return {'status': 'error', 'error': msg}
-
-    def ok(self, **params):
-        return dict({'status': 'ok'}, **params)
-
-    def post(self, url, **params):
-        if url[-1] != '/':
-            url += '/'
-        url = urljoin(url, 'json')
-        headers = {'Content-Type': 'application/json'}
-        params = self.add_common_params(params)
-        log_params = dict(params, api_key='...', api_secret='...')
-        if 'number' in log_params:
-            log_params['number'] = phone_number_hash(log_params['number'])
-        params = json.dumps(params, sort_keys=True)
-        log.info('PhoneService (nexmo) request: %s %s', url, log_params)
-        try:
-            resp = requests.post(url, data=params, headers=headers)
-            log.info('PhoneService (nexmo) response: %s', resp.content)
-            resp = resp.json()
-        except Exception:
-            msg = 'Failed sending request to Nexmo'
-            log.exception(msg)
-            return self.error(msg)
-        if resp.get('status') == '0':
-            return self.ok(request_id=resp.get('request_id'))
-        return self.error(resp.get('error_text'))
-
-    def verify(self, number):
-        url = urljoin(self.BASE_URL, 'verify')
-        # Required. Brand or name of your app, service the verification is
-        # for. This alphanumeric (maximum length 18 characters) will be
-        # used inside the body of all SMS and TTS messages sent (e.g. "Your
-        # <brand> PIN code is ..")
-        brand = self.config.get('site_name')[:18]
-        return self.post(url, number=number, brand=brand)
-
-    def check(self, request_id, pin):
-        url = urljoin(self.BASE_URL, 'verify/check')
-        return self.post(url, request_id=request_id, code=pin)

http://git-wip-us.apache.org/repos/asf/allura/blob/d52f8e2a/Allura/allura/lib/plugin.py
----------------------------------------------------------------------
diff --git a/Allura/allura/lib/plugin.py b/Allura/allura/lib/plugin.py
index 29a3ee6..54b27aa 100644
--- a/Allura/allura/lib/plugin.py
+++ b/Allura/allura/lib/plugin.py
@@ -18,6 +18,10 @@
 '''
 Allura plugins for authentication and project registration
 '''
+from __future__ import print_function
+from __future__ import division
+from __future__ import absolute_import
+from __future__ import unicode_literals
 import re
 import os
 import logging
@@ -25,8 +29,8 @@ import subprocess
 import string
 import crypt
 import random
-from urllib2 import urlopen
-from cStringIO import StringIO
+from urllib.request import urlopen
+from io import StringIO
 from random import randint
 from hashlib import sha256
 from base64 import b64encode
@@ -360,13 +364,13 @@ class LocalAuthenticationProvider(AuthenticationProvider):
         user.disabled = True
         session(user).flush(user)
         if kw.get('audit', True):
-            h.auditlog_user(u'Account disabled', user=user)
+            h.auditlog_user('Account disabled', user=user)
 
     def enable_user(self, user, **kw):
         user.disabled = False
         session(user).flush(user)
         if kw.get('audit', True):
-            h.auditlog_user(u'Account enabled', user=user)
+            h.auditlog_user('Account enabled', user=user)
 
     def activate_user(self, user, **kw):
         user.pending = False
@@ -414,7 +418,7 @@ class LocalAuthenticationProvider(AuthenticationProvider):
         from allura import model as M
         if salt is None:
             salt = ''.join(chr(randint(1, 0x7f))
-                           for i in xrange(M.User.SALT_LEN))
+                           for i in range(M.User.SALT_LEN))
         hashpass = sha256(salt + password.encode('utf-8')).digest()
         return 'sha256' + salt + b64encode(hashpass)
 
@@ -701,56 +705,13 @@ class ProjectRegistrationProvider(object):
         now = datetime.utcnow().replace(tzinfo=FixedOffset(0, 'UTC'))
         project_count = len(list(user.my_projects()))
         rate_limits = json.loads(config.get('project.rate_limits', '{}'))
-        for rate, count in rate_limits.items():
+        for rate, count in list(rate_limits.items()):
             user_age = now - user._id.generation_time
             user_age = (user_age.microseconds +
                         (user_age.seconds + user_age.days * 24 * 3600) * 10 ** 6) / 10 ** 6
             if user_age < int(rate) and project_count >= count:
                 raise forge_exc.ProjectRatelimitError()
 
-    def phone_verified(self, user, neighborhood):
-        """
-        Check if user has completed phone verification.
-
-        Returns True if one of the following is true:
-            - phone verification is disabled
-            - :param user: has 'admin' access to :param neighborhood:
-            - :param user: is has 'admin' access for some project, which belongs
-              to :param neighborhood:
-            - phone is already verified for a :param user:
-
-        Otherwise returns False.
-        """
-        if not asbool(config.get('project.verify_phone')):
-            return True
-        if security.has_access(neighborhood, 'admin', user=user)():
-            return True
-        admin_in = [p for p in user.my_projects_by_role_name('Admin')
-                    if p.neighborhood_id == neighborhood._id]
-        if len(admin_in) > 0:
-            return True
-        return bool(user.get_tool_data('phone_verification', 'number_hash'))
-
-    def verify_phone(self, user, number):
-        ok = {'status': 'ok'}
-        if not asbool(config.get('project.verify_phone')):
-            return ok
-        return g.phone_service.verify(number)
-
-    def check_phone_verification(self, user, request_id, pin, number_hash):
-        ok = {'status': 'ok'}
-        if not asbool(config.get('project.verify_phone')):
-            return ok
-        res = g.phone_service.check(request_id, pin)
-        if res.get('status') == 'ok':
-            user.set_tool_data('phone_verification', number_hash=number_hash)
-            msg = 'Phone verification succeeded. Hash: {}'.format(number_hash)
-            h.auditlog_user(msg, user=user)
-        else:
-            msg = 'Phone verification failed. Hash: {}'.format(number_hash)
-            h.auditlog_user(msg, user=user)
-        return res
-
     def register_neighborhood_project(self, neighborhood, users, allow_register=False):
         from allura import model as M
         shortname = '--init--'
@@ -816,9 +777,6 @@ class ProjectRegistrationProvider(object):
 
         self.rate_limit(user, neighborhood)
 
-        if not self.phone_verified(user, neighborhood):
-            raise forge_exc.ProjectPhoneVerificationError()
-
         if user_project and shortname.startswith('u/'):
             check_shortname = shortname.replace('u/', '', 1)
         else:
@@ -895,8 +853,8 @@ class ProjectRegistrationProvider(object):
             for i, tool in enumerate(project_template['tools'].keys()):
                 tool_config = project_template['tools'][tool]
                 tool_options = tool_config.get('options', {})
-                for k, v in tool_options.iteritems():
-                    if isinstance(v, basestring):
+                for k, v in tool_options.items():
+                    if isinstance(v, str):
                         tool_options[k] = \
                             string.Template(v).safe_substitute(
                                 p.__dict__.get('root_project', {}))
@@ -919,7 +877,7 @@ class ProjectRegistrationProvider(object):
         if 'labels' in project_template:
             p.labels = project_template['labels']
         if 'trove_cats' in project_template:
-            for trove_type in project_template['trove_cats'].keys():
+            for trove_type in list(project_template['trove_cats'].keys()):
                 troves = getattr(p, 'trove_%s' % trove_type)
                 for trove_id in project_template['trove_cats'][trove_type]:
                     troves.append(
@@ -1228,7 +1186,7 @@ class ThemeProvider(object):
             Takes an instance of class Application, or else a string.
             Expected to be overriden by derived Themes.
         """
-        if isinstance(app, unicode):
+        if isinstance(app, str):
             app = str(app)
         if isinstance(app, str):
             if app in self.icons and size in self.icons[app]:

http://git-wip-us.apache.org/repos/asf/allura/blob/d52f8e2a/Allura/allura/lib/repository.py
----------------------------------------------------------------------
diff --git a/Allura/allura/lib/repository.py b/Allura/allura/lib/repository.py
index 31e1395..77105b6 100644
--- a/Allura/allura/lib/repository.py
+++ b/Allura/allura/lib/repository.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
-from urllib import quote
+from urllib.parse import quote
 
 from pylons import tmpl_context as c, app_globals as g
 from pylons import request
@@ -330,4 +334,4 @@ class RestWebhooksLookup(BaseController):
         for hook in self.app._webhooks:
             if hook.type == name and hook.api_controller:
                 return hook.api_controller(hook, self.app), remainder
-        raise exc.HTTPNotFound, name
+        raise exc.HTTPNotFound(name)

http://git-wip-us.apache.org/repos/asf/allura/blob/d52f8e2a/Allura/allura/lib/search.py
----------------------------------------------------------------------
diff --git a/Allura/allura/lib/search.py b/Allura/allura/lib/search.py
index 2ee9b82..86e10c6 100644
--- a/Allura/allura/lib/search.py
+++ b/Allura/allura/lib/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
@@ -18,8 +22,8 @@
 import re
 import socket
 from logging import getLogger
-from urllib import urlencode
-from itertools import imap
+from urllib.parse import urlencode
+
 
 import markdown
 import jinja2
@@ -111,7 +115,7 @@ class SearchIndexable(object):
         # the same suffixes, but different field types. E.g.:
         # query 'shortname:test' with fields.keys() == ['name_t', 'shortname_s']
         # will be translated to 'shortname_t:test', which makes no sense
-        fields = sorted(fields.keys(), key=len, reverse=True)
+        fields = sorted(list(fields.keys()), key=len, reverse=True)
         for f in fields:
             if '_' in f:
                 base, typ = f.rsplit('_', 1)
@@ -161,7 +165,7 @@ def search_artifact(atype, q, history=False, rows=10, short_timeout=False, filte
         'type_s:%s' % fields['type_s'],
         'project_id_s:%s' % c.project._id,
         'mount_point_s:%s' % c.app.config.options.mount_point ]
-    for name, values in (filter or {}).iteritems():
+    for name, values in (filter or {}).items():
         field_name = name + '_s'
         parts = []
         for v in values:
@@ -192,8 +196,8 @@ def site_admin_search(model, q, field, **kw):
         q = obj.translate_query(q, fields)
     else:
         # construct query for a specific selected field
-        q = obj.translate_query(u'%s:%s' % (field, q), fields)
-    fq = [u'type_s:%s' % model.type_s]
+        q = obj.translate_query('%s:%s' % (field, q), fields)
+    fq = ['type_s:%s' % model.type_s]
     return search(q, fq=fq, ignore_errors=False, **kw)
 
 
@@ -294,9 +298,9 @@ def search_app(q='', fq=None, app=True, **kw):
                     if aref and aref.artifact:
                         doc['url_paginated'] = aref.artifact.url_paginated()
                 return doc
-            results = imap(historize_urls, results)
-            results = imap(add_matches, results)
-            results = imap(paginate_comment_urls, results)
+            results = map(historize_urls, results)
+            results = map(add_matches, results)
+            results = map(paginate_comment_urls, results)
 
     # Provide sort urls to the view
     score_url = 'score desc'

http://git-wip-us.apache.org/repos/asf/allura/blob/d52f8e2a/Allura/allura/lib/security.py
----------------------------------------------------------------------
diff --git a/Allura/allura/lib/security.py b/Allura/allura/lib/security.py
index b26a23c..38419c9 100644
--- a/Allura/allura/lib/security.py
+++ b/Allura/allura/lib/security.py
@@ -18,6 +18,10 @@
 """
 This module provides the security predicates used in decorating various models.
 """
+from __future__ import print_function
+from __future__ import division
+from __future__ import absolute_import
+from __future__ import unicode_literals
 import logging
 from collections import defaultdict
 
@@ -28,6 +32,7 @@ from itertools import chain
 from ming.utils import LazyProperty
 
 from allura.lib.utils import TruthyCallable
+import collections
 
 log = logging.getLogger(__name__)
 
@@ -95,7 +100,7 @@ class Credentials(object):
         roles_by_project = dict((pid, []) for pid in project_ids)
         for role in q:
             roles_by_project[role['project_id']].append(role)
-        for pid, roles in roles_by_project.iteritems():
+        for pid, roles in roles_by_project.items():
             self.users[user_id, pid] = RoleCache(self, roles)
 
     def load_project_roles(self, *project_ids):
@@ -110,7 +115,7 @@ class Credentials(object):
         roles_by_project = dict((pid, []) for pid in project_ids)
         for role in q:
             roles_by_project[role['project_id']].append(role)
-        for pid, roles in roles_by_project.iteritems():
+        for pid, roles in roles_by_project.items():
             self.projects[pid] = RoleCache(self, roles)
 
     def project_roles(self, project_id):
@@ -169,13 +174,13 @@ class RoleCache(object):
         self.q = q
 
     def find(self, **kw):
-        tests = kw.items()
+        tests = list(kw.items())
 
         def _iter():
             for r in self:
                 for k, v in tests:
                     val = r.get(k)
-                    if callable(v):
+                    if isinstance(v, collections.Callable):
                         if not v(val):
                             break
                     elif v != val:
@@ -190,7 +195,7 @@ class RoleCache(object):
         return None
 
     def __iter__(self):
-        return self.index.itervalues()
+        return iter(self.index.values())
 
     def __len__(self):
         return len(self.index)
@@ -242,7 +247,7 @@ class RoleCache(object):
     @LazyProperty
     def reaching_roles(self):
         def _iter():
-            to_visit = self.index.items()
+            to_visit = list(self.index.items())
             project_ids = set([r['project_id'] for _id, r in to_visit])
             pr_index = {r['_id']: r for r in self.cred.project_role.find({
                 'project_id': {'$in': list(project_ids)},

http://git-wip-us.apache.org/repos/asf/allura/blob/d52f8e2a/Allura/allura/lib/solr.py
----------------------------------------------------------------------
diff --git a/Allura/allura/lib/solr.py b/Allura/allura/lib/solr.py
index e8d9f7b..e2a3ff1 100644
--- a/Allura/allura/lib/solr.py
+++ b/Allura/allura/lib/solr.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
@@ -45,7 +49,7 @@ def escape_solr_arg(term):
     """ Apply escaping to the passed in query terms
         escaping special characters like : , etc"""
     term = term.replace('\\', r'\\')   # escape \ first
-    for char, escaped_char in escape_rules.iteritems():
+    for char, escaped_char in escape_rules.items():
         term = term.replace(char, escaped_char)
 
     return term
@@ -144,7 +148,7 @@ class MockSOLR(object):
 
     def search(self, q, fq=None, **kw):
         if q is None: q = ''  # shlex will hang on None
-        if isinstance(q, unicode):
+        if isinstance(q, str):
             q = q.encode('latin-1')
         # Parse query
         preds = []
@@ -160,7 +164,7 @@ class MockSOLR(object):
             else:
                 preds.append(('text', part))
         result = self.MockHits()
-        for obj in self.db.values():
+        for obj in list(self.db.values()):
             for field, value in preds:
                 neg = False
                 if field[0] == '!':

http://git-wip-us.apache.org/repos/asf/allura/blob/d52f8e2a/Allura/allura/lib/spam/__init__.py
----------------------------------------------------------------------
diff --git a/Allura/allura/lib/spam/__init__.py b/Allura/allura/lib/spam/__init__.py
index 4bf3917..77a25bb 100644
--- a/Allura/allura/lib/spam/__init__.py
+++ b/Allura/allura/lib/spam/__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