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:40:58 UTC

[36/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/ForgeSVN/forgesvn/tests/model/test_repository.py
----------------------------------------------------------------------
diff --git a/ForgeSVN/forgesvn/tests/model/test_repository.py b/ForgeSVN/forgesvn/tests/model/test_repository.py
index 744d30a..e558758 100644
--- a/ForgeSVN/forgesvn/tests/model/test_repository.py
+++ b/ForgeSVN/forgesvn/tests/model/test_repository.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 os
 import shutil
 import unittest
@@ -27,7 +31,7 @@ from zipfile import ZipFile
 from collections import defaultdict
 from pylons import tmpl_context as c, app_globals as g
 import mock
-from nose.tools import assert_equal
+from nose.tools import assert_equal, assert_in
 from datadiff.tools import assert_equals
 import tg
 import ming
@@ -244,83 +248,83 @@ class TestSVNRepo(unittest.TestCase, RepoImplTestBase):
              'refs': ['HEAD'],
              'committed': {
                  'date': datetime(2013, 11, 8, 13, 38, 11, 152821),
-                 'name': u'coldmind', 'email': ''},
-             'message': u'',
+                 'name': 'coldmind', 'email': ''},
+             'message': '',
              'rename_details': {},
              'id': 6,
              'authored': {
                  'date': datetime(2013, 11, 8, 13, 38, 11, 152821),
-                 'name': u'coldmind',
+                 'name': 'coldmind',
                  'email': ''
              }, 'size': None},
             {'parents': [4],
              'refs': [],
              'committed': {
                  'date': datetime(2010, 11, 18, 20, 14, 21, 515743),
-                 'name': u'rick446',
+                 'name': 'rick446',
                  'email': ''},
-             'message': u'Copied a => b',
+             'message': 'Copied a => b',
              'rename_details': {},
              'id': 5,
              'authored': {
                  'date': datetime(2010, 11, 18, 20, 14, 21, 515743),
-                 'name': u'rick446',
+                 'name': 'rick446',
                  'email': ''},
              'size': None},
             {'parents': [3],
              'refs': [],
              'committed': {
                  'date': datetime(2010, 10, 8, 15, 32, 59, 383719),
-                 'name': u'rick446',
+                 'name': 'rick446',
                  'email': ''},
-             'message': u'Remove hello.txt',
+             'message': 'Remove hello.txt',
              'rename_details': {},
              'id': 4,
              'authored': {
                  'date': datetime(2010, 10, 8, 15, 32, 59, 383719),
-                 'name': u'rick446',
+                 'name': 'rick446',
                  'email': ''},
              'size': None},
             {'parents': [2],
              'refs': [],
              'committed': {
                  'date': datetime(2010, 10, 8, 15, 32, 48, 272296),
-                 'name': u'rick446',
+                 'name': 'rick446',
                  'email': ''},
-             'message': u'Modify readme',
+             'message': 'Modify readme',
              'rename_details': {},
              'id': 3,
              'authored':
              {'date': datetime(2010, 10, 8, 15, 32, 48, 272296),
-              'name': u'rick446',
+              'name': 'rick446',
               'email': ''},
              'size': None},
             {'parents': [1],
              'refs': [],
              'committed': {
                  'date': datetime(2010, 10, 8, 15, 32, 36, 221863),
-                 'name': u'rick446',
+                 'name': 'rick446',
                  'email': ''},
-             'message': u'Add path',
+             'message': 'Add path',
              'rename_details': {},
              'id': 2,
              'authored': {
                  'date': datetime(2010, 10, 8, 15, 32, 36, 221863),
-                 'name': u'rick446',
+                 'name': 'rick446',
                  'email': ''},
              'size': None},
             {'parents': [],
              'refs': [],
              'committed': {
                  'date': datetime(2010, 10, 8, 15, 32, 7, 238375),
-                 'name': u'rick446',
+                 'name': 'rick446',
                  'email': ''},
-             'message': u'Create readme',
+             'message': 'Create readme',
              'rename_details': {},
              'id': 1,
              'authored': {
                  'date': datetime(2010, 10, 8, 15, 32, 7, 238375),
-                 'name': u'rick446',
+                 'name': 'rick446',
                  'email': ''},
              'size': None}])
 
@@ -329,24 +333,24 @@ class TestSVNRepo(unittest.TestCase, RepoImplTestBase):
         assert_equal(entries, [
             {'authored': {'date': datetime(2010, 10, 8, 15, 32, 48, 272296),
                           'email': '',
-                          'name': u'rick446'},
+                          'name': 'rick446'},
              'committed': {'date': datetime(2010, 10, 8, 15, 32, 48, 272296),
                            'email': '',
-                           'name': u'rick446'},
+                           'name': 'rick446'},
              'id': 3,
-             'message': u'Modify readme',
+             'message': 'Modify readme',
              'parents': [2],
              'refs': [],
              'size': 28,
              'rename_details': {}},
             {'authored': {'date': datetime(2010, 10, 8, 15, 32, 7, 238375),
                           'email': '',
-                          'name': u'rick446'},
+                          'name': 'rick446'},
              'committed': {'date': datetime(2010, 10, 8, 15, 32, 7, 238375),
                            'email': '',
-                           'name': u'rick446'},
+                           'name': 'rick446'},
              'id': 1,
-             'message': u'Create readme',
+             'message': 'Create readme',
              'parents': [],
              'refs': [],
              'size': 15,
@@ -358,7 +362,7 @@ class TestSVNRepo(unittest.TestCase, RepoImplTestBase):
         assert not self.repo.is_file('/a')
 
     def test_paged_diffs(self):
-        entry = self.repo.commit(self.repo.log(2, id_only=True).next())
+        entry = self.repo.commit(next(self.repo.log(2, id_only=True)))
         self.assertEqual(entry.diffs, entry.paged_diffs())
         self.assertEqual(entry.diffs, entry.paged_diffs(start=0))
         added_expected = entry.diffs.added[1:3]
@@ -373,14 +377,14 @@ class TestSVNRepo(unittest.TestCase, RepoImplTestBase):
         self.assertEqual(sorted(actual.keys()), sorted(empty.keys()))
 
     def test_diff_create_file(self):
-        entry = self.repo.commit(self.repo.log(1, id_only=True).next())
+        entry = self.repo.commit(next(self.repo.log(1, id_only=True)))
         self.assertEqual(
             entry.diffs, dict(
                 copied=[], changed=[],
                 removed=[], added=['/README'], total=1))
 
     def test_diff_create_path(self):
-        entry = self.repo.commit(self.repo.log(2, id_only=True).next())
+        entry = self.repo.commit(next(self.repo.log(2, id_only=True)))
         actual = entry.diffs
         actual.added = sorted(actual.added)
         self.assertEqual(
@@ -391,23 +395,23 @@ class TestSVNRepo(unittest.TestCase, RepoImplTestBase):
                     '/a/b/c/hello.txt']), total=4))
 
     def test_diff_modify_file(self):
-        entry = self.repo.commit(self.repo.log(3, id_only=True).next())
+        entry = self.repo.commit(next(self.repo.log(3, id_only=True)))
         self.assertEqual(
             entry.diffs, dict(
                 copied=[], changed=['/README'],
                 removed=[], added=[], total=1))
 
     def test_diff_delete(self):
-        entry = self.repo.commit(self.repo.log(4, id_only=True).next())
+        entry = self.repo.commit(next(self.repo.log(4, id_only=True)))
         self.assertEqual(
             entry.diffs, dict(
                 copied=[], changed=[],
                 removed=['/a/b/c/hello.txt'], added=[], total=1))
 
     def test_diff_copy(self):
-        entry = self.repo.commit(self.repo.log(5, id_only=True).next())
+        entry = self.repo.commit(next(self.repo.log(5, id_only=True)))
         assert_equals(dict(entry.diffs), dict(
-                copied=[{'new': u'/b', 'old': u'/a', 'diff': '', 'ratio': 1}],
+                copied=[{'new': '/b', 'old': '/a', 'diff': '', 'ratio': 1}],
                 changed=[], removed=[], added=[], total=1))
 
     def test_commit(self):
@@ -582,42 +586,42 @@ class TestSVNRepo(unittest.TestCase, RepoImplTestBase):
             'after': 'r6',
             'before': 'r4',
             'commits': [{
-                'id': u'r6',
-                'url': u'http://localhost/p/test/src/6/',
+                'id': 'r6',
+                'url': 'http://localhost/p/test/src/6/',
                 'timestamp': datetime(2013, 11, 8, 13, 38, 11, 152000),
-                'message': u'',
-                'author': {'name': u'coldmind',
-                           'email': u'',
-                           'username': u''},
-                'committer': {'name': u'coldmind',
-                              'email': u'',
-                              'username': u''},
-                'added': [u'/ЗРЯЧИЙ_ТА_ПОБАЧИТЬ'],
+                'message': '',
+                'author': {'name': 'coldmind',
+                           'email': '',
+                           'username': ''},
+                'committer': {'name': 'coldmind',
+                              'email': '',
+                              'username': ''},
+                'added': ['/ЗРЯЧИЙ_ТА_ПОБАЧИТЬ'],
                 'removed': [],
                 'modified': [],
                 'copied': []
             }, {
-                'id': u'r5',
-                'url': u'http://localhost/p/test/src/5/',
+                'id': 'r5',
+                'url': 'http://localhost/p/test/src/5/',
                 'timestamp': datetime(2010, 11, 18, 20, 14, 21, 515000),
-                'message': u'Copied a => b',
-                'author': {'name': u'rick446',
-                           'email': u'',
-                           'username': u''},
-                'committer': {'name': u'rick446',
-                              'email': u'',
-                              'username': u''},
+                'message': 'Copied a => b',
+                'author': {'name': 'rick446',
+                           'email': '',
+                           'username': ''},
+                'committer': {'name': 'rick446',
+                              'email': '',
+                              'username': ''},
                 'added': [],
                 'removed': [],
                 'modified': [],
                 'copied': [
-                    {'new': u'/b', 'old': u'/a', 'diff': '', 'ratio': 1},
+                    {'new': '/b', 'old': '/a', 'diff': '', 'ratio': 1},
                 ],
             }],
             'repository': {
-                'name': u'SVN',
-                'full_name': u'/p/test/src/',
-                'url': u'http://localhost/p/test/src/',
+                'name': 'SVN',
+                'full_name': '/p/test/src/',
+                'url': 'http://localhost/p/test/src/',
             },
         }
         assert_equals(payload, expected_payload)
@@ -658,7 +662,7 @@ class TestSVNRev(unittest.TestCase):
                  + self.rev.diffs.changed
                  + self.rev.diffs.copied)
         for d in diffs:
-            print d
+            print(d)
 
     def _oid(self, rev_id):
         return '%s:%s' % (self.repo._id, rev_id)
@@ -693,10 +697,10 @@ class TestSVNRev(unittest.TestCase):
         ThreadLocalORMSession.flush_all()
         send_notifications(self.repo, [self.repo.rev_to_commit_id(1)])
         ThreadLocalORMSession.flush_all()
-        n = M.Notification.query.find(
-            dict(subject='[test:src] [r1] - rick446: Create readme')).first()
+        n = M.Notification.query.find({'subject': '[test:src] New commit by rick446'}).first()
         assert n
-        assert_equal(n.text, 'Create readme http://localhost:8080/p/test/src/1/')
+        assert_in('[New commit to **test:SVN**][3]\n by [rick446][1]', n.text)
+        assert_in('**Summary**: ```Create readme```', n.text)
 
 
 class _Test(unittest.TestCase):
@@ -706,14 +710,14 @@ class _Test(unittest.TestCase):
         t, isnew = M.repository.Tree.upsert(object_id)
         repo = getattr(self, 'repo', None)
         t.repo = repo
-        for k, v in kwargs.iteritems():
-            if isinstance(v, basestring):
+        for k, v in kwargs.items():
+            if isinstance(v, str):
                 obj = M.repository.Blob(
-                    t, k, self.idgen.next())
+                    t, k, next(self.idgen))
                 t.blob_ids.append(Object(
                     name=k, id=obj._id))
             else:
-                obj = self._make_tree(self.idgen.next(), **v)
+                obj = self._make_tree(next(self.idgen), **v)
                 t.tree_ids.append(Object(
                     name=k, id=obj._id))
         session(t).flush()
@@ -978,21 +982,21 @@ class TestCommit(_TestWithRepo):
             return counter.i
         counter.i = 0
         blobs = defaultdict(counter)
-        from cStringIO import StringIO
+        from io import StringIO
         return lambda blob: StringIO(str(blobs[blob.path()]))
 
     def test_diffs_file_renames(self):
         def open_blob(blob):
             blobs = {
-                u'a': u'Leia',
-                u'/b/a/a': u'Darth Vader',
-                u'/b/a/b': u'Luke Skywalker',
-                u'/b/b': u'Death Star will destroy you',
-                u'/b/c': u'Luke Skywalker',  # moved from /b/a/b
+                'a': 'Leia',
+                '/b/a/a': 'Darth Vader',
+                '/b/a/b': 'Luke Skywalker',
+                '/b/b': 'Death Star will destroy you',
+                '/b/c': 'Luke Skywalker',  # moved from /b/a/b
                 # moved from /b/b and modified
-                u'/b/a/z': u'Death Star will destroy you\nALL',
+                '/b/a/z': 'Death Star will destroy you\nALL',
             }
-            from cStringIO import StringIO
+            from io import StringIO
             return StringIO(blobs.get(blob.path(), ''))
         self.repo._impl.open_blob = open_blob
 
@@ -1126,7 +1130,7 @@ class TestDirectRepoAccess(object):
     def test_paged_diffs(self):
         diffs = self.rev.diffs
         expected = {
-            'added': [u'/ЗРЯЧИЙ_ТА_ПОБАЧИТЬ'],
+            'added': ['/ЗРЯЧИЙ_ТА_ПОБАЧИТЬ'],
             'removed': [],
             'changed': [],
             'copied': [],
@@ -1137,7 +1141,7 @@ class TestDirectRepoAccess(object):
         _id = self.repo._impl._oid(2)
         diffs = self.repo.commit(_id).diffs
         expected = {
-            'added': [u'/a', u'/a/b', u'/a/b/c', u'/a/b/c/hello.txt'],
+            'added': ['/a', '/a/b', '/a/b/c', '/a/b/c/hello.txt'],
             'removed': [],
             'changed': [],
             'copied': [],
@@ -1150,7 +1154,7 @@ class TestDirectRepoAccess(object):
         expected = {
             'added': [],
             'removed': [],
-            'changed': [u'/README'],
+            'changed': ['/README'],
             'copied': [],
             'total': 1,
         }

http://git-wip-us.apache.org/repos/asf/allura/blob/d52f8e2a/ForgeSVN/forgesvn/tests/model/test_svnimplementation.py
----------------------------------------------------------------------
diff --git a/ForgeSVN/forgesvn/tests/model/test_svnimplementation.py b/ForgeSVN/forgesvn/tests/model/test_svnimplementation.py
index c4ab72e..543eea4 100644
--- a/ForgeSVN/forgesvn/tests/model/test_svnimplementation.py
+++ b/ForgeSVN/forgesvn/tests/model/test_svnimplementation.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/ForgeSVN/forgesvn/tests/test_svn_app.py
----------------------------------------------------------------------
diff --git a/ForgeSVN/forgesvn/tests/test_svn_app.py b/ForgeSVN/forgesvn/tests/test_svn_app.py
index 6ea576a..492bea3 100644
--- a/ForgeSVN/forgesvn/tests/test_svn_app.py
+++ b/ForgeSVN/forgesvn/tests/test_svn_app.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/ForgeSVN/forgesvn/tests/test_tasks.py
----------------------------------------------------------------------
diff --git a/ForgeSVN/forgesvn/tests/test_tasks.py b/ForgeSVN/forgesvn/tests/test_tasks.py
index 35b426e..6b22570 100644
--- a/ForgeSVN/forgesvn/tests/test_tasks.py
+++ b/ForgeSVN/forgesvn/tests/test_tasks.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
 import shutil
 import unittest
 import os

http://git-wip-us.apache.org/repos/asf/allura/blob/d52f8e2a/ForgeSVN/forgesvn/version.py
----------------------------------------------------------------------
diff --git a/ForgeSVN/forgesvn/version.py b/ForgeSVN/forgesvn/version.py
index 1b493f8..8f5910c 100644
--- a/ForgeSVN/forgesvn/version.py
+++ b/ForgeSVN/forgesvn/version.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/ForgeSVN/forgesvn/widgets.py
----------------------------------------------------------------------
diff --git a/ForgeSVN/forgesvn/widgets.py b/ForgeSVN/forgesvn/widgets.py
index c0c880f..eb00048 100644
--- a/ForgeSVN/forgesvn/widgets.py
+++ b/ForgeSVN/forgesvn/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/ForgeSVN/setup.py
----------------------------------------------------------------------
diff --git a/ForgeSVN/setup.py b/ForgeSVN/setup.py
index 40dcf73..8dfe24a 100644
--- a/ForgeSVN/setup.py
+++ b/ForgeSVN/setup.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
@@ -25,7 +29,7 @@ from forgesvn.version import __version__
 try:
     import pysvn
 except ImportError:
-    print '\npysvn must be installed for ForgeSVN to work\n'
+    print('\npysvn must be installed for ForgeSVN to work\n')
     raise
 
 setup(name='ForgeSVN',

http://git-wip-us.apache.org/repos/asf/allura/blob/d52f8e2a/ForgeShortUrl/forgeshorturl/main.py
----------------------------------------------------------------------
diff --git a/ForgeShortUrl/forgeshorturl/main.py b/ForgeShortUrl/forgeshorturl/main.py
index b259e92..05a6879 100644
--- a/ForgeShortUrl/forgeshorturl/main.py
+++ b/ForgeShortUrl/forgeshorturl/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/ForgeShortUrl/forgeshorturl/model/__init__.py
----------------------------------------------------------------------
diff --git a/ForgeShortUrl/forgeshorturl/model/__init__.py b/ForgeShortUrl/forgeshorturl/model/__init__.py
index 34e1ff7..efeccc0 100644
--- a/ForgeShortUrl/forgeshorturl/model/__init__.py
+++ b/ForgeShortUrl/forgeshorturl/model/__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/ForgeShortUrl/forgeshorturl/model/shorturl.py
----------------------------------------------------------------------
diff --git a/ForgeShortUrl/forgeshorturl/model/shorturl.py b/ForgeShortUrl/forgeshorturl/model/shorturl.py
index 6682632..9dbde04 100644
--- a/ForgeShortUrl/forgeshorturl/model/shorturl.py
+++ b/ForgeShortUrl/forgeshorturl/model/shorturl.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/ForgeShortUrl/forgeshorturl/tests/functional/test.py
----------------------------------------------------------------------
diff --git a/ForgeShortUrl/forgeshorturl/tests/functional/test.py b/ForgeShortUrl/forgeshorturl/tests/functional/test.py
index 0cc8db3..2f92ee5 100644
--- a/ForgeShortUrl/forgeshorturl/tests/functional/test.py
+++ b/ForgeShortUrl/forgeshorturl/tests/functional/test.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/ForgeShortUrl/forgeshorturl/widgets/short_url.py
----------------------------------------------------------------------
diff --git a/ForgeShortUrl/forgeshorturl/widgets/short_url.py b/ForgeShortUrl/forgeshorturl/widgets/short_url.py
index 0094402..e5c8813 100644
--- a/ForgeShortUrl/forgeshorturl/widgets/short_url.py
+++ b/ForgeShortUrl/forgeshorturl/widgets/short_url.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/ForgeShortUrl/setup.py
----------------------------------------------------------------------
diff --git a/ForgeShortUrl/setup.py b/ForgeShortUrl/setup.py
index 7450912..7e9545d 100644
--- a/ForgeShortUrl/setup.py
+++ b/ForgeShortUrl/setup.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/ForgeTracker/forgetracker/command/fix_discussion.py
----------------------------------------------------------------------
diff --git a/ForgeTracker/forgetracker/command/fix_discussion.py b/ForgeTracker/forgetracker/command/fix_discussion.py
index 95bddb4..4f89f7c 100644
--- a/ForgeTracker/forgetracker/command/fix_discussion.py
+++ b/ForgeTracker/forgetracker/command/fix_discussion.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/ForgeTracker/forgetracker/config/resources.py
----------------------------------------------------------------------
diff --git a/ForgeTracker/forgetracker/config/resources.py b/ForgeTracker/forgetracker/config/resources.py
index c62d4c4..e836151 100644
--- a/ForgeTracker/forgetracker/config/resources.py
+++ b/ForgeTracker/forgetracker/config/resources.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/ForgeTracker/forgetracker/import_support.py
----------------------------------------------------------------------
diff --git a/ForgeTracker/forgetracker/import_support.py b/ForgeTracker/forgetracker/import_support.py
index affe94b..9097768 100644
--- a/ForgeTracker/forgetracker/import_support.py
+++ b/ForgeTracker/forgetracker/import_support.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 logging
 import json
 from datetime import datetime
-from cStringIO import StringIO
+from io import StringIO
 
 # Non-stdlib imports
 from pylons import tmpl_context as c
@@ -32,6 +36,7 @@ from allura.lib.plugin import ImportIdConverter
 
 # Local imports
 from forgetracker import model as TM
+import collections
 
 try:
     from forgeimporters.base import ProjectExtractor
@@ -197,13 +202,13 @@ class ImportSupport(object):
 
     def make_artifact(self, ticket_dict):
         remapped = {}
-        for f, v in ticket_dict.iteritems():
+        for f, v in ticket_dict.items():
             transform = self.FIELD_MAP.get(f, ())
             if transform is None:
                 continue
             elif transform is True:
                 remapped[f] = v
-            elif callable(transform):
+            elif isinstance(transform, collections.Callable):
                 transform(remapped, f, v)
             elif transform is ():
                 self.custom(remapped, f, v, ticket_dict.get('status'))
@@ -215,12 +220,12 @@ class ImportSupport(object):
             self.description_processing(remapped['description']))
         creator = owner = ''
         if ticket_dict.get('submitter') and not remapped.get('reported_by_id'):
-            creator = u'*Originally created by:* {0}\n'.format(
+            creator = '*Originally created by:* {0}\n'.format(
                 h.really_unicode(ticket_dict['submitter']))
         if ticket_dict.get('assigned_to') and not remapped.get('assigned_to_id'):
-            owner = u'*Originally owned by:* {0}\n'.format(
+            owner = '*Originally owned by:* {0}\n'.format(
                     h.really_unicode(ticket_dict['assigned_to']))
-        remapped['description'] = u'{0}{1}{2}{3}'.format(creator, owner,
+        remapped['description'] = '{0}{1}{2}{3}'.format(creator, owner,
                                                          '\n' if creator or owner else '', description)
 
         ticket_num = ticket_dict['id']
@@ -258,7 +263,7 @@ class ImportSupport(object):
         text = h.really_unicode(
             self.comment_processing(comment_dict['comment']))
         if not author_id and comment_dict['submitter']:
-            text = u'*Originally posted by:* {0}\n\n{1}'.format(
+            text = '*Originally posted by:* {0}\n\n{1}'.format(
                 h.really_unicode(comment_dict['submitter']), text)
         comment = thread.post(text=text, timestamp=ts)
         comment.author_id = author_id
@@ -307,7 +312,7 @@ class ImportSupport(object):
     def validate_user_mapping(self):
         if 'user_map' not in self.options:
             self.options['user_map'] = {}
-        for foreign_user, allura_user in self.options['user_map'].iteritems():
+        for foreign_user, allura_user in self.options['user_map'].items():
             u = M.User.by_username(allura_user)
             if not u:
                 raise ImportException(
@@ -323,7 +328,7 @@ class ImportSupport(object):
         self.validate_user_mapping()
 
         project_doc = json.loads(doc)
-        tracker_names = project_doc['trackers'].keys()
+        tracker_names = list(project_doc['trackers'].keys())
         if len(tracker_names) > 1:
             self.errors.append('Only single tracker import is supported')
             return self.errors, self.warnings
@@ -343,7 +348,7 @@ option user_map to avoid losing username information. Unknown users: %s''' % unk
         self.validate_user_mapping()
 
         project_doc = json.loads(doc)
-        tracker_names = project_doc['trackers'].keys()
+        tracker_names = list(project_doc['trackers'].keys())
         if len(tracker_names) > 1:
             self.errors.append('Only single tracker import is supported')
             return self.errors, self.warnings
@@ -365,7 +370,7 @@ option user_map to avoid losing username information. Unknown users: %s''' % unk
             for a_entry in attachments:
                 try:
                     self.make_attachment(a['id'], t._id, a_entry)
-                except Exception, e:
+                except Exception as e:
                     self.warnings.append(
                         'Could not import attachment, skipped: %s' % e)
             log.info('Imported ticket: %d', t.ticket_num)

http://git-wip-us.apache.org/repos/asf/allura/blob/d52f8e2a/ForgeTracker/forgetracker/model/__init__.py
----------------------------------------------------------------------
diff --git a/ForgeTracker/forgetracker/model/__init__.py b/ForgeTracker/forgetracker/model/__init__.py
index 2585be9..6e3512a 100644
--- a/ForgeTracker/forgetracker/model/__init__.py
+++ b/ForgeTracker/forgetracker/model/__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 ticket import Globals, Bin, Ticket, TicketAttachment, MovedTicket
+from .ticket import Globals, Bin, Ticket, TicketAttachment, MovedTicket

http://git-wip-us.apache.org/repos/asf/allura/blob/d52f8e2a/ForgeTracker/forgetracker/model/ticket.py
----------------------------------------------------------------------
diff --git a/ForgeTracker/forgetracker/model/ticket.py b/ForgeTracker/forgetracker/model/ticket.py
index 925a067..efe010e 100644
--- a/ForgeTracker/forgetracker/model/ticket.py
+++ b/ForgeTracker/forgetracker/model/ticket.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 urllib
+import urllib.request, urllib.parse, urllib.error
 import json
 import difflib
 from datetime import datetime, timedelta
@@ -80,7 +84,7 @@ SOLR_TYPE_DEFAULTS = dict(_b=False, _i=0)
 
 
 def get_default_for_solr_type(solr_type):
-    return SOLR_TYPE_DEFAULTS.get(solr_type, u'')
+    return SOLR_TYPE_DEFAULTS.get(solr_type, '')
 
 config = utils.ConfigProxy(
     common_suffix='forgemail.domain',
@@ -273,7 +277,7 @@ class Globals(MappedClass):
             app_config_id=self.app_config_id)).sort('ticket_num').all()
         filtered = self.filtered_by_subscription({t._id: t for t in tickets})
         original_ticket_nums = {t._id: t.ticket_num for t in tickets}
-        users = User.query.find({'_id': {'$in': filtered.keys()}}).all()
+        users = User.query.find({'_id': {'$in': list(filtered.keys())}}).all()
         moved_tickets = {}
         for ticket in tickets:
             moved = ticket.move(tracker, notify=False)
@@ -315,7 +319,7 @@ class Globals(MappedClass):
                 'original_num': original_ticket_nums[_id],
                 'destination_num': moved_tickets[_id].ticket_num,
                 'summary': moved_tickets[_id].summary
-            } for _id, t in moved_tickets.iteritems()
+            } for _id, t in moved_tickets.items()
                 if (not t.private or
                     self.app_config.options.get('TicketMonitoringType') ==
                     'AllTicketChanges')]
@@ -379,7 +383,7 @@ class Globals(MappedClass):
             if labels:
                 values['labels'] = self.append_new_labels(
                     ticket.labels, labels.split(','))
-            for k, v in sorted(values.iteritems()):
+            for k, v in sorted(values.items()):
                 if k == 'assigned_to_id':
                     new_user = User.query.get(_id=v)
                     old_user = User.query.get(_id=getattr(ticket, k))
@@ -405,7 +409,7 @@ class Globals(MappedClass):
                         v,
                         getattr(ticket, k))
                 setattr(ticket, k, v)
-            for k, v in sorted(custom_values.iteritems()):
+            for k, v in sorted(custom_values.items()):
                 def cf_val(cf):
                     return ticket.get_custom_user(cf.name) \
                         if cf.type == 'user' \
@@ -428,7 +432,7 @@ class Globals(MappedClass):
 
         filtered_changes = self.filtered_by_subscription(changed_tickets)
         users = User.query.find(
-            {'_id': {'$in': filtered_changes.keys()}}).all()
+            {'_id': {'$in': list(filtered_changes.keys())}}).all()
 
         def changes_iter(user):
             for t_id in filtered_changes.get(user._id, []):
@@ -446,12 +450,12 @@ class Globals(MappedClass):
         )
         tmpl = g.jinja2_env.get_template('forgetracker:data/mass_report.html')
         head = []
-        for f, v in sorted(values.iteritems()):
+        for f, v in sorted(values.items()):
             if f == 'assigned_to_id':
                 user = User.query.get(_id=v)
                 v = user.display_name if user else v
             head.append('- **%s**: %s' % (get_label(f), v))
-        for f, v in sorted(custom_values.iteritems()):
+        for f, v in sorted(custom_values.items()):
             cf = custom_fields[f]
             if cf.type == 'user':
                 user = User.by_username(v)
@@ -472,7 +476,7 @@ class Globals(MappedClass):
             monitoring_email = self.app_config.options.get(
                 'TicketMonitoringEmail')
             visible_changes = []
-            for t_id, t in changed_tickets.items():
+            for t_id, t in list(changed_tickets.items()):
                 if (not t.private or
                         self.app_config.options.get('TicketMonitoringType') ==
                         'AllTicketChanges'):
@@ -498,19 +502,19 @@ class Globals(MappedClass):
     def filtered_by_subscription(self, tickets, project_id=None, app_config_id=None):
         p_id = project_id if project_id else c.project._id
         ac_id = app_config_id if app_config_id else self.app_config_id
-        ticket_ids = tickets.keys()
+        ticket_ids = list(tickets.keys())
         tickets_index_id = {
-            ticket.index_id(): t_id for t_id, ticket in tickets.iteritems()}
+            ticket.index_id(): t_id for t_id, ticket in tickets.items()}
         subscriptions = Mailbox.query.find({
             'project_id': p_id,
             'app_config_id': ac_id,
-            'artifact_index_id': {'$in': tickets_index_id.keys() + [None]}})
+            'artifact_index_id': {'$in': list(tickets_index_id.keys()) + [None]}})
         filtered = {}
         for subscription in subscriptions:
             if subscription.artifact_index_id is None:
                 # subscribed to entire tool, will see all changes
                 filtered[subscription.user_id] = set(ticket_ids)
-            elif subscription.artifact_index_id in tickets_index_id.keys():
+            elif subscription.artifact_index_id in list(tickets_index_id.keys()):
                 user = filtered.setdefault(subscription.user_id, set())
                 user.add(tickets_index_id[subscription.artifact_index_id])
         return filtered
@@ -559,7 +563,7 @@ class TicketHistory(Snapshot):
             text=self.data.summary)
         # Tracker uses search with default solr parser. It would match only on
         # `text`, so we're appending all other field values into `text`, to match on it too.
-        result['text'] += pformat(result.values())
+        result['text'] += pformat(list(result.values()))
         return result
 
 
@@ -583,7 +587,7 @@ class Bin(Artifact, ActivityObject):
         params = dict(q=(h.really_unicode(self.terms).encode('utf-8') or ''))
         if self.sort:
             params['sort'] = self.sort
-        return base + urllib.urlencode(params)
+        return base + urllib.parse.urlencode(params)
 
     def shorthand_id(self):
         return self.summary
@@ -664,7 +668,7 @@ class Ticket(VersionedArtifact, ActivityObject, VotableArtifact):
                 session(ticket).flush(ticket)
                 h.log_action(log, 'opened').info('')
                 return ticket
-            except OperationFailure, err:
+            except OperationFailure as err:
                 if 'duplicate' in err.args[0]:
                     log.warning('Try to create duplicate ticket %s',
                                 ticket.url())
@@ -692,11 +696,11 @@ class Ticket(VersionedArtifact, ActivityObject, VotableArtifact):
             votes_total_i=(self.votes_up - self.votes_down),
             import_id_s=ImportIdConverter.get().simplify(self.import_id)
         )
-        for k, v in self.custom_fields.iteritems():
+        for k, v in self.custom_fields.items():
             # Pre solr-4.2.1 code expects all custom fields to be indexed
             # as strings.
             if not config.get_bool('new_solr'):
-                result[k + '_s'] = unicode(v)
+                result[k + '_s'] = str(v)
 
             # Now let's also index with proper Solr types.
             solr_type = self.app.globals.get_custom_field_solr_type(k)
@@ -711,7 +715,7 @@ class Ticket(VersionedArtifact, ActivityObject, VotableArtifact):
         # Tracker uses search with default solr parser. It would match only on
         # `text`, so we're appending all other field values into `text`, to
         # match on it too.
-        result['text'] += pformat(result.values())
+        result['text'] += pformat(list(result.values()))
         return result
 
     @classmethod
@@ -804,11 +808,11 @@ class Ticket(VersionedArtifact, ActivityObject, VotableArtifact):
                 fld = f
                 break
         if not fld:
-            raise KeyError, 'Custom field "%s" does not exist.' % custom_user_field_name
+            raise KeyError('Custom field "%s" does not exist.' % custom_user_field_name)
         if fld.type != 'user':
-            raise TypeError, 'Custom field "%s" is of type "%s"; expected ' \
+            raise TypeError('Custom field "%s" is of type "%s"; expected ' \
                              'type "user".' % (
-                                 custom_user_field_name, fld.type)
+                                 custom_user_field_name, fld.type))
         username = self.custom_fields.get(custom_user_field_name)
         if not username:
             return None
@@ -963,7 +967,7 @@ class Ticket(VersionedArtifact, ActivityObject, VotableArtifact):
         attachment = None
         if 'attachment' in ticket_form:
             attachment = ticket_form.pop('attachment')
-        for k, v in ticket_form.iteritems():
+        for k, v in ticket_form.items():
             if k == 'assigned_to':
                 if v:
                     user = c.project.user_in_project(v)
@@ -972,7 +976,7 @@ class Ticket(VersionedArtifact, ActivityObject, VotableArtifact):
             else:
                 setattr(self, k, v)
         if 'custom_fields' in ticket_form:
-            for k, v in ticket_form['custom_fields'].iteritems():
+            for k, v in ticket_form['custom_fields'].items():
                 if k in custom_users:
                     # restrict custom user field values to project members
                     user = self.app_config.project.user_in_project(v)
@@ -1059,7 +1063,7 @@ class Ticket(VersionedArtifact, ActivityObject, VotableArtifact):
                 h.log_action(log, 'moved').info('Ticket %s moved to %s' %
                                                 (prior_url, new_url))
                 break
-            except OperationFailure, err:
+            except OperationFailure as err:
                 if 'duplicate' in err.args[0]:
                     log.warning(
                         'Try to create duplicate ticket %s when moving from %s' %

http://git-wip-us.apache.org/repos/asf/allura/blob/d52f8e2a/ForgeTracker/forgetracker/plugins.py
----------------------------------------------------------------------
diff --git a/ForgeTracker/forgetracker/plugins.py b/ForgeTracker/forgetracker/plugins.py
index c610de8..99b7dba 100644
--- a/ForgeTracker/forgetracker/plugins.py
+++ b/ForgeTracker/forgetracker/plugins.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/ForgeTracker/forgetracker/search.py
----------------------------------------------------------------------
diff --git a/ForgeTracker/forgetracker/search.py b/ForgeTracker/forgetracker/search.py
index 78181b8..841a588 100644
--- a/ForgeTracker/forgetracker/search.py
+++ b/ForgeTracker/forgetracker/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
@@ -56,8 +60,8 @@ def get_facets(solr_hit):
     """
     result = {}
     if solr_hit is not None:
-        for facet_name, values in solr_hit.facets['facet_fields'].iteritems():
+        for facet_name, values in solr_hit.facets['facet_fields'].items():
             field_name = facet_name.rsplit('_s', 1)[0]
-            values = [(values[i], values[i+1]) for i in xrange(0, len(values), 2)]
+            values = [(values[i], values[i+1]) for i in range(0, len(values), 2)]
             result[field_name] = values
     return result

http://git-wip-us.apache.org/repos/asf/allura/blob/d52f8e2a/ForgeTracker/forgetracker/site_stats.py
----------------------------------------------------------------------
diff --git a/ForgeTracker/forgetracker/site_stats.py b/ForgeTracker/forgetracker/site_stats.py
index f9f36ce..71c5be0 100644
--- a/ForgeTracker/forgetracker/site_stats.py
+++ b/ForgeTracker/forgetracker/site_stats.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/ForgeTracker/forgetracker/tasks.py
----------------------------------------------------------------------
diff --git a/ForgeTracker/forgetracker/tasks.py b/ForgeTracker/forgetracker/tasks.py
index 0fdebc5..b36b1dd 100644
--- a/ForgeTracker/forgetracker/tasks.py
+++ b/ForgeTracker/forgetracker/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/ForgeTracker/forgetracker/tests/command/test_fix_discussion.py
----------------------------------------------------------------------
diff --git a/ForgeTracker/forgetracker/tests/command/test_fix_discussion.py b/ForgeTracker/forgetracker/tests/command/test_fix_discussion.py
index 8560556..9b09842 100644
--- a/ForgeTracker/forgetracker/tests/command/test_fix_discussion.py
+++ b/ForgeTracker/forgetracker/tests/command/test_fix_discussion.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/ForgeTracker/forgetracker/tests/functional/test_rest.py
----------------------------------------------------------------------
diff --git a/ForgeTracker/forgetracker/tests/functional/test_rest.py b/ForgeTracker/forgetracker/tests/functional/test_rest.py
index aa728e1..a474704 100644
--- a/ForgeTracker/forgetracker/tests/functional/test_rest.py
+++ b/ForgeTracker/forgetracker/tests/functional/test_rest.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/ForgeTracker/forgetracker/tests/functional/test_root.py
----------------------------------------------------------------------
diff --git a/ForgeTracker/forgetracker/tests/functional/test_root.py b/ForgeTracker/forgetracker/tests/functional/test_root.py
index 7f67b3a..6760afb 100644
--- a/ForgeTracker/forgetracker/tests/functional/test_root.py
+++ b/ForgeTracker/forgetracker/tests/functional/test_root.py
@@ -16,13 +16,17 @@
 #       KIND, either express or implied.  See the License for the
 #       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 datetime import datetime
 
-import urllib
+import urllib.request, urllib.parse, urllib.error
 import os
 import time
 import json
-import StringIO
+import io
 import allura
 import mock
 
@@ -73,7 +77,7 @@ class TrackerTestController(TestController):
                 field.options = [('', False)] + [(u.username, False)
                                                  for u in p.users()]
 
-        for k, v in kw.iteritems():
+        for k, v in kw.items():
             form['ticket_form.%s' % k] = v
         resp = form.submit()
         assert resp.status_int != 200, resp
@@ -510,12 +514,12 @@ class TestFunctionalController(TrackerTestController):
         r = self.app.get('/p/test/bugs/edit/')
         opts = r.html.find('select', attrs={'name': '_type'})
         opts = opts.findAll('option')
-        assert_equal(opts[0].get('value'), u'')
-        assert_equal(opts[0].getText(), u'no change')
-        assert_equal(opts[1].get('value'), u'Bug')
-        assert_equal(opts[1].getText(), u'Bug')
-        assert_equal(opts[2].get('value'), u'Feature Request')
-        assert_equal(opts[2].getText(), u'Feature Request')
+        assert_equal(opts[0].get('value'), '')
+        assert_equal(opts[0].getText(), 'no change')
+        assert_equal(opts[1].get('value'), 'Bug')
+        assert_equal(opts[1].getText(), 'Bug')
+        assert_equal(opts[2].get('value'), 'Feature Request')
+        assert_equal(opts[2].getText(), 'Feature Request')
 
     def test_mass_edit_private_field(self):
         kw = {'private': True}
@@ -714,7 +718,7 @@ class TestFunctionalController(TrackerTestController):
 
         # Make sure the 'Create Ticket' button is disabled for user without 'create' perm
         r = self.app.get('/bugs/', extra_environ=dict(username='*anonymous'))
-        create_button = r.html.find('a', attrs={'href': u'/p/test/bugs/new/'})
+        create_button = r.html.find('a', attrs={'href': '/p/test/bugs/new/'})
         assert_equal(create_button['class'], 'sidebar-disabled')
 
     def test_render_markdown_syntax(self):
@@ -727,8 +731,8 @@ class TestFunctionalController(TrackerTestController):
         # Create ticket
         params = dict(ticket_num=1,
                       app_config_id=c.app.config._id,
-                      summary=u'test md cache',
-                      description=u'# Test markdown cached_convert',
+                      summary='test md cache',
+                      description='# Test markdown cached_convert',
                       mod_date=datetime(2010, 1, 1, 1, 1, 1))
         ticket = tm.Ticket(**params)
 
@@ -775,12 +779,12 @@ class TestFunctionalController(TrackerTestController):
             'status': 'ccc',
             '_milestone': '',
             'assigned_to': '',
-            'labels': u'yellow,greén'.encode('utf-8'),
+            'labels': 'yellow,greén'.encode('utf-8'),
             'comment': ''
         })
         response = self.app.get('/bugs/1/')
         assert_true('yellow' in response)
-        assert_true(u'greén' in response)
+        assert_true('greén' in response)
         assert_true('<li><strong>labels</strong>:  --&gt; yellow, greén</li>' in response)
         self.app.post('/bugs/1/update_ticket', {
             'summary': 'zzz',
@@ -850,8 +854,8 @@ class TestFunctionalController(TrackerTestController):
         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 comment'
         self.app.post(f['action'].encode('utf-8'), params=params,
                       headers={'Referer': '/bugs/1/'.encode("utf-8")})
@@ -904,11 +908,11 @@ class TestFunctionalController(TrackerTestController):
 
         uploaded = PIL.Image.open(file_path)
         r = self.app.get('/bugs/1/attachment/' + filename)
-        downloaded = PIL.Image.open(StringIO.StringIO(r.body))
+        downloaded = PIL.Image.open(io.StringIO(r.body))
         assert uploaded.size == downloaded.size
         r = self.app.get('/bugs/1/attachment/' + filename + '/thumb')
 
-        thumbnail = PIL.Image.open(StringIO.StringIO(r.body))
+        thumbnail = PIL.Image.open(io.StringIO(r.body))
         assert thumbnail.size == (100, 100)
 
     def test_sidebar_static_page(self):
@@ -1067,9 +1071,9 @@ class TestFunctionalController(TrackerTestController):
             '/admin/bugs/set_custom_fields',
             params=variable_encode(params))
         r = self.app.get('/bugs/new/')
-        assert u'<option value="oné">oné</option>'.encode('utf-8') in r
-        assert u'<option value="one and á half">one and á half</option>'.encode('utf-8') in r
-        assert u'<option value="two">two</option>' in r
+        assert '<option value="oné">oné</option>'.encode('utf-8') in r
+        assert '<option value="one and á half">one and á half</option>'.encode('utf-8') in r
+        assert '<option value="two">two</option>' in r
 
     def test_select_custom_field_invalid_quotes(self):
         params = dict(
@@ -1084,9 +1088,9 @@ class TestFunctionalController(TrackerTestController):
             '/admin/bugs/set_custom_fields',
             params=variable_encode(params))
         r = self.app.get('/bugs/new/')
-        assert u'<option value="closéd">closéd</option>'.encode('utf-8') in r
-        assert u'<option value="quote">quote</option>' in r
-        assert u'<option value="missing">missing</option>' in r
+        assert '<option value="closéd">closéd</option>'.encode('utf-8') in r
+        assert '<option value="quote">quote</option>' in r
+        assert '<option value="missing">missing</option>' in r
 
     def test_custom_field_update_comments(self):
         params = dict(
@@ -1310,7 +1314,7 @@ class TestFunctionalController(TrackerTestController):
 
     def test_search_with_strange_chars(self):
         r = self.app.get('/p/test/bugs/search/?' +
-                         urllib.urlencode({'q': 'tést'}))
+                         urllib.parse.urlencode({'q': 'tést'}))
         assert 'Search bugs: tést' in r
 
     def test_saved_search_with_strange_chars(self):
@@ -1436,8 +1440,8 @@ class TestFunctionalController(TrackerTestController):
         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']] = post_content
         r = self.app.post(f['action'].encode('utf-8'), params=params,
                           headers={'Referer': '/bugs/1/'.encode("utf-8")})
@@ -1452,8 +1456,8 @@ class TestFunctionalController(TrackerTestController):
         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['ticket_form.summary'] = new_summary
         r = self.app.post(f['action'].encode('utf-8'), params=params,
                           headers={'Referer': '/bugs/1/'.encode("utf-8")})
@@ -1471,8 +1475,8 @@ class TestFunctionalController(TrackerTestController):
         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']] = post_content
         r = self.app.post(f['action'].encode('utf-8'), params=params,
                           headers={'Referer': '/bugs/1/'.encode("utf-8")})
@@ -1499,8 +1503,8 @@ class TestFunctionalController(TrackerTestController):
         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']] = post_content
         self.app.post(f['action'].encode('utf-8'), params=params,
                       headers={'Referer': '/bugs/1/'.encode("utf-8")})
@@ -1513,8 +1517,8 @@ class TestFunctionalController(TrackerTestController):
         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,
@@ -1698,9 +1702,9 @@ class TestFunctionalController(TrackerTestController):
 - **Status**: unread --> accepted
 - **Milestone**: 1.0 --> 2.0
 '''
-        email = u'\n'.join([email_header, first_ticket_changes, ''])
+        email = '\n'.join([email_header, first_ticket_changes, ''])
         assert_equal(email, first_user_email.kwargs.text)
-        email = u'\n'.join([email_header, second_ticket_changes, ''])
+        email = '\n'.join([email_header, second_ticket_changes, ''])
         assert_equal(email, second_user_email.kwargs.text)
         assert_in(email_header, admin_email.kwargs.text)
         assert_in(first_ticket_changes, admin_email.kwargs.text)
@@ -1854,7 +1858,7 @@ class TestFunctionalController(TrackerTestController):
             tickets[1]._id: tickets[1],
         }
         filtered_changes = c.app.globals.filtered_by_subscription(changes)
-        filtered_users = [uid for uid, data in filtered_changes.iteritems()]
+        filtered_users = [uid for uid, data in filtered_changes.items()]
         assert_equal(sorted(filtered_users),
                      sorted([u._id for u in users[:-1] + [admin]]))
         ticket_ids = [t._id for t in tickets]
@@ -2098,8 +2102,8 @@ class TestFunctionalController(TrackerTestController):
         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']] = post_content
         r = self.app.post(f['action'].encode('utf-8'), params=params,
                           headers={'Referer': '/p/test2/bugs2/1/'.encode("utf-8")})
@@ -2201,7 +2205,7 @@ class TestFunctionalController(TrackerTestController):
         M.Notification.query.remove()
         r = self.app.get('/p/test/bugs/2/')
         field_name = None  # comment text textarea name
-        for name, field in r.forms[2].fields.iteritems():
+        for name, field in r.forms[2].fields.items():
             if field[0].tag == 'textarea':
                 field_name = name
         assert field_name, "Can't find comment field"
@@ -2258,7 +2262,7 @@ class TestFunctionalController(TrackerTestController):
         self.new_ticket(summary='test ticket')
         r = self.app.get('/p/test/bugs/1/')
         field_name = None  # comment text textarea name
-        for name, field in r.forms[2].fields.iteritems():
+        for name, field in r.forms[2].fields.items():
             if field[0].tag == 'textarea':
                 field_name = name
         assert field_name, "Can't find comment field"
@@ -2295,8 +2299,8 @@ class TestFunctionalController(TrackerTestController):
         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 comment'
         self.app.post(f['action'].encode('utf-8'), params=params,
                       headers={'Referer': '/bugs/1/'.encode("utf-8")})
@@ -2378,7 +2382,7 @@ class TestMilestoneAdmin(TrackerTestController):
                  show_in_search='on',
                  type='milestone',
                  milestones=[
-                     dict((k, v) for k, v in d.iteritems()) for d in mf['milestones']])
+                     dict((k, v) for k, v in d.items()) for d in mf['milestones']])
             for mf in milestones]}
         return self._post(params)
 
@@ -2540,7 +2544,7 @@ class TestEmailMonitoring(TrackerTestController):
             if (('thread' in f['action']) and ('post' in f['action'])):
                 params = {i['name']: i.get('value', '')
                           for i in f.findAll('input')
-                          if i.has_key('name')}
+                          if 'name' in i}
                 params[f.find('textarea')['name']] = 'foobar'
                 self.app.post(str(f['action']), params)
                 break  # Do it only once if many forms met
@@ -3040,7 +3044,7 @@ class TestNotificationEmailGrouping(TrackerTestController):
 
     def test_comments(self):
         def find(d, pred):
-            for n, v in d.items():
+            for n, v in list(d.items()):
                 if pred(v):
                     return (n, v)
 
@@ -3123,9 +3127,9 @@ class TestArtifactLinks(TrackerTestController):
         assert_equal(ticket_features.app.config._id, features.config._id)
 
         c.app = bugs
-        link = u'<div class="markdown_content"><p><a class="alink" href="/p/test/bugs/1">[#1]</a></p></div>'
+        link = '<div class="markdown_content"><p><a class="alink" href="/p/test/bugs/1">[#1]</a></p></div>'
         assert_equal(g.markdown.convert('[#1]'), link)
 
         c.app = features
-        link = u'<div class="markdown_content"><p><a class="alink" href="/p/test/features/1">[#1]</a></p></div>'
+        link = '<div class="markdown_content"><p><a class="alink" href="/p/test/features/1">[#1]</a></p></div>'
         assert_equal(g.markdown.convert('[#1]'), link)

http://git-wip-us.apache.org/repos/asf/allura/blob/d52f8e2a/ForgeTracker/forgetracker/tests/test_app.py
----------------------------------------------------------------------
diff --git a/ForgeTracker/forgetracker/tests/test_app.py b/ForgeTracker/forgetracker/tests/test_app.py
index 94d593b..5590153 100644
--- a/ForgeTracker/forgetracker/tests/test_app.py
+++ b/ForgeTracker/forgetracker/tests/test_app.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 TestBulkExport(TrackerTestController):
         assert_equal(posts_foo[0]['text'], 'silly comment')
 
         tracker_config = tracker['tracker_config']
-        assert_true('options' in tracker_config.keys())
+        assert_true('options' in list(tracker_config.keys()))
         assert_equal(tracker_config['options']['mount_point'], 'bugs')
 
         milestones = sorted(tracker['milestones'],

http://git-wip-us.apache.org/repos/asf/allura/blob/d52f8e2a/ForgeTracker/forgetracker/tests/test_tracker_roles.py
----------------------------------------------------------------------
diff --git a/ForgeTracker/forgetracker/tests/test_tracker_roles.py b/ForgeTracker/forgetracker/tests/test_tracker_roles.py
index 92bed3f..2d2672b 100644
--- a/ForgeTracker/forgetracker/tests/test_tracker_roles.py
+++ b/ForgeTracker/forgetracker/tests/test_tracker_roles.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/ForgeTracker/forgetracker/tests/unit/__init__.py
----------------------------------------------------------------------
diff --git a/ForgeTracker/forgetracker/tests/unit/__init__.py b/ForgeTracker/forgetracker/tests/unit/__init__.py
index 0a2189a..17c893b 100644
--- a/ForgeTracker/forgetracker/tests/unit/__init__.py
+++ b/ForgeTracker/forgetracker/tests/unit/__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/ForgeTracker/forgetracker/tests/unit/test_globals_model.py
----------------------------------------------------------------------
diff --git a/ForgeTracker/forgetracker/tests/unit/test_globals_model.py b/ForgeTracker/forgetracker/tests/unit/test_globals_model.py
index e3945dc..d41d74b 100644
--- a/ForgeTracker/forgetracker/tests/unit/test_globals_model.py
+++ b/ForgeTracker/forgetracker/tests/unit/test_globals_model.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/ForgeTracker/forgetracker/tests/unit/test_milestone_controller.py
----------------------------------------------------------------------
diff --git a/ForgeTracker/forgetracker/tests/unit/test_milestone_controller.py b/ForgeTracker/forgetracker/tests/unit/test_milestone_controller.py
index b77cf74..326d18c 100644
--- a/ForgeTracker/forgetracker/tests/unit/test_milestone_controller.py
+++ b/ForgeTracker/forgetracker/tests/unit/test_milestone_controller.py
@@ -18,6 +18,10 @@
 #       under the License.
 
 
+from __future__ import print_function
+from __future__ import division
+from __future__ import absolute_import
+from __future__ import unicode_literals
 from mock import Mock
 from nose.tools import assert_equal
 
@@ -29,7 +33,7 @@ from forgetracker.tracker_main import MilestoneController
 def test_unicode_lookup():
     # can't use name= in constructor, that's special attribute for Mock
     milestone = Mock()
-    milestone.name = u'Перспектива'
+    milestone.name = 'Перспектива'
     milestone_field = Mock(milestones=[milestone])
     milestone_field.name = '_milestone'
 
@@ -43,4 +47,4 @@ def test_unicode_lookup():
         mc = MilestoneController(root, field, milestone_urlparam)
 
     assert mc.milestone  # check that it is found
-    assert_equal(mc.milestone.name, u'Перспектива')
+    assert_equal(mc.milestone.name, 'Перспектива')

http://git-wip-us.apache.org/repos/asf/allura/blob/d52f8e2a/ForgeTracker/forgetracker/tests/unit/test_root_controller.py
----------------------------------------------------------------------
diff --git a/ForgeTracker/forgetracker/tests/unit/test_root_controller.py b/ForgeTracker/forgetracker/tests/unit/test_root_controller.py
index ae2f7a1..8b73a82 100644
--- a/ForgeTracker/forgetracker/tests/unit/test_root_controller.py
+++ b/ForgeTracker/forgetracker/tests/unit/test_root_controller.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/ForgeTracker/forgetracker/tests/unit/test_search.py
----------------------------------------------------------------------
diff --git a/ForgeTracker/forgetracker/tests/unit/test_search.py b/ForgeTracker/forgetracker/tests/unit/test_search.py
index e991c28..fd9cb28 100644
--- a/ForgeTracker/forgetracker/tests/unit/test_search.py
+++ b/ForgeTracker/forgetracker/tests/unit/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/ForgeTracker/forgetracker/tests/unit/test_ticket_custom_fields_form.py
----------------------------------------------------------------------
diff --git a/ForgeTracker/forgetracker/tests/unit/test_ticket_custom_fields_form.py b/ForgeTracker/forgetracker/tests/unit/test_ticket_custom_fields_form.py
index 3c8ea70..5b5d3d4 100644
--- a/ForgeTracker/forgetracker/tests/unit/test_ticket_custom_fields_form.py
+++ b/ForgeTracker/forgetracker/tests/unit/test_ticket_custom_fields_form.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/ForgeTracker/forgetracker/tests/unit/test_ticket_form.py
----------------------------------------------------------------------
diff --git a/ForgeTracker/forgetracker/tests/unit/test_ticket_form.py b/ForgeTracker/forgetracker/tests/unit/test_ticket_form.py
index a0743eb..885669c 100644
--- a/ForgeTracker/forgetracker/tests/unit/test_ticket_form.py
+++ b/ForgeTracker/forgetracker/tests/unit/test_ticket_form.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/ForgeTracker/forgetracker/tests/unit/test_ticket_model.py
----------------------------------------------------------------------
diff --git a/ForgeTracker/forgetracker/tests/unit/test_ticket_model.py b/ForgeTracker/forgetracker/tests/unit/test_ticket_model.py
index 889f342..241b78f 100644
--- a/ForgeTracker/forgetracker/tests/unit/test_ticket_model.py
+++ b/ForgeTracker/forgetracker/tests/unit/test_ticket_model.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,19 +21,14 @@
 
 from pylons import tmpl_context as c
 from datetime import datetime
-import urllib2
+import urllib.request, urllib.error, urllib.parse
 
 import mock
 from ming.orm.ormsession import ThreadLocalORMSession
 from ming.orm import session
 from ming import schema
-from nose.tools import (
-    raises,
-    assert_equal,
-    assert_in,
-    assert_true,
-    assert_false,
-)
+from nose.tools import raises, assert_equal, assert_in
+
 from forgetracker.model import Ticket, TicketAttachment
 from forgetracker.tests.unit import TrackerTestWithModel
 from forgetracker.import_support import ResettableStream
@@ -92,25 +91,6 @@ class TestTicketModel(TrackerTestWithModel):
         assert_in('allura_id', t.activity_extras)
         assert_equal(t.activity_extras['summary'], t.summary)
 
-    def test_has_activity_access(self):
-        t = Ticket(summary='ticket', ticket_num=666)
-        assert_true(t.has_activity_access('read', c.user, 'activity'))
-        t.deleted = True
-        assert_false(t.has_activity_access('read', c.user, 'activity'))
-
-    def test_comment_has_activity_access(self):
-        t = Ticket(summary='ticket', ticket_num=666, deleted=True)
-        p = t.discussion_thread.add_post(text='test post')
-        assert_equal(p.status, 'ok')
-        assert_true(p.has_activity_access('read', c.user, 'activity'))
-        p.status = 'spam'
-        assert_false(p.has_activity_access('read', c.user, 'activity'))
-        p.status = 'pending'
-        assert_false(p.has_activity_access('read', c.user, 'activity'))
-        p.status = 'ok'
-        p.deleted = True
-        assert_false(p.has_activity_access('read', c.user, 'activity'))
-
     def test_private_ticket(self):
         from allura.model import ProjectRole
         from allura.model import ACE, DENY_ALL
@@ -317,7 +297,7 @@ class TestTicketModel(TrackerTestWithModel):
             ticket.summary = 'test ticket'
             ticket.description = 'test description'
         assert_equal(len(ticket.attachments), 0)
-        f = urllib2.urlopen('file://%s' % __file__)
+        f = urllib.request.urlopen('file://%s' % __file__)
         TicketAttachment.save_attachment(
             'test_ticket_model.py', ResettableStream(f),
             artifact_id=ticket._id)
@@ -330,7 +310,7 @@ class TestTicketModel(TrackerTestWithModel):
 
     def test_json_parents(self):
         ticket = Ticket.new()
-        json_keys = ticket.__json__().keys()
+        json_keys = list(ticket.__json__().keys())
         assert_in('related_artifacts', json_keys)  # from Artifact
         assert_in('votes_up', json_keys)  # VotableArtifact
         assert_in('ticket_num', json_keys)  # Ticket

http://git-wip-us.apache.org/repos/asf/allura/blob/d52f8e2a/ForgeTracker/forgetracker/tracker_main.py
----------------------------------------------------------------------
diff --git a/ForgeTracker/forgetracker/tracker_main.py b/ForgeTracker/forgetracker/tracker_main.py
index 596ec54..25ec779 100644
--- a/ForgeTracker/forgetracker/tracker_main.py
+++ b/ForgeTracker/forgetracker/tracker_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
@@ -20,7 +24,7 @@ import logging
 import re
 from datetime import datetime, timedelta
 from functools import partial
-from urllib import urlencode, unquote
+from urllib.parse import urlencode, unquote
 from webob import exc
 import json
 
@@ -151,11 +155,11 @@ def get_change_text(name, new_value, old_value):
 def attachments_info(attachments):
     text = []
     for attach in attachments:
-        text.append(u"{} ({}; {})".format(
+        text.append("{} ({}; {})".format(
             h.really_unicode(attach.filename),
             h.do_filesizeformat(attach.length),
             attach.content_type))
-    return u"\n".join(text)
+    return "\n".join(text)
 
 
 def render_changes(changes, comment=None):
@@ -354,11 +358,8 @@ class ForgeTrackerApp(Application):
         links.append(SitemapEntry('View Stats', self.config.url()
                      + 'stats', ui_icon=g.icons['stats']))
         discussion = c.app.config.discussion
-        pending_mod_count = M.Post.query.find({
-            'discussion_id': discussion._id,
-            'status': 'pending',
-            'deleted': False,
-        }).count()
+        pending_mod_count = M.Post.query.find(
+            {'discussion_id': discussion._id, 'status': 'pending'}).count()
         if pending_mod_count and has_access(discussion, 'moderate')():
             links.append(
                 SitemapEntry(
@@ -829,7 +830,7 @@ class RootController(BaseController, FeedController):
         response.headers['Content-Type'] = ''
         response.content_type = 'application/xml'
         d = dict(title='Ticket search results', link=h.absurl(c.app.url),
-                 description='You searched for %s' % q, language=u'en')
+                 description='You searched for %s' % q, language='en')
         if request.environ['PATH_INFO'].endswith('.atom'):
             feed = FG.Atom1Feed(**d)
         else:
@@ -1025,7 +1026,6 @@ class RootController(BaseController, FeedController):
         q = dict(
             discussion_id=c.app.config.discussion_id,
             status='ok',
-            deleted=False,
         )
         if when is not None:
             q['timestamp'] = {'$gte': when}
@@ -1358,7 +1358,7 @@ class TicketController(BaseController, FeedController):
                         subscribed=subscribed, voting_enabled=voting_enabled,
                         page=page, limit=limit, count=post_count)
         else:
-            raise exc.HTTPNotFound, 'Ticket #%s does not exist.' % self.ticket_num
+            raise exc.HTTPNotFound('Ticket #%s does not exist.' % self.ticket_num)
 
     def get_feed(self, project, app, user):
         """Return a :class:`allura.controllers.feed.FeedArgs` object describing
@@ -1621,7 +1621,7 @@ class TrackerAdminController(DefaultAdminController):
         c.form = W.field_admin
         c.app = self.app
         columns = dict((column, get_label(column))
-                       for column in self.app.globals['show_in_search'].keys())
+                       for column in list(self.app.globals['show_in_search'].keys()))
         return dict(app=self.app, globals=self.app.globals, columns=columns)
 
     @expose('jinja:forgetracker:templates/tracker/admin_options.html')
@@ -1643,7 +1643,7 @@ class TrackerAdminController(DefaultAdminController):
     @validate(W.options_admin, error_handler=options)
     def set_options(self, **kw):
         require_access(self.app, 'configure')
-        for k, v in kw.iteritems():
+        for k, v in kw.items():
             self.app.config.options[k] = v
         flash('Options updated')
         redirect(c.project.url() + 'admin/tools')
@@ -1651,8 +1651,8 @@ class TrackerAdminController(DefaultAdminController):
     @expose()
     @require_post()
     def allow_default_field(self, **post_data):
-        for column in self.app.globals['show_in_search'].keys():
-            if post_data.has_key(column) and post_data[column] == 'on':
+        for column in list(self.app.globals['show_in_search'].keys()):
+            if column in post_data and post_data[column] == 'on':
                 self.app.globals['show_in_search'][column] = True
             else:
                 self.app.globals['show_in_search'][column] = False
@@ -1803,7 +1803,7 @@ class RootRestController(BaseController):
 
         results = TM.Ticket.paged_search(
             c.app.config, c.user, q, limit, page, sort, show_deleted=False)
-        results['tickets'] = map(_convert_ticket, results['tickets'])
+        results['tickets'] = list(map(_convert_ticket, results['tickets']))
         return results
 
     @expose()

http://git-wip-us.apache.org/repos/asf/allura/blob/d52f8e2a/ForgeTracker/forgetracker/version.py
----------------------------------------------------------------------
diff --git a/ForgeTracker/forgetracker/version.py b/ForgeTracker/forgetracker/version.py
index 1b493f8..8f5910c 100644
--- a/ForgeTracker/forgetracker/version.py
+++ b/ForgeTracker/forgetracker/version.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/ForgeTracker/forgetracker/widgets/admin.py
----------------------------------------------------------------------
diff --git a/ForgeTracker/forgetracker/widgets/admin.py b/ForgeTracker/forgetracker/widgets/admin.py
index 586ce6f..2aba8f8 100644
--- a/ForgeTracker/forgetracker/widgets/admin.py
+++ b/ForgeTracker/forgetracker/widgets/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

http://git-wip-us.apache.org/repos/asf/allura/blob/d52f8e2a/ForgeTracker/forgetracker/widgets/admin_custom_fields.py
----------------------------------------------------------------------
diff --git a/ForgeTracker/forgetracker/widgets/admin_custom_fields.py b/ForgeTracker/forgetracker/widgets/admin_custom_fields.py
index 19b55ec..36c9093 100644
--- a/ForgeTracker/forgetracker/widgets/admin_custom_fields.py
+++ b/ForgeTracker/forgetracker/widgets/admin_custom_fields.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/ForgeTracker/forgetracker/widgets/bin_form.py
----------------------------------------------------------------------
diff --git a/ForgeTracker/forgetracker/widgets/bin_form.py b/ForgeTracker/forgetracker/widgets/bin_form.py
index a62026e..e4aeff7 100644
--- a/ForgeTracker/forgetracker/widgets/bin_form.py
+++ b/ForgeTracker/forgetracker/widgets/bin_form.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/ForgeTracker/forgetracker/widgets/ticket_form.py
----------------------------------------------------------------------
diff --git a/ForgeTracker/forgetracker/widgets/ticket_form.py b/ForgeTracker/forgetracker/widgets/ticket_form.py
index 476d9b3..b60db19 100644
--- a/ForgeTracker/forgetracker/widgets/ticket_form.py
+++ b/ForgeTracker/forgetracker/widgets/ticket_form.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 GenericTicketForm(ew.SimpleForm):
         since normally `ProjectUserCombo` shows without any options, and loads
         them asynchronously (via ajax).
         """
-        if isinstance(user, basestring):
+        if isinstance(user, str):
             user = M.User.by_username(user)
         if user and user != M.User.anonymous():
             field.options = [

http://git-wip-us.apache.org/repos/asf/allura/blob/d52f8e2a/ForgeTracker/forgetracker/widgets/ticket_search.py
----------------------------------------------------------------------
diff --git a/ForgeTracker/forgetracker/widgets/ticket_search.py b/ForgeTracker/forgetracker/widgets/ticket_search.py
index 21c413a..93338e8 100644
--- a/ForgeTracker/forgetracker/widgets/ticket_search.py
+++ b/ForgeTracker/forgetracker/widgets/ticket_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
@@ -45,7 +49,7 @@ class TicketSearchResults(ew_core.SimpleForm):
     def __init__(self, filters, *args, **kw):
         super(TicketSearchResults, self).__init__(*args, **kw)
         self.filters = {}
-        for name, field in filters.iteritems():
+        for name, field in filters.items():
             self.filters[name] = options = [{
                 'value': val,
                 'label': '%s (%s)' % (val, count),

http://git-wip-us.apache.org/repos/asf/allura/blob/d52f8e2a/ForgeTracker/setup.py
----------------------------------------------------------------------
diff --git a/ForgeTracker/setup.py b/ForgeTracker/setup.py
index d3e00cc..7d3e7a6 100644
--- a/ForgeTracker/setup.py
+++ b/ForgeTracker/setup.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/ForgeUserStats/forgeuserstats/controllers/userstats.py
----------------------------------------------------------------------
diff --git a/ForgeUserStats/forgeuserstats/controllers/userstats.py b/ForgeUserStats/forgeuserstats/controllers/userstats.py
index 6194257..87ef7be 100644
--- a/ForgeUserStats/forgeuserstats/controllers/userstats.py
+++ b/ForgeUserStats/forgeuserstats/controllers/userstats.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
@@ -126,8 +130,8 @@ class ForgeUserStatsController(BaseController):
                     categories[cat] += 1
                 else:
                     categories[cat] = 1
-        categories = sorted(categories.items(),
-                            key=lambda (x, y): y, reverse=True)
+        categories = sorted(list(categories.items()),
+                            key=lambda x_y: x_y[1], reverse=True)
 
         ret_dict['lastmonth_logins'] = stats.getLastMonthLogins()
         ret_dict['categories'] = categories