You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@allura.apache.org by br...@apache.org on 2020/01/21 16:03:22 UTC

[allura] branch db/7878 created (now 6daa349)

This is an automated email from the ASF dual-hosted git repository.

brondsem pushed a change to branch db/7878
in repository https://gitbox.apache.org/repos/asf/allura.git.


      at 6daa349  [#7878] stronger test validation that form submits are plain strings

This branch includes the following new commits:

     new 20f59c5  [#7878] run: python-modernize -n -w --no-diffs -f unicode_type .
     new 4e8c766  [#7878] run: python-modernize --future-unicode -n -w --no-diffs -f unicode_future .
     new f4165c6  [#7878] ming needs this to be "str" (not py2 unicode)  Can revert when fully on py3 (str/unicode there is ok)
     new 41ec930  [#7878] misc unicode fixes
     new ca9454e  [#7878] http headers must be str (not unicode in py2 or bytes in py3).  Can be rolled back when in py3 exclusively I think
     new c076249  [#7878] AntiSpam fixes for unicode
     new 2439e26  [#7878] upload_files as binary
     new 6fb573f  [#7878] quote in py2 should always work on bytes, and it does internal caching so very weird distant code can get errors if safe param is unicode
     new 95a902c  [#7878] tests need to treat responses as unicode text
     new e974746  [#7878] MockSOLR: handle unicode better
     new 8302904  [#7878] encode form submits in tests
     new 6daa349  [#7878] stronger test validation that form submits are plain strings

The 12 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.



[allura] 10/12: [#7878] MockSOLR: handle unicode better

Posted by br...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

brondsem pushed a commit to branch db/7878
in repository https://gitbox.apache.org/repos/asf/allura.git

commit e9747460463bdbe5a5db49f2fabfd731158a593e
Author: Dave Brondsema <db...@slashdotmedia.com>
AuthorDate: Fri Jan 17 16:41:25 2020 +0000

    [#7878] MockSOLR: handle unicode better
---
 Allura/allura/lib/solr.py | 12 +++++++++---
 1 file changed, 9 insertions(+), 3 deletions(-)

diff --git a/Allura/allura/lib/solr.py b/Allura/allura/lib/solr.py
index 307a4ab..5ad5979 100644
--- a/Allura/allura/lib/solr.py
+++ b/Allura/allura/lib/solr.py
@@ -152,11 +152,17 @@ class MockSOLR(object):
     def search(self, q, fq=None, **kw):
         if q is None:
             q = ''  # shlex will hang on None
-        if isinstance(q, six.text_type):
-            q = q.encode('latin-1')
         # Parse query
         preds = []
-        q_parts = shlex.split(q)
+        if six.PY2:
+            # shlex can't handle unicode in py2
+            q_parts = [
+                _.decode('latin-1')
+                for _ in
+                shlex.split(q.encode('latin-1'))
+            ]
+        else:
+            q_parts = shlex.split(q)
         if fq:
             q_parts += fq
         for part in q_parts:


[allura] 03/12: [#7878] ming needs this to be "str" (not py2 unicode) Can revert when fully on py3 (str/unicode there is ok)

Posted by br...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

brondsem pushed a commit to branch db/7878
in repository https://gitbox.apache.org/repos/asf/allura.git

commit f4165c662df32ed94c450bfdc5917dbdcc60d824
Author: Dave Brondsema <db...@slashdotmedia.com>
AuthorDate: Thu Jan 16 18:17:16 2020 +0000

    [#7878] ming needs this to be "str" (not py2 unicode)  Can revert when fully on py3 (str/unicode there is ok)
---
 Allura/allura/model/artifact.py                | 22 +++++++++++-----------
 Allura/allura/model/attachments.py             |  2 +-
 Allura/allura/model/auth.py                    | 14 +++++++-------
 Allura/allura/model/discuss.py                 | 10 +++++-----
 Allura/allura/model/filesystem.py              |  2 +-
 Allura/allura/model/index.py                   |  4 ++--
 Allura/allura/model/monq_model.py              |  2 +-
 Allura/allura/model/multifactor.py             |  4 ++--
 Allura/allura/model/neighborhood.py            |  2 +-
 Allura/allura/model/notification.py            |  6 +++---
 Allura/allura/model/oauth.py                   | 10 +++++-----
 Allura/allura/model/project.py                 |  8 ++++----
 Allura/allura/model/repository.py              | 10 +++++-----
 Allura/allura/model/stats.py                   |  2 +-
 Allura/allura/model/webhook.py                 |  2 +-
 Allura/allura/tests/model/test_artifact.py     |  2 +-
 ForgeBlog/forgeblog/model/blog.py              |  8 ++++----
 ForgeChat/forgechat/model/chat.py              |  4 ++--
 ForgeDiscussion/forgediscussion/model/forum.py | 10 +++++-----
 ForgeFeedback/forgefeedback/model/feedback.py  |  2 +-
 ForgeGit/forgegit/model/git_repo.py            |  2 +-
 ForgeSVN/forgesvn/model/svn.py                 |  2 +-
 ForgeShortUrl/forgeshorturl/model/shorturl.py  |  2 +-
 ForgeTracker/forgetracker/model/ticket.py      | 12 ++++++------
 ForgeUserStats/forgeuserstats/model/stats.py   |  2 +-
 ForgeWiki/forgewiki/model/wiki.py              |  8 ++++----
 26 files changed, 77 insertions(+), 77 deletions(-)

diff --git a/Allura/allura/model/artifact.py b/Allura/allura/model/artifact.py
index 89f2159..71a22ee 100644
--- a/Allura/allura/model/artifact.py
+++ b/Allura/allura/model/artifact.py
@@ -65,7 +65,7 @@ class Artifact(MappedClass, SearchIndexable):
     """
     class __mongometa__:
         session = artifact_orm_session
-        name = 'artifact'
+        name = str('artifact')
         indexes = [
             ('app_config_id', 'labels'),
         ]
@@ -492,7 +492,7 @@ class Snapshot(Artifact):
     """
     class __mongometa__:
         session = artifact_orm_session
-        name = 'artifact_snapshot'
+        name = str('artifact_snapshot')
         unique_indexes = [('artifact_class', 'artifact_id', 'version')]
         indexes = [('artifact_id', 'version'),
                    'author.id',
@@ -568,7 +568,7 @@ class VersionedArtifact(Artifact):
     """
     class __mongometa__:
         session = artifact_orm_session
-        name = 'versioned_artifact'
+        name = str('versioned_artifact')
         history_class = Snapshot
 
     version = FieldProperty(S.Int, if_missing=0)
@@ -687,7 +687,7 @@ class Message(Artifact):
 
     class __mongometa__:
         session = artifact_orm_session
-        name = 'message'
+        name = str('message')
     type_s = 'Generic Message'
 
     _id = FieldProperty(str, if_missing=h.gen_message_id)
@@ -735,7 +735,7 @@ class AwardFile(File):
 
     class __mongometa__:
         session = main_orm_session
-        name = 'award_file'
+        name = str('award_file')
     award_id = FieldProperty(S.ObjectId)
 
 
@@ -743,7 +743,7 @@ class Award(Artifact):
 
     class __mongometa__:
         session = main_orm_session
-        name = 'award'
+        name = str('award')
         indexes = ['short']
     type_s = 'Generic Award'
 
@@ -786,7 +786,7 @@ class AwardGrant(Artifact):
     "An :class:`Award <allura.model.artifact.Award>` can be bestowed upon a project by a neighborhood"
     class __mongometa__:
         session = main_orm_session
-        name = 'grant'
+        name = str('grant')
         indexes = ['short']
     type_s = 'Generic Award Grant'
 
@@ -858,7 +858,7 @@ class Feed(MappedClass):
     """
     class __mongometa__:
         session = project_orm_session
-        name = 'artifact_feed'
+        name = str('artifact_feed')
         indexes = [
             'pubdate',
             ('artifact_ref.project_id', 'artifact_ref.mount_point'),
@@ -997,7 +997,7 @@ class VotableArtifact(MappedClass):
 
     class __mongometa__:
         session = main_orm_session
-        name = 'vote'
+        name = str('vote')
 
     votes = FieldProperty(int, if_missing=0)
     votes_up = FieldProperty(int, if_missing=0)
@@ -1124,7 +1124,7 @@ class MovedArtifact(Artifact):
 
     class __mongometa__:
         session = artifact_orm_session
-        name = 'moved_artifact'
+        name = str('moved_artifact')
 
     _id = FieldProperty(S.ObjectId)
     app_config_id = ForeignIdProperty(
@@ -1136,7 +1136,7 @@ class MovedArtifact(Artifact):
 class SpamCheckResult(MappedClass):
     class __mongometa__:
         session = main_orm_session
-        name = 'spam_check_result'
+        name = str('spam_check_result')
         indexes = [
             ('project_id', 'result'),
             ('user_id', 'result'),
diff --git a/Allura/allura/model/attachments.py b/Allura/allura/model/attachments.py
index ad5549b..b62cd80 100644
--- a/Allura/allura/model/attachments.py
+++ b/Allura/allura/model/attachments.py
@@ -31,7 +31,7 @@ class BaseAttachment(File):
     ArtifactType = None
 
     class __mongometa__:
-        name = 'attachment'
+        name = str('attachment')
         polymorphic_on = 'attachment_type'
         polymorphic_identity = None
         session = project_orm_session
diff --git a/Allura/allura/model/auth.py b/Allura/allura/model/auth.py
index 12571a9..fdda9ca 100644
--- a/Allura/allura/model/auth.py
+++ b/Allura/allura/model/auth.py
@@ -68,7 +68,7 @@ class EmailAddress(MappedClass):
     re_format = re.compile('^.*\s+<(.*)>\s*$')
 
     class __mongometa__:
-        name = 'email_address'
+        name = str('email_address')
         session = main_orm_session
         indexes = ['nonce', ]
         unique_indexes = [('email', 'claimed_by_user_id'), ]
@@ -176,7 +176,7 @@ please visit the following URL:
 
 class AuthGlobals(MappedClass):
     class __mongometa__:
-        name = 'auth_globals'
+        name = str('auth_globals')
         session = main_orm_session
 
     _id = FieldProperty(int)
@@ -225,7 +225,7 @@ class User(MappedClass, ActivityNode, ActivityObject, SearchIndexable):
     SALT_LEN = 8
 
     class __mongometa__:
-        name = 'user'
+        name = str('user')
         session = main_orm_session
         indexes = ['tool_data.sfx.userid', 'tool_data.AuthPasswordReset.hash']
         unique_indexes = ['username']
@@ -818,7 +818,7 @@ class User(MappedClass, ActivityNode, ActivityObject, SearchIndexable):
 class OldProjectRole(MappedClass):
     class __mongometa__:
         session = project_orm_session
-        name = 'user'
+        name = str('user')
         unique_indexes = [('user_id', 'project_id', 'name')]
 
 
@@ -836,7 +836,7 @@ class ProjectRole(MappedClass):
 
     class __mongometa__:
         session = main_orm_session
-        name = 'project_role'
+        name = str('project_role')
         unique_indexes = [('user_id', 'project_id', 'name')]
         indexes = [
             ('user_id',),
@@ -968,7 +968,7 @@ class ProjectRole(MappedClass):
 
 
 audit_log = collection(
-    'audit_log', main_doc_session,
+    str('audit_log'), main_doc_session,
     Field('_id', S.ObjectId()),
     Field('project_id', S.ObjectId, if_missing=None,
           index=True),  # main view of audit log queries by project_id
@@ -1039,7 +1039,7 @@ class UserLoginDetails(MappedClass):
     """
 
     class __mongometa__:
-        name = 'user_login_details'
+        name = str('user_login_details')
         session = main_explicitflush_orm_session
         indexes = ['user_id']
         unique_indexes = [('user_id', 'ip', 'ua'),  # DuplicateKeyError checked in add_login_detail
diff --git a/Allura/allura/model/discuss.py b/Allura/allura/model/discuss.py
index ed3db2f..c0b559a 100644
--- a/Allura/allura/model/discuss.py
+++ b/Allura/allura/model/discuss.py
@@ -49,7 +49,7 @@ log = logging.getLogger(__name__)
 class Discussion(Artifact, ActivityObject):
 
     class __mongometa__:
-        name = 'discussion'
+        name = str('discussion')
     type_s = 'Discussion'
 
     parent_id = FieldProperty(schema.Deprecated)
@@ -134,7 +134,7 @@ class Discussion(Artifact, ActivityObject):
 class Thread(Artifact, ActivityObject):
 
     class __mongometa__:
-        name = 'thread'
+        name = str('thread')
         indexes = [
             ('artifact_id',),
             ('ref_id',),
@@ -458,7 +458,7 @@ class Thread(Artifact, ActivityObject):
 class PostHistory(Snapshot):
 
     class __mongometa__:
-        name = 'post_history'
+        name = str('post_history')
 
     artifact_id = ForeignIdProperty('Post')
 
@@ -493,7 +493,7 @@ class PostHistory(Snapshot):
 class Post(Message, VersionedArtifact, ActivityObject, ReactableArtifact):
 
     class __mongometa__:
-        name = 'post'
+        name = str('post')
         history_class = PostHistory
         indexes = [
             # used in general lookups, last_post, etc
@@ -817,7 +817,7 @@ class DiscussionAttachment(BaseAttachment):
     thumbnail_size = (100, 100)
 
     class __mongometa__:
-        polymorphic_identity = 'DiscussionAttachment'
+        polymorphic_identity = str('DiscussionAttachment')
         indexes = ['filename', 'discussion_id', 'thread_id', 'post_id']
 
     discussion_id = FieldProperty(schema.ObjectId)
diff --git a/Allura/allura/model/filesystem.py b/Allura/allura/model/filesystem.py
index e1c1bed..70f335b 100644
--- a/Allura/allura/model/filesystem.py
+++ b/Allura/allura/model/filesystem.py
@@ -47,7 +47,7 @@ class File(MappedClass):
 
     class __mongometa__:
         session = project_orm_session
-        name = 'fs'
+        name = str('fs')
         indexes = ['filename']
 
     _id = FieldProperty(schema.ObjectId)
diff --git a/Allura/allura/model/index.py b/Allura/allura/model/index.py
index f468608..02edea3 100644
--- a/Allura/allura/model/index.py
+++ b/Allura/allura/model/index.py
@@ -42,7 +42,7 @@ log = logging.getLogger(__name__)
 
 # Collection definitions
 ArtifactReferenceDoc = collection(
-    'artifact_reference', main_doc_session,
+    str('artifact_reference'), main_doc_session,
     Field('_id', str),
     Field('artifact_reference', dict(
         cls=S.Binary(),
@@ -54,7 +54,7 @@ ArtifactReferenceDoc = collection(
 )
 
 ShortlinkDoc = collection(
-    'shortlink', main_doc_session,
+    str('shortlink'), main_doc_session,
     Field('_id', S.ObjectId()),
     # index needed for from_artifact() and index_tasks.py:del_artifacts
     Field('ref_id', str, index=True),
diff --git a/Allura/allura/model/monq_model.py b/Allura/allura/model/monq_model.py
index 97db4e2..ded8229 100644
--- a/Allura/allura/model/monq_model.py
+++ b/Allura/allura/model/monq_model.py
@@ -65,7 +65,7 @@ class MonQTask(MappedClass):
 
     class __mongometa__:
         session = task_orm_session
-        name = 'monq_task'
+        name = str('monq_task')
         indexes = [
             [
                 # used in MonQTask.get() method
diff --git a/Allura/allura/model/multifactor.py b/Allura/allura/model/multifactor.py
index 5f6c6ee..d67bcb1 100644
--- a/Allura/allura/model/multifactor.py
+++ b/Allura/allura/model/multifactor.py
@@ -34,7 +34,7 @@ class TotpKey(MappedClass):
 
     class __mongometa__:
         session = main_orm_session
-        name = 'multifactor_totp'
+        name = str('multifactor_totp')
         unique_indexes = ['user_id']
 
     _id = FieldProperty(S.ObjectId)
@@ -49,7 +49,7 @@ class RecoveryCode(MappedClass):
 
     class __mongometa__:
         session = main_orm_session
-        name = 'multifactor_recovery_code'
+        name = str('multifactor_recovery_code')
         indexes = ['user_id']
 
     _id = FieldProperty(S.ObjectId)
diff --git a/Allura/allura/model/neighborhood.py b/Allura/allura/model/neighborhood.py
index ac40a13..f030077 100644
--- a/Allura/allura/model/neighborhood.py
+++ b/Allura/allura/model/neighborhood.py
@@ -63,7 +63,7 @@ class Neighborhood(MappedClass):
     '''
     class __mongometa__:
         session = main_orm_session
-        name = 'neighborhood'
+        name = str('neighborhood')
         unique_indexes = ['url_prefix']
 
     _id = FieldProperty(S.ObjectId)
diff --git a/Allura/allura/model/notification.py b/Allura/allura/model/notification.py
index 4ea53eb..e085897 100644
--- a/Allura/allura/model/notification.py
+++ b/Allura/allura/model/notification.py
@@ -73,7 +73,7 @@ class Notification(MappedClass):
 
     class __mongometa__:
         session = main_orm_session
-        name = 'notification'
+        name = str('notification')
         indexes = ['project_id']
 
     _id = FieldProperty(str, if_missing=h.gen_message_id)
@@ -388,7 +388,7 @@ class Mailbox(MappedClass):
 
     class __mongometa__:
         session = main_orm_session
-        name = 'mailbox'
+        name = str('mailbox')
         unique_indexes = [
             ('user_id', 'project_id', 'app_config_id',
              'artifact_index_id', 'topic', 'is_flash'),
@@ -710,7 +710,7 @@ class SiteNotification(MappedClass):
 
     class __mongometa__:
         session = main_orm_session
-        name = 'site_notification'
+        name = str('site_notification')
         indexes = [
             ('active', '_id'),
         ]
diff --git a/Allura/allura/model/oauth.py b/Allura/allura/model/oauth.py
index b1d324d..e2016ae 100644
--- a/Allura/allura/model/oauth.py
+++ b/Allura/allura/model/oauth.py
@@ -40,7 +40,7 @@ class OAuthToken(MappedClass):
 
     class __mongometa__:
         session = main_orm_session
-        name = 'oauth_token'
+        name = str('oauth_token')
         indexes = ['api_key']
         polymorphic_on = 'type'
         polymorphic_identity = None
@@ -60,8 +60,8 @@ class OAuthToken(MappedClass):
 class OAuthConsumerToken(OAuthToken):
 
     class __mongometa__:
-        polymorphic_identity = 'consumer'
-        name = 'oauth_consumer_token'
+        polymorphic_identity = str('consumer')
+        name = str('oauth_consumer_token')
         unique_indexes = [('name', 'user_id')]
 
     type = FieldProperty(str, if_missing='consumer')
@@ -105,7 +105,7 @@ class OAuthConsumerToken(OAuthToken):
 class OAuthRequestToken(OAuthToken):
 
     class __mongometa__:
-        polymorphic_identity = 'request'
+        polymorphic_identity = str('request')
 
     type = FieldProperty(str, if_missing='request')
     consumer_token_id = ForeignIdProperty('OAuthConsumerToken')
@@ -119,7 +119,7 @@ class OAuthRequestToken(OAuthToken):
 class OAuthAccessToken(OAuthToken):
 
     class __mongometa__:
-        polymorphic_identity = 'access'
+        polymorphic_identity = str('access')
 
     type = FieldProperty(str, if_missing='access')
     consumer_token_id = ForeignIdProperty('OAuthConsumerToken')
diff --git a/Allura/allura/model/project.py b/Allura/allura/model/project.py
index 5d0b2a7..4aedf25 100644
--- a/Allura/allura/model/project.py
+++ b/Allura/allura/model/project.py
@@ -86,7 +86,7 @@ class ProjectCategory(MappedClass):
 
     class __mongometa__:
         session = main_orm_session
-        name = 'project_category'
+        name = str('project_category')
 
     _id = FieldProperty(S.ObjectId)
     parent_id = FieldProperty(S.ObjectId, if_missing=None)
@@ -119,7 +119,7 @@ class TroveCategory(MappedClass):
 
     class __mongometa__:
         session = main_orm_session
-        name = 'trove_category'
+        name = str('trove_category')
         extensions = [TroveCategoryMapperExtension]
         indexes = ['trove_cat_id', 'trove_parent_id', 'shortname', 'fullpath']
 
@@ -192,7 +192,7 @@ class Project(SearchIndexable, MappedClass, ActivityNode, ActivityObject):
 
     class __mongometa__:
         session = main_orm_session
-        name = 'project'
+        name = str('project')
         indexes = [
             'name',
             'neighborhood_id',
@@ -1301,7 +1301,7 @@ class AppConfig(MappedClass, ActivityObject):
 
     class __mongometa__:
         session = project_orm_session
-        name = 'config'
+        name = str('config')
         indexes = [
             'project_id',
             'options.import_id',
diff --git a/Allura/allura/model/repository.py b/Allura/allura/model/repository.py
index 512dd68..cf688df 100644
--- a/Allura/allura/model/repository.py
+++ b/Allura/allura/model/repository.py
@@ -344,7 +344,7 @@ class Repository(Artifact, ActivityObject):
     BATCH_SIZE = 100
 
     class __mongometa__:
-        name = 'generic-repository'
+        name = str('generic-repository')
         indexes = ['upstream_repo.name']
     _impl = None
     repo_id = 'repo'
@@ -794,7 +794,7 @@ class MergeRequest(VersionedArtifact, ActivityObject):
     statuses = ['open', 'merged', 'rejected']
 
     class __mongometa__:
-        name = 'merge-request'
+        name = str('merge-request')
         indexes = ['commit_id', 'creator_id']
         unique_indexes = [('app_config_id', 'request_number')]
     type_s = 'MergeRequest'
@@ -989,7 +989,7 @@ class MergeRequest(VersionedArtifact, ActivityObject):
 # One of these for each commit in the physical repo on disk. The _id is the
 # hexsha of the commit (for Git and Hg).
 CommitDoc = collection(
-    'repo_ci', main_doc_session,
+    str('repo_ci'), main_doc_session,
     Field('_id', str),
     Field('tree_id', str),
     Field('committed', SUser),
@@ -1001,7 +1001,7 @@ CommitDoc = collection(
 
 # Basic tree information
 TreeDoc = collection(
-    'repo_tree', main_doc_session,
+    str('repo_tree'), main_doc_session,
     Field('_id', str),
     Field('tree_ids', [dict(name=str, id=str)]),
     Field('blob_ids', [dict(name=str, id=str)]),
@@ -1009,7 +1009,7 @@ TreeDoc = collection(
 
 # Information about the last commit to touch a tree
 LastCommitDoc = collection(
-    'repo_last_commit', main_doc_session,
+    str('repo_last_commit'), main_doc_session,
     Field('_id', S.ObjectId()),
     Field('commit_id', str),
     Field('path', str),
diff --git a/Allura/allura/model/stats.py b/Allura/allura/model/stats.py
index 726f08a..9cee2e2 100644
--- a/Allura/allura/model/stats.py
+++ b/Allura/allura/model/stats.py
@@ -33,7 +33,7 @@ from allura.model.session import main_orm_session
 class Stats(MappedClass):
 
     class __mongometa__:
-        name = 'basestats'
+        name = str('basestats')
         session = main_orm_session
         unique_indexes = ['_id']
 
diff --git a/Allura/allura/model/webhook.py b/Allura/allura/model/webhook.py
index b4810d2..5f923ca 100644
--- a/Allura/allura/model/webhook.py
+++ b/Allura/allura/model/webhook.py
@@ -30,7 +30,7 @@ import six
 
 class Webhook(Artifact):
     class __mongometa__:
-        name = 'webhook'
+        name = str('webhook')
         unique_indexes = [('app_config_id', 'type', 'hook_url')]
 
     type = FieldProperty(str)
diff --git a/Allura/allura/tests/model/test_artifact.py b/Allura/allura/tests/model/test_artifact.py
index 06b2b2a..e9d617b 100644
--- a/Allura/allura/tests/model/test_artifact.py
+++ b/Allura/allura/tests/model/test_artifact.py
@@ -46,7 +46,7 @@ from forgewiki import model as WM
 class Checkmessage(M.Message):
 
     class __mongometa__:
-        name = 'checkmessage'
+        name = str('checkmessage')
 
     def url(self):
         return ''
diff --git a/ForgeBlog/forgeblog/model/blog.py b/ForgeBlog/forgeblog/model/blog.py
index e2c536d..83e8161 100644
--- a/ForgeBlog/forgeblog/model/blog.py
+++ b/ForgeBlog/forgeblog/model/blog.py
@@ -43,7 +43,7 @@ config = utils.ConfigProxy(
 class Globals(MappedClass):
 
     class __mongometa__:
-        name = 'blog-globals'
+        name = str('blog-globals')
         session = M.project_orm_session
         indexes = ['app_config_id']
 
@@ -57,7 +57,7 @@ class Globals(MappedClass):
 class BlogPostSnapshot(M.Snapshot):
 
     class __mongometa__:
-        name = 'blog_post_snapshot'
+        name = str('blog_post_snapshot')
     type_s = 'Blog Post Snapshot'
 
     def original(self):
@@ -102,7 +102,7 @@ class BlogPostSnapshot(M.Snapshot):
 class BlogPost(M.VersionedArtifact, ActivityObject):
 
     class __mongometa__:
-        name = 'blog_post'
+        name = str('blog_post')
         history_class = BlogPostSnapshot
         unique_indexes = [('app_config_id', 'slug')]
         indexes = [
@@ -334,7 +334,7 @@ class BlogAttachment(M.BaseAttachment):
     thumbnail_size = (100, 100)
 
     class __mongometa__:
-        polymorphic_identity = 'BlogAttachment'
+        polymorphic_identity = str('BlogAttachment')
     attachment_type = FieldProperty(str, if_missing='BlogAttachment')
 
 
diff --git a/ForgeChat/forgechat/model/chat.py b/ForgeChat/forgechat/model/chat.py
index 7513f8e..e444438 100644
--- a/ForgeChat/forgechat/model/chat.py
+++ b/ForgeChat/forgechat/model/chat.py
@@ -29,7 +29,7 @@ from allura.model.types import MarkdownCache
 class ChatChannel(MappedClass):
 
     class __mongometa__:
-        name = 'globals'
+        name = str('globals')
         session = M.main_orm_session
         indexes = ['project_id']
         unique_indexes = ['channel']
@@ -43,7 +43,7 @@ class ChatChannel(MappedClass):
 class ChatMessage(M.Artifact):
 
     class __mongometa__:
-        name = 'chat_message'
+        name = str('chat_message')
         indexes = ['timestamp']
     type_s = 'Chat Message'
 
diff --git a/ForgeDiscussion/forgediscussion/model/forum.py b/ForgeDiscussion/forgediscussion/model/forum.py
index e4e1b98..1916f55 100644
--- a/ForgeDiscussion/forgediscussion/model/forum.py
+++ b/ForgeDiscussion/forgediscussion/model/forum.py
@@ -42,7 +42,7 @@ log = logging.getLogger(__name__)
 class Forum(M.Discussion):
 
     class __mongometa__:
-        name = 'forum'
+        name = str('forum')
     type_s = 'Discussion'
 
     parent_id = FieldProperty(schema.ObjectId, if_missing=None)
@@ -138,7 +138,7 @@ class Forum(M.Discussion):
 class ForumThread(M.Thread):
 
     class __mongometa__:
-        name = 'forum_thread'
+        name = str('forum_thread')
         indexes = [
             'flags',
             'discussion_id',
@@ -209,7 +209,7 @@ class ForumThread(M.Thread):
 class ForumPostHistory(M.PostHistory):
 
     class __mongometa__:
-        name = 'post_history'
+        name = str('post_history')
 
     artifact_id = ForeignIdProperty('ForumPost')
 
@@ -217,7 +217,7 @@ class ForumPostHistory(M.PostHistory):
 class ForumPost(M.Post):
 
     class __mongometa__:
-        name = 'forum_post'
+        name = str('forum_post')
         history_class = ForumPostHistory
         indexes = [
             'timestamp',  # for the posts_24hr site_stats query
@@ -253,7 +253,7 @@ class ForumAttachment(M.DiscussionAttachment):
     PostClass = ForumPost
 
     class __mongometa__:
-        polymorphic_identity = 'ForumAttachment'
+        polymorphic_identity = str('ForumAttachment')
     attachment_type = FieldProperty(str, if_missing='ForumAttachment')
 
 
diff --git a/ForgeFeedback/forgefeedback/model/feedback.py b/ForgeFeedback/forgefeedback/model/feedback.py
index b3e566f..0dc78a2 100755
--- a/ForgeFeedback/forgefeedback/model/feedback.py
+++ b/ForgeFeedback/forgefeedback/model/feedback.py
@@ -41,7 +41,7 @@ log = logging.getLogger(__name__)
 class Feedback(VersionedArtifact, ActivityObject):
 
     class __mongometa__:
-        name = 'feedback'
+        name = str('feedback')
         indexes = [
             ('project_id', 'reported_by_id'),
         ]
diff --git a/ForgeGit/forgegit/model/git_repo.py b/ForgeGit/forgegit/model/git_repo.py
index 7cb4bcf..67e0081 100644
--- a/ForgeGit/forgegit/model/git_repo.py
+++ b/ForgeGit/forgegit/model/git_repo.py
@@ -64,7 +64,7 @@ class Repository(M.Repository):
     type_s = 'Git Repository'
 
     class __mongometa__:
-        name = 'git-repository'
+        name = str('git-repository')
 
     @LazyProperty
     def _impl(self):
diff --git a/ForgeSVN/forgesvn/model/svn.py b/ForgeSVN/forgesvn/model/svn.py
index fdf7bf1..b9f1ecc 100644
--- a/ForgeSVN/forgesvn/model/svn.py
+++ b/ForgeSVN/forgesvn/model/svn.py
@@ -56,7 +56,7 @@ class Repository(M.Repository):
     type_s = 'SVN Repository'
 
     class __mongometa__:
-        name = 'svn-repository'
+        name = str('svn-repository')
     branches = FieldProperty([dict(name=str, object_id=str)])
     _refresh_precompute = False
 
diff --git a/ForgeShortUrl/forgeshorturl/model/shorturl.py b/ForgeShortUrl/forgeshorturl/model/shorturl.py
index b1c2559..98fecae 100644
--- a/ForgeShortUrl/forgeshorturl/model/shorturl.py
+++ b/ForgeShortUrl/forgeshorturl/model/shorturl.py
@@ -28,7 +28,7 @@ from allura import model as M
 class ShortUrl(M.Artifact):
 
     class __mongometa__:
-        name = 'short_urls'
+        name = str('short_urls')
         unique_indexes = [('short_name', 'app_config_id')]
 
     type_s = 'ShortUrl'
diff --git a/ForgeTracker/forgetracker/model/ticket.py b/ForgeTracker/forgetracker/model/ticket.py
index 52563d7..6824e42 100644
--- a/ForgeTracker/forgetracker/model/ticket.py
+++ b/ForgeTracker/forgetracker/model/ticket.py
@@ -95,7 +95,7 @@ config = utils.ConfigProxy(
 class Globals(MappedClass):
 
     class __mongometa__:
-        name = 'globals'
+        name = str('globals')
         session = project_orm_session
         indexes = ['app_config_id']
 
@@ -540,7 +540,7 @@ class Globals(MappedClass):
 class TicketHistory(Snapshot):
 
     class __mongometa__:
-        name = 'ticket_history'
+        name = str('ticket_history')
 
     def original(self):
         return Ticket.query.get(_id=self.artifact_id)
@@ -586,7 +586,7 @@ class TicketHistory(Snapshot):
 class Bin(Artifact, ActivityObject):
 
     class __mongometa__:
-        name = 'bin'
+        name = str('bin')
 
     type_s = 'Bin'
     _id = FieldProperty(schema.ObjectId)
@@ -628,7 +628,7 @@ class Bin(Artifact, ActivityObject):
 class Ticket(VersionedArtifact, ActivityObject, VotableArtifact):
 
     class __mongometa__:
-        name = 'ticket'
+        name = str('ticket')
         history_class = TicketHistory
         indexes = [
             'ticket_num',
@@ -1368,7 +1368,7 @@ class TicketAttachment(BaseAttachment):
     ArtifactType = Ticket
 
     class __mongometa__:
-        polymorphic_identity = 'TicketAttachment'
+        polymorphic_identity = str('TicketAttachment')
     attachment_type = FieldProperty(str, if_missing='TicketAttachment')
 
 
@@ -1376,7 +1376,7 @@ class MovedTicket(MovedArtifact):
 
     class __mongometa__:
         session = artifact_orm_session
-        name = 'moved_ticket'
+        name = str('moved_ticket')
         indexes = [
             ('app_config_id', 'ticket_num'),
         ]
diff --git a/ForgeUserStats/forgeuserstats/model/stats.py b/ForgeUserStats/forgeuserstats/model/stats.py
index 4ac9bd6..1298a09 100644
--- a/ForgeUserStats/forgeuserstats/model/stats.py
+++ b/ForgeUserStats/forgeuserstats/model/stats.py
@@ -30,7 +30,7 @@ from allura.model import Stats
 class UserStats(Stats):
 
     class __mongometa__:
-        name = 'userstats'
+        name = str('userstats')
         session = main_orm_session
         unique_indexes = ['_id', 'user_id']
 
diff --git a/ForgeWiki/forgewiki/model/wiki.py b/ForgeWiki/forgewiki/model/wiki.py
index 2d1b71c..a829e10 100644
--- a/ForgeWiki/forgewiki/model/wiki.py
+++ b/ForgeWiki/forgewiki/model/wiki.py
@@ -55,7 +55,7 @@ config = utils.ConfigProxy(
 class Globals(MappedClass):
 
     class __mongometa__:
-        name = 'wiki-globals'
+        name = str('wiki-globals')
         session = project_orm_session
         indexes = ['app_config_id']
 
@@ -69,7 +69,7 @@ class Globals(MappedClass):
 class PageHistory(Snapshot):
 
     class __mongometa__:
-        name = 'page_history'
+        name = str('page_history')
 
     def original(self):
         return Page.query.get(_id=self.artifact_id)
@@ -105,7 +105,7 @@ class PageHistory(Snapshot):
 class Page(VersionedArtifact, ActivityObject):
 
     class __mongometa__:
-        name = 'page'
+        name = str('page')
         history_class = PageHistory
         unique_indexes = [('app_config_id', 'title')]
 
@@ -282,7 +282,7 @@ class WikiAttachment(BaseAttachment):
     thumbnail_size = (100, 100)
 
     class __mongometa__:
-        polymorphic_identity = 'WikiAttachment'
+        polymorphic_identity = str('WikiAttachment')
     attachment_type = FieldProperty(str, if_missing='WikiAttachment')
 
 Mapper.compile_all()


[allura] 07/12: [#7878] upload_files as binary

Posted by br...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

brondsem pushed a commit to branch db/7878
in repository https://gitbox.apache.org/repos/asf/allura.git

commit 2439e2647615ee96bfebbc719c0db25d2053f40d
Author: Dave Brondsema <db...@slashdotmedia.com>
AuthorDate: Thu Jan 16 22:43:18 2020 +0000

    [#7878] upload_files as binary
---
 Allura/allura/tests/functional/test_discuss.py               | 12 ++++++------
 ForgeBlog/forgeblog/tests/functional/test_root.py            |  6 +++---
 .../forgediscussion/tests/functional/test_forum.py           |  8 ++++----
 ForgeImporters/forgeimporters/tests/forge/test_tracker.py    |  4 ++--
 .../forgeimporters/trac/tests/functional/test_trac.py        |  2 +-
 ForgeImporters/forgeimporters/trac/tests/test_tickets.py     |  6 +++---
 ForgeTracker/forgetracker/tests/functional/test_root.py      |  6 +++---
 7 files changed, 22 insertions(+), 22 deletions(-)

diff --git a/Allura/allura/tests/functional/test_discuss.py b/Allura/allura/tests/functional/test_discuss.py
index 4a87fd1..51f080e 100644
--- a/Allura/allura/tests/functional/test_discuss.py
+++ b/Allura/allura/tests/functional/test_discuss.py
@@ -433,7 +433,7 @@ class TestAttachment(TestDiscussBase):
 
     def test_attach(self):
         r = self.app.post(self.post_link + 'attach',
-                          upload_files=[('file_info', 'test.txt', 'HiThere!')])
+                          upload_files=[('file_info', 'test.txt', b'HiThere!')])
         r = self.app.get(self.thread_link)
         assert '<div class="attachment_holder">' in r
         alink = self.attach_link()
@@ -441,12 +441,12 @@ class TestAttachment(TestDiscussBase):
         assert r.content_type == 'text/plain'
         assert r.content_disposition == 'attachment;filename="test.txt"', 'Attachments should force download'
         r = self.app.post(self.post_link + 'attach',
-                          upload_files=[('file_info', 'test.o12', 'HiThere!')])
+                          upload_files=[('file_info', 'test.o12', b'HiThere!')])
         r = self.app.post(alink, params=dict(delete='on'))
 
     def test_attach_svg(self):
         r = self.app.post(self.post_link + 'attach',
-                          upload_files=[('file_info', 'test.svg', '<svg onclick="prompt(document.domain)"></svg>')])
+                          upload_files=[('file_info', 'test.svg', b'<svg onclick="prompt(document.domain)"></svg>')])
         alink = self.attach_link()
         r = self.app.get(alink)
         assert r.content_type == 'image/svg+xml'
@@ -455,7 +455,7 @@ class TestAttachment(TestDiscussBase):
     def test_attach_img(self):
         r = self.app.post(self.post_link + 'attach',
                           upload_files=[('file_info', 'handtinyblack.gif',
-                                         'GIF89a\x01\x00\x01\x00\x00\xff\x00,\x00\x00\x00\x00\x01\x00\x01\x00\x00\x02\x00;')])
+                                         b'GIF89a\x01\x00\x01\x00\x00\xff\x00,\x00\x00\x00\x00\x01\x00\x01\x00\x00\x02\x00;')])
         alink = self.attach_link()
         r = self.app.get(alink)
         assert r.content_type == 'image/gif'
@@ -475,8 +475,8 @@ class TestAttachment(TestDiscussBase):
         params[post_form.find('textarea')['name']] = 'Reply'
         r = self.app.post(self.post_link + 'reply',
                           params=params,
-                          upload_files=[('file_info', 'test.txt', 'HiThere!'),
-                                        ('file_info', 'test2.txt', 'HiAgain!')])
+                          upload_files=[('file_info', 'test.txt', b'HiThere!'),
+                                        ('file_info', 'test2.txt', b'HiAgain!')])
         r = self.app.get(self.thread_link)
         assert "test.txt" in r
 
diff --git a/ForgeBlog/forgeblog/tests/functional/test_root.py b/ForgeBlog/forgeblog/tests/functional/test_root.py
index 8c61cfa..87fcc53 100644
--- a/ForgeBlog/forgeblog/tests/functional/test_root.py
+++ b/ForgeBlog/forgeblog/tests/functional/test_root.py
@@ -188,8 +188,8 @@ class Test(TestController):
 
     def test_post_attachments(self):
         # create
-        upload = ('attachment', 'nums.txt', '123412341234')
-        upload2 = ('attachment', 'more_nums.txt', '56789')
+        upload = ('attachment', 'nums.txt', b'123412341234')
+        upload2 = ('attachment', 'more_nums.txt', b'56789')
         self._post(extra_app_post_params=dict(upload_files=[upload, upload2]))
 
         # check it is listed
@@ -199,7 +199,7 @@ class Test(TestController):
 
         # edit
         slug = '/%s/my-post' % d
-        upload = ('attachment', 'letters.txt', 'abcdefghij')
+        upload = ('attachment', 'letters.txt', b'abcdefghij')
         self._post(slug=slug,
                    extra_app_post_params=dict(upload_files=[upload]))
 
diff --git a/ForgeDiscussion/forgediscussion/tests/functional/test_forum.py b/ForgeDiscussion/forgediscussion/tests/functional/test_forum.py
index 13c1f07..eef13ab 100644
--- a/ForgeDiscussion/forgediscussion/tests/functional/test_forum.py
+++ b/ForgeDiscussion/forgediscussion/tests/functional/test_forum.py
@@ -263,13 +263,13 @@ class TestForumMessageHandling(TestController):
         r = self.app.get(thd_url + reply.slug + '/')
         # Check attachments
         r = self.app.post(url + 'attach',
-                          upload_files=[('file_info', 'test.txt', 'This is a textfile')])
+                          upload_files=[('file_info', 'test.txt', b'This is a textfile')])
         r = self.app.post(url + 'attach',
                           upload_files=[('file_info', 'test.asdfasdtxt',
-                                         'This is a textfile')])
+                                         b'This is a textfile')])
         r = self.app.post(url + 'attach',
-                          upload_files=[('file_info', 'test1.txt', 'This is a textfile'),
-                                        ('file_info', 'test2.txt', 'This is a textfile')])
+                          upload_files=[('file_info', 'test1.txt', b'This is a textfile'),
+                                        ('file_info', 'test2.txt', b'This is a textfile')])
         r = self.app.get(url)
         assert "test1.txt" in r
         assert "test2.txt" in r
diff --git a/ForgeImporters/forgeimporters/tests/forge/test_tracker.py b/ForgeImporters/forgeimporters/tests/forge/test_tracker.py
index 99a098c..e56c5e4 100644
--- a/ForgeImporters/forgeimporters/tests/forge/test_tracker.py
+++ b/ForgeImporters/forgeimporters/tests/forge/test_tracker.py
@@ -357,7 +357,7 @@ class TestForgeTrackerImportController(TestController, TestCase):
     def test_create(self, import_tool, sui):
         project = M.Project.query.get(shortname='test')
         params = {
-            'tickets_json': webtest.Upload('tickets.json', '{"key": "val"}'),
+            'tickets_json': webtest.Upload('tickets.json', b'{"key": "val"}'),
             'mount_label': 'mylabel',
             'mount_point': 'mymount',
         }
@@ -378,7 +378,7 @@ class TestForgeTrackerImportController(TestController, TestCase):
         project.set_tool_data('ForgeTrackerImporter', pending=1)
         ThreadLocalORMSession.flush_all()
         params = {
-            'tickets_json': webtest.Upload('tickets.json', '{"key": "val"}'),
+            'tickets_json': webtest.Upload('tickets.json', b'{"key": "val"}'),
             'mount_label': 'mylabel',
             'mount_point': 'mymount',
         }
diff --git a/ForgeImporters/forgeimporters/trac/tests/functional/test_trac.py b/ForgeImporters/forgeimporters/trac/tests/functional/test_trac.py
index e9ce596..babe127 100644
--- a/ForgeImporters/forgeimporters/trac/tests/functional/test_trac.py
+++ b/ForgeImporters/forgeimporters/trac/tests/functional/test_trac.py
@@ -37,7 +37,7 @@ class TestTracImportController(TestController):
         form['trac_url'] = 'http://example.com/trac'
         form['project_name'] = 'My Project'
         form['project_shortname'] = 'my-project'
-        form['user_map'] = '', ''
+        form['user_map'] = ('', b'')
 
         with patch('forgeimporters.trac.requests.head') as mock_head:
             mock_head.return_value.status_code = 200  # so our 'trac_url' above is deemed as an okay URL
diff --git a/ForgeImporters/forgeimporters/trac/tests/test_tickets.py b/ForgeImporters/forgeimporters/trac/tests/test_tickets.py
index 005f9f2..210e2fe 100644
--- a/ForgeImporters/forgeimporters/trac/tests/test_tickets.py
+++ b/ForgeImporters/forgeimporters/trac/tests/test_tickets.py
@@ -136,7 +136,7 @@ class TestTracTicketImportController(TestController, TestCase):
                       )
         r = self.app.post('/p/test/admin/bugs/_importer/create', params,
                           upload_files=[(
-                              'user_map', 'myfile', '{"orig_user": "new_user"}'
+                              'user_map', 'myfile', b'{"orig_user": "new_user"}'
                           )],
                           status=302)
         self.assertEqual(r.location, 'http://localhost/p/test/admin/')
@@ -163,7 +163,7 @@ class TestTracTicketImportController(TestController, TestCase):
                       )
         r = self.app.post('/p/test/admin/bugs/_importer/create', params,
                           upload_files=[(
-                              'user_map', 'myfile', '{"orig_user": "new_user"}'
+                              'user_map', 'myfile', b'{"orig_user": "new_user"}'
                           )],
                           status=302).follow()
         self.assertIn('Please wait and try again', r)
@@ -180,7 +180,7 @@ class TestTracTicketImportController(TestController, TestCase):
                       )
         r = self.app.post('/p/test/admin/bugs/_importer/create', params,
                           upload_files=[(
-                              'user_map', 'myfile', '{"orig_user": "new_user"}'
+                              'user_map', 'myfile', b'{"orig_user": "new_user"}'
                           )])
         self.assertEqual(import_tool.post.call_count, 0)
 
diff --git a/ForgeTracker/forgetracker/tests/functional/test_root.py b/ForgeTracker/forgetracker/tests/functional/test_root.py
index 75256f2..8e39790 100644
--- a/ForgeTracker/forgetracker/tests/functional/test_root.py
+++ b/ForgeTracker/forgetracker/tests/functional/test_root.py
@@ -928,7 +928,7 @@ class TestFunctionalController(TrackerTestController):
         r = self.app.get('/bugs/1/', dict(page=1))
         post_link = str(r.html.find('div', {'class': 'edit_post_form reply'}).find('form')['action'])
         self.app.post(post_link + 'attach',
-                      upload_files=[('file_info', 'test.txt', 'HiThere!')])
+                      upload_files=[('file_info', 'test.txt', b'HiThere!')])
         r = self.app.get('/bugs/1/', dict(page=1))
         assert '<i class="fa fa-trash-o" aria-hidden="true"></i>' in r
         r.forms[5].submit()
@@ -2370,7 +2370,7 @@ class TestFunctionalController(TrackerTestController):
         r = self.app.get('/p/test/bugs/1/')
         post_link = str(r.html.find('div', {'class': 'edit_post_form reply'}).find('form')['action'])
         r = self.app.post(post_link + 'attach',
-                          upload_files=[('file_info', 'test.txt', 'test')])
+                          upload_files=[('file_info', 'test.txt', b'test')])
         # move ticket
         p = M.Project.query.get(shortname='test2')
         bugs2 = p.app_instance('bugs2')
@@ -2444,7 +2444,7 @@ class TestFunctionalController(TrackerTestController):
         r = self.app.get('/bugs/1/', dict(page=1))
         post_link = str(r.html.find('div', {'class': 'edit_post_form reply'}).find('form')['action'])
         self.app.post(post_link + 'attach',
-                      upload_files=[('file_info', 'test.txt', 'test attach')])
+                      upload_files=[('file_info', 'test.txt', b'test attach')])
         r = self.app.get('/p/test/bugs/1/')
         discussion_url = r.html.findAll('form')[-1]['action'][:-4]
         r = self.app.get('/rest/p/test/bugs/1/')


[allura] 12/12: [#7878] stronger test validation that form submits are plain strings

Posted by br...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

brondsem pushed a commit to branch db/7878
in repository https://gitbox.apache.org/repos/asf/allura.git

commit 6daa3490fd4aaa69654d5e1d259a111cbc24ec74
Author: Dave Brondsema <db...@slashdotmedia.com>
AuthorDate: Tue Jan 21 15:38:43 2020 +0000

    [#7878] stronger test validation that form submits are plain strings
---
 Allura/allura/tests/functional/test_admin.py       |   2 +-
 Allura/allura/tests/functional/test_site_admin.py  |   2 +-
 Allura/allura/tests/test_webhooks.py               |   2 +-
 AlluraTest/alluratest/validation.py                |  33 ++++++-
 ForgeBlog/forgeblog/tests/functional/test_root.py  |   2 +-
 .../forgediscussion/tests/functional/test_forum.py |   2 +-
 .../forgegit/tests/functional/test_controllers.py  |   4 +-
 .../forgetracker/tests/functional/test_root.py     | 104 ++++++++++-----------
 ForgeWiki/forgewiki/tests/functional/test_root.py  |   4 +-
 9 files changed, 90 insertions(+), 65 deletions(-)

diff --git a/Allura/allura/tests/functional/test_admin.py b/Allura/allura/tests/functional/test_admin.py
index d01eca0..a05e85e 100644
--- a/Allura/allura/tests/functional/test_admin.py
+++ b/Allura/allura/tests/functional/test_admin.py
@@ -448,7 +448,7 @@ class TestProjectAdmin(TestController):
         screenshots = project.get_screenshots()
         assert_equals(screenshots[0].filename, 'admin_24.png')
         # reverse order
-        params = dict((str(ss._id), len(screenshots) - 1 - i)
+        params = dict((str(ss._id), str(len(screenshots) - 1 - i))
                       for i, ss in enumerate(screenshots))
         self.app.post('/admin/sort_screenshots', params)
         assert_equals(project.get_screenshots()[0].filename, 'admin_32.png')
diff --git a/Allura/allura/tests/functional/test_site_admin.py b/Allura/allura/tests/functional/test_site_admin.py
index e287f0a..55c3155 100644
--- a/Allura/allura/tests/functional/test_site_admin.py
+++ b/Allura/allura/tests/functional/test_site_admin.py
@@ -815,7 +815,7 @@ class TestDeleteProjects(TestController):
 
     @patch('allura.controllers.site_admin.DeleteProjects', autospec=True)
     def test_admins_and_devs_are_disabled(self, dp):
-        data = {'projects': '/p/test\np/test2', 'disable_users': True}
+        data = {'projects': '/p/test\np/test2', 'disable_users': 'True'}
         self.app.post('/nf/admin/delete_projects/really_delete', data)
         dp.post.assert_called_once_with('--disable-users p/test p/test2')
 
diff --git a/Allura/allura/tests/test_webhooks.py b/Allura/allura/tests/test_webhooks.py
index 9dbba01..43f0cdf 100644
--- a/Allura/allura/tests/test_webhooks.py
+++ b/Allura/allura/tests/test_webhooks.py
@@ -319,7 +319,7 @@ class TestWebhookController(TestController):
 
         # invalid id in hidden field, just in case
         r = self.app.get(self.url + '/repo-push/%s' % wh._id)
-        data = {k: v[0].value for (k, v) in r.forms[0].fields.items()}
+        data = {k: v[0].value for (k, v) in r.forms[0].fields.items() if k}
         data['webhook'] = six.text_type(invalid._id)
         self.app.post(self.url + '/repo-push/edit', data, status=404)
 
diff --git a/AlluraTest/alluratest/validation.py b/AlluraTest/alluratest/validation.py
index 3972cd4..e7bcd85 100644
--- a/AlluraTest/alluratest/validation.py
+++ b/AlluraTest/alluratest/validation.py
@@ -31,6 +31,7 @@ import json
 import urllib2
 import re
 import pkg_resources
+import six
 
 import webtest
 from webtest import TestApp
@@ -261,17 +262,41 @@ class PostParamCheckingTestApp(AntiSpamTestApp):
             if not isinstance(k, basestring):
                 raise TypeError('%s key %s is %s, not str' %
                                 (method, k, type(k)))
-            if not isinstance(v, (basestring, webtest.forms.File)):
+            self._validate_val(k, v, method)
+
+    def _validate_val(self, k, v, method):
+        if isinstance(v, (list, tuple)):
+            for vv in v:
+                self._validate_val(k, vv, method)
+        elif not isinstance(v, (basestring, webtest.forms.File, webtest.forms.Upload)):
+            raise TypeError(
+                '%s key %s has value %s of type %s, not str. ' %
+                (method, k, v, type(v)))
+        elif six.PY2 and isinstance(v, six.text_type):
+            try:
+                v.encode('ascii')
+                #pass
+            except UnicodeEncodeError:
                 raise TypeError(
-                    '%s key %s has value %s of type %s, not str. ' %
+                    '%s key "%s" has value "%s" of type %s, should be utf-8 encoded. ' %
                     (method, k, v, type(v)))
 
     def get(self, *args, **kwargs):
-        self._validate_params(kwargs.get('params'), 'get')
+        params = None
+        if 'params' in kwargs:
+            params = kwargs['params']
+        elif len(args) > 1:
+            params = args[1]
+        self._validate_params(params, 'get')
         return super(PostParamCheckingTestApp, self).get(*args, **kwargs)
 
     def post(self, *args, **kwargs):
-        self._validate_params(kwargs.get('params'), 'post')
+        params = None
+        if 'params' in kwargs:
+            params = kwargs['params']
+        elif len(args) > 1:
+            params = args[1]
+        self._validate_params(params, 'post')
         return super(PostParamCheckingTestApp, self).post(*args, **kwargs)
 
 
diff --git a/ForgeBlog/forgeblog/tests/functional/test_root.py b/ForgeBlog/forgeblog/tests/functional/test_root.py
index 87fcc53..c893532 100644
--- a/ForgeBlog/forgeblog/tests/functional/test_root.py
+++ b/ForgeBlog/forgeblog/tests/functional/test_root.py
@@ -239,7 +239,7 @@ class Test(TestController):
     def test_post_revert(self):
         self._post()
         d = self._blog_date()
-        self._post('/%s/my-post' % d, text='sometést')
+        self._post('/%s/my-post' % d, text='sometést'.encode('utf-8'))
         response = self.app.post('/blog/%s/my-post/revert' % d, params=dict(version='1'))
         assert '.' in response.json['location']
         response = self.app.get('/blog/%s/my-post/' % d)
diff --git a/ForgeDiscussion/forgediscussion/tests/functional/test_forum.py b/ForgeDiscussion/forgediscussion/tests/functional/test_forum.py
index 3d9752b..0b7a7c0 100644
--- a/ForgeDiscussion/forgediscussion/tests/functional/test_forum.py
+++ b/ForgeDiscussion/forgediscussion/tests/functional/test_forum.py
@@ -700,7 +700,7 @@ class TestForum(TestController):
             if field.has_attr('name'):
                 params[field['name']] = field.get('value') or ''
         self.app.post(str(subscribe_url), params=params)
-        self.app.post('/discussion/general/subscribe_to_forum', {'subscribe': True})
+        self.app.post('/discussion/general/subscribe_to_forum', {'subscribe': 'True'})
         f = thread.html.find('div', {'class': 'comment-row reply_post_form'}).find('form')
         rep_url = f.get('action')
         params = dict()
diff --git a/ForgeGit/forgegit/tests/functional/test_controllers.py b/ForgeGit/forgegit/tests/functional/test_controllers.py
index 88c0338..217c48c 100644
--- a/ForgeGit/forgegit/tests/functional/test_controllers.py
+++ b/ForgeGit/forgegit/tests/functional/test_controllers.py
@@ -367,7 +367,7 @@ class TestRootController(_TestCase):
 
         # subscribe
         r = self.app.post(str(ci + 'tree/subscribe'),
-                          {'subscribe': True},
+                          {'subscribe': 'True'},
                           extra_environ={'username': str(user.username)})
         assert_equal(r.json, {'status': 'ok', 'subscribed': True})
         # user is subscribed
@@ -379,7 +379,7 @@ class TestRootController(_TestCase):
 
         # unsubscribe
         r = self.app.post(str(ci + 'tree/subscribe'),
-                          {'unsubscribe': True},
+                          {'unsubscribe': 'True'},
                           extra_environ={'username': str(user.username)})
         assert_equal(r.json, {'status': 'ok', 'subscribed': False})
         # user is not subscribed
diff --git a/ForgeTracker/forgetracker/tests/functional/test_root.py b/ForgeTracker/forgetracker/tests/functional/test_root.py
index 396d01c..8c50d0d 100644
--- a/ForgeTracker/forgetracker/tests/functional/test_root.py
+++ b/ForgeTracker/forgetracker/tests/functional/test_root.py
@@ -420,7 +420,7 @@ class TestFunctionalController(TrackerTestController):
         r = self.app.get('/p/test/bugs/edit/?q=ticket')
         self.app.post('/p/test/bugs/update_tickets', {
             '__search': '',
-            '__ticket_ids': [first_ticket._id],
+            '__ticket_ids': [str(first_ticket._id)],
             '_milestone': '2.0',
         })
         M.MonQTask.run_ready()
@@ -431,8 +431,8 @@ class TestFunctionalController(TrackerTestController):
         self.app.post('/p/test/bugs/update_tickets', {
             '__search': '',
             '__ticket_ids': (
-                first_ticket._id,
-                second_ticket._id),
+                str(first_ticket._id),
+                str(second_ticket._id)),
             '_milestone': '1.0',
         })
         M.MonQTask.run_ready()
@@ -444,8 +444,8 @@ class TestFunctionalController(TrackerTestController):
         self.app.post('/p/test/bugs/update_tickets', {
             '__search': '',
             '__ticket_ids': (
-                first_ticket._id,
-                second_ticket._id),
+                str(first_ticket._id),
+                str(second_ticket._id)),
             'status': 'accepted',
         })
         M.MonQTask.run_ready()
@@ -465,9 +465,9 @@ class TestFunctionalController(TrackerTestController):
         self.app.post('/p/test/bugs/update_tickets', {
             '__search': '',
             '__ticket_ids': (
-                ticket1._id,
-                ticket2._id,
-                ticket3._id),
+                str(ticket1._id),
+                str(ticket2._id),
+                str(ticket3._id)),
             'labels': 'tag2, tag3',
         })
         M.MonQTask.run_ready()
@@ -503,8 +503,8 @@ class TestFunctionalController(TrackerTestController):
         self.app.post('/p/test/bugs/update_tickets', {
             '__search': '',
             '__ticket_ids': (
-                ticket1._id,
-                ticket2._id,),
+                str(ticket1._id),
+                str(ticket2._id),),
             'status': 'accepted',
             '_major': 'False'
         })
@@ -524,8 +524,8 @@ class TestFunctionalController(TrackerTestController):
         self.app.post('/p/test/bugs/update_tickets', {
             '__search': '',
             '__ticket_ids': (
-                ticket1._id,
-                ticket2._id,),
+                str(ticket1._id),
+                str(ticket2._id),),
             'status': 'accepted',
             '_major': 'True'
         })
@@ -542,7 +542,7 @@ class TestFunctionalController(TrackerTestController):
         self.app.post('/p/test/bugs/update_tickets', {
             '__search': '',
             '__ticket_ids': (
-                ticket2._id,),
+                str(ticket2._id),),
             '_major': 'False'
         })
         M.MonQTask.run_ready()
@@ -552,8 +552,8 @@ class TestFunctionalController(TrackerTestController):
         self.app.post('/p/test/bugs/update_tickets', {
             '__search': '',
             '__ticket_ids': (
-                ticket1._id,
-                ticket2._id,),
+                str(ticket1._id),
+                str(ticket2._id),),
             'status': 'accepted',
             '_major': ''
         })
@@ -596,9 +596,9 @@ class TestFunctionalController(TrackerTestController):
         self.app.post('/p/test/bugs/update_tickets', {
             '__search': '',
             '__ticket_ids': (
-                ticket1._id,
-                ticket2._id,),
-            'private': False
+                str(ticket1._id),
+                str(ticket2._id),),
+            'private': 'False',
         })
         M.MonQTask.run_ready()
         r = self.app.get('/p/test/bugs/1/')
@@ -613,9 +613,9 @@ class TestFunctionalController(TrackerTestController):
         self.app.post('/p/test/bugs/update_tickets', {
             '__search': '',
             '__ticket_ids': (
-                ticket1._id,
-                ticket2._id,),
-            'private': True
+                str(ticket1._id),
+                str(ticket2._id),),
+            'private': 'True'
         })
         M.MonQTask.run_ready()
         r = self.app.get('/p/test/bugs/1/')
@@ -631,8 +631,8 @@ class TestFunctionalController(TrackerTestController):
         self.app.post('/p/test/bugs/update_tickets', {
             '__search': '',
             '__ticket_ids': (
-                ticket1._id,
-                ticket2._id,),
+                str(ticket1._id),
+                str(ticket2._id),),
             'private': ''
         })
         M.MonQTask.run_ready()
@@ -925,14 +925,14 @@ class TestFunctionalController(TrackerTestController):
         params[f.find('textarea')['name']] = 'test comment'
         self.app.post(f['action'].encode('utf-8'), params=params,
                       headers={'Referer': '/bugs/1/'.encode("utf-8")})
-        r = self.app.get('/bugs/1/', dict(page=1))
+        r = self.app.get('/bugs/1/', dict(page='1'))
         post_link = str(r.html.find('div', {'class': 'edit_post_form reply'}).find('form')['action'])
         self.app.post(post_link + 'attach',
                       upload_files=[('file_info', 'test.txt', b'HiThere!')])
-        r = self.app.get('/bugs/1/', dict(page=1))
+        r = self.app.get('/bugs/1/', dict(page='1'))
         assert '<i class="fa fa-trash-o" aria-hidden="true"></i>' in r
         r.forms[5].submit()
-        r = self.app.get('/bugs/1/', dict(page=1))
+        r = self.app.get('/bugs/1/', dict(page='1'))
         assert '<i class="fa fa-trash-o" aria-hidden="true"></i>' not in r
 
     def test_new_text_attachment_content(self):
@@ -1216,7 +1216,7 @@ class TestFunctionalController(TrackerTestController):
             'summary': 'zzz',
             'description': 'bbb',
             'status': 'ccc',
-            '_milestone': 'aaaé',
+            '_milestone': 'aaaé'.encode('utf-8'),
             'assigned_to': '',
             'labels': '',
             'comment': ''
@@ -1390,7 +1390,7 @@ class TestFunctionalController(TrackerTestController):
         '''Sidebar must be visible even with a strange characters in saved search terms'''
         r = self.app.post('/admin/bugs/bins/save_bin', {
             'summary': 'Strange chars in terms here',
-            'terms': 'labels:tést',
+            'terms': 'labels:tést'.encode('utf-8'),
             'old_summary': '',
             'sort': ''}).follow()
         r = self.app.get('/bugs/')
@@ -1514,7 +1514,7 @@ class TestFunctionalController(TrackerTestController):
         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")})
-        r = self.app.get('/bugs/1/', dict(page=1))
+        r = self.app.get('/bugs/1/', dict(page='1'))
         assert_true(post_content in r)
         assert_true(len(r.html.findAll(attrs={'class': 'discussion-post'})) == 1)
 
@@ -1530,7 +1530,7 @@ class TestFunctionalController(TrackerTestController):
         params['ticket_form.summary'] = new_summary
         r = self.app.post(f['action'].encode('utf-8'), params=params,
                           headers={'Referer': '/bugs/1/'.encode("utf-8")})
-        r = self.app.get('/bugs/1/', dict(page=1))
+        r = self.app.get('/bugs/1/', dict(page='1'))
         assert_true(summary + ' --&gt; ' + new_summary in r)
         assert_true(len(r.html.findAll(attrs={'class': 'discussion-post meta_post'})) == 1)
 
@@ -1549,9 +1549,9 @@ class TestFunctionalController(TrackerTestController):
         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")})
-        r = self.app.get('/bugs/1/', dict(page=-1))
+        r = self.app.get('/bugs/1/', dict(page='-1'))
         assert_true(summary in r)
-        r = self.app.get('/bugs/1/', dict(page=1))
+        r = self.app.get('/bugs/1/', dict(page='1'))
         assert_true(post_content in r)
         # no pager if just one page
         assert_false('Page 1 of 1' in r)
@@ -1559,7 +1559,7 @@ class TestFunctionalController(TrackerTestController):
         for i in range(2):
             r = self.app.post(f['action'].encode('utf-8'), params=params,
                               headers={'Referer': '/bugs/1/'.encode("utf-8")})
-        r = self.app.get('/bugs/1/', dict(page=1, limit=2))
+        r = self.app.get('/bugs/1/', dict(page='1', limit='2'))
         assert_true('Page 2 of 2' in r)
 
     def test_discussion_feed(self):
@@ -1737,9 +1737,9 @@ class TestFunctionalController(TrackerTestController):
         self.app.post('/p/test/bugs/update_tickets', {
             '__search': '',
             '__ticket_ids': (
-                first_ticket._id,
-                second_ticket._id,
-                third_ticket._id),
+                str(first_ticket._id),
+                str(second_ticket._id),
+                str(third_ticket._id)),
             'status': 'accepted',
             '_milestone': '2.0',
             'assigned_to': 'test-admin'})
@@ -1815,7 +1815,7 @@ class TestFunctionalController(TrackerTestController):
         M.MonQTask.query.remove()
         self.app.post('/p/test/bugs/update_tickets', {
             '__search': '',
-            '__ticket_ids': [ticket._id],
+            '__ticket_ids': [str(ticket._id)],
             'status': 'accepted'})
         M.MonQTask.run_ready()
         emails = M.MonQTask.query.find(dict(task_name='allura.tasks.mail_tasks.sendmail')).all()
@@ -1855,7 +1855,7 @@ class TestFunctionalController(TrackerTestController):
         M.MonQTask.query.remove()
         self.app.post('/p/test/bugs/update_tickets', {
             '__search': '',
-            '__ticket_ids': [t._id for t in tickets],
+            '__ticket_ids': [str(t._id) for t in tickets],
             'status': 'accepted'})
         M.MonQTask.run_ready()
         emails = M.MonQTask.query.find(dict(task_name='allura.tasks.mail_tasks.sendmail')).all()
@@ -1896,7 +1896,7 @@ class TestFunctionalController(TrackerTestController):
         M.MonQTask.query.remove()
         self.app.post('/p/test/bugs/update_tickets', {
             '__search': '',
-            '__ticket_ids': [t._id for t in tickets],
+            '__ticket_ids': [str(t._id) for t in tickets],
             'status': 'accepted'})
         M.MonQTask.run_ready()
         emails = M.MonQTask.query.find(dict(task_name='allura.tasks.mail_tasks.sendmail')).all()
@@ -2050,7 +2050,7 @@ class TestFunctionalController(TrackerTestController):
         env = {'username': str('test-user')}
         post_data = {
             'ticket_form.summary': 'Private ticket title',
-            'ticket_form.private': True
+            'ticket_form.private': 'True'
         }
         self.app.post('/bugs/save_ticket', post_data, extra_environ=env)
         # ... and can see it
@@ -2193,7 +2193,7 @@ class TestFunctionalController(TrackerTestController):
         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")})
-        r = self.app.get('/p/test2/bugs2/1/', dict(page=1))
+        r = self.app.get('/p/test2/bugs2/1/', dict(page='1'))
         assert_true(post_content in r)
         comments_cnt = len(r.html.findAll(attrs={'class': 'discussion-post'}))
         assert_equal(comments_cnt, 2)  # moved auto comment + new comment
@@ -2320,7 +2320,7 @@ class TestFunctionalController(TrackerTestController):
         user = M.User.query.get(username='test-user')
 
         # subscribe test-user to ticket #2
-        self.app.post('/p/test/bugs/2/subscribe', {'subscribe': True},
+        self.app.post('/p/test/bugs/2/subscribe', {'subscribe': 'True'},
                       extra_environ={'username': str('test-user')})
         assert M.Mailbox.query.get(user_id=user._id,
                                    project_id=p._id,
@@ -2443,7 +2443,7 @@ class TestFunctionalController(TrackerTestController):
         params[f.find('textarea')['name']] = 'test comment'
         self.app.post(f['action'].encode('utf-8'), params=params,
                       headers={'Referer': '/bugs/1/'.encode("utf-8")})
-        r = self.app.get('/bugs/1/', dict(page=1))
+        r = self.app.get('/bugs/1/', dict(page='1'))
         post_link = str(r.html.find('div', {'class': 'edit_post_form reply'}).find('form')['action'])
         self.app.post(post_link + 'attach',
                       upload_files=[('file_info', 'test.txt', b'test attach')])
@@ -2572,7 +2572,7 @@ class TestFunctionalController(TrackerTestController):
             'status': 'closed',
             'assigned_to': '',
             'labels': '',
-            'private': True,
+            'private': 'True',
             'comment': 'closing ticket of a user that is gone'
         })
         self.app.get('/p/test/bugs/1/', status=200)
@@ -2590,9 +2590,9 @@ class TestFunctionalController(TrackerTestController):
         self.app.post('/p/test/bugs/update_tickets', {
             '__search': '',
             '__ticket_ids': (
-                first_ticket._id,
-                second_ticket._id),
-            'deleted': True})
+                str(first_ticket._id),
+                str(second_ticket._id)),
+            'deleted': 'True'})
         M.MonQTask.run_ready()
 
         r = self.app.get('/bugs/')
@@ -3026,7 +3026,7 @@ class TestBulkMove(TrackerTestController):
         original_tracker = original_p.app_instance('bugs')
         self.app.post('/p/test/bugs/move_tickets', {
             'tracker': str(tracker.config._id),
-            '__ticket_ids': [t._id for t in tickets],
+            '__ticket_ids': [str(t._id) for t in tickets],
             '__search': '',
         })
         M.MonQTask.run_ready()
@@ -3064,7 +3064,7 @@ class TestBulkMove(TrackerTestController):
         M.MonQTask.query.remove()
         self.app.post('/p/test/bugs/move_tickets', {
             'tracker': str(tracker.config._id),
-            '__ticket_ids': [t._id for t in tickets],
+            '__ticket_ids': [str(t._id) for t in tickets],
             '__search': '',
         })
         M.MonQTask.run_ready()
@@ -3127,7 +3127,7 @@ class TestBulkMove(TrackerTestController):
         M.MonQTask.query.remove()
         self.app.post('/p/test/bugs/move_tickets', {
             'tracker': str(tracker.config._id),
-            '__ticket_ids': [t._id for t in tickets],
+            '__ticket_ids': [str(t._id) for t in tickets],
             '__search': '',
         })
         M.MonQTask.run_ready()
@@ -3183,7 +3183,7 @@ class TestBulkMove(TrackerTestController):
         tracker = p.app_instance('bugs2')
         self.app.post('/p/test/bugs/move_tickets', {
             'tracker': str(tracker.config._id),
-            '__ticket_ids': [t._id for t in tickets],
+            '__ticket_ids': [str(t._id) for t in tickets],
             '__search': '',
         })
         M.MonQTask.run_ready()
@@ -3231,7 +3231,7 @@ class TestBulkMove(TrackerTestController):
         tracker = p.app_instance('bugs2')
         self.app.post('/p/test/bugs/move_tickets', {
             'tracker': str(tracker.config._id),
-            '__ticket_ids': [t._id for t in tickets],
+            '__ticket_ids': [str(t._id) for t in tickets],
             '__search': '',
         })
         M.MonQTask.run_ready()
diff --git a/ForgeWiki/forgewiki/tests/functional/test_root.py b/ForgeWiki/forgewiki/tests/functional/test_root.py
index 871aabc..8bba3a2 100644
--- a/ForgeWiki/forgewiki/tests/functional/test_root.py
+++ b/ForgeWiki/forgewiki/tests/functional/test_root.py
@@ -844,7 +844,7 @@ class TestRootController(TestController):
         sidebar_menu = r.html.find('div', attrs={'id': 'sidebar'})
         assert 'Subscribe to wiki' in str(sidebar_menu)
         # subscribe
-        self.app.post('/p/test/wiki/subscribe', {'subscribe': True},
+        self.app.post('/p/test/wiki/subscribe', {'subscribe': 'True'},
                       extra_environ={'username': str(user.username)}).follow()
         # user is subscribed
         assert M.Mailbox.subscribed(user_id=user._id)
@@ -852,7 +852,7 @@ class TestRootController(TestController):
         sidebar_menu = r.html.find('div', attrs={'id': 'sidebar'})
         assert 'Unsubscribe' in str(sidebar_menu)
         # unsubscribe
-        self.app.post('/p/test/wiki/subscribe', {'unsubscribe': True},
+        self.app.post('/p/test/wiki/subscribe', {'unsubscribe': 'True'},
                       extra_environ={'username': str(user.username)}).follow()
         # user is not subscribed
         assert not M.Mailbox.subscribed(user_id=user._id)


[allura] 06/12: [#7878] AntiSpam fixes for unicode

Posted by br...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

brondsem pushed a commit to branch db/7878
in repository https://gitbox.apache.org/repos/asf/allura.git

commit c0762494f14f078dc9cd58197018f087a2df5926
Author: Dave Brondsema <db...@slashdotmedia.com>
AuthorDate: Thu Jan 16 22:21:02 2020 +0000

    [#7878] AntiSpam fixes for unicode
---
 Allura/allura/lib/utils.py | 26 +++++++++++++++++---------
 1 file changed, 17 insertions(+), 9 deletions(-)

diff --git a/Allura/allura/lib/utils.py b/Allura/allura/lib/utils.py
index a88e553..585f344 100644
--- a/Allura/allura/lib/utils.py
+++ b/Allura/allura/lib/utils.py
@@ -15,6 +15,9 @@
 #       specific language governing permissions and limitations
 #       under the License.
 from __future__ import unicode_literals
+
+import base64
+import operator
 from contextlib import contextmanager
 import time
 import string
@@ -229,27 +232,25 @@ class AntiSpam(object):
 
     @staticmethod
     def _wrap(s):
-        '''Encode a string to make it HTML id-safe (starts with alpha, includes
+        '''Encode bytes to make it HTML id-safe (starts with alpha, includes
         only digits, hyphens, underscores, colons, and periods).  Luckily, base64
         encoding doesn't use hyphens, underscores, colons, nor periods, so we'll
         use these characters to replace its plus, slash, equals, and newline.
         '''
-        tx_tbl = string.maketrans('+/', '-_')
-        s = binascii.b2a_base64(s)
+        s = base64.b64encode(s)
         s = s.rstrip('=\n')
-        s = s.translate(tx_tbl)
+        s = s.replace('+', '-').replace('/', '_')
         s = 'X' + s
         return s
 
     @staticmethod
     def _unwrap(s):
-        tx_tbl = string.maketrans('-_', '+/')
         s = s[1:]
-        s = str(s).translate(tx_tbl)
+        s = s.replace('-', '+').replace('_', '/')
         i = len(s) % 4
         if i > 0:
             s += '=' * (4 - i)
-        s = binascii.a2b_base64(s + '\n')
+        s = base64.b64decode(s + '\n')
         return s
 
     def enc(self, plain, css_safe=False):
@@ -259,10 +260,16 @@ class AntiSpam(object):
         '''
         # Plain starts with its length, includes the ordinals for its
         #   characters, and is padded with random data
+
+        # limit to plain ascii, which should be sufficient for field names
+        # I don't think the logic below would work with non-ascii multi-byte text anyway
+        plain.encode('ascii')
+
         plain = ([len(plain)]
                  + map(ord, plain)
                  + self.random_padding[:len(self.spinner_ord) - len(plain) - 1])
-        enc = ''.join(chr(p ^ s) for p, s in zip(plain, self.spinner_ord))
+        enc = ''.join(six.unichr(p ^ s) for p, s in zip(plain, self.spinner_ord))
+        enc = six.ensure_binary(enc)
         enc = self._wrap(enc)
         if css_safe:
             enc = ''.join(ch for ch in enc if ch.isalpha())
@@ -270,10 +277,11 @@ class AntiSpam(object):
 
     def dec(self, enc):
         enc = self._unwrap(enc)
+        enc = six.ensure_text(enc)
         enc = list(map(ord, enc))
         plain = [e ^ s for e, s in zip(enc, self.spinner_ord)]
         plain = plain[1:1 + plain[0]]
-        plain = ''.join(map(chr, plain))
+        plain = ''.join(map(six.unichr, plain))
         return plain
 
     def extra_fields(self):


[allura] 08/12: [#7878] quote in py2 should always work on bytes, and it does internal caching so very weird distant code can get errors if safe param is unicode

Posted by br...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

brondsem pushed a commit to branch db/7878
in repository https://gitbox.apache.org/repos/asf/allura.git

commit 6fb573fd7f95c326757dcc6184502743865a6e5c
Author: Dave Brondsema <db...@slashdotmedia.com>
AuthorDate: Thu Jan 16 22:50:21 2020 +0000

    [#7878] quote in py2 should always work on bytes, and it does internal caching so very weird distant code can get errors if safe param is unicode
---
 Allura/allura/lib/helpers.py | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/Allura/allura/lib/helpers.py b/Allura/allura/lib/helpers.py
index 2ded70d..6c35dcb 100644
--- a/Allura/allura/lib/helpers.py
+++ b/Allura/allura/lib/helpers.py
@@ -164,14 +164,14 @@ def monkeypatch(*objs):
     return patchem
 
 
-def urlquote(url, safe="/"):
+def urlquote(url, safe=b"/"):
     try:
         return urllib.quote(str(url), safe=safe)
     except UnicodeEncodeError:
         return urllib.quote(url.encode('utf-8'), safe=safe)
 
 
-def urlquoteplus(url, safe=""):
+def urlquoteplus(url, safe=b""):
     try:
         return urllib.quote_plus(str(url), safe=safe)
     except UnicodeEncodeError:


[allura] 01/12: [#7878] run: python-modernize -n -w --no-diffs -f unicode_type .

Posted by br...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

brondsem pushed a commit to branch db/7878
in repository https://gitbox.apache.org/repos/asf/allura.git

commit 20f59c56c221a77e03d65c879c34f373d2fbbeae
Author: Dave Brondsema <db...@slashdotmedia.com>
AuthorDate: Thu Jan 16 18:02:27 2020 +0000

    [#7878] run: python-modernize -n -w --no-diffs -f unicode_type .
---
 Allura/allura/lib/app_globals.py                   |  3 +-
 Allura/allura/lib/helpers.py                       |  9 +++---
 Allura/allura/lib/macro.py                         |  3 +-
 Allura/allura/lib/mail_util.py                     |  6 ++--
 Allura/allura/lib/markdown_extensions.py           |  3 +-
 Allura/allura/lib/plugin.py                        |  3 +-
 Allura/allura/lib/search.py                        |  3 +-
 Allura/allura/lib/solr.py                          |  3 +-
 Allura/allura/lib/spam/stopforumspamfilter.py      |  5 ++--
 Allura/allura/lib/utils.py                         |  5 ++--
 Allura/allura/lib/widgets/project_list.py          | 11 ++++----
 Allura/allura/model/webhook.py                     |  7 +++--
 Allura/allura/scripts/scripttask.py                |  3 +-
 Allura/allura/scripts/trac_export.py               |  3 +-
 Allura/allura/tasks/mail_tasks.py                  |  5 ++--
 Allura/allura/tests/test_helpers.py                |  7 +++--
 Allura/allura/tests/test_mail_util.py              | 15 +++++-----
 Allura/allura/tests/test_webhooks.py               | 33 +++++++++++-----------
 Allura/allura/webhooks.py                          |  7 +++--
 ForgeBlog/forgeblog/main.py                        |  3 +-
 .../forgegit/tests/functional/test_controllers.py  |  3 +-
 ForgeImporters/forgeimporters/github/tracker.py    |  3 +-
 ForgeImporters/forgeimporters/github/wiki.py       |  2 +-
 ForgeTracker/forgetracker/model/ticket.py          |  2 +-
 ForgeWiki/forgewiki/converters.py                  |  3 +-
 scripts/teamforge-import.py                        |  3 +-
 26 files changed, 88 insertions(+), 65 deletions(-)

diff --git a/Allura/allura/lib/app_globals.py b/Allura/allura/lib/app_globals.py
index 6ef4626..8770a5c 100644
--- a/Allura/allura/lib/app_globals.py
+++ b/Allura/allura/lib/app_globals.py
@@ -66,6 +66,7 @@ from allura.lib.widgets import analytics
 from allura.lib.security import Credentials
 from allura.lib.solr import MockSOLR, make_solr_from_config
 from allura.model.session import artifact_orm_session
+import six
 
 __all__ = ['Globals']
 
@@ -583,7 +584,7 @@ class Globals(object):
 
     @LazyProperty
     def noreply(self):
-        return unicode(config.get('noreply', 'noreply@%s' % config['domain']))
+        return six.text_type(config.get('noreply', 'noreply@%s' % config['domain']))
 
     @property
     def build_key(self):
diff --git a/Allura/allura/lib/helpers.py b/Allura/allura/lib/helpers.py
index 69342c5..1ea60ef 100644
--- a/Allura/allura/lib/helpers.py
+++ b/Allura/allura/lib/helpers.py
@@ -41,6 +41,7 @@ import cgi
 
 import emoji
 import tg
+import six
 try:
     import cchardet as chardet
 except ImportError:
@@ -182,13 +183,13 @@ def _attempt_encodings(s, encodings):
     for enc in encodings:
         try:
             if enc is None:
-                return unicode(s)  # try default encoding
+                return six.text_type(s)  # try default encoding
             else:
-                return unicode(s, enc)
+                return six.text_type(s, enc)
         except (UnicodeDecodeError, LookupError):
             pass
     # Return the repr of the str -- should always be safe
-    return unicode(repr(str(s)))[1:-1]
+    return six.text_type(repr(str(s)))[1:-1]
 
 
 def really_unicode(s):
@@ -1209,7 +1210,7 @@ def slugify(name, allow_periods=False):
     """
     dash_collapse_pattern = r'[^.\w]+' if allow_periods else r'[^\w]+'
     slug = re.sub(r'(^-)|(-$)', '',  # leading - or trailing - gets removed
-                  unicode(
+                  six.text_type(
                       re.sub(dash_collapse_pattern, '-',  # replace non ". alphanum_" sequences into single -
                              re.sub(r"'", '',  # remove any apostrophes
                                     unicodedata.normalize('NFKD', name)
diff --git a/Allura/allura/lib/macro.py b/Allura/allura/lib/macro.py
index 4dc1f54..ae467cd 100644
--- a/Allura/allura/lib/macro.py
+++ b/Allura/allura/lib/macro.py
@@ -36,6 +36,7 @@ from bs4 import BeautifulSoup
 from allura.lib.utils import socket_default_timeout
 from . import helpers as h
 from . import security
+import six
 
 log = logging.getLogger(__name__)
 
@@ -62,7 +63,7 @@ class parse(object):
             if s.startswith('quote '):
                 return '[[' + s[len('quote '):] + ']]'
             try:
-                parts = [unicode(x, 'utf-8')
+                parts = [six.text_type(x, 'utf-8')
                          for x in shlex.split(s.encode('utf-8'))]
                 if not parts:
                     return '[[' + s + ']]'
diff --git a/Allura/allura/lib/mail_util.py b/Allura/allura/lib/mail_util.py
index f1de6de..c6c6aac 100644
--- a/Allura/allura/lib/mail_util.py
+++ b/Allura/allura/lib/mail_util.py
@@ -52,11 +52,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) != six.text_type:
         raise TypeError('This must be unicode: %r' % text)
     head = header.Header(text)
     for m in more_text:
-        if type(m) != unicode:
+        if type(m) != six.text_type:
             raise TypeError('This must be unicode: %r' % text)
         head.append(m)
     return head
@@ -285,7 +285,7 @@ class SMTPClient(object):
         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))
+                        map(six.text_type, addrs))
             return
         try:
             self._client.sendmail(
diff --git a/Allura/allura/lib/markdown_extensions.py b/Allura/allura/lib/markdown_extensions.py
index a8c38c4..f27e7a3 100644
--- a/Allura/allura/lib/markdown_extensions.py
+++ b/Allura/allura/lib/markdown_extensions.py
@@ -33,6 +33,7 @@ from . import macro
 from . import helpers as h
 from allura import model as M
 from allura.lib.utils import ForgeHTMLSanitizerFilter, is_nofollow_url
+import six
 
 log = logging.getLogger(__name__)
 
@@ -450,7 +451,7 @@ class RelativeLinkRewriter(markdown.postprocessors.Postprocessor):
             rewrite(link, 'src')
 
         # html5lib parser adds html/head/body tags, so output <body> without its own tags
-        return unicode(soup.body)[len('<body>'):-len('</body>')]
+        return six.text_type(soup.body)[len('<body>'):-len('</body>')]
 
     def _rewrite(self, tag, attr):
         val = tag.get(attr)
diff --git a/Allura/allura/lib/plugin.py b/Allura/allura/lib/plugin.py
index d3c3318..33c61c7 100644
--- a/Allura/allura/lib/plugin.py
+++ b/Allura/allura/lib/plugin.py
@@ -33,6 +33,7 @@ from hashlib import sha256
 from base64 import b64encode
 from datetime import datetime, timedelta
 import calendar
+import six
 
 try:
     import ldap
@@ -1466,7 +1467,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, six.text_type):
             app = str(app)
         if isinstance(app, str):
             if app in self.icons and size in self.icons[app]:
diff --git a/Allura/allura/lib/search.py b/Allura/allura/lib/search.py
index c94c526..fdad56e 100644
--- a/Allura/allura/lib/search.py
+++ b/Allura/allura/lib/search.py
@@ -31,6 +31,7 @@ from pysolr import SolrError
 from allura.lib import helpers as h
 from allura.lib.solr import escape_solr_arg
 from allura.lib.utils import urlencode
+import six
 
 log = getLogger(__name__)
 
@@ -141,7 +142,7 @@ def search(q, short_timeout=False, ignore_errors=True, **kw):
     except (SolrError, socket.error) as e:
         log.exception('Error in solr search')
         if not ignore_errors:
-            match = re.search(r'<pre>(.*)</pre>', unicode(e))
+            match = re.search(r'<pre>(.*)</pre>', six.text_type(e))
             raise SearchError(u'Error running search query: %s' %
                               (match.group(1) if match else e))
 
diff --git a/Allura/allura/lib/solr.py b/Allura/allura/lib/solr.py
index e4adba8..3df3a6f 100644
--- a/Allura/allura/lib/solr.py
+++ b/Allura/allura/lib/solr.py
@@ -21,6 +21,7 @@ import logging
 from tg import config
 from paste.deploy.converters import asbool
 import pysolr
+import six
 
 
 log = logging.getLogger(__name__)
@@ -150,7 +151,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, six.text_type):
             q = q.encode('latin-1')
         # Parse query
         preds = []
diff --git a/Allura/allura/lib/spam/stopforumspamfilter.py b/Allura/allura/lib/spam/stopforumspamfilter.py
index 616d99f..9499fec 100644
--- a/Allura/allura/lib/spam/stopforumspamfilter.py
+++ b/Allura/allura/lib/spam/stopforumspamfilter.py
@@ -24,6 +24,7 @@ from tg import request
 
 from allura.lib import utils
 from allura.lib.spam import SpamFilter
+import six
 
 log = logging.getLogger(__name__)
 
@@ -49,7 +50,7 @@ class StopForumSpamSpamFilter(SpamFilter):
                 if int(record[1]) > int(config.get('spam.stopforumspam.threshold', 20)):
                     ip = record[0]
                     # int is the smallest memory representation of an IP addr
-                    ip_int = int(ipaddress.ip_address(unicode(ip)))
+                    ip_int = int(ipaddress.ip_address(six.text_type(ip)))
                     self.packed_ips.add(ip_int)
         # to get actual memory usage, use: from pympler.asizeof import asizeof
         log.info('Read stopforumspam file; %s recs, probably %s bytes stored in memory', len(self.packed_ips),
@@ -58,7 +59,7 @@ class StopForumSpamSpamFilter(SpamFilter):
     def check(self, text, artifact=None, user=None, content_type='comment', **kw):
         ip = utils.ip_address(request)
         if ip:
-            ip_int = int(ipaddress.ip_address(unicode(ip)))
+            ip_int = int(ipaddress.ip_address(six.text_type(ip)))
             res = ip_int in self.packed_ips
             self.record_result(res, artifact, user)
         else:
diff --git a/Allura/allura/lib/utils.py b/Allura/allura/lib/utils.py
index 8529a23..2554af3 100644
--- a/Allura/allura/lib/utils.py
+++ b/Allura/allura/lib/utils.py
@@ -54,6 +54,7 @@ from ew import jinja2_ew as ew
 from ming.utils import LazyProperty
 from ming.odm.odmsession import ODMCursor
 from ming.odm import session
+import six
 
 MARKDOWN_EXTENSIONS = ['.markdown', '.mdown', '.mkdn', '.mkd', '.md']
 
@@ -785,8 +786,8 @@ def smart_str(s, encoding='utf-8', strings_only=False, errors='strict'):
                 # further exception.
                 return ' '.join([smart_str(arg, encoding, strings_only,
                                            errors) for arg in s])
-            return unicode(s).encode(encoding, errors)
-    elif isinstance(s, unicode):
+            return six.text_type(s).encode(encoding, errors)
+    elif isinstance(s, six.text_type):
         r = s.encode(encoding, errors)
         return r
     elif s and encoding != 'utf-8':
diff --git a/Allura/allura/lib/widgets/project_list.py b/Allura/allura/lib/widgets/project_list.py
index 1d511b5..ad9d063 100644
--- a/Allura/allura/lib/widgets/project_list.py
+++ b/Allura/allura/lib/widgets/project_list.py
@@ -23,6 +23,7 @@ from paste.deploy.converters import asbool
 
 from allura import model as M
 from allura.lib.security import Credentials
+import six
 
 
 class ProjectSummary(ew_core.Widget):
@@ -49,21 +50,21 @@ class ProjectSummary(ew_core.Widget):
         if response['accolades'] is None:
             response['accolades'] = value.accolades
 
-        if type(response['columns']) == unicode:
+        if type(response['columns']) == six.text_type:
             response['columns'] = int(response['columns'])
 
         true_list = ['true', 't', '1', 'yes', 'y']
-        if type(response['show_proj_icon']) == unicode:
+        if type(response['show_proj_icon']) == six.text_type:
             if response['show_proj_icon'].lower() in true_list:
                 response['show_proj_icon'] = True
             else:
                 response['show_proj_icon'] = False
-        if type(response['show_download_button']) == unicode:
+        if type(response['show_download_button']) == six.text_type:
             if response['show_download_button'].lower() in true_list:
                 response['show_download_button'] = True
             else:
                 response['show_download_button'] = False
-        if type(response['show_awards_banner']) == unicode:
+        if type(response['show_awards_banner']) == six.text_type:
             if response['show_awards_banner'].lower() in true_list:
                 response['show_awards_banner'] = True
             else:
@@ -101,7 +102,7 @@ class ProjectList(ew_core.Widget):
         if response['accolades_index'] is None and response['show_awards_banner']:
             response['accolades_index'] = M.Project.accolades_index(projects)
 
-        if type(response['columns']) == unicode:
+        if type(response['columns']) == six.text_type:
             response['columns'] = int(response['columns'])
 
         return response
diff --git a/Allura/allura/model/webhook.py b/Allura/allura/model/webhook.py
index bb36e5f..96a9ded 100644
--- a/Allura/allura/model/webhook.py
+++ b/Allura/allura/model/webhook.py
@@ -24,6 +24,7 @@ from tg import config
 
 from allura.model import Artifact
 from allura.lib import helpers as h
+import six
 
 
 class Webhook(Artifact):
@@ -64,9 +65,9 @@ class Webhook(Artifact):
 
     def __json__(self):
         return {
-            '_id': unicode(self._id),
+            '_id': six.text_type(self._id),
             'url': h.absurl(u'/rest' + self.url()),
-            'type': unicode(self.type),
-            'hook_url': unicode(self.hook_url),
+            'type': six.text_type(self.type),
+            'hook_url': six.text_type(self.hook_url),
             'mod_date': self.mod_date,
         }
diff --git a/Allura/allura/scripts/scripttask.py b/Allura/allura/scripts/scripttask.py
index c46d7b6..fb97c2a 100644
--- a/Allura/allura/scripts/scripttask.py
+++ b/Allura/allura/scripts/scripttask.py
@@ -50,6 +50,7 @@ import shlex
 import sys
 
 from allura.lib.decorators import task
+import six
 
 
 log = logging.getLogger(__name__)
@@ -74,7 +75,7 @@ class ScriptTask(object):
     @classmethod
     def _execute_task(cls, arg_string):
         try:
-            if isinstance(arg_string, unicode):
+            if isinstance(arg_string, six.text_type):
                 # shlex doesn't support unicode fully, so encode it http://stackoverflow.com/a/14219159/79697
                 # decode has to happen in the arg parser (e.g. see delete_projects.py)
                 arg_string = arg_string.encode('utf8')
diff --git a/Allura/allura/scripts/trac_export.py b/Allura/allura/scripts/trac_export.py
index 86b90bb..5560fd0 100644
--- a/Allura/allura/scripts/trac_export.py
+++ b/Allura/allura/scripts/trac_export.py
@@ -31,6 +31,7 @@ from itertools import islice
 from bs4 import BeautifulSoup, NavigableString
 import dateutil.parser
 import pytz
+import six
 
 try:
     from forgeimporters.base import ProjectExtractor
@@ -172,7 +173,7 @@ class TracExport(object):
                 r'.* by ', '', comment.find('h3', 'change').text).strip()
             c['date'] = self.trac2z_date(
                 comment.find('a', 'timeline')['title'].replace(' in Timeline', ''))
-            changes = unicode(comment.find('ul', 'changes') or '')
+            changes = six.text_type(comment.find('ul', 'changes') or '')
             body = comment.find('div', 'comment')
             body = body.renderContents('utf8').decode('utf8') if body else ''
             c['comment'] = html2text.html2text(changes + body)
diff --git a/Allura/allura/tasks/mail_tasks.py b/Allura/allura/tasks/mail_tasks.py
index 43a9f1a..bab4d69 100644
--- a/Allura/allura/tasks/mail_tasks.py
+++ b/Allura/allura/tasks/mail_tasks.py
@@ -27,6 +27,7 @@ from allura.lib import helpers as h
 from allura.lib.decorators import task
 from allura.lib import mail_util
 from allura.lib import exceptions as exc
+import six
 
 log = logging.getLogger(__name__)
 
@@ -265,8 +266,8 @@ def send_system_mail_to_user(user_or_emailaddr, subject, text):
             config['site_name'],
             config['forgemail.return_path']
         ),
-        'sender': unicode(config['forgemail.return_path']),
-        'reply_to': unicode(config['forgemail.return_path']),
+        'sender': six.text_type(config['forgemail.return_path']),
+        'reply_to': six.text_type(config['forgemail.return_path']),
         'message_id': h.gen_message_id(),
         'subject': subject,
         'text': text,
diff --git a/Allura/allura/tests/test_helpers.py b/Allura/allura/tests/test_helpers.py
index 8fccf90..dd3e264 100644
--- a/Allura/allura/tests/test_helpers.py
+++ b/Allura/allura/tests/test_helpers.py
@@ -41,6 +41,7 @@ from allura.lib.security import has_access
 from allura.lib.security import Credentials
 from allura.tests import decorators as td
 from alluratest.controller import setup_basic_test
+import six
 
 
 def setUp(self):
@@ -91,12 +92,12 @@ def test_really_unicode():
     assert s.startswith(u'\ufeff')
     s = h.really_unicode(
         open(path.join(here_dir, 'data/unicode_test.txt')).read())
-    assert isinstance(s, unicode)
+    assert isinstance(s, six.text_type)
     # try non-ascii string in legacy 8bit encoding
     h.really_unicode(u'\u0410\u0401'.encode('cp1251'))
     # ensure invalid encodings are handled gracefully
     s = h._attempt_encodings('foo', ['LKDJFLDK'])
-    assert isinstance(s, unicode)
+    assert isinstance(s, six.text_type)
 
 
 def test_find_project():
@@ -181,7 +182,7 @@ def test_context_setters():
 
 def test_encode_keys():
     kw = h.encode_keys({u'foo': 5})
-    assert type(kw.keys()[0]) != unicode
+    assert type(kw.keys()[0]) != six.text_type
 
 
 def test_ago():
diff --git a/Allura/allura/tests/test_mail_util.py b/Allura/allura/tests/test_mail_util.py
index 7b75f16..deb16e0 100644
--- a/Allura/allura/tests/test_mail_util.py
+++ b/Allura/allura/tests/test_mail_util.py
@@ -40,6 +40,7 @@ from allura.lib.mail_util import (
 )
 from allura.lib.exceptions import AddressException
 from allura.tests import decorators as td
+import six
 
 config = ConfigProxy(
     common_suffix='forgemail.domain',
@@ -96,7 +97,7 @@ class TestReactor(unittest.TestCase):
         msg1['Message-ID'] = '<fo...@bar.com>'
         s_msg = msg1.as_string()
         msg2 = parse_message(s_msg)
-        assert isinstance(msg2['payload'], unicode)
+        assert isinstance(msg2['payload'], six.text_type)
         assert_in(u'всех', msg2['payload'])
 
     def test_more_encodings(self):
@@ -115,7 +116,7 @@ c28uMyIsClRoZSBhcHBsaWNhdGlvbidzIGNvbW11bmljYXRpb24gd29yayB3ZWxsICxidXQgdGhl
 IGZ0cCx0ZWxuZXQscGluZyBjYW4ndCB3b3JrICEKCgpXaHk/
 """
         msg = parse_message(s_msg)
-        assert isinstance(msg['payload'], unicode)
+        assert isinstance(msg['payload'], six.text_type)
         assert_in(u'The Snap7 application', msg['payload'])
 
         s_msg = u"""Date: Sat, 25 May 2019 09:32:00 +1000
@@ -134,7 +135,7 @@ Content-Transfer-Encoding: 8bit
 > 
 """
         msg = parse_message(s_msg)
-        assert isinstance(msg['payload'], unicode)
+        assert isinstance(msg['payload'], six.text_type)
         assert_in(u'• foo', msg['payload'])
 
         s_msg = u"""Date: Sat, 25 May 2019 09:32:00 +1000
@@ -147,7 +148,7 @@ Content-Transfer-Encoding: 8BIT
 programmed or èrogrammed ?
 """
         msg = parse_message(s_msg)
-        assert isinstance(msg['payload'], unicode)
+        assert isinstance(msg['payload'], six.text_type)
         assert_in(u'èrogrammed', msg['payload'])
 
     def test_more_encodings_multipart(self):
@@ -177,8 +178,8 @@ Content-Type: text/html; charset="utf-8"
 &gt; • foo.txt (1.0 kB; text/plain)
 """
         msg = parse_message(s_msg)
-        assert isinstance(msg['parts'][1]['payload'], unicode)
-        assert isinstance(msg['parts'][2]['payload'], unicode)
+        assert isinstance(msg['parts'][1]['payload'], six.text_type)
+        assert isinstance(msg['parts'][2]['payload'], six.text_type)
         assert_in(u'• foo', msg['parts'][1]['payload'])
         assert_in(u'• foo', msg['parts'][2]['payload'])
 
@@ -207,7 +208,7 @@ Content-Type: text/html; charset="utf-8"
         for part in msg2['parts']:
             if part['payload'] is None:
                 continue
-            assert isinstance(part['payload'], unicode)
+            assert isinstance(part['payload'], six.text_type)
 
 
 class TestHeader(object):
diff --git a/Allura/allura/tests/test_webhooks.py b/Allura/allura/tests/test_webhooks.py
index 497dad8..60853be 100644
--- a/Allura/allura/tests/test_webhooks.py
+++ b/Allura/allura/tests/test_webhooks.py
@@ -48,6 +48,7 @@ from alluratest.controller import (
     TestController,
     TestRestApiBase,
 )
+import six
 
 
 # important to be distinct from 'test' and 'test2' which ForgeGit and
@@ -123,7 +124,7 @@ class TestValidators(TestWebhookBase):
         wh.app_config_id = app.config._id
         session(wh).flush(wh)
         assert_equal(v.to_python(wh._id), wh)
-        assert_equal(v.to_python(unicode(wh._id)), wh)
+        assert_equal(v.to_python(six.text_type(wh._id)), wh)
 
 
 class TestWebhookController(TestController):
@@ -275,7 +276,7 @@ class TestWebhookController(TestController):
         form = r.forms[0]
         assert_equal(form['url'].value, data1['url'])
         assert_equal(form['secret'].value, data1['secret'])
-        assert_equal(form['webhook'].value, unicode(wh1._id))
+        assert_equal(form['webhook'].value, six.text_type(wh1._id))
         form['url'] = 'http://host.org/hook'
         form['secret'] = 'new secret'
         msg = 'edit webhook repo-push\n{} => {}\n{}'.format(
@@ -318,7 +319,7 @@ class TestWebhookController(TestController):
         # invalid id in hidden field, just in case
         r = self.app.get(self.url + '/repo-push/%s' % wh._id)
         data = {k: v[0].value for (k, v) in r.forms[0].fields.items()}
-        data['webhook'] = unicode(invalid._id)
+        data['webhook'] = six.text_type(invalid._id)
         self.app.post(self.url + '/repo-push/edit', data, status=404)
 
         # empty values
@@ -337,7 +338,7 @@ class TestWebhookController(TestController):
         self.create_webhook(data).follow()
         assert_equal(M.Webhook.query.find().count(), 1)
         wh = M.Webhook.query.get(hook_url=data['url'])
-        data = {'webhook': unicode(wh._id)}
+        data = {'webhook': six.text_type(wh._id)}
         msg = 'delete webhook repo-push {} {}'.format(
             wh.hook_url, self.git.config.url())
         with td.audits(msg):
@@ -357,7 +358,7 @@ class TestWebhookController(TestController):
         data = {'webhook': ''}
         self.app.post(self.url + '/repo-push/delete', data, status=404)
 
-        data = {'webhook': unicode(invalid._id)}
+        data = {'webhook': six.text_type(invalid._id)}
         self.app.post(self.url + '/repo-push/delete', data, status=404)
         assert_equal(M.Webhook.query.find().count(), 1)
 
@@ -658,7 +659,7 @@ class TestModels(TestWebhookBase):
 
     def test_json(self):
         expected = {
-            '_id': unicode(self.wh._id),
+            '_id': six.text_type(self.wh._id),
             'url': u'http://localhost/rest/adobe/adobe-1/admin'
                    u'/src/webhooks/repo-push/{}'.format(self.wh._id),
             'type': u'repo-push',
@@ -711,12 +712,12 @@ class TestWebhookRestController(TestRestApiBase):
     def test_webhooks_list(self):
         r = self.api_get(self.url)
         webhooks = [{
-            '_id': unicode(wh._id),
+            '_id': six.text_type(wh._id),
             'url': 'http://localhost/rest/adobe/adobe-1/admin'
                    '/src/webhooks/repo-push/{}'.format(wh._id),
             'type': 'repo-push',
             'hook_url': 'http://httpbin.org/post/{}'.format(n),
-            'mod_date': unicode(wh.mod_date),
+            'mod_date': six.text_type(wh.mod_date),
         } for n, wh in enumerate(self.webhooks)]
         expected = {
             'webhooks': webhooks,
@@ -731,12 +732,12 @@ class TestWebhookRestController(TestRestApiBase):
         webhook = self.webhooks[0]
         r = self.api_get('{}/repo-push/{}'.format(self.url, webhook._id))
         expected = {
-            '_id': unicode(webhook._id),
+            '_id': six.text_type(webhook._id),
             'url': 'http://localhost/rest/adobe/adobe-1/admin'
                    '/src/webhooks/repo-push/{}'.format(webhook._id),
             'type': 'repo-push',
             'hook_url': 'http://httpbin.org/post/0',
-            'mod_date': unicode(webhook.mod_date),
+            'mod_date': six.text_type(webhook.mod_date),
         }
         dd.assert_equal(r.status_int, 200)
         dd.assert_equal(r.json, expected)
@@ -775,12 +776,12 @@ class TestWebhookRestController(TestRestApiBase):
         webhook = M.Webhook.query.get(hook_url=data['url'])
         assert_equal(webhook.secret, 'super-secret')  # secret generated
         expected = {
-            '_id': unicode(webhook._id),
+            '_id': six.text_type(webhook._id),
             'url': 'http://localhost/rest/adobe/adobe-1/admin'
                    '/src/webhooks/repo-push/{}'.format(webhook._id),
             'type': 'repo-push',
             'hook_url': data['url'],
-            'mod_date': unicode(webhook.mod_date),
+            'mod_date': six.text_type(webhook.mod_date),
         }
         dd.assert_equal(r.json, expected)
         assert_equal(M.Webhook.query.find().count(), len(self.webhooks) + 1)
@@ -835,12 +836,12 @@ class TestWebhookRestController(TestRestApiBase):
         assert_equal(webhook.hook_url, data['url'])
         assert_equal(webhook.secret, 'secret-0')
         expected = {
-            '_id': unicode(webhook._id),
+            '_id': six.text_type(webhook._id),
             'url': 'http://localhost/rest/adobe/adobe-1/admin'
                    '/src/webhooks/repo-push/{}'.format(webhook._id),
             'type': 'repo-push',
             'hook_url': data['url'],
-            'mod_date': unicode(webhook.mod_date),
+            'mod_date': six.text_type(webhook.mod_date),
         }
         dd.assert_equal(r.json, expected)
 
@@ -855,12 +856,12 @@ class TestWebhookRestController(TestRestApiBase):
         assert_equal(webhook.hook_url, 'http://hook.slack.com/abcd')
         assert_equal(webhook.secret, 'new-secret')
         expected = {
-            '_id': unicode(webhook._id),
+            '_id': six.text_type(webhook._id),
             'url': 'http://localhost/rest/adobe/adobe-1/admin'
                    '/src/webhooks/repo-push/{}'.format(webhook._id),
             'type': 'repo-push',
             'hook_url': 'http://hook.slack.com/abcd',
-            'mod_date': unicode(webhook.mod_date),
+            'mod_date': six.text_type(webhook.mod_date),
         }
         dd.assert_equal(r.json, expected)
 
diff --git a/Allura/allura/webhooks.py b/Allura/allura/webhooks.py
index be9b75b..d07268d 100644
--- a/Allura/allura/webhooks.py
+++ b/Allura/allura/webhooks.py
@@ -41,6 +41,7 @@ from allura.lib import helpers as h
 from allura.lib.decorators import require_post, task
 from allura.lib.utils import DateJSONEncoder
 from allura import model as M
+import six
 
 
 log = logging.getLogger(__name__)
@@ -200,7 +201,7 @@ class WebhookController(BaseController, AdminControllerMixin):
             raise exc.HTTPNotFound()
         c.form_values = {'url': kw.get('url') or wh.hook_url,
                          'secret': kw.get('secret') or wh.secret,
-                         'webhook': unicode(wh._id)}
+                         'webhook': six.text_type(wh._id)}
         return {'sender': self.sender,
                 'action': 'edit',
                 'form': form}
@@ -219,7 +220,7 @@ class WebhookRestController(BaseController):
         if error:
             _error = {}
             for k, v in error.iteritems():
-                _error[k] = unicode(v)
+                _error[k] = six.text_type(v)
             return _error
         error = getattr(e, 'msg', None)
         if not error:
@@ -297,7 +298,7 @@ class WebhookRestController(BaseController):
         try:
             params = {'secret': kw.pop('secret', old_secret),
                       'url': kw.pop('url', old_url),
-                      'webhook': unicode(webhook._id)}
+                      'webhook': six.text_type(webhook._id)}
             valid = form.to_python(params)
         except Exception as e:
             response.status_int = 400
diff --git a/ForgeBlog/forgeblog/main.py b/ForgeBlog/forgeblog/main.py
index 179340b..d867c1b 100644
--- a/ForgeBlog/forgeblog/main.py
+++ b/ForgeBlog/forgeblog/main.py
@@ -58,6 +58,7 @@ from allura.controllers.feed import FeedArgs, FeedController
 from forgeblog import model as BM
 from forgeblog import version
 from forgeblog import widgets
+import six
 
 log = logging.getLogger(__name__)
 
@@ -542,7 +543,7 @@ class BlogAdminController(DefaultAdminController):
     @require_post()
     def set_exfeed(self, new_exfeed=None, **kw):
         exfeed_val = kw.get('exfeed', [])
-        if type(exfeed_val) == unicode:
+        if type(exfeed_val) == six.text_type:
             tmp_exfeed_list = []
             tmp_exfeed_list.append(exfeed_val)
         else:
diff --git a/ForgeGit/forgegit/tests/functional/test_controllers.py b/ForgeGit/forgegit/tests/functional/test_controllers.py
index 61b821d..afaaea3 100644
--- a/ForgeGit/forgegit/tests/functional/test_controllers.py
+++ b/ForgeGit/forgegit/tests/functional/test_controllers.py
@@ -41,6 +41,7 @@ from allura.tests.decorators import with_tool
 from allura.tests.test_globals import squish_spaces
 from forgegit.tests import with_git
 from forgegit import model as GM
+import six
 
 
 class _TestCase(TestController):
@@ -813,7 +814,7 @@ class TestFork(_TestCase):
         assert '<p>changed description</p' in r
         assert 'Merge Request #1: changed summary (open)' in r
         changes = r.html.findAll('div', attrs={'class': 'markdown_content'})[-1]
-        dd_assert_equal(unicode(changes), """
+        dd_assert_equal(six.text_type(changes), """
 <div class="markdown_content"><ul>
 <li>
 <p><strong>Summary</strong>: summary --&gt; changed summary</p>
diff --git a/ForgeImporters/forgeimporters/github/tracker.py b/ForgeImporters/forgeimporters/github/tracker.py
index 0c54f3f..1a915bc 100644
--- a/ForgeImporters/forgeimporters/github/tracker.py
+++ b/ForgeImporters/forgeimporters/github/tracker.py
@@ -19,6 +19,7 @@ import re
 import logging
 from datetime import datetime
 from urllib2 import HTTPError
+import six
 
 try:
     from cStringIO import StringIO
@@ -242,7 +243,7 @@ class GitHubTrackerImporter(ToolImporter):
         for milestone in self.open_milestones:
             global_milestones['milestones'].append({
                 'name': milestone[0],
-                'due_date': unicode(milestone[1].date()) if milestone[1] else None,
+                'due_date': six.text_type(milestone[1].date()) if milestone[1] else None,
                 'complete': False,
             })
         return [global_milestones]
diff --git a/ForgeImporters/forgeimporters/github/wiki.py b/ForgeImporters/forgeimporters/github/wiki.py
index 53093f4..220eef3 100644
--- a/ForgeImporters/forgeimporters/github/wiki.py
+++ b/ForgeImporters/forgeimporters/github/wiki.py
@@ -427,7 +427,7 @@ class GitHubWikiImporter(ToolImporter):
                     a.string = new_page
                 elif a.string == prefix + page:
                     a.string = new_prefix + new_page
-        return unicode(soup)
+        return six.text_type(soup)
 
     def _prepare_textile_text(self, text):
         # need to convert lists properly
diff --git a/ForgeTracker/forgetracker/model/ticket.py b/ForgeTracker/forgetracker/model/ticket.py
index 85210d3..a3b5f46 100644
--- a/ForgeTracker/forgetracker/model/ticket.py
+++ b/ForgeTracker/forgetracker/model/ticket.py
@@ -716,7 +716,7 @@ class Ticket(VersionedArtifact, ActivityObject, VotableArtifact):
             # 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'] = six.text_type(v)
 
             # Now let's also index with proper Solr types.
             solr_type = self.app.globals.get_custom_field_solr_type(k)
diff --git a/ForgeWiki/forgewiki/converters.py b/ForgeWiki/forgewiki/converters.py
index 2793967..c4fea2f 100644
--- a/ForgeWiki/forgewiki/converters.py
+++ b/ForgeWiki/forgewiki/converters.py
@@ -18,6 +18,7 @@
 #-*- python -*-
 import re
 from bs4 import BeautifulSoup
+import six
 
 _inline_img = re.compile(r'\[\[(File|Image):([^\]|]+)[^]]*\]\]', re.UNICODE)
 _inline_img_markdown = r'[[img src=\2]]'
@@ -53,7 +54,7 @@ def _convert_toc(wiki_html):
     soup = BeautifulSoup(wiki_html, 'html.parser')
     for toc_div in soup.findAll('div', id='toc'):
         toc_div.replaceWith('[TOC]')
-    return unicode(soup)
+    return six.text_type(soup)
 
 
 def mediawiki2markdown(source):
diff --git a/scripts/teamforge-import.py b/scripts/teamforge-import.py
index 9e7ba6b..9bd88a6 100644
--- a/scripts/teamforge-import.py
+++ b/scripts/teamforge-import.py
@@ -41,6 +41,7 @@ from ming.base import Object
 from allura import model as M
 from allura.lib import helpers as h
 from allura.lib import utils
+import six
 
 log = logging.getLogger('teamforge-import')
 
@@ -663,7 +664,7 @@ def load(project_id, *paths):
     in_file = os.path.join(options.output_dir, project_id, *paths)
     with open(in_file) as input:
         content = input.read()
-    return unicode(content, 'utf-8')
+    return six.text_type(content, 'utf-8')
 
 
 def loadjson(*args):


[allura] 11/12: [#7878] encode form submits in tests

Posted by br...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

brondsem pushed a commit to branch db/7878
in repository https://gitbox.apache.org/repos/asf/allura.git

commit 830290422607862bde0389138a266fecd98f12c0
Author: Dave Brondsema <db...@slashdotmedia.com>
AuthorDate: Fri Jan 17 16:42:21 2020 +0000

    [#7878] encode form submits in tests
---
 Allura/allura/tests/functional/test_feeds.py            |  8 ++++----
 Allura/allura/tests/functional/test_rest.py             |  8 ++++----
 ForgeTracker/forgetracker/tests/functional/test_root.py | 16 +++++++++-------
 3 files changed, 17 insertions(+), 15 deletions(-)

diff --git a/Allura/allura/tests/functional/test_feeds.py b/Allura/allura/tests/functional/test_feeds.py
index 863e9a2..16e3f5d 100644
--- a/Allura/allura/tests/functional/test_feeds.py
+++ b/Allura/allura/tests/functional/test_feeds.py
@@ -46,16 +46,16 @@ class TestFeeds(TestController):
                     status='open',
                     description='This is a description'))),
             status=302)
-        title = 'Descri\xe7\xe3o e Arquitetura'.encode('utf-8')
+        title = 'Descri\xe7\xe3o e Arquitetura'
         self.app.post(
-            '/wiki/%s/update' % title,
+            h.urlquote('/wiki/%s/update' % title),
             params=dict(
-                title=title,
+                title=title.encode('utf-8'),
                 text="Nothing much",
                 labels='',
             ),
             status=302)
-        self.app.get('/wiki/%s/' % title)
+        self.app.get(h.urlquote('/wiki/%s/' % title))
 
     def test_project_feed(self):
         self.app.get('/feed.rss')
diff --git a/Allura/allura/tests/functional/test_rest.py b/Allura/allura/tests/functional/test_rest.py
index 7cbff40..892db1d 100644
--- a/Allura/allura/tests/functional/test_rest.py
+++ b/Allura/allura/tests/functional/test_rest.py
@@ -310,15 +310,15 @@ class TestRestHome(TestRestApiBase):
 
     def test_unicode(self):
         self.app.post(
-            '/wiki/tést/update',
+            h.urlquote('/wiki/tést/update'),
             params={
-                'title': 'tést',
+                'title': 'tést'.encode('utf-8'),
                 'text': 'sometext',
                 'labels': '',
                 })
-        r = self.api_get('/rest/p/test/wiki/tést/')
+        r = self.api_get(h.urlquote('/rest/p/test/wiki/tést/'))
         assert r.status_int == 200
-        assert r.json['title'].encode('utf-8') == 'tést', r.json
+        assert r.json['title'] == 'tést', r.json
 
     @td.with_wiki
     def test_deny_access(self):
diff --git a/ForgeTracker/forgetracker/tests/functional/test_root.py b/ForgeTracker/forgetracker/tests/functional/test_root.py
index 8d624af..396d01c 100644
--- a/ForgeTracker/forgetracker/tests/functional/test_root.py
+++ b/ForgeTracker/forgetracker/tests/functional/test_root.py
@@ -1129,7 +1129,7 @@ class TestFunctionalController(TrackerTestController):
         params = dict(
             custom_fields=[
                 dict(name='_testselect', label='Test', type='select',
-                     options='oné "one and á half" two'),
+                     options='oné "one and á half" two'.encode('utf-8')),
             ],
             open_status_names='aa bb',
             closed_status_names='cc',
@@ -1138,15 +1138,17 @@ class TestFunctionalController(TrackerTestController):
             '/admin/bugs/set_custom_fields',
             params=variable_encode(params))
         r = self.app.get('/bugs/new/')
-        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
+
+        r.mustcontain('<option value="oné">oné</option>')
+        assert '<option value="oné">oné</option>' in r.text
+        assert '<option value="one and á half">one and á half</option>' in r.text
+        assert '<option value="two">two</option>' in r.text
 
     def test_select_custom_field_invalid_quotes(self):
         params = dict(
             custom_fields=[
                 dict(name='_testselect', label='Test', type='select',
-                     options='closéd "quote missing'),
+                     options='closéd "quote missing'.encode('utf-8')),
             ],
             open_status_names='aa bb',
             closed_status_names='cc',
@@ -1203,7 +1205,7 @@ class TestFunctionalController(TrackerTestController):
                 show_in_search='on',
                 type='milestone',
                 milestones=[
-                    dict(name='aaaé'),
+                    dict(name='aaaé'.encode('utf-8')),
                     dict(name='bbb'),
                     dict(name='ccc')])]}
         self.app.post('/admin/bugs/set_custom_fields',
@@ -1244,7 +1246,7 @@ class TestFunctionalController(TrackerTestController):
         self.app.post('/bugs/update_milestones', {
             'field_name': '_milestone',
             'milestones-0.old_name': '1.0',
-            'milestones-0.new_name': 'zzzé',
+            'milestones-0.new_name': 'zzzé'.encode('utf-8'),
             'milestones-0.description': '',
             'milestones-0.complete': 'Open',
             'milestones-0.due_date': ''


[allura] 04/12: [#7878] misc unicode fixes

Posted by br...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

brondsem pushed a commit to branch db/7878
in repository https://gitbox.apache.org/repos/asf/allura.git

commit 41ec930cfd7cde10cc223b6197f286e62c67a6bc
Author: Dave Brondsema <db...@slashdotmedia.com>
AuthorDate: Thu Jan 16 21:27:12 2020 +0000

    [#7878] misc unicode fixes
---
 Allura/allura/lib/AsciiDammit.py                   | 258 ++++++++++-----------
 Allura/allura/lib/helpers.py                       |   4 +-
 Allura/allura/lib/macro.py                         |   4 +-
 Allura/allura/tests/model/test_filesystem.py       |   2 +-
 Allura/allura/tests/test_app.py                    |  10 +-
 Allura/allura/tests/test_helpers.py                |   8 +-
 Allura/allura/tests/test_mail_util.py              |   2 +-
 Allura/allura/tests/test_tasks.py                  |   2 +-
 Allura/allura/tests/unit/test_repo.py              |  27 ++-
 AlluraTest/alluratest/test_syntax.py               |   4 +-
 ForgeGit/forgegit/model/git_repo.py                |   8 +-
 ForgeGit/forgegit/tests/model/test_repository.py   |   4 +-
 ForgeSVN/forgesvn/model/svn.py                     |   2 +-
 .../forgetracker/tests/functional/test_root.py     |   3 +-
 ForgeWiki/forgewiki/tests/functional/test_rest.py  |   4 +-
 15 files changed, 174 insertions(+), 168 deletions(-)

diff --git a/Allura/allura/lib/AsciiDammit.py b/Allura/allura/lib/AsciiDammit.py
index 95d27dd..ea27a9f 100644
--- a/Allura/allura/lib/AsciiDammit.py
+++ b/Allura/allura/lib/AsciiDammit.py
@@ -26,134 +26,134 @@ __license__ = "Public domain"
 import re
 import types
 
-CHARS = {'\x80': ('EUR', 'euro'),
-         '\x81': ' ',
-         '\x82': (',', 'sbquo'),
-         '\x83': ('f', 'fnof'),
-         '\x84': (',,', 'bdquo'),
-         '\x85': ('...', 'hellip'),
-         '\x86': ('+', 'dagger'),
-         '\x87': ('++', 'Dagger'),
-         '\x88': ('^', 'caret'),
-         '\x89': '%',
-         '\x8A': ('S', 'Scaron'),
-         '\x8B': ('<', 'lt;'),
-         '\x8C': ('OE', 'OElig'),
-         '\x8D': '?',
-         '\x8E': 'Z',
-         '\x8F': '?',
-         '\x90': '?',
-         '\x91': ("'", 'lsquo'),
-         '\x92': ("'", 'rsquo'),
-         '\x93': ('"', 'ldquo'),
-         '\x94': ('"', 'rdquo'),
-         '\x95': ('*', 'bull'),
-         '\x96': ('-', 'ndash'),
-         '\x97': ('--', 'mdash'),
-         '\x98': ('~', 'tilde'),
-         '\x99': ('(TM)', 'trade'),
-         '\x9a': ('s', 'scaron'),
-         '\x9b': ('>', 'gt'),
-         '\x9c': ('oe', 'oelig'),
-         '\x9d': '?',
-         '\x9e': 'z',
-         '\x9f': ('Y', 'Yuml'),
-         '\xa0': (' ', 'nbsp'),
-         '\xa1': ('!', 'iexcl'),
-         '\xa2': ('c', 'cent'),
-         '\xa3': ('GBP', 'pound'),
-         '\xa4': ('$', 'curren'),  # This approximation is especially lame.
-         '\xa5': ('YEN', 'yen'),
-         '\xa6': ('|', 'brvbar'),
-         '\xa7': ('S', 'sect'),
-         '\xa8': ('..', 'uml'),
-         '\xa9': ('', 'copy'),
-         '\xaa': ('(th)', 'ordf'),
-         '\xab': ('<<', 'laquo'),
-         '\xac': ('!', 'not'),
-         '\xad': (' ', 'shy'),
-         '\xae': ('(R)', 'reg'),
-         '\xaf': ('-', 'macr'),
-         '\xb0': ('o', 'deg'),
-         '\xb1': ('+-', 'plusmm'),
-         '\xb2': ('2', 'sup2'),
-         '\xb3': ('3', 'sup3'),
-         '\xb4': ("'", 'acute'),
-         '\xb5': ('u', 'micro'),
-         '\xb6': ('P', 'para'),
-         '\xb7': ('*', 'middot'),
-         '\xb8': (',', 'cedil'),
-         '\xb9': ('1', 'sup1'),
-         '\xba': ('(th)', 'ordm'),
-         '\xbb': ('>>', 'raquo'),
-         '\xbc': ('1/4', 'frac14'),
-         '\xbd': ('1/2', 'frac12'),
-         '\xbe': ('3/4', 'frac34'),
-         '\xbf': ('?', 'iquest'),
-         '\xc0': ('A', "Agrave"),
-         '\xc1': ('A', "Aacute"),
-         '\xc2': ('A', "Acirc"),
-         '\xc3': ('A', "Atilde"),
-         '\xc4': ('A', "Auml"),
-         '\xc5': ('A', "Aring"),
-         '\xc6': ('AE', "Aelig"),
-         '\xc7': ('C', "Ccedil"),
-         '\xc8': ('E', "Egrave"),
-         '\xc9': ('E', "Eacute"),
-         '\xca': ('E', "Ecirc"),
-         '\xcb': ('E', "Euml"),
-         '\xcc': ('I', "Igrave"),
-         '\xcd': ('I', "Iacute"),
-         '\xce': ('I', "Icirc"),
-         '\xcf': ('I', "Iuml"),
-         '\xd0': ('D', "Eth"),
-         '\xd1': ('N', "Ntilde"),
-         '\xd2': ('O', "Ograve"),
-         '\xd3': ('O', "Oacute"),
-         '\xd4': ('O', "Ocirc"),
-         '\xd5': ('O', "Otilde"),
-         '\xd6': ('O', "Ouml"),
-         '\xd7': ('*', "times"),
-         '\xd8': ('O', "Oslash"),
-         '\xd9': ('U', "Ugrave"),
-         '\xda': ('U', "Uacute"),
-         '\xdb': ('U', "Ucirc"),
-         '\xdc': ('U', "Uuml"),
-         '\xdd': ('Y', "Yacute"),
-         '\xde': ('b', "Thorn"),
-         '\xdf': ('B', "szlig"),
-         '\xe0': ('a', "agrave"),
-         '\xe1': ('a', "aacute"),
-         '\xe2': ('a', "acirc"),
-         '\xe3': ('a', "atilde"),
-         '\xe4': ('a', "auml"),
-         '\xe5': ('a', "aring"),
-         '\xe6': ('ae', "aelig"),
-         '\xe7': ('c', "ccedil"),
-         '\xe8': ('e', "egrave"),
-         '\xe9': ('e', "eacute"),
-         '\xea': ('e', "ecirc"),
-         '\xeb': ('e', "euml"),
-         '\xec': ('i', "igrave"),
-         '\xed': ('i', "iacute"),
-         '\xee': ('i', "icirc"),
-         '\xef': ('i', "iuml"),
-         '\xf0': ('o', "eth"),
-         '\xf1': ('n', "ntilde"),
-         '\xf2': ('o', "ograve"),
-         '\xf3': ('o', "oacute"),
-         '\xf4': ('o', "ocirc"),
-         '\xf5': ('o', "otilde"),
-         '\xf6': ('o', "ouml"),
-         '\xf7': ('/', "divide"),
-         '\xf8': ('o', "oslash"),
-         '\xf9': ('u', "ugrave"),
-         '\xfa': ('u', "uacute"),
-         '\xfb': ('u', "ucirc"),
-         '\xfc': ('u', "uuml"),
-         '\xfd': ('y', "yacute"),
-         '\xfe': ('b', "thorn"),
-         '\xff': ('y', "yuml"),
+CHARS = {b'\x80': ('EUR', 'euro'),
+         b'\x81': ' ',
+         b'\x82': (',', 'sbquo'),
+         b'\x83': ('f', 'fnof'),
+         b'\x84': (',,', 'bdquo'),
+         b'\x85': ('...', 'hellip'),
+         b'\x86': ('+', 'dagger'),
+         b'\x87': ('++', 'Dagger'),
+         b'\x88': ('^', 'caret'),
+         b'\x89': '%',
+         b'\x8A': ('S', 'Scaron'),
+         b'\x8B': ('<', 'lt;'),
+         b'\x8C': ('OE', 'OElig'),
+         b'\x8D': '?',
+         b'\x8E': 'Z',
+         b'\x8F': '?',
+         b'\x90': '?',
+         b'\x91': ("'", 'lsquo'),
+         b'\x92': ("'", 'rsquo'),
+         b'\x93': ('"', 'ldquo'),
+         b'\x94': ('"', 'rdquo'),
+         b'\x95': ('*', 'bull'),
+         b'\x96': ('-', 'ndash'),
+         b'\x97': ('--', 'mdash'),
+         b'\x98': ('~', 'tilde'),
+         b'\x99': ('(TM)', 'trade'),
+         b'\x9a': ('s', 'scaron'),
+         b'\x9b': ('>', 'gt'),
+         b'\x9c': ('oe', 'oelig'),
+         b'\x9d': '?',
+         b'\x9e': 'z',
+         b'\x9f': ('Y', 'Yuml'),
+         b'\xa0': (' ', 'nbsp'),
+         b'\xa1': ('!', 'iexcl'),
+         b'\xa2': ('c', 'cent'),
+         b'\xa3': ('GBP', 'pound'),
+         b'\xa4': ('$', 'curren'),  # This approximation is especially lame.
+         b'\xa5': ('YEN', 'yen'),
+         b'\xa6': ('|', 'brvbar'),
+         b'\xa7': ('S', 'sect'),
+         b'\xa8': ('..', 'uml'),
+         b'\xa9': ('', 'copy'),
+         b'\xaa': ('(th)', 'ordf'),
+         b'\xab': ('<<', 'laquo'),
+         b'\xac': ('!', 'not'),
+         b'\xad': (' ', 'shy'),
+         b'\xae': ('(R)', 'reg'),
+         b'\xaf': ('-', 'macr'),
+         b'\xb0': ('o', 'deg'),
+         b'\xb1': ('+-', 'plusmm'),
+         b'\xb2': ('2', 'sup2'),
+         b'\xb3': ('3', 'sup3'),
+         b'\xb4': ("'", 'acute'),
+         b'\xb5': ('u', 'micro'),
+         b'\xb6': ('P', 'para'),
+         b'\xb7': ('*', 'middot'),
+         b'\xb8': (',', 'cedil'),
+         b'\xb9': ('1', 'sup1'),
+         b'\xba': ('(th)', 'ordm'),
+         b'\xbb': ('>>', 'raquo'),
+         b'\xbc': ('1/4', 'frac14'),
+         b'\xbd': ('1/2', 'frac12'),
+         b'\xbe': ('3/4', 'frac34'),
+         b'\xbf': ('?', 'iquest'),
+         b'\xc0': ('A', "Agrave"),
+         b'\xc1': ('A', "Aacute"),
+         b'\xc2': ('A', "Acirc"),
+         b'\xc3': ('A', "Atilde"),
+         b'\xc4': ('A', "Auml"),
+         b'\xc5': ('A', "Aring"),
+         b'\xc6': ('AE', "Aelig"),
+         b'\xc7': ('C', "Ccedil"),
+         b'\xc8': ('E', "Egrave"),
+         b'\xc9': ('E', "Eacute"),
+         b'\xca': ('E', "Ecirc"),
+         b'\xcb': ('E', "Euml"),
+         b'\xcc': ('I', "Igrave"),
+         b'\xcd': ('I', "Iacute"),
+         b'\xce': ('I', "Icirc"),
+         b'\xcf': ('I', "Iuml"),
+         b'\xd0': ('D', "Eth"),
+         b'\xd1': ('N', "Ntilde"),
+         b'\xd2': ('O', "Ograve"),
+         b'\xd3': ('O', "Oacute"),
+         b'\xd4': ('O', "Ocirc"),
+         b'\xd5': ('O', "Otilde"),
+         b'\xd6': ('O', "Ouml"),
+         b'\xd7': ('*', "times"),
+         b'\xd8': ('O', "Oslash"),
+         b'\xd9': ('U', "Ugrave"),
+         b'\xda': ('U', "Uacute"),
+         b'\xdb': ('U', "Ucirc"),
+         b'\xdc': ('U', "Uuml"),
+         b'\xdd': ('Y', "Yacute"),
+         b'\xde': ('b', "Thorn"),
+         b'\xdf': ('B', "szlig"),
+         b'\xe0': ('a', "agrave"),
+         b'\xe1': ('a', "aacute"),
+         b'\xe2': ('a', "acirc"),
+         b'\xe3': ('a', "atilde"),
+         b'\xe4': ('a', "auml"),
+         b'\xe5': ('a', "aring"),
+         b'\xe6': ('ae', "aelig"),
+         b'\xe7': ('c', "ccedil"),
+         b'\xe8': ('e', "egrave"),
+         b'\xe9': ('e', "eacute"),
+         b'\xea': ('e', "ecirc"),
+         b'\xeb': ('e', "euml"),
+         b'\xec': ('i', "igrave"),
+         b'\xed': ('i', "iacute"),
+         b'\xee': ('i', "icirc"),
+         b'\xef': ('i', "iuml"),
+         b'\xf0': ('o', "eth"),
+         b'\xf1': ('n', "ntilde"),
+         b'\xf2': ('o', "ograve"),
+         b'\xf3': ('o', "oacute"),
+         b'\xf4': ('o', "ocirc"),
+         b'\xf5': ('o', "otilde"),
+         b'\xf6': ('o', "ouml"),
+         b'\xf7': ('/', "divide"),
+         b'\xf8': ('o', "oslash"),
+         b'\xf9': ('u', "ugrave"),
+         b'\xfa': ('u', "uacute"),
+         b'\xfb': ('u', "ucirc"),
+         b'\xfc': ('u', "uuml"),
+         b'\xfd': ('y', "yacute"),
+         b'\xfe': ('b', "thorn"),
+         b'\xff': ('y', "yuml"),
          }
 
 
@@ -213,7 +213,7 @@ def demoronise(t):
 
 if __name__ == '__main__':
 
-    french = '\x93Sacr\xe9 bleu!\x93'
+    french = b'\x93Sacr\xe9 bleu!\x93'
     print "First we mangle some French."
     print asciiDammit(french)
     print htmlDammit(french)
diff --git a/Allura/allura/lib/helpers.py b/Allura/allura/lib/helpers.py
index c204aaf..c39ee78 100644
--- a/Allura/allura/lib/helpers.py
+++ b/Allura/allura/lib/helpers.py
@@ -724,7 +724,7 @@ def render_any_markup(name, txt, code_mode=False, linenumbers_style=TABLE):
     renders any other markup format using the pypeline
     Returns jinja-safe text
     """
-    if txt == '':
+    if not txt:
         txt = '<p><em>Empty File</em></p>'
     else:
         fmt = g.pypeline_markup.can_render(name)
@@ -970,7 +970,7 @@ class exceptionless(object):
                         'Error calling %s(args=%s, kwargs=%s): %s',
                         fname, args, kwargs, str(e))
                 return self.error_result
-        inner.__name__ = fname
+        inner.__name__ = str(fname)
         return inner
 
 
diff --git a/Allura/allura/lib/macro.py b/Allura/allura/lib/macro.py
index 4a2ec43..911be84 100644
--- a/Allura/allura/lib/macro.py
+++ b/Allura/allura/lib/macro.py
@@ -453,8 +453,8 @@ def members(limit=20):
 def embed(url=None):
     consumer = oembed.OEmbedConsumer()
     endpoint = oembed.OEmbedEndpoint('http://www.youtube.com/oembed',
-                                     ['http://*.youtube.com/*', 'https://*.youtube.com/*',
-                                      'http://*.youtube-nocookie.com/*', 'https://*.youtube-nocookie.com/*',
+                                     [str('http://*.youtube.com/*'), str('https://*.youtube.com/*'),
+                                      str('http://*.youtube-nocookie.com/*'), str('https://*.youtube-nocookie.com/*'),
                                       ])
     consumer.addEndpoint(endpoint)
 
diff --git a/Allura/allura/tests/model/test_filesystem.py b/Allura/allura/tests/model/test_filesystem.py
index 665ec22..f8f1e7c 100644
--- a/Allura/allura/tests/model/test_filesystem.py
+++ b/Allura/allura/tests/model/test_filesystem.py
@@ -116,7 +116,7 @@ class TestFile(TestCase):
         assert self.db.fs.chunks.count() == 1
         self._assert_content(f, 'test1')
         with f.wfile() as fp:
-            fp.write('test2')
+            fp.write(b'test2')
         self.session.flush()
         assert self.db.fs.count() == 1
         assert self.db.fs.files.count() == 2
diff --git a/Allura/allura/tests/test_app.py b/Allura/allura/tests/test_app.py
index 25e39c2..8ea1b68 100644
--- a/Allura/allura/tests/test_app.py
+++ b/Allura/allura/tests/test_app.py
@@ -139,17 +139,17 @@ def test_handle_artifact_unicode(qg):
 
     a = app.Application(c.project, c.app.config)
 
-    msg = dict(payload='foo ƒ†©¥˙¨ˆ', message_id=1, headers={})
+    msg = dict(payload='foo ƒ†©¥˙¨ˆ'.encode('utf-8'), message_id=1, headers={})
     a.handle_artifact_message(ticket, msg)
-    assert_equal(post.attach.call_args[0][1].getvalue(), 'foo ƒ†©¥˙¨ˆ')
+    assert_equal(post.attach.call_args[0][1].getvalue(), 'foo ƒ†©¥˙¨ˆ'.encode('utf-8'))
 
-    msg = dict(payload='foo', message_id=1, headers={})
+    msg = dict(payload='foo'.encode('utf-8'), message_id=1, headers={})
     a.handle_artifact_message(ticket, msg)
     assert_equal(post.attach.call_args[0][1].getvalue(), 'foo')
 
-    msg = dict(payload="\x94my quote\x94", message_id=1, headers={})
+    msg = dict(payload="\x94my quote\x94".encode('utf-8'), message_id=1, headers={})
     a.handle_artifact_message(ticket, msg)
-    assert_equal(post.attach.call_args[0][1].getvalue(), '\x94my quote\x94')
+    assert_equal(post.attach.call_args[0][1].getvalue(), '\x94my quote\x94'.encode('utf-8'))
 
     # assert against prod example
     msg_raw = """Message-Id: <15...@webmail.messagingengine.com>
diff --git a/Allura/allura/tests/test_helpers.py b/Allura/allura/tests/test_helpers.py
index 73f8fe4..873e82f 100644
--- a/Allura/allura/tests/test_helpers.py
+++ b/Allura/allura/tests/test_helpers.py
@@ -89,16 +89,18 @@ def test_escape_json():
 
 def test_really_unicode():
     here_dir = path.dirname(__file__)
-    s = h.really_unicode('\xef\xbb\xbf<?xml version="1.0" encoding="utf-8" ?>')
-    assert s.startswith('\ufeff')
+    s = h.really_unicode(b'\xef\xbb\xbf<?xml version="1.0" encoding="utf-8" ?>')
+    assert s.startswith('\ufeff'), repr(s)
     s = h.really_unicode(
         open(path.join(here_dir, 'data/unicode_test.txt')).read())
     assert isinstance(s, six.text_type)
     # try non-ascii string in legacy 8bit encoding
     h.really_unicode('\u0410\u0401'.encode('cp1251'))
     # ensure invalid encodings are handled gracefully
-    s = h._attempt_encodings('foo', ['LKDJFLDK'])
+    s = h._attempt_encodings(b'foo', ['LKDJFLDK'])
     assert isinstance(s, six.text_type)
+    # unicode stays the same
+    assert_equals(h.really_unicode('¬∂•°‹'), '¬∂•°‹')
 
 
 def test_find_project():
diff --git a/Allura/allura/tests/test_mail_util.py b/Allura/allura/tests/test_mail_util.py
index eba9b8b..bdae63f 100644
--- a/Allura/allura/tests/test_mail_util.py
+++ b/Allura/allura/tests/test_mail_util.py
@@ -216,7 +216,7 @@ class TestHeader(object):
 
     @raises(TypeError)
     def test_bytestring(self):
-        our_header = Header('[asdf2:wiki] Discussion for Home page')
+        our_header = Header(b'[asdf2:wiki] Discussion for Home page')
         assert_equal(str(our_header), '[asdf2:wiki] Discussion for Home page')
 
     def test_ascii(self):
diff --git a/Allura/allura/tests/test_tasks.py b/Allura/allura/tests/test_tasks.py
index e9c966b..74c9372 100644
--- a/Allura/allura/tests/test_tasks.py
+++ b/Allura/allura/tests/test_tasks.py
@@ -591,7 +591,7 @@ def raise_exc():
     errs = []
     for x in range(10):
         try:
-            assert False, 'assert %d' % x
+            assert False, str('assert %d' % x)
         except:
             errs.append(sys.exc_info())
     raise CompoundError(*errs)
diff --git a/Allura/allura/tests/unit/test_repo.py b/Allura/allura/tests/unit/test_repo.py
index 1e20c59..83827e5 100644
--- a/Allura/allura/tests/unit/test_repo.py
+++ b/Allura/allura/tests/unit/test_repo.py
@@ -18,6 +18,8 @@
 from __future__ import unicode_literals
 import datetime
 import unittest
+
+import six
 from mock import patch, Mock, MagicMock, call
 from nose.tools import assert_equal
 from datadiff import tools as dd
@@ -109,18 +111,18 @@ class TestBlob(unittest.TestCase):
 
     def test_has_html_view_text_mime(self):
         blob = M.repository.Blob(MagicMock(), 'INSTALL', 'blob1')
-        blob.content_type = 'text/plain'
+        blob.content_type = b'text/plain'
         assert_equal(blob.has_html_view, True)
 
     def test_has_html_view_text_ext(self):
         blob = M.repository.Blob(MagicMock(), 'INSTALL.txt', 'blob1')
-        blob.content_type = 'foo/bar'
+        blob.content_type = b'foo/bar'
         assert_equal(blob.has_html_view, True)
 
     def test_has_html_view_text_contents(self):
         blob = M.repository.Blob(MagicMock(), 'INSTALL', 'blob1')
-        blob.content_type = 'foo/bar'
-        blob.text = 'hello world, this is text here'
+        blob.content_type = b'foo/bar'
+        blob.text = b'hello world, this is text here'
         assert_equal(blob.has_html_view, True)
 
     def test_has_html_view_bin_ext(self):
@@ -129,14 +131,14 @@ class TestBlob(unittest.TestCase):
 
     def test_has_html_view_bin_content(self):
         blob = M.repository.Blob(MagicMock(), 'myfile', 'blob1')
-        blob.content_type = 'whatever'
-        blob.text = '\0\0\0\0'
+        blob.content_type = b'whatever'
+        blob.text = b'\0\0\0\0'
         assert_equal(blob.has_html_view, False)
 
     def test_has_html_view__local_setting_override_bin(self):
         blob = M.repository.Blob(MagicMock(), 'myfile.dat', 'blob1')
-        blob.content_type = 'whatever'
-        blob.text = '\0\0\0\0'
+        blob.content_type = b'whatever'
+        blob.text = b'\0\0\0\0'
         blob.repo._additional_viewable_extensions = ['.dat']
         assert_equal(blob.has_html_view, True)
 
@@ -276,10 +278,11 @@ class TestZipDir(unittest.TestCase):
         with self.assertRaises(Exception) as cm:
             zipdir(src, zipfile)
         emsg = str(cm.exception)
-        self.assertTrue(
-            "Command: "
-            "['/bin/zip', '-y', '-q', '-r', '/fake/zip/file.tmp', 'repo'] "
-            "returned non-zero exit code 1" in emsg)
+        self.assertIn(
+            "Command: " +
+            ("['/bin/zip', '-y', '-q', '-r', '/fake/zip/file.tmp', 'repo'] " if six.PY3 else
+             "[u'/bin/zip', u'-y', u'-q', u'-r', u'/fake/zip/file.tmp', u'repo'] ") +
+            "returned non-zero exit code 1", emsg)
         self.assertTrue("STDOUT: 1" in emsg)
         self.assertTrue("STDERR: 2" in emsg)
 
diff --git a/AlluraTest/alluratest/test_syntax.py b/AlluraTest/alluratest/test_syntax.py
index 7b06d62..a6f5fff 100644
--- a/AlluraTest/alluratest/test_syntax.py
+++ b/AlluraTest/alluratest/test_syntax.py
@@ -114,11 +114,11 @@ def create_many_lint_methods():
             continue
 
         lint_test_method = lambda self, these_files=files: run_linter(these_files)
-        lint_test_method.__name__ = 'test_pylint_{}'.format(i)
+        lint_test_method.__name__ = str('test_pylint_{}'.format(i))
         setattr(TestLinters, 'test_pylint_{}'.format(i), lint_test_method)
 
         pyflake_test_method = lambda self, these_files=files: run_pyflakes(these_files)
-        pyflake_test_method.__name__ = 'test_pyflakes_{}'.format(i)
+        pyflake_test_method.__name__ = str('test_pyflakes_{}'.format(i))
         setattr(TestLinters, 'test_pyflakes_{}'.format(i), pyflake_test_method)
 
 
diff --git a/ForgeGit/forgegit/model/git_repo.py b/ForgeGit/forgegit/model/git_repo.py
index 67e0081..26f8bae 100644
--- a/ForgeGit/forgegit/model/git_repo.py
+++ b/ForgeGit/forgegit/model/git_repo.py
@@ -490,7 +490,7 @@ class GitImplementation(M.RepositoryImplementation):
     def _object(self, oid):
         evens = oid[::2]
         odds = oid[1::2]
-        binsha = ''
+        binsha = b''
         for e, o in zip(evens, odds):
             binsha += chr(int(e + o, 16))
         return git.Object.new_from_sha(self._git, binsha)
@@ -772,17 +772,17 @@ class _OpenedGitBlob(object):
         '''
         Yields one line at a time, reading from the stream
         '''
-        buffer = ''
+        buffer = b''
         while True:
             # Replenish buffer until we have a line break
-            while '\n' not in buffer:
+            while b'\n' not in buffer:
                 chars = self._stream.read(self.CHUNK_SIZE)
                 if not chars:
                     break
                 buffer += chars
             if not buffer:
                 break
-            eol = buffer.find('\n')
+            eol = buffer.find(b'\n')
             if eol == -1:
                 # end without \n
                 yield buffer
diff --git a/ForgeGit/forgegit/tests/model/test_repository.py b/ForgeGit/forgegit/tests/model/test_repository.py
index 5799c72..fed9243 100644
--- a/ForgeGit/forgegit/tests/model/test_repository.py
+++ b/ForgeGit/forgegit/tests/model/test_repository.py
@@ -455,9 +455,9 @@ class TestGitRepo(unittest.TestCase, RepoImplTestBase):
                 pass
         multipart_msg = sendmail.call_args_list[0][0][6]
         text_msg = sendmail.call_args_list[1][0][6]
-        text_body = text_msg.get_payload(decode=True)
+        text_body = text_msg.get_payload(decode=True).decode('utf-8')
         html_body = email.iterators.typed_subpart_iterator(multipart_msg, 'text', 'html').next()\
-            .get_payload(decode=True)
+            .get_payload(decode=True).decode('utf-8')
 
         # no extra HTML in commit messages
         assert_in('''-----
diff --git a/ForgeSVN/forgesvn/model/svn.py b/ForgeSVN/forgesvn/model/svn.py
index b9f1ecc..b8c835c 100644
--- a/ForgeSVN/forgesvn/model/svn.py
+++ b/ForgeSVN/forgesvn/model/svn.py
@@ -703,7 +703,7 @@ class SVNImplementation(M.RepositoryImplementation):
         try:
             # need to set system locale to handle all symbols in filename
             import locale
-            locale.setlocale(locale.LC_ALL, 'en_US.UTF-8')
+            locale.setlocale(locale.LC_ALL, str('en_US.UTF-8'))
             self._svn.export(path,
                              tmpdest,
                              revision=pysvn.Revision(
diff --git a/ForgeTracker/forgetracker/tests/functional/test_root.py b/ForgeTracker/forgetracker/tests/functional/test_root.py
index 8a62959..ede3637 100644
--- a/ForgeTracker/forgetracker/tests/functional/test_root.py
+++ b/ForgeTracker/forgetracker/tests/functional/test_root.py
@@ -53,6 +53,7 @@ from forgetracker import model as tm
 from allura.lib.security import has_access, Credentials
 from allura.lib import helpers as h
 from allura.lib.search import SearchError
+from allura.lib.utils import urlencode
 from allura.tests import decorators as td
 from allura.tasks import mail_tasks
 from ming.orm.ormsession import ThreadLocalORMSession
@@ -1380,7 +1381,7 @@ class TestFunctionalController(TrackerTestController):
 
     def test_search_with_strange_chars(self):
         r = self.app.get('/p/test/bugs/search/?' +
-                         urllib.urlencode({'q': 'tést'}))
+                         urlencode({'q': 'tést'}))
         assert 'Search bugs: tést' in r
 
     def test_saved_search_with_strange_chars(self):
diff --git a/ForgeWiki/forgewiki/tests/functional/test_rest.py b/ForgeWiki/forgewiki/tests/functional/test_rest.py
index 9e804a7..7d2e795 100644
--- a/ForgeWiki/forgewiki/tests/functional/test_rest.py
+++ b/ForgeWiki/forgewiki/tests/functional/test_rest.py
@@ -116,8 +116,8 @@ class TestWikiApi(TestRestApiBase):
         assert_equal(r.json['text'], 'foo <img src=x onerror=alert(1)> bar')
 
     def test_json_encoding_directly(self):
-        # used in @expose('json')
-        assert_equal(tg.jsonify.encode('<'), '"\\u003C"')
+        # used in @expose('json'), monkey-patched in our patches.py
+        assert_equal(tg.jsonify.encode('<'), '"\u003C"')
         # make sure these are unchanged
         assert_equal(json.dumps('<'), '"<"')
 


[allura] 09/12: [#7878] tests need to treat responses as unicode text

Posted by br...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

brondsem pushed a commit to branch db/7878
in repository https://gitbox.apache.org/repos/asf/allura.git

commit 95a902c8e9624303634d116bf0802b97dcb30b74
Author: Dave Brondsema <db...@slashdotmedia.com>
AuthorDate: Thu Jan 16 23:17:49 2020 +0000

    [#7878] tests need to treat responses as unicode text
---
 Allura/allura/tests/functional/test_admin.py       | 12 ++++----
 Allura/allura/tests/functional/test_auth.py        |  2 +-
 Allura/allura/tests/functional/test_discuss.py     | 11 +++-----
 Allura/allura/tests/functional/test_root.py        |  4 +--
 .../forgediscussion/tests/functional/test_forum.py |  9 +++---
 .../forgetracker/tests/functional/test_root.py     | 32 +++++++++++-----------
 6 files changed, 33 insertions(+), 37 deletions(-)

diff --git a/Allura/allura/tests/functional/test_admin.py b/Allura/allura/tests/functional/test_admin.py
index 6a50b1b..d01eca0 100644
--- a/Allura/allura/tests/functional/test_admin.py
+++ b/Allura/allura/tests/functional/test_admin.py
@@ -63,7 +63,7 @@ class TestProjectAdmin(TestController):
                         'utf-8') * 45,
                 labels='aaa,bbb'))
         r = self.app.get('/admin/overview')
-        assert 'A Test Project ?\xc2\xbf A' in r
+        assert b'A Test Project ?\xc2\xbf A' in r.body
         assert 'Test Subproject' not in r
         assert 'Milkshakes are for crazy monkeys' in r
         sidebar = r.html.find(id='sidebar')
@@ -707,8 +707,8 @@ class TestProjectAdmin(TestController):
                 'table', {'id': 'usergroup_admin'}).findAll('tr')[2]
             mem_holder = r.html.find(
                 'table', {'id': 'usergroup_admin'}).findAll('tr')[3]
-            assert 'All users in Admin group' in str(dev_holder)
-            assert 'All users in Developer group' in str(mem_holder)
+            assert 'All users in Admin group' in dev_holder.text
+            assert 'All users in Developer group' in mem_holder.text
 
         r = self.app.get('/admin/groups/')
 
@@ -806,7 +806,7 @@ class TestProjectAdmin(TestController):
                               params={'name': 'RoleNew1'})
         r = self.app.get('/admin/groups/')
         role_holder = r.html.find('table', {'id': 'usergroup_admin'}).findAll('tr')[4]
-        assert 'RoleNew1' in str(role_holder)
+        assert 'RoleNew1' in role_holder.text
         role_id = role_holder['data-group']
 
         # add test-user to role
@@ -821,7 +821,7 @@ class TestProjectAdmin(TestController):
                 'group_name': 'RoleNew1'})
         assert 'deleted' in self.webflash(r)
         r = self.app.get('/admin/groups/', status=200)
-        roles = [str(t) for t in r.html.findAll('td', {'class': 'group'})]
+        roles = [t.text for t in r.html.findAll('td', {'class': 'group'})]
         assert 'RoleNew1' not in roles
 
         # make sure can still access homepage after one of user's roles were
@@ -1047,7 +1047,7 @@ class TestExport(TestController):
         session.return_value = {'result': [{"total_size": 10000}]}
         export_tasks.bulk_export.post(['wiki'])
         r = self.app.get('/admin/export')
-        assert_in('<h2>Busy</h2>', r.body)
+        assert_in('<h2>Busy</h2>', r.text)
 
     @td.with_user_project('test-user')
     def test_bulk_export_path_for_user_project(self):
diff --git a/Allura/allura/tests/functional/test_auth.py b/Allura/allura/tests/functional/test_auth.py
index 4f326db..64ba3e7 100644
--- a/Allura/allura/tests/functional/test_auth.py
+++ b/Allura/allura/tests/functional/test_auth.py
@@ -1150,7 +1150,7 @@ class TestPreferences(TestController):
         # Check if setting a wrong date everything works correctly
         r = self.app.post('/auth/user_info/change_personal_data',
                           params=dict(birthdate='30/02/1998', _session_id=self.app.cookies['_session_id']))
-        assert 'Please enter a valid date' in str(r)
+        assert 'Please enter a valid date' in r.text
         user = M.User.query.get(username='test-admin')
         sex = user.sex
         assert sex == setsex
diff --git a/Allura/allura/tests/functional/test_discuss.py b/Allura/allura/tests/functional/test_discuss.py
index 51f080e..64791da 100644
--- a/Allura/allura/tests/functional/test_discuss.py
+++ b/Allura/allura/tests/functional/test_discuss.py
@@ -369,10 +369,8 @@ class TestDiscuss(TestDiscussBase):
         reply_form = r.html.find(
             'div', {'class': 'edit_post_form reply'}).find('form')
         post_link = str(reply_form['action'])
-        assert 'This is a post' in str(
-            r.html.find('div', {'class': 'display_post'}))
-        assert 'Last edit:' not in str(
-            r.html.find('div', {'class': 'display_post'}))
+        assert 'This is a post' in r.html.find('div', {'class': 'display_post'}).text
+        assert 'Last edit:' not in r.html.find('div', {'class': 'display_post'}).text
         params = dict()
         inputs = reply_form.findAll('input')
         for field in inputs:
@@ -383,9 +381,8 @@ class TestDiscuss(TestDiscussBase):
         assert create_activity.call_count == 1, create_activity.call_count
         assert create_activity.call_args[0][1] == 'modified'
         r = self.app.get(thread_url)
-        assert 'zzz' in str(r.html.find('div', {'class': 'display_post'}))
-        assert 'Last edit: Test Admin ' in str(
-            r.html.find('div', {'class': 'display_post'}))
+        assert 'zzz' in r.html.find('div', {'class': 'display_post'}).text
+        assert 'Last edit: Test Admin ' in r.html.find('div', {'class': 'display_post'}).text
 
     def test_deleted_post(self):
         r = self._make_post('This is a post')
diff --git a/Allura/allura/tests/functional/test_root.py b/Allura/allura/tests/functional/test_root.py
index b91a97e..e9c630c 100644
--- a/Allura/allura/tests/functional/test_root.py
+++ b/Allura/allura/tests/functional/test_root.py
@@ -88,8 +88,8 @@ class TestRootController(TestController):
 
         response = self.app.get('/neighborhood')
         # inject it into the sidebar data
-        content = str(response.html.find('div', {'id': 'content_base'}))
-        assert '<script>' not in content
+        content = response.html.find('div', {'id': 'content_base'}).prettify()
+        assert '<script>' not in content, content
         assert '&lt;script&gt;' in content
 
     def test_strange_accept_headers(self):
diff --git a/ForgeDiscussion/forgediscussion/tests/functional/test_forum.py b/ForgeDiscussion/forgediscussion/tests/functional/test_forum.py
index eef13ab..3d9752b 100644
--- a/ForgeDiscussion/forgediscussion/tests/functional/test_forum.py
+++ b/ForgeDiscussion/forgediscussion/tests/functional/test_forum.py
@@ -674,9 +674,8 @@ class TestForum(TestController):
         params[reply_form.find('textarea')['name']] = 'zzz'
         self.app.post(post_link, params)
         r = self.app.get(thread_url)
-        assert 'zzz' in str(r.html.find('div', {'class': 'display_post'}))
-        assert 'Last edit: Test Admin ' in str(
-            r.html.find('div', {'class': 'display_post'}))
+        assert 'zzz' in r.html.find('div', {'class': 'display_post'}).text
+        assert 'Last edit: Test Admin ' in r.html.find('div', {'class': 'display_post'}).text
 
     def test_subscription_controls(self):
         r = self.app.get('/discussion/create_topic/')
@@ -824,8 +823,8 @@ class TestForum(TestController):
         r = self.app.get('/discussion/testforum/')
         rows = self.get_table_rows(r, 'forum_threads')
         assert_equal(len(rows), 2)
-        assert 'topic1' in str(rows[0])
-        assert 'topic2' in str(rows[1])
+        assert 'topic1' in rows[0].text
+        assert 'topic2' in rows[1].text
 
         # Reset Sticky flag
         r = self.app.post(url1 + 'moderate', params=dict(
diff --git a/ForgeTracker/forgetracker/tests/functional/test_root.py b/ForgeTracker/forgetracker/tests/functional/test_root.py
index 8e39790..8d624af 100644
--- a/ForgeTracker/forgetracker/tests/functional/test_root.py
+++ b/ForgeTracker/forgetracker/tests/functional/test_root.py
@@ -1606,16 +1606,16 @@ class TestFunctionalController(TrackerTestController):
         ThreadLocalORMSession.flush_all()
         response = self.app.get('/p/test/bugs/?sort=summary+asc')
         ticket_rows = response.html.find('table', {'class': 'ticket-list'}).find('tbody')
-        assert_in('test first ticket', str(ticket_rows))
-        assert_in('test second ticket', str(ticket_rows))
+        assert_in('test first ticket', ticket_rows.text)
+        assert_in('test second ticket', ticket_rows.text)
         edit_link = response.html.find('a', {'title': 'Bulk Edit'})
         expected_link = "/p/test/bugs/edit/?q=%21status%3Awont-fix+%26%26+%21status%3Aclosed"\
                         "&sort=snippet_s+asc&limit=25&filter=&page=0"
         assert_equal(expected_link, edit_link['href'])
         response = self.app.get(edit_link['href'])
         ticket_rows = response.html.find('tbody', {'class': 'ticket-list'})
-        assert_in('test first ticket', str(ticket_rows))
-        assert_in('test second ticket', str(ticket_rows))
+        assert_in('test first ticket', ticket_rows.text)
+        assert_in('test second ticket', ticket_rows.text)
 
     def test_bulk_edit_milestone(self):
         self.new_ticket(summary='test first ticket',
@@ -1629,17 +1629,17 @@ class TestFunctionalController(TrackerTestController):
         ThreadLocalORMSession.flush_all()
         response = self.app.get('/p/test/bugs/milestone/1.0/?sort=ticket_num+asc')
         ticket_rows = response.html.find('table', {'class': 'ticket-list'}).find('tbody')
-        assert_in('test first ticket', str(ticket_rows))
-        assert_in('test second ticket', str(ticket_rows))
-        assert_in('test third ticket', str(ticket_rows))
+        assert_in('test first ticket', ticket_rows.text)
+        assert_in('test second ticket', ticket_rows.text)
+        assert_in('test third ticket', ticket_rows.text)
         edit_link = response.html.find('a', {'title': 'Bulk Edit'})
         expected_link = "/p/test/bugs/edit/?q=_milestone%3A1.0&sort=ticket_num_i+asc&limit=25&filter=&page=0"
         assert_equal(expected_link, edit_link['href'])
         response = self.app.get(edit_link['href'])
         ticket_rows = response.html.find('tbody', {'class': 'ticket-list'})
-        assert_in('test first ticket', str(ticket_rows))
-        assert_in('test second ticket', str(ticket_rows))
-        assert_in('test third ticket', str(ticket_rows))
+        assert_in('test first ticket', ticket_rows.text)
+        assert_in('test second ticket', ticket_rows.text)
+        assert_in('test third ticket', ticket_rows.text)
 
     def test_bulk_edit_search(self):
         self.new_ticket(summary='test first ticket', status='open')
@@ -1650,17 +1650,17 @@ class TestFunctionalController(TrackerTestController):
         ThreadLocalORMSession.flush_all()
         response = self.app.get('/p/test/bugs/search/?q=status%3Aopen')
         ticket_rows = response.html.find('table', {'class': 'ticket-list'}).find('tbody')
-        assert_in('test first ticket', str(ticket_rows))
-        assert_in('test second ticket', str(ticket_rows))
-        assert_false('test third ticket' in str(ticket_rows))
+        assert_in('test first ticket', ticket_rows.text)
+        assert_in('test second ticket', ticket_rows.text)
+        assert_false('test third ticket' in ticket_rows.text)
         edit_link = response.html.find('a', {'title': 'Bulk Edit'})
         expected_link = "/p/test/bugs/edit/?q=status%3Aopen&limit=25&filter=%7B%7D&page=0"
         assert_equal(expected_link, edit_link['href'])
         response = self.app.get(edit_link['href'])
         ticket_rows = response.html.find('tbody', {'class': 'ticket-list'})
-        assert_in('test first ticket', str(ticket_rows))
-        assert_in('test second ticket', str(ticket_rows))
-        assert_false('test third ticket' in str(ticket_rows))
+        assert_in('test first ticket', ticket_rows.text)
+        assert_in('test second ticket', ticket_rows.text)
+        assert_false('test third ticket' in ticket_rows.text)
 
     def test_new_ticket_notification_contains_attachments(self):
         file_name = 'tést_root.py'.encode('utf-8')


[allura] 05/12: [#7878] http headers must be str (not unicode in py2 or bytes in py3). Can be rolled back when in py3 exclusively I think

Posted by br...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

brondsem pushed a commit to branch db/7878
in repository https://gitbox.apache.org/repos/asf/allura.git

commit ca9454ea8d19420ffa7fd9068befba166dfd5a59
Author: Dave Brondsema <db...@slashdotmedia.com>
AuthorDate: Thu Jan 16 21:27:28 2020 +0000

    [#7878] http headers must be str (not unicode in py2 or bytes in py3).  Can be rolled back when in py3 exclusively I think
---
 Allura/allura/config/middleware.py                 |   4 +-
 Allura/allura/controllers/attachments.py           |   2 +-
 Allura/allura/controllers/auth.py                  |   2 +-
 Allura/allura/controllers/basetest_project_root.py |   2 +-
 Allura/allura/controllers/feed.py                  |   8 +-
 Allura/allura/controllers/project.py               |   2 +-
 Allura/allura/controllers/repository.py            |  12 +-
 Allura/allura/controllers/rest.py                  |   4 +-
 Allura/allura/controllers/root.py                  |   2 +-
 Allura/allura/controllers/task.py                  |   8 +-
 Allura/allura/lib/app_globals.py                   |   2 +-
 Allura/allura/lib/custom_middleware.py             |  22 +--
 Allura/allura/lib/decorators.py                    |   2 +-
 Allura/allura/lib/helpers.py                       |   2 +-
 Allura/allura/lib/utils.py                         |   9 +-
 Allura/allura/tests/functional/test_admin.py       |  12 +-
 Allura/allura/tests/functional/test_auth.py        | 156 +++++++--------
 Allura/allura/tests/functional/test_discuss.py     |  28 +--
 Allura/allura/tests/functional/test_feeds.py       |   3 +-
 Allura/allura/tests/functional/test_home.py        |   6 +-
 .../allura/tests/functional/test_neighborhood.py   | 210 ++++++++++-----------
 Allura/allura/tests/functional/test_rest.py        |  12 +-
 Allura/allura/tests/functional/test_root.py        |   6 +-
 Allura/allura/tests/functional/test_search.py      |   2 +-
 Allura/allura/tests/functional/test_site_admin.py  |  44 ++---
 .../allura/tests/functional/test_trovecategory.py  |   2 +-
 .../allura/tests/functional/test_user_profile.py   |   4 +-
 Allura/allura/tests/model/test_auth.py             |   2 +-
 Allura/allura/tests/model/test_filesystem.py       |   2 +-
 Allura/allura/tests/test_security.py               |   2 +-
 Allura/allura/tests/test_webhooks.py               |   6 +-
 .../allura/tests/unit/test_ldap_auth_provider.py   |   4 +-
 Allura/allura/webhooks.py                          |   2 +-
 AlluraTest/alluratest/controller.py                |   4 +-
 ForgeActivity/forgeactivity/main.py                |   6 +-
 .../forgeactivity/tests/functional/test_root.py    |   8 +-
 ForgeBlog/forgeblog/main.py                        |   6 +-
 ForgeBlog/forgeblog/tests/functional/test_rest.py  |  22 +--
 ForgeBlog/forgeblog/tests/functional/test_root.py  |  12 +-
 .../forgediscussion/tests/functional/test_forum.py |  16 +-
 .../tests/functional/test_forum_admin.py           |  14 +-
 .../forgediscussion/tests/functional/test_rest.py  |   8 +-
 .../forgegit/tests/functional/test_controllers.py  |  10 +-
 .../tests/github/functional/test_github.py         |   4 +-
 .../trac/tests/functional/test_trac.py             |   2 +-
 ForgeLink/forgelink/link_main.py                   |   2 +
 ForgeLink/forgelink/tests/functional/test_rest.py  |   8 +-
 .../forgeshorturl/tests/functional/test.py         |   6 +-
 .../forgetracker/tests/functional/test_root.py     |  72 +++----
 .../tests/unit/test_milestone_controller.py        |   2 +-
 ForgeTracker/forgetracker/tracker_main.py          |   6 +-
 ForgeWiki/forgewiki/tests/functional/test_root.py  | 171 ++++++++---------
 ForgeWiki/forgewiki/wiki_main.py                   |  14 +-
 scripts/perf/call_count.py                         |   2 +-
 54 files changed, 495 insertions(+), 486 deletions(-)

diff --git a/Allura/allura/config/middleware.py b/Allura/allura/config/middleware.py
index 1834cc6..e4f57f8 100644
--- a/Allura/allura/config/middleware.py
+++ b/Allura/allura/config/middleware.py
@@ -19,6 +19,8 @@
 
 """WSGI middleware initialization for the allura application."""
 from __future__ import unicode_literals
+
+import ast
 import importlib
 import mimetypes
 
@@ -164,7 +166,7 @@ def _make_core_app(root, global_conf, full_stack=True, **app_conf):
         use_cache=not asbool(global_conf['debug']),
         script_name=app_conf.get('ew.script_name', '/_ew_resources/'),
         url_base=app_conf.get('ew.url_base', '/_ew_resources/'),
-        extra_headers=eval(app_conf.get('ew.extra_headers', 'None')),
+        extra_headers=ast.literal_eval(app_conf.get('ew.extra_headers', '[]')),
         cache_max_age=asint(app_conf.get('ew.cache_header_seconds', 60*60*24*365)),
 
         # settings to pass through to jinja Environment for EW core widgets
diff --git a/Allura/allura/controllers/attachments.py b/Allura/allura/controllers/attachments.py
index 15419f2..9c762c7 100644
--- a/Allura/allura/controllers/attachments.py
+++ b/Allura/allura/controllers/attachments.py
@@ -49,7 +49,7 @@ class AttachmentsController(BaseController):
     def _lookup(self, filename=None, *args):
         if filename:
             if not args:
-                filename = request.path.rsplit('/', 1)[-1]
+                filename = request.path.rsplit(str('/'), 1)[-1]
             filename = unquote(filename)
             return self.AttachmentControllerClass(filename, self.artifact), args
         else:
diff --git a/Allura/allura/controllers/auth.py b/Allura/allura/controllers/auth.py
index b6e21c5..82ecdf3 100644
--- a/Allura/allura/controllers/auth.py
+++ b/Allura/allura/controllers/auth.py
@@ -392,7 +392,7 @@ class AuthController(BaseController):
             return_to = self._verify_return_to(kwargs.get('return_to'))
             redirect(return_to)
 
-    @expose(content_type='text/plain')
+    @expose(content_type=str('text/plain'))
     def refresh_repo(self, *repo_path):
         # post-commit hooks use this
         if not repo_path:
diff --git a/Allura/allura/controllers/basetest_project_root.py b/Allura/allura/controllers/basetest_project_root.py
index acdaf3e..ca2b4b6 100644
--- a/Allura/allura/controllers/basetest_project_root.py
+++ b/Allura/allura/controllers/basetest_project_root.py
@@ -112,7 +112,7 @@ class BasetestProjectRootController(WsgiDispatchController, ProjectController):
 
     def _perform_call(self, context):
         """ Called from a WebTest 'app' instance, going through TurboGears dispatcher code
-        Example: self.app.get('/auth/', extra_environ={'disable_auth_magic': "True"})
+        Example: self.app.get('/auth/', extra_environ={'disable_auth_magic': str("True")})
         """
         environ = context.request.environ
         c.app = None
diff --git a/Allura/allura/controllers/feed.py b/Allura/allura/controllers/feed.py
index b426717..5801f3a 100644
--- a/Allura/allura/controllers/feed.py
+++ b/Allura/allura/controllers/feed.py
@@ -66,8 +66,8 @@ class FeedController(object):
     a customized feed should override :meth:`get_feed`.
 
     """
-    FEED_TYPES = ['.atom', '.rss']
-    FEED_NAMES = ['feed{0}'.format(typ) for typ in FEED_TYPES]
+    FEED_TYPES = [str('.atom'), str('.rss')]
+    FEED_NAMES = [str('feed{0}'.format(typ)) for typ in FEED_TYPES]
 
     def __getattr__(self, name):
         if name in self.FEED_NAMES:
@@ -100,8 +100,8 @@ class FeedController(object):
             feed_def.url,
             feed_def.description,
             since, until, page, limit)
-        response.headers['Content-Type'] = ''
-        response.content_type = 'application/xml'
+        response.headers['Content-Type'] = str('')
+        response.content_type = str('application/xml')
         return feed.writeString('utf-8')
 
     def get_feed(self, project, app, user):
diff --git a/Allura/allura/controllers/project.py b/Allura/allura/controllers/project.py
index bdbad3c..79fd453 100644
--- a/Allura/allura/controllers/project.py
+++ b/Allura/allura/controllers/project.py
@@ -481,7 +481,7 @@ class ScreenshotsController(object):
         if args:
             filename = unquote(filename)
         else:
-            filename = unquote(request.path.rsplit('/', 1)[-1])
+            filename = unquote(request.path.rsplit(str('/'), 1)[-1])
         return ScreenshotController(filename), args
 
 
diff --git a/Allura/allura/controllers/repository.py b/Allura/allura/controllers/repository.py
index b4d5a33..9687741 100644
--- a/Allura/allura/controllers/repository.py
+++ b/Allura/allura/controllers/repository.py
@@ -800,7 +800,7 @@ class TreeBrowser(BaseController, DispatchIndex):
             # Might be a file rather than a dir
             filename = h.really_unicode(
                 unquote(
-                    request.environ['PATH_INFO'].rsplit('/')[-1]))
+                    request.environ['PATH_INFO'].rsplit(str('/'))[-1]))
             if filename:
                 try:
                     obj = self._tree[filename]
@@ -812,7 +812,7 @@ class TreeBrowser(BaseController, DispatchIndex):
                         self._tree,
                         filename), rest
         elif rest == ('index', ):
-            rest = (request.environ['PATH_INFO'].rsplit('/')[-1],)
+            rest = (request.environ['PATH_INFO'].rsplit(str('/'))[-1],)
         try:
             tree = self._tree[next]
         except KeyError:
@@ -870,15 +870,15 @@ class FileBrowser(BaseController):
     def raw(self, **kw):
         content_type = self._blob.content_type.encode('utf-8')
         filename = self._blob.name.encode('utf-8')
-        response.headers['Content-Type'] = ''
+        response.headers['Content-Type'] = str('')
         response.content_type = content_type
         if self._blob.content_encoding is not None:
             content_encoding = self._blob.content_encoding.encode('utf-8')
-            response.headers['Content-Encoding'] = ''
+            response.headers['Content-Encoding'] = str('')
             response.content_encoding = content_encoding
         response.headers.add(
-            'Content-Disposition',
-            'attachment;filename="%s"' % filename)
+            str('Content-Disposition'),
+            str('attachment;filename="%s"') % filename)
         return iter(self._blob)
 
     def diff(self, prev_commit, fmt=None, prev_file=None, **kw):
diff --git a/Allura/allura/controllers/rest.py b/Allura/allura/controllers/rest.py
index 8b81d99..532a073 100644
--- a/Allura/allura/controllers/rest.py
+++ b/Allura/allura/controllers/rest.py
@@ -391,8 +391,8 @@ class ProjectRestController(object):
     @expose('json:')
     def index(self, **kw):
         if 'doap' in kw:
-            response.headers['Content-Type'] = ''
-            response.content_type = 'application/rdf+xml'
+            response.headers['Content-Type'] = str('')
+            response.content_type = str('application/rdf+xml')
             return '<?xml version="1.0" encoding="UTF-8" ?>' + c.project.doap()
         return c.project.__json__()
 
diff --git a/Allura/allura/controllers/root.py b/Allura/allura/controllers/root.py
index 9eb4603..b8a41a6 100644
--- a/Allura/allura/controllers/root.py
+++ b/Allura/allura/controllers/root.py
@@ -113,7 +113,7 @@ class RootController(WsgiDispatchController):
             # pylons.configuration defaults to "no-cache" only.
             # See also http://blog.55minutes.com/2011/10/how-to-defeat-the-browser-back-button-cache/ and
             # https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/http-caching?hl=en#defining_optimal_cache-control_policy
-            response.headers['Cache-Control'] = 'no-cache, no-store, must-revalidate'
+            response.headers[str('Cache-Control')] = str('no-cache, no-store, must-revalidate')
 
     @expose()
     @with_trailing_slash
diff --git a/Allura/allura/controllers/task.py b/Allura/allura/controllers/task.py
index f7651f4..1429ac9 100644
--- a/Allura/allura/controllers/task.py
+++ b/Allura/allura/controllers/task.py
@@ -17,6 +17,10 @@
 
 
 from __future__ import unicode_literals
+
+import six
+
+
 class TaskController(object):
 
     '''WSGI app providing web-like RPC
@@ -33,6 +37,6 @@ class TaskController(object):
         nocapture = environ['nocapture']
         result = task(restore_context=False, nocapture=nocapture)
         py_response = context.response
-        py_response.headers['Content-Type'] = 'text/plain'  # `None` default is problematic for some middleware
-        py_response.body = result or ''
+        py_response.headers['Content-Type'] = str('text/plain')  # `None` default is problematic for some middleware
+        py_response.body = six.ensure_binary(result or b'')
         return py_response
diff --git a/Allura/allura/lib/app_globals.py b/Allura/allura/lib/app_globals.py
index 5f783e4..05e284f 100644
--- a/Allura/allura/lib/app_globals.py
+++ b/Allura/allura/lib/app_globals.py
@@ -354,7 +354,7 @@ class Globals(object):
             except AttributeError:
                 script_without_ming_middleware = True
             else:
-                script_without_ming_middleware = env['PATH_INFO'] == '--script--'
+                script_without_ming_middleware = env['PATH_INFO'] == str('--script--')
             if script_without_ming_middleware:
                 kwargs['flush_immediately'] = True
             else:
diff --git a/Allura/allura/lib/custom_middleware.py b/Allura/allura/lib/custom_middleware.py
index 79092fa..ab7ba60 100644
--- a/Allura/allura/lib/custom_middleware.py
+++ b/Allura/allura/lib/custom_middleware.py
@@ -76,10 +76,10 @@ class StaticFilesMiddleware(object):
                 resource_cls = ep.load().has_resource(resource_path)
                 if resource_cls:
                     file_path = pkg_resources.resource_filename(resource_cls.__module__, resource_path)
-                    return fileapp.FileApp(file_path, [('Access-Control-Allow-Origin', '*')])
+                    return fileapp.FileApp(file_path, [(str('Access-Control-Allow-Origin'), str('*'))])
         filename = environ['PATH_INFO'][len(self.script_name):]
         file_path = pkg_resources.resource_filename('allura', os.path.join('public', 'nf', filename))
-        return fileapp.FileApp(file_path, [('Access-Control-Allow-Origin', '*')])
+        return fileapp.FileApp(file_path, [(str('Access-Control-Allow-Origin'), str('*'))])
 
 
 class CORSMiddleware(object):
@@ -92,7 +92,7 @@ class CORSMiddleware(object):
         self.cache_preflight = cache or None
 
     def __call__(self, environ, start_response):
-        is_api_request = environ.get('PATH_INFO', '').startswith('/rest/')
+        is_api_request = environ.get('PATH_INFO', '').startswith(str('/rest/'))
         valid_cors = 'HTTP_ORIGIN' in environ
         if not is_api_request or not valid_cors:
             return self.app(environ, start_response)
@@ -121,17 +121,17 @@ class CORSMiddleware(object):
         return r(environ, start_response)
 
     def get_response_headers(self, preflight=False):
-        headers = [('Access-Control-Allow-Origin', '*')]
+        headers = [(str('Access-Control-Allow-Origin'), str('*'))]
         if preflight:
             ac_methods = ', '.join(self.allowed_methods)
             ac_headers = ', '.join(self.allowed_headers)
             headers.extend([
-                ('Access-Control-Allow-Methods', ac_methods),
-                ('Access-Control-Allow-Headers', ac_headers),
+                (str('Access-Control-Allow-Methods'), str(ac_methods)),
+                (str('Access-Control-Allow-Headers'), str(ac_headers)),
             ])
             if self.cache_preflight:
                 headers.append(
-                    ('Access-Control-Max-Age', str(self.cache_preflight))
+                    (str('Access-Control-Max-Age'), str(self.cache_preflight))
                 )
         return headers
 
@@ -151,7 +151,7 @@ class LoginRedirectMiddleware(object):
 
     def __call__(self, environ, start_response):
         status, headers, app_iter, exc_info = call_wsgi_application(self.app, environ)
-        is_api_request = environ.get('PATH_INFO', '').startswith('/rest/')
+        is_api_request = environ.get('PATH_INFO', '').startswith(str('/rest/'))
         if status[:3] == '401' and not is_api_request:
             login_url = tg.config.get('auth.login_url', '/auth/')
             if environ['REQUEST_METHOD'] == 'GET':
@@ -207,7 +207,7 @@ class CSRFMiddleware(object):
         def session_start_response(status, headers, exc_info=None):
             if dict(headers).get('Content-Type', '').startswith('text/html'):
                 headers.append(
-                    ('Set-cookie',
+                    (str('Set-cookie'),
                      str('%s=%s; Path=/' % (self._cookie_name, cookie))))
             return start_response(status, headers, exc_info)
 
@@ -397,12 +397,12 @@ class RememberLoginMiddleware(object):
                     session._set_cookie_expires(login_expires)
                 # Replace the cookie header that SessionMiddleware set
                 # with one that has the new expires parameter value
-                cookie = session.cookie[session.key].output(header='')
+                cookie = session.cookie[session.key].output(header=str(''))
                 for i in range(len(headers)):
                     header, contents = headers[i]
                     if header == 'Set-cookie' and \
                             contents.lstrip().startswith(session.key):
-                        headers[i] = ('Set-cookie', cookie)
+                        headers[i] = (str('Set-cookie'), cookie)
                         break
             return start_response(status, headers, exc_info)
 
diff --git a/Allura/allura/lib/decorators.py b/Allura/allura/lib/decorators.py
index eed03b7..95ab5a9 100644
--- a/Allura/allura/lib/decorators.py
+++ b/Allura/allura/lib/decorators.py
@@ -110,7 +110,7 @@ class require_post(object):
             if request.method != 'POST':
                 if self.redir is not None:
                     redirect(self.redir)
-                raise exc.HTTPMethodNotAllowed(headers={'Allow': 'POST'})
+                raise exc.HTTPMethodNotAllowed(headers={str('Allow'): str('POST')})
         before_validate(check_method)(func)
         return func
 
diff --git a/Allura/allura/lib/helpers.py b/Allura/allura/lib/helpers.py
index c39ee78..2ded70d 100644
--- a/Allura/allura/lib/helpers.py
+++ b/Allura/allura/lib/helpers.py
@@ -598,7 +598,7 @@ class fixed_attrs_proxy(proxy):
             setattr(self, k, v)
 
 
-@tg.expose(content_type='text/plain')
+@tg.expose(content_type=str('text/plain'))
 def json_validation_error(controller, **kwargs):
     exc = request.validation['exception']
     result = dict(status='Validation Error',
diff --git a/Allura/allura/lib/utils.py b/Allura/allura/lib/utils.py
index 2e6ea3b..a88e553 100644
--- a/Allura/allura/lib/utils.py
+++ b/Allura/allura/lib/utils.py
@@ -502,7 +502,7 @@ def serve_file(fp, filename, content_type, last_modified=None,
         etag = '{0}?{1}'.format(filename, last_modified).encode('utf-8')
     if etag:
         etag_cache(etag)
-    tg.response.headers['Content-Type'] = ''
+    tg.response.headers['Content-Type'] = str('')
     tg.response.content_type = content_type.encode('utf-8')
     tg.response.cache_expires = cache_expires or asint(
         tg.config.get('files_expires_header_secs', 60 * 60))
@@ -514,15 +514,16 @@ def serve_file(fp, filename, content_type, last_modified=None,
     if 'Cache-Control' in tg.response.headers:
         del tg.response.headers['Cache-Control']
     if not embed:
+        from allura.lib import helpers as h
         tg.response.headers.add(
-            'Content-Disposition',
-            'attachment;filename="%s"' % filename.encode('utf-8'))
+            str('Content-Disposition'),
+            str('attachment;filename="%s"' % h.urlquote(filename)))
     # http://code.google.com/p/modwsgi/wiki/FileWrapperExtension
     block_size = 4096
     if 'wsgi.file_wrapper' in tg.request.environ:
         return tg.request.environ['wsgi.file_wrapper'](fp, block_size)
     else:
-        return iter(lambda: fp.read(block_size), '')
+        return iter(lambda: fp.read(block_size), b'')
 
 
 class ForgeHTMLSanitizerFilter(html5lib.filters.sanitizer.Filter):
diff --git a/Allura/allura/tests/functional/test_admin.py b/Allura/allura/tests/functional/test_admin.py
index 66e6d32..6a50b1b 100644
--- a/Allura/allura/tests/functional/test_admin.py
+++ b/Allura/allura/tests/functional/test_admin.py
@@ -827,7 +827,7 @@ class TestProjectAdmin(TestController):
         # make sure can still access homepage after one of user's roles were
         # deleted
         r = self.app.get('/p/test/wiki/',
-                         extra_environ=dict(username='test-user')).follow()
+                         extra_environ=dict(username=str('test-user'))).follow()
         assert r.status == '200 OK'
 
     def test_change_perms(self):
@@ -977,17 +977,17 @@ class TestExport(TestController):
 
     def test_access(self):
         r = self.app.get('/admin/export',
-                         extra_environ={'username': '*anonymous'}).follow()
+                         extra_environ={'username': str('*anonymous')}).follow()
         assert_equals(r.request.url,
                       'http://localhost/auth/?return_to=%2Fadmin%2Fexport')
         self.app.get('/admin/export',
-                     extra_environ={'username': 'test-user'},
+                     extra_environ={'username': str('test-user')},
                      status=403)
         r = self.app.post('/admin/export',
-                          extra_environ={'username': '*anonymous'}).follow()
+                          extra_environ={'username': str('*anonymous')}).follow()
         assert_equals(r.request.url, 'http://localhost/auth/')
         self.app.post('/admin/export',
-                      extra_environ={'username': 'test-user'},
+                      extra_environ={'username': str('test-user')},
                       status=403)
 
     def test_ini_option(self):
@@ -1262,7 +1262,7 @@ class TestRestInstallTool(TestRestApiBase):
             'mount_label': 'wiki_label1'
         }
         r = self.app.post('/rest/p/test/admin/install_tool/',
-                          extra_environ={'username': '*anonymous'},
+                          extra_environ={'username': str('*anonymous')},
                           status=401,
                           params=data)
         assert_equals(r.status, '401 Unauthorized')
diff --git a/Allura/allura/tests/functional/test_auth.py b/Allura/allura/tests/functional/test_auth.py
index 653609e..4f326db 100644
--- a/Allura/allura/tests/functional/test_auth.py
+++ b/Allura/allura/tests/functional/test_auth.py
@@ -84,7 +84,7 @@ class TestAuth(TestController):
         r = self.app.post('/auth/do_login', antispam=True, params=dict(
             username='test-user', password='foo', honey1='robot',  # bad honeypot value
             _session_id=self.app.cookies['_session_id']),
-                          extra_environ={'regular_antispam_err_handling_even_when_tests': 'true'},
+                          extra_environ={'regular_antispam_err_handling_even_when_tests': str('true')},
                           status=302)
         wf = json.loads(self.webflash(r))
         assert_equal(wf['status'], 'error')
@@ -102,18 +102,18 @@ class TestAuth(TestController):
         assert 'Invalid login' in str(r), r.showbrowser()
 
     def test_login_invalid_username(self):
-        extra = {'username': '*anonymous'}
+        extra = {'username': str('*anonymous')}
         r = self.app.get('/auth/', extra_environ=extra)
         f = r.forms[0]
         encoded = self.app.antispam_field_names(f)
         f[encoded['username']] = 'test@user.com'
         f[encoded['password']] = 'foo'
-        r = f.submit(extra_environ={'username': '*anonymous'})
+        r = f.submit(extra_environ={'username': str('*anonymous')})
         r.mustcontain('Usernames only include small letters, ')
 
     def test_login_diff_ips_ok(self):
         # exercises AntiSpam.validate methods
-        extra = {'username': '*anonymous', 'REMOTE_ADDR': '11.22.33.44'}
+        extra = {'username': str('*anonymous'), 'REMOTE_ADDR': str('11.22.33.44')}
         r = self.app.get('/auth/', extra_environ=extra)
 
         f = r.forms[0]
@@ -121,19 +121,19 @@ class TestAuth(TestController):
         f[encoded['username']] = 'test-user'
         f[encoded['password']] = 'foo'
         with audits('Successful login', user=True):
-            r = f.submit(extra_environ={'username': '*anonymous', 'REMOTE_ADDR': '11.22.33.99'})
+            r = f.submit(extra_environ={'username': str('*anonymous'), 'REMOTE_ADDR': str('11.22.33.99')})
 
     def test_login_diff_ips_bad(self):
         # exercises AntiSpam.validate methods
-        extra = {'username': '*anonymous', 'REMOTE_ADDR': '24.52.32.123'}
+        extra = {'username': str('*anonymous'), 'REMOTE_ADDR': str('24.52.32.123')}
         r = self.app.get('/auth/', extra_environ=extra)
 
         f = r.forms[0]
         encoded = self.app.antispam_field_names(f)
         f[encoded['username']] = 'test-user'
         f[encoded['password']] = 'foo'
-        r = f.submit(extra_environ={'username': '*anonymous', 'REMOTE_ADDR': '11.22.33.99',
-                                    'regular_antispam_err_handling_even_when_tests': 'true'},
+        r = f.submit(extra_environ={'username': str('*anonymous'), 'REMOTE_ADDR': str('11.22.33.99'),
+                                    'regular_antispam_err_handling_even_when_tests': str('true')},
                      status=302)
         wf = json.loads(self.webflash(r))
         assert_equal(wf['status'], 'error')
@@ -143,7 +143,7 @@ class TestAuth(TestController):
     @patch('allura.tasks.mail_tasks.sendsimplemail')
     def test_login_hibp_compromised_password_untrusted_client(self, sendsimplemail):
         # first & only login by this user, so won't have any trusted previous logins
-        self.app.extra_environ = {'disable_auth_magic': 'True'}
+        self.app.extra_environ = {'disable_auth_magic': str('True')}
         r = self.app.get('/auth/')
         f = r.forms[0]
         encoded = self.app.antispam_field_names(f)
@@ -165,7 +165,7 @@ class TestAuth(TestController):
 
     @patch('allura.tasks.mail_tasks.sendsimplemail')
     def test_login_hibp_compromised_password_trusted_client(self, sendsimplemail):
-        self.app.extra_environ = {'disable_auth_magic': 'True'}
+        self.app.extra_environ = {'disable_auth_magic': str('True')}
 
         # regular login first, so IP address will be recorded and then trusted
         r = self.app.get('/auth/')
@@ -201,43 +201,43 @@ class TestAuth(TestController):
     def test_login_disabled(self):
         u = M.User.query.get(username='test-user')
         u.disabled = True
-        r = self.app.get('/auth/', extra_environ={'username': '*anonymous'})
+        r = self.app.get('/auth/', extra_environ={'username': str('*anonymous')})
         f = r.forms[0]
         encoded = self.app.antispam_field_names(f)
         f[encoded['username']] = 'test-user'
         f[encoded['password']] = 'foo'
         with audits('Failed login', user=True):
-            r = f.submit(extra_environ={'username': '*anonymous'})
+            r = f.submit(extra_environ={'username': str('*anonymous')})
 
     def test_login_pending(self):
         u = M.User.query.get(username='test-user')
         u.pending = True
-        r = self.app.get('/auth/', extra_environ={'username': '*anonymous'})
+        r = self.app.get('/auth/', extra_environ={'username': str('*anonymous')})
         f = r.forms[0]
         encoded = self.app.antispam_field_names(f)
         f[encoded['username']] = 'test-user'
         f[encoded['password']] = 'foo'
         with audits('Failed login', user=True):
-            r = f.submit(extra_environ={'username': '*anonymous'})
+            r = f.submit(extra_environ={'username': str('*anonymous')})
 
     def test_login_overlay(self):
-        r = self.app.get('/auth/login_fragment/', extra_environ={'username': '*anonymous'})
+        r = self.app.get('/auth/login_fragment/', extra_environ={'username': str('*anonymous')})
         f = r.forms[0]
         encoded = self.app.antispam_field_names(f)
         f[encoded['username']] = 'test-user'
         f[encoded['password']] = 'foo'
         with audits('Successful login', user=True):
-            r = f.submit(extra_environ={'username': '*anonymous'})
+            r = f.submit(extra_environ={'username': str('*anonymous')})
 
     def test_logout(self):
-        self.app.extra_environ = {'disable_auth_magic': 'True'}
+        self.app.extra_environ = {'disable_auth_magic': str('True')}
         nav_pattern = ('nav', {'class': 'nav-main'})
         r = self.app.get('/auth/')
 
         r = self.app.post('/auth/do_login', params=dict(
             username='test-user', password='foo',
             _session_id=self.app.cookies['_session_id']),
-            extra_environ={'REMOTE_ADDR': '127.0.0.1'},
+            extra_environ={'REMOTE_ADDR': str('127.0.0.1')},
             antispam=True).follow().follow()
 
         logged_in_session = r.session['_id']
@@ -258,8 +258,8 @@ class TestAuth(TestController):
 
         self.app.get('/').follow()  # establish session
         self.app.post('/auth/do_login',
-                      headers={'User-Agent': 'browser'},
-                      extra_environ={'REMOTE_ADDR': '127.0.0.1'},
+                      headers={str('User-Agent'): str('browser')},
+                      extra_environ={'REMOTE_ADDR': str('127.0.0.1')},
                       params=dict(
                           username='test-user',
                           password='foo',
@@ -316,7 +316,7 @@ class TestAuth(TestController):
                           'password': 'foo',
                           '_session_id': self.app.cookies['_session_id'],
                       },
-                      extra_environ=dict(username='test-admin'))
+                      extra_environ=dict(username=str('test-admin')))
 
         assert M.EmailAddress.find(dict(email=email_address, claimed_by_user_id=user._id)).count() == 1
         r = self.app.post('/auth/preferences/update_emails',
@@ -328,7 +328,7 @@ class TestAuth(TestController):
                               'password': 'foo',
                               '_session_id': self.app.cookies['_session_id'],
                           },
-                          extra_environ=dict(username='test-admin'))
+                          extra_environ=dict(username=str('test-admin')))
 
         assert json.loads(self.webflash(r))['status'] == 'error', self.webflash(r)
         assert M.EmailAddress.find(dict(email=email_address, claimed_by_user_id=user._id)).count() == 1
@@ -362,7 +362,7 @@ class TestAuth(TestController):
                               'password': 'foo',
                               '_session_id': self.app.cookies['_session_id'],
                           },
-                          extra_environ=dict(username='test-admin'))
+                          extra_environ=dict(username=str('test-admin')))
 
         assert json.loads(self.webflash(r))['status'] == 'ok'
         assert json.loads(self.webflash(r))['message'] == 'A verification email has been sent.  ' \
@@ -406,7 +406,7 @@ class TestAuth(TestController):
                               'password': 'foo',
                               '_session_id': self.app.cookies['_session_id'],
                           },
-                          extra_environ=dict(username='test-user-1'))
+                          extra_environ=dict(username=str('test-user-1')))
 
         assert json.loads(self.webflash(r))['status'] == 'ok'
         assert json.loads(self.webflash(r))['message'] == 'A verification email has been sent.  ' \
@@ -430,7 +430,7 @@ class TestAuth(TestController):
                                   'password': 'foo',
                                   '_session_id': self.app.cookies['_session_id'],
                               },
-                              extra_environ=dict(username='test-user-1'))
+                              extra_environ=dict(username=str('test-user-1')))
             assert json.loads(self.webflash(r))['status'] == 'ok'
 
             r = self.app.post('/auth/preferences/update_emails',
@@ -442,7 +442,7 @@ class TestAuth(TestController):
                                   'password': 'foo',
                                   '_session_id': self.app.cookies['_session_id'],
                               },
-                              extra_environ=dict(username='test-user-1'))
+                              extra_environ=dict(username=str('test-user-1')))
 
             assert json.loads(self.webflash(r))['status'] == 'error'
             assert json.loads(self.webflash(r))['message'] == 'You cannot claim more than 2 email addresses.'
@@ -468,7 +468,7 @@ class TestAuth(TestController):
 
         r = self.app.post('/auth/send_verification_link',
                           params=dict(a=email_address, _session_id=self.app.cookies['_session_id']),
-                          extra_environ=dict(username='test-user-1', _session_id=self.app.cookies['_session_id']))
+                          extra_environ=dict(username=str('test-user-1'), _session_id=self.app.cookies['_session_id']))
 
         assert json.loads(self.webflash(r))['status'] == 'ok'
         assert json.loads(self.webflash(r))['message'] == 'Verification link sent'
@@ -494,7 +494,7 @@ class TestAuth(TestController):
         self.app.post('/auth/send_verification_link',
                       params=dict(a=email_address,
                                   _session_id=self.app.cookies['_session_id']),
-                      extra_environ=dict(username='test-user'))
+                      extra_environ=dict(username=str('test-user')))
 
         user1 = M.User.query.get(username='test-user-1')
         user1.claim_address(email_address)
@@ -502,7 +502,7 @@ class TestAuth(TestController):
         email1.confirmed = True
         ThreadLocalORMSession.flush_all()
         # Verify first email with the verification link
-        r = self.app.get('/auth/verify_addr', params=dict(a=email.nonce), extra_environ=dict(username='test-user'))
+        r = self.app.get('/auth/verify_addr', params=dict(a=email.nonce), extra_environ=dict(username=str('test-user')))
 
         assert json.loads(self.webflash(r))['status'] == 'error'
         email = M.EmailAddress.find(dict(email=email_address, claimed_by_user_id=user._id)).first()
@@ -522,20 +522,20 @@ class TestAuth(TestController):
         self.app.post('/auth/send_verification_link',
                       params=dict(a=email_address,
                                   _session_id=self.app.cookies['_session_id']),
-                      extra_environ=dict(username='test-user'))
+                      extra_environ=dict(username=str('test-user')))
 
         # logged out, gets redirected to login page
-        r = self.app.get('/auth/verify_addr', params=dict(a=email.nonce), extra_environ=dict(username='*anonymous'))
+        r = self.app.get('/auth/verify_addr', params=dict(a=email.nonce), extra_environ=dict(username=str('*anonymous')))
         assert_in('/auth/?return_to=%2Fauth%2Fverify_addr', r.location)
 
         # logged in as someone else
-        r = self.app.get('/auth/verify_addr', params=dict(a=email.nonce), extra_environ=dict(username='test-admin'))
+        r = self.app.get('/auth/verify_addr', params=dict(a=email.nonce), extra_environ=dict(username=str('test-admin')))
         assert_in('/auth/?return_to=%2Fauth%2Fverify_addr', r.location)
         assert_equal('You must be logged in to the correct account', json.loads(self.webflash(r))['message'])
         assert_equal('warning', json.loads(self.webflash(r))['status'])
 
         # logged in as correct user
-        r = self.app.get('/auth/verify_addr', params=dict(a=email.nonce), extra_environ=dict(username='test-user'))
+        r = self.app.get('/auth/verify_addr', params=dict(a=email.nonce), extra_environ=dict(username=str('test-user')))
         assert_in('confirmed', json.loads(self.webflash(r))['message'])
         assert_equal('ok', json.loads(self.webflash(r))['status'])
 
@@ -595,7 +595,7 @@ class TestAuth(TestController):
         self.app.get('/').follow()  # establish session
         change_params['_session_id'] = self.app.cookies['_session_id']
         self.app.post('/auth/preferences/update_emails',
-                      extra_environ=dict(username='test-admin'),
+                      extra_environ=dict(username=str('test-admin')),
                       params=change_params)
 
         u = M.User.by_username('test-admin')
@@ -613,7 +613,7 @@ class TestAuth(TestController):
         # Change password
         with audits('Password changed', user=True):
             self.app.post('/auth/preferences/change_password',
-                          extra_environ=dict(username='test-admin'),
+                          extra_environ=dict(username=str('test-admin')),
                           params={
                               'oldpw': 'foo',
                               'pw': 'asdfasdf',
@@ -644,7 +644,7 @@ class TestAuth(TestController):
 
         # Attempt change password with weak pwd
         r = self.app.post('/auth/preferences/change_password',
-                      extra_environ=dict(username='test-admin'),
+                      extra_environ=dict(username=str('test-admin')),
                       params={
                           'oldpw': 'foo',
                           'pw': 'password',
@@ -655,7 +655,7 @@ class TestAuth(TestController):
         assert 'Unsafe' in str(r.headers)
 
         r = self.app.post('/auth/preferences/change_password',
-                          extra_environ=dict(username='test-admin'),
+                          extra_environ=dict(username=str('test-admin')),
                           params={
                               'oldpw': 'foo',
                               'pw': '3j84rhoirwnoiwrnoiw',
@@ -671,7 +671,7 @@ class TestAuth(TestController):
     @td.with_user_project('test-admin')
     def test_prefs(self):
         r = self.app.get('/auth/preferences/',
-                         extra_environ=dict(username='test-admin'))
+                         extra_environ=dict(username=str('test-admin')))
         # check preconditions of test data
         assert 'test@example.com' not in r
         assert 'test-admin@users.localhost' in r
@@ -681,7 +681,7 @@ class TestAuth(TestController):
         # add test@example
         with td.audits('New email address: test@example.com', user=True):
             r = self.app.post('/auth/preferences/update_emails',
-                              extra_environ=dict(username='test-admin'),
+                              extra_environ=dict(username=str('test-admin')),
                               params={
                                   'new_addr.addr': 'test@example.com',
                                   'new_addr.claim': 'Claim Address',
@@ -698,7 +698,7 @@ class TestAuth(TestController):
         # remove test-admin@users.localhost
         with td.audits('Email address deleted: test-admin@users.localhost', user=True):
             r = self.app.post('/auth/preferences/update_emails',
-                              extra_environ=dict(username='test-admin'),
+                              extra_environ=dict(username=str('test-admin')),
                               params={
                                   'addr-1.ord': '1',
                                   'addr-1.delete': 'on',
@@ -720,7 +720,7 @@ class TestAuth(TestController):
                               params={'preferences.display_name': 'Admin',
                                       '_session_id': self.app.cookies['_session_id'],
                                       },
-                              extra_environ=dict(username='test-admin'))
+                              extra_environ=dict(username=str('test-admin')))
 
     @td.with_user_project('test-admin')
     def test_email_prefs_change_requires_password(self):
@@ -734,21 +734,21 @@ class TestAuth(TestController):
         }
         r = self.app.post('/auth/preferences/update_emails',
                           params=new_email_params,
-                          extra_environ=dict(username='test-admin'))
+                          extra_environ=dict(username=str('test-admin')))
         assert_in('You must provide your current password to claim new email', self.webflash(r))
         assert_not_in('test@example.com', r.follow())
         new_email_params['password'] = 'bad pass'
 
         r = self.app.post('/auth/preferences/update_emails',
                           params=new_email_params,
-                          extra_environ=dict(username='test-admin'))
+                          extra_environ=dict(username=str('test-admin')))
         assert_in('You must provide your current password to claim new email', self.webflash(r))
         assert_not_in('test@example.com', r.follow())
         new_email_params['password'] = 'foo'  # valid password
 
         r = self.app.post('/auth/preferences/update_emails',
                           params=new_email_params,
-                          extra_environ=dict(username='test-admin'))
+                          extra_environ=dict(username=str('test-admin')))
         assert_not_in('You must provide your current password to claim new email', self.webflash(r))
         assert_in('test@example.com', r.follow())
 
@@ -760,14 +760,14 @@ class TestAuth(TestController):
         }
         r = self.app.post('/auth/preferences/update_emails',
                           params=change_primary_params,
-                          extra_environ=dict(username='test-admin'))
+                          extra_environ=dict(username=str('test-admin')))
         assert_in('You must provide your current password to change primary address', self.webflash(r))
         assert_equal(M.User.by_username('test-admin').get_pref('email_address'), 'test-admin@users.localhost')
         change_primary_params['password'] = 'bad pass'
 
         r = self.app.post('/auth/preferences/update_emails',
                           params=change_primary_params,
-                          extra_environ=dict(username='test-admin'))
+                          extra_environ=dict(username=str('test-admin')))
         assert_in('You must provide your current password to change primary address', self.webflash(r))
         assert_equal(M.User.by_username('test-admin').get_pref('email_address'), 'test-admin@users.localhost')
         change_primary_params['password'] = 'foo'  # valid password
@@ -775,7 +775,7 @@ class TestAuth(TestController):
         self.app.get('/auth/preferences/')  # let previous 'flash' message cookie get used up
         r = self.app.post('/auth/preferences/update_emails',
                           params=change_primary_params,
-                          extra_environ=dict(username='test-admin'))
+                          extra_environ=dict(username=str('test-admin')))
         assert_not_in('You must provide your current password to change primary address', self.webflash(r))
         assert_equal(M.User.by_username('test-admin').get_pref('email_address'), 'test@example.com')
 
@@ -790,26 +790,26 @@ class TestAuth(TestController):
         }
         r = self.app.post('/auth/preferences/update_emails',
                           params=remove_email_params,
-                          extra_environ=dict(username='test-admin'))
+                          extra_environ=dict(username=str('test-admin')))
         assert_in('You must provide your current password to delete an email', self.webflash(r))
         assert_in('test@example.com', r.follow())
         remove_email_params['password'] = 'bad pass'
         r = self.app.post('/auth/preferences/update_emails',
                           params=remove_email_params,
-                          extra_environ=dict(username='test-admin'))
+                          extra_environ=dict(username=str('test-admin')))
         assert_in('You must provide your current password to delete an email', self.webflash(r))
         assert_in('test@example.com', r.follow())
         remove_email_params['password'] = 'foo'  # vallid password
         r = self.app.post('/auth/preferences/update_emails',
                           params=remove_email_params,
-                          extra_environ=dict(username='test-admin'))
+                          extra_environ=dict(username=str('test-admin')))
         assert_not_in('You must provide your current password to delete an email', self.webflash(r))
         assert_not_in('test@example.com', r.follow())
 
     @td.with_user_project('test-admin')
     def test_prefs_subscriptions(self):
         r = self.app.get('/auth/subscriptions/',
-                         extra_environ=dict(username='test-admin'))
+                         extra_environ=dict(username=str('test-admin')))
         subscriptions = M.Mailbox.query.find(dict(
             user_id=c.user._id, is_flash=False)).all()
         # make sure page actually lists all the user's subscriptions
@@ -868,7 +868,7 @@ class TestAuth(TestController):
     @td.with_user_project('test-admin')
     def test_prefs_subscriptions_subscribe(self):
         resp = self.app.get('/auth/subscriptions/',
-                            extra_environ=dict(username='test-admin'))
+                            extra_environ=dict(username=str('test-admin')))
         form = self._find_subscriptions_form(resp)
         # find not subscribed tool, subscribe and verify
         field_name = self._find_subscriptions_field(form, subscribed=False)
@@ -884,7 +884,7 @@ class TestAuth(TestController):
     @td.with_user_project('test-admin')
     def test_prefs_subscriptions_unsubscribe(self):
         resp = self.app.get('/auth/subscriptions/',
-                            extra_environ=dict(username='test-admin'))
+                            extra_environ=dict(username=str('test-admin')))
         form = self._find_subscriptions_form(resp)
         field_name = self._find_subscriptions_field(form, subscribed=True)
         s_id = ObjectId(form.fields[field_name + '.subscription_id'][0].value)
@@ -1047,7 +1047,7 @@ class TestAuth(TestController):
             dict(user_id=user._id, project_id=p._id)).count() == 0
 
         self.app.get('/p/test/admin/permissions',
-                     extra_environ=dict(username='aaa'), status=403)
+                     extra_environ=dict(username=str('aaa')), status=403)
         assert M.ProjectRole.query.find(
             dict(user_id=user._id, project_id=p._id)).count() <= 1
 
@@ -1060,7 +1060,7 @@ class TestAuth(TestController):
         sess = session(user)
         assert not user.disabled
         r = self.app.get('/p/test/admin/',
-                         extra_environ={'username': 'test-admin'})
+                         extra_environ={'username': str('test-admin')})
         assert_equal(r.status_int, 200, 'Redirect to %s' % r.location)
         user.disabled = True
         sess.save(user)
@@ -1068,7 +1068,7 @@ class TestAuth(TestController):
         user = M.User.query.get(username='test-admin')
         assert user.disabled
         r = self.app.get('/p/test/admin/',
-                         extra_environ={'username': 'test-admin'})
+                         extra_environ={'username': str('test-admin')})
         assert_equal(r.status_int, 302)
         assert_equal(r.location, 'http://localhost/auth/?return_to=%2Fp%2Ftest%2Fadmin%2F')
 
@@ -1509,7 +1509,7 @@ class TestPasswordReset(TestController):
     def setUp(self):
         super(TestPasswordReset, self).setUp()
         # so test-admin isn't automatically logged in for all requests
-        self.app.extra_environ = {'disable_auth_magic': 'True'}
+        self.app.extra_environ = {'disable_auth_magic': str('True')}
 
     @patch('allura.tasks.mail_tasks.sendmail')
     @patch('allura.lib.helpers.gen_message_id')
@@ -1768,7 +1768,7 @@ class TestOAuth(TestController):
                                   }, status=302)
         r = self.app.get('/auth/oauth/')
         assert_equal(r.forms[1].action, 'generate_access_token')
-        r = r.forms[1].submit(extra_environ={'username': 'test-user'})  # not the right user
+        r = r.forms[1].submit(extra_environ={'username': str('test-user')})  # not the right user
         assert_in("Invalid app ID", self.webflash(r))                   # gets an error
         r = self.app.get('/auth/oauth/')                                # do it again
         r = r.forms[1].submit()                                         # as correct user
@@ -2083,7 +2083,7 @@ class TestDisableAccount(TestController):
     def test_not_authenticated(self):
         r = self.app.get(
             '/auth/disable/',
-            extra_environ={'username': '*anonymous'})
+            extra_environ={'username': str('*anonymous')})
         assert_equal(r.status_int, 302)
         assert_equal(r.location,
                      'http://localhost/auth/?return_to=%2Fauth%2Fdisable%2F')
@@ -2125,21 +2125,21 @@ class TestDisableAccount(TestController):
 
 class TestPasswordExpire(TestController):
     def login(self, username='test-user', pwd='foo', query_string=''):
-        extra = extra_environ={'username': '*anonymous', 'REMOTE_ADDR':'127.0.0.1'}
+        extra = extra_environ={'username': str('*anonymous'), 'REMOTE_ADDR': str('127.0.0.1')}
         r = self.app.get('/auth/' + query_string, extra_environ=extra)
 
         f = r.forms[0]
         encoded = self.app.antispam_field_names(f)
         f[encoded['username']] = username
         f[encoded['password']] = pwd
-        return f.submit(extra_environ={'username': '*anonymous'})
+        return f.submit(extra_environ={'username': str('*anonymous')})
 
     def assert_redirects(self, where='/'):
-        resp = self.app.get(where, extra_environ={'username': 'test-user'}, status=302)
+        resp = self.app.get(where, extra_environ={'username': str('test-user')}, status=302)
         assert_equal(resp.location, 'http://localhost/auth/pwd_expired?' + urlencode({'return_to': where}))
 
     def assert_not_redirects(self, where='/neighborhood'):
-        self.app.get(where, extra_environ={'username': 'test-user'}, status=200)
+        self.app.get(where, extra_environ={'username': str('test-user')}, status=200)
 
     def test_disabled(self):
         r = self.login()
@@ -2191,7 +2191,7 @@ class TestPasswordExpire(TestController):
             r = self.login()
             assert_true(self.expired(r))
             self.assert_redirects()
-            r = self.app.get('/auth/logout', extra_environ={'username': 'test-user'})
+            r = self.app.get('/auth/logout', extra_environ={'username': str('test-user')})
             assert_false(self.expired(r))
             self.assert_not_redirects()
 
@@ -2205,12 +2205,12 @@ class TestPasswordExpire(TestController):
             user = M.User.by_username('test-user')
             old_update_time = user.last_password_updated
             old_password = user.password
-            r = self.app.get('/auth/pwd_expired', extra_environ={'username': 'test-user'})
+            r = self.app.get('/auth/pwd_expired', extra_environ={'username': str('test-user')})
             f = r.forms[0]
             f['oldpw'] = 'foo'
             f['pw'] = 'qwerty'
             f['pw2'] = 'qwerty'
-            r = f.submit(extra_environ={'username': 'test-user'}, status=302)
+            r = f.submit(extra_environ={'username': str('test-user')}, status=302)
             assert_equal(r.location, 'http://localhost/')
             assert_false(self.expired(r))
             user = M.User.by_username('test-user')
@@ -2245,12 +2245,12 @@ class TestPasswordExpire(TestController):
             session(user).flush(user)
 
             # Change expired password
-            r = self.app.get('/auth/pwd_expired', extra_environ={'username': 'test-user'})
+            r = self.app.get('/auth/pwd_expired', extra_environ={'username': str('test-user')})
             f = r.forms[0]
             f['oldpw'] = 'foo'
             f['pw'] = 'qwerty'
             f['pw2'] = 'qwerty'
-            r = f.submit(extra_environ={'username': 'test-user'}, status=302)
+            r = f.submit(extra_environ={'username': str('test-user')}, status=302)
             assert_equal(r.location, 'http://localhost/')
 
             user = M.User.by_username('test-user')
@@ -2264,12 +2264,12 @@ class TestPasswordExpire(TestController):
         user = M.User.by_username('test-user')
         old_update_time = user.last_password_updated
         old_password = user.password
-        r = self.app.get('/auth/pwd_expired', extra_environ={'username': 'test-user'})
+        r = self.app.get('/auth/pwd_expired', extra_environ={'username': str('test-user')})
         f = r.forms[0]
         f['oldpw'] = oldpw
         f['pw'] = pw
         f['pw2'] = pw2
-        r = f.submit(extra_environ={'username': 'test-user'})
+        r = f.submit(extra_environ={'username': str('test-user')})
         assert_true(self.expired(r))
         user = M.User.by_username('test-user')
         assert_equal(user.last_password_updated, old_update_time)
@@ -2308,20 +2308,20 @@ class TestPasswordExpire(TestController):
             # but if user tries to go directly there anyway, intercept and redirect back
             self.assert_redirects(where=return_to)
 
-            r = self.app.get('/auth/pwd_expired', extra_environ={'username': 'test-user'})
+            r = self.app.get('/auth/pwd_expired', extra_environ={'username': str('test-user')})
             f = r.forms[0]
             f['oldpw'] = 'foo'
             f['pw'] = 'qwerty'
             f['pw2'] = 'qwerty'
             f['return_to'] = return_to
-            r = f.submit(extra_environ={'username': 'test-user'}, status=302)
+            r = f.submit(extra_environ={'username': str('test-user')}, status=302)
             assert_equal(r.location, 'http://localhost/p/test/tickets/?milestone=1.0&page=2')
 
 
 class TestCSRFProtection(TestController):
     def test_blocks_invalid(self):
         # so test-admin isn't automatically logged in for all requests
-        self.app.extra_environ = {'disable_auth_magic': 'True', 'REMOTE_ADDR': '127.0.0.1'}
+        self.app.extra_environ = {'disable_auth_magic': str('True'), 'REMOTE_ADDR': str('127.0.0.1')}
 
         # regular login
         r = self.app.get('/auth/')
@@ -2545,7 +2545,7 @@ class TestTwoFactor(TestController):
         self._init_totp()
 
         # so test-admin isn't automatically logged in for all requests
-        self.app.extra_environ = {'disable_auth_magic': 'True'}
+        self.app.extra_environ = {'disable_auth_magic': str('True')}
 
         # regular login
         r = self.app.get('/auth/?return_to=/p/foo')
@@ -2582,7 +2582,7 @@ class TestTwoFactor(TestController):
         self._init_totp()
 
         # so test-admin isn't automatically logged in for all requests
-        self.app.extra_environ = {'disable_auth_magic': 'True'}
+        self.app.extra_environ = {'disable_auth_magic': str('True')}
 
         # regular login
         r = self.app.get('/auth/?return_to=/p/foo')
@@ -2613,7 +2613,7 @@ class TestTwoFactor(TestController):
         self._init_totp()
 
         # so test-admin isn't automatically logged in for all requests
-        self.app.extra_environ = {'disable_auth_magic': 'True'}
+        self.app.extra_environ = {'disable_auth_magic': str('True')}
 
         # regular login
         r = self.app.get('/auth/')
@@ -2642,7 +2642,7 @@ class TestTwoFactor(TestController):
         self._init_totp()
 
         # so test-admin isn't automatically logged in for all requests
-        self.app.extra_environ = {'disable_auth_magic': 'True'}
+        self.app.extra_environ = {'disable_auth_magic': str('True')}
 
         # regular login
         r = self.app.get('/auth/?return_to=/p/foo')
@@ -2689,7 +2689,7 @@ class TestTwoFactor(TestController):
         self._init_totp()
 
         # so test-admin isn't automatically logged in for all requests
-        self.app.extra_environ = {'disable_auth_magic': 'True'}
+        self.app.extra_environ = {'disable_auth_magic': str('True')}
 
         # regular login
         r = self.app.get('/auth/?return_to=/p/foo')
diff --git a/Allura/allura/tests/functional/test_discuss.py b/Allura/allura/tests/functional/test_discuss.py
index e94bf53..4a87fd1 100644
--- a/Allura/allura/tests/functional/test_discuss.py
+++ b/Allura/allura/tests/functional/test_discuss.py
@@ -81,8 +81,8 @@ class TestDiscuss(TestDiscussBase):
                 params[field['name']] = field.get('value') or ''
         params[f.find('textarea')['name']] = text
         r = self.app.post(f['action'].encode('utf-8'), params=params,
-                          headers={'Referer': thread_link.encode("utf-8")},
-                          extra_environ=dict(username='root'))
+                          headers={str('Referer'): str(thread_link.encode("utf-8"))},
+                          extra_environ=dict(username=str('root')))
         r = r.follow()
         return r
 
@@ -107,7 +107,7 @@ class TestDiscuss(TestDiscussBase):
         params[post_form.find('textarea')['name']] = 'This is a new post'
         r = self.app.post(post_link,
                           params=params,
-                          headers={'Referer': thread_link.encode("utf-8")})
+                          headers={str('Referer'): str(thread_link.encode("utf-8"))})
         r = r.follow()
         assert 'This is a new post' in r, r
         r = self.app.get(post_link)
@@ -121,7 +121,7 @@ class TestDiscuss(TestDiscussBase):
         params[post_form.find('textarea')['name']] = 'Tis a reply'
         r = self.app.post(post_link + 'reply',
                           params=params,
-                          headers={'Referer': post_link.encode("utf-8")})
+                          headers={str('Referer'): str(post_link.encode("utf-8"))})
         r = self.app.get(thread_link)
         assert 'Tis a reply' in r, r
         permalinks = [post.find('form')['action'].encode('utf-8')
@@ -149,7 +149,7 @@ class TestDiscuss(TestDiscussBase):
         # ok initially
         non_admin = 'test-user'
         self.app.get(thread_url, status=200,
-                     extra_environ=dict(username=non_admin))
+                     extra_environ=dict(username=str(non_admin)))
 
         # set wiki page private
         from forgewiki.model import Page
@@ -163,14 +163,14 @@ class TestDiscuss(TestDiscussBase):
         ]
 
         self.app.get(thread_url, status=200,  # ok
-                     extra_environ=dict(username='test-admin'))
+                     extra_environ=dict(username=str('test-admin')))
         self.app.get(thread_url, status=403,  # forbidden
-                     extra_environ=dict(username=non_admin))
+                     extra_environ=dict(username=str(non_admin)))
 
     def test_spam_link(self):
         r = self._make_post('Test post')
         assert '<span><i class="fa fa-exclamation" aria-hidden="true"></i></span>' in r
-        r = self.app.get('/wiki/Home/', extra_environ={'username': 'test-user-1'})
+        r = self.app.get('/wiki/Home/', extra_environ={'username': str('test-user-1')})
         assert '<span><i class="fa fa-exclamation" aria-hidden="true"></i></span>' not in r, 'User without moderate perm must not see Spam link'
 
     @patch('allura.controllers.discuss.g.spam_checker.submit_spam')
@@ -214,7 +214,7 @@ class TestDiscuss(TestDiscussBase):
             update_link,
             params={
                 'text': '- [x] checkbox'},
-            extra_environ=dict(username='*anonymous'))
+            extra_environ=dict(username=str('*anonymous')))
         assert response.json['status'] == 'no_permission'
 
     def test_comment_post_reaction_new(self):
@@ -239,14 +239,14 @@ class TestDiscuss(TestDiscussBase):
             react_link,
             params={
                 'r': ':+1:'},
-            extra_environ=dict(username='*anonymous'))
+            extra_environ=dict(username=str('*anonymous')))
         assert response.json['error'] == 'no_permission'
         # even anon can't send invalid reactions
         response = self.app.post(
             react_link,
             params={
                 'r': 'invalid'},
-            extra_environ=dict(username='*anonymous'))
+            extra_environ=dict(username=str('*anonymous')))
         assert response.json['error'] == 'no_permission'
 
     def test_comment_post_reaction_change(self):
@@ -417,7 +417,7 @@ class TestAttachment(TestDiscussBase):
                 params[field['name']] = field.get('value') or ''
         params[f.find('textarea')['name']] = 'Test Post'
         r = self.app.post(f['action'].encode('utf-8'), params=params,
-                          headers={'Referer': self.thread_link.encode('utf-8')})
+                          headers={str('Referer'): str(self.thread_link.encode('utf-8'))})
         r = r.follow()
         self.post_link = str(
             r.html.find('div', {'class': 'edit_post_form reply'}).find('form')['action'])
@@ -500,8 +500,8 @@ class TestAttachment(TestDiscussBase):
         self.app.get(thumblink, status=404)
 
     def test_unmoderated_post_attachments(self):
-        ordinary_user = {'username': 'test-user'}
-        moderator = {'username': 'test-admin'}
+        ordinary_user = {'username': str('test-user')}
+        moderator = {'username': str('test-admin')}
         # set up attachment
         f = os.path.join(os.path.dirname(__file__), '..', 'data', 'user.png')
         with open(f) as f:
diff --git a/Allura/allura/tests/functional/test_feeds.py b/Allura/allura/tests/functional/test_feeds.py
index 7558db9..863e9a2 100644
--- a/Allura/allura/tests/functional/test_feeds.py
+++ b/Allura/allura/tests/functional/test_feeds.py
@@ -20,6 +20,7 @@ from formencode.variabledecode import variable_encode
 
 from allura.tests import TestController
 from allura.tests import decorators as td
+from allura.lib import helpers as h
 
 
 class TestFeeds(TestController):
@@ -91,7 +92,7 @@ class TestFeeds(TestController):
             summary='This is a new ticket',
             status='unread',
             milestone='',
-            description='This is another description'), extra_environ=dict(username='root'))
+            description='This is another description'), extra_environ=dict(username=str('root')))
         r = self.app.get('/bugs/1/feed.atom')
         assert '=&amp;gt' in r
         assert '\n+' in r
diff --git a/Allura/allura/tests/functional/test_home.py b/Allura/allura/tests/functional/test_home.py
index 4ab8147..8a5beb9 100644
--- a/Allura/allura/tests/functional/test_home.py
+++ b/Allura/allura/tests/functional/test_home.py
@@ -222,13 +222,13 @@ class TestProjectHome(TestController):
 
     def test_members_anonymous(self):
         r = self.app.get('/p/test/_members/',
-                         extra_environ=dict(username='*anonymous'))
+                         extra_environ=dict(username=str('*anonymous')))
         assert '<td>Test Admin</td>' in r
         assert '<td><a href="/u/test-admin/">test-admin</a></td>' in r
         assert '<td>Admin</td>' in r
 
     def test_toolaccess_before_subproject(self):
-        self.app.extra_environ = {'username': 'test-admin'}
+        self.app.extra_environ = {'username': str('test-admin')}
         # Add the subproject with a wiki.
         self.app.post('/p/test/admin/update_mounts', params={
             'new.install': 'install',
@@ -259,7 +259,7 @@ class TestProjectHome(TestController):
         })
 
         # Try to access the  installed tool as anon.
-        r = self.app.get('/p/test/test-mount/test-sub/', extra_environ=dict(username='*anonymous'), status=404)
+        r = self.app.get('/p/test/test-mount/test-sub/', extra_environ=dict(username=str('*anonymous')), status=404)
 
         # Try to access the installed tool as Admin.
         r = self.app.get('/p/test/test-mount/test-sub/').follow()
diff --git a/Allura/allura/tests/functional/test_neighborhood.py b/Allura/allura/tests/functional/test_neighborhood.py
index 2e7d65a..11e5074 100644
--- a/Allura/allura/tests/functional/test_neighborhood.py
+++ b/Allura/allura/tests/functional/test_neighborhood.py
@@ -46,13 +46,13 @@ class TestNeighborhood(TestController):
         r = r.follow()
         assert 'This is the "Adobe" neighborhood' in str(r), str(r)
         r = self.app.get(
-            '/adobe/admin/', extra_environ=dict(username='test-user'),
+            '/adobe/admin/', extra_environ=dict(username=str('test-user')),
             status=403)
 
     def test_redirect(self):
         r = self.app.post('/adobe/_admin/update',
                           params=dict(redirect='wiki/Home/'),
-                          extra_environ=dict(username='root'))
+                          extra_environ=dict(username=str('root')))
         r = self.app.get('/adobe/')
         assert r.location.endswith('/adobe/wiki/Home/')
 
@@ -62,25 +62,25 @@ class TestNeighborhood(TestController):
         assert 'This is the "Adobe" neighborhood' in str(r), str(r)
 
     def test_admin(self):
-        r = self.app.get('/adobe/_admin/', extra_environ=dict(username='root'))
+        r = self.app.get('/adobe/_admin/', extra_environ=dict(username=str('root')))
         r = self.app.get('/adobe/_admin/overview',
-                         extra_environ=dict(username='root'))
+                         extra_environ=dict(username=str('root')))
         r = self.app.get('/adobe/_admin/accolades',
-                         extra_environ=dict(username='root'))
+                         extra_environ=dict(username=str('root')))
         neighborhood = M.Neighborhood.query.get(name='Adobe')
         neighborhood.features['google_analytics'] = True
         r = self.app.post('/adobe/_admin/update',
                           params=dict(name='Mozq1', css='',
                                       homepage='# MozQ1!', tracking_id='U-123456'),
-                          extra_environ=dict(username='root'))
+                          extra_environ=dict(username=str('root')))
         r = self.app.post('/adobe/_admin/update',
                           params=dict(name='Mozq1', css='',
                                       homepage='# MozQ1!\n[Root]'),
-                          extra_environ=dict(username='root'))
+                          extra_environ=dict(username=str('root')))
         # make sure project_template is validated as proper json
         r = self.app.post('/adobe/_admin/update',
                           params=dict(project_template='{'),
-                          extra_environ=dict(username='root'))
+                          extra_environ=dict(username=str('root')))
         assert 'Invalid JSON' in r
 
     def test_admin_overview_audit_log(self):
@@ -106,7 +106,7 @@ class TestNeighborhood(TestController):
 
         }
         self.app.post('/p/_admin/update', params=params,
-                      extra_environ=dict(username='root'))
+                      extra_environ=dict(username=str('root')))
         # must get as many log records as many values are updated
         assert M.AuditLog.query.find().count() == len(params)
 
@@ -128,9 +128,9 @@ class TestNeighborhood(TestController):
         self.app.post('/p/_admin/update',
                       params=dict(name='Projects',
                                   prohibited_tools='wiki, tickets'),
-                      extra_environ=dict(username='root'))
+                      extra_environ=dict(username=str('root')))
 
-        r = self.app.get('/p/_admin/overview', extra_environ=dict(username='root'))
+        r = self.app.get('/p/_admin/overview', extra_environ=dict(username=str('root')))
         assert 'wiki, tickets' in r
 
         c.user = M.User.query.get(username='root')
@@ -143,7 +143,7 @@ class TestNeighborhood(TestController):
         r = self.app.post('/p/_admin/update',
                           params=dict(name='Projects',
                                       prohibited_tools='wiki, test'),
-                          extra_environ=dict(username='root'))
+                          extra_environ=dict(username=str('root')))
         assert 'error' in self.webflash(r), self.webflash(r)
 
     @td.with_wiki
@@ -153,26 +153,26 @@ class TestNeighborhood(TestController):
         r = self.app.post('/p/_admin/update',
                           params=dict(name='Projects',
                                       anchored_tools='wiki:Wiki, tickets:Ticket'),
-                          extra_environ=dict(username='root'))
+                          extra_environ=dict(username=str('root')))
         assert 'error' not in self.webflash(r)
         r = self.app.post('/p/_admin/update',
                           params=dict(name='Projects',
                                       anchored_tools='w!iki:Wiki, tickets:Ticket'),
-                          extra_environ=dict(username='root'))
+                          extra_environ=dict(username=str('root')))
         assert 'error' in self.webflash(r)
         assert_equal(neighborhood.anchored_tools, 'wiki:Wiki, tickets:Ticket')
 
         r = self.app.post('/p/_admin/update',
                           params=dict(name='Projects',
                                       anchored_tools='wiki:Wiki,'),
-                          extra_environ=dict(username='root'))
+                          extra_environ=dict(username=str('root')))
         assert 'error' in self.webflash(r)
         assert_equal(neighborhood.anchored_tools, 'wiki:Wiki, tickets:Ticket')
 
         r = self.app.post('/p/_admin/update',
                           params=dict(name='Projects',
                                       anchored_tools='badname,'),
-                          extra_environ=dict(username='root'))
+                          extra_environ=dict(username=str('root')))
         assert 'error' in self.webflash(r)
         assert_equal(neighborhood.anchored_tools, 'wiki:Wiki, tickets:Ticket')
 
@@ -192,7 +192,7 @@ class TestNeighborhood(TestController):
 
     def test_show_title(self):
         r = self.app.get('/adobe/_admin/overview',
-                         extra_environ=dict(username='root'))
+                         extra_environ=dict(username=str('root')))
         neighborhood = M.Neighborhood.query.get(name='Adobe')
         # if not set show_title must be True
         assert neighborhood.show_title
@@ -203,17 +203,17 @@ class TestNeighborhood(TestController):
                                       homepage='# MozQ1!',
                                       tracking_id='U-123456',
                                       show_title='false'),
-                          extra_environ=dict(username='root'))
+                          extra_environ=dict(username=str('root')))
         # no title now
-        r = self.app.get('/adobe/', extra_environ=dict(username='root'))
+        r = self.app.get('/adobe/', extra_environ=dict(username=str('root')))
         assert 'class="project_title"' not in str(r)
         r = self.app.get('/adobe/wiki/Home/',
-                         extra_environ=dict(username='root'))
+                         extra_environ=dict(username=str('root')))
         assert 'class="project_title"' not in str(r)
 
         # title must be present on project page
         r = self.app.get('/adobe/adobe-1/admin/',
-                         extra_environ=dict(username='root'))
+                         extra_environ=dict(username=str('root')))
         assert 'class="project_title"' in str(r)
 
     def test_admin_stats_del_count(self):
@@ -222,7 +222,7 @@ class TestNeighborhood(TestController):
         proj.deleted = True
         ThreadLocalORMSession.flush_all()
         r = self.app.get('/adobe/_admin/stats/',
-                         extra_environ=dict(username='root'))
+                         extra_environ=dict(username=str('root')))
         assert 'Deleted: 1' in r
         assert 'Private: 0' in r
 
@@ -233,7 +233,7 @@ class TestNeighborhood(TestController):
         proj.private = True
         ThreadLocalORMSession.flush_all()
         r = self.app.get('/adobe/_admin/stats/',
-                         extra_environ=dict(username='root'))
+                         extra_environ=dict(username=str('root')))
         assert 'Deleted: 0' in r
         assert 'Private: 1' in r
 
@@ -243,7 +243,7 @@ class TestNeighborhood(TestController):
         proj.private = False
         ThreadLocalORMSession.flush_all()
         r = self.app.get('/adobe/_admin/stats/adminlist',
-                         extra_environ=dict(username='root'))
+                         extra_environ=dict(username=str('root')))
         pq = M.Project.query.find(
             dict(neighborhood_id=neighborhood._id, deleted=False))
         pq.sort('name')
@@ -267,11 +267,11 @@ class TestNeighborhood(TestController):
         file_data = file(file_path).read()
         upload = ('icon', file_name, file_data)
 
-        r = self.app.get('/adobe/_admin/', extra_environ=dict(username='root'))
+        r = self.app.get('/adobe/_admin/', extra_environ=dict(username=str('root')))
         r = self.app.post('/adobe/_admin/update',
                           params=dict(name='Mozq1', css='',
                                       homepage='# MozQ1'),
-                          extra_environ=dict(username='root'), upload_files=[upload])
+                          extra_environ=dict(username=str('root')), upload_files=[upload])
         r = self.app.get('/adobe/icon')
         image = PIL.Image.open(StringIO(r.body))
         assert image.size == (48, 48)
@@ -283,33 +283,33 @@ class TestNeighborhood(TestController):
         neighborhood = M.Neighborhood.query.get(name='Adobe')
         neighborhood.features['google_analytics'] = True
         r = self.app.get('/adobe/_admin/overview',
-                         extra_environ=dict(username='root'))
+                         extra_environ=dict(username=str('root')))
         assert 'Google Analytics ID' in r
         r = self.app.get('/adobe/adobe-1/admin/overview',
-                         extra_environ=dict(username='root'))
+                         extra_environ=dict(username=str('root')))
         assert 'Google Analytics ID' in r
         r = self.app.post('/adobe/_admin/update',
                           params=dict(name='Adobe', css='',
                                       homepage='# MozQ1', tracking_id='U-123456'),
-                          extra_environ=dict(username='root'), status=302)
+                          extra_environ=dict(username=str('root')), status=302)
         r = self.app.post('/adobe/adobe-1/admin/update',
                           params=dict(tracking_id='U-654321'),
-                          extra_environ=dict(username='root'), status=302)
+                          extra_environ=dict(username=str('root')), status=302)
         r = self.app.get('/adobe/adobe-1/admin/overview',
-                         extra_environ=dict(username='root'))
+                         extra_environ=dict(username=str('root')))
         assert "_add_tracking('nbhd', 'U-123456');" in r, r
         assert "_add_tracking('proj', 'U-654321');" in r
         # analytics not allowed
         neighborhood = M.Neighborhood.query.get(name='Adobe')
         neighborhood.features['google_analytics'] = False
         r = self.app.get('/adobe/_admin/overview',
-                         extra_environ=dict(username='root'))
+                         extra_environ=dict(username=str('root')))
         assert 'Google Analytics ID' not in r
         r = self.app.get('/adobe/adobe-1/admin/overview',
-                         extra_environ=dict(username='root'))
+                         extra_environ=dict(username=str('root')))
         assert 'Google Analytics ID' not in r
         r = self.app.get('/adobe/adobe-1/admin/overview',
-                         extra_environ=dict(username='root'))
+                         extra_environ=dict(username=str('root')))
         assert "_add_tracking('nbhd', 'U-123456');" not in r
         assert "_add_tracking('proj', 'U-654321');" not in r
 
@@ -323,7 +323,7 @@ class TestNeighborhood(TestController):
         r = self.app.get('/adobe/')
         assert test_css not in r
         r = self.app.get('/adobe/_admin/overview',
-                         extra_environ=dict(username='root'))
+                         extra_environ=dict(username=str('root')))
         assert custom_css not in r
 
         neighborhood = M.Neighborhood.query.get(name='Adobe')
@@ -333,7 +333,7 @@ class TestNeighborhood(TestController):
             r = r.follow()
         assert test_css in r
         r = self.app.get('/adobe/_admin/overview',
-                         extra_environ=dict(username='root'))
+                         extra_environ=dict(username=str('root')))
         assert custom_css in r
 
         neighborhood = M.Neighborhood.query.get(name='Adobe')
@@ -343,7 +343,7 @@ class TestNeighborhood(TestController):
             r = r.follow()
         assert test_css in r
         r = self.app.get('/adobe/_admin/overview',
-                         extra_environ=dict(username='root'))
+                         extra_environ=dict(username=str('root')))
         assert custom_css in r
 
     def test_picker_css(self):
@@ -351,7 +351,7 @@ class TestNeighborhood(TestController):
         neighborhood.features['css'] = 'picker'
 
         r = self.app.get('/adobe/_admin/overview',
-                         extra_environ=dict(username='root'))
+                         extra_environ=dict(username=str('root')))
         assert 'Project title, font' in r
         assert 'Project title, color' in r
         assert 'Bar on top' in r
@@ -367,7 +367,7 @@ class TestNeighborhood(TestController):
                                   'css-barontop': '#555555',
                                   'css-titlebarbackground': '#333',
                                   'css-titlebarcolor': '#444'},
-                          extra_environ=dict(username='root'), upload_files=[])
+                          extra_environ=dict(username=str('root')), upload_files=[])
         neighborhood = M.Neighborhood.query.get(name='Adobe')
         assert '/*projecttitlefont*/.project_title{font-family:arial,sans-serif;}' in neighborhood.css
         assert '/*projecttitlecolor*/.project_title{color:green;}' in neighborhood.css
@@ -384,7 +384,7 @@ class TestNeighborhood(TestController):
                               project_unixname='maxproject1', project_name='Max project1',
                               project_description='', neighborhood='Projects'),
                           antispam=True,
-                          extra_environ=dict(username='root'), status=302)
+                          extra_environ=dict(username=str('root')), status=302)
         assert '/p/maxproject1/admin' in r.location
 
         # Set max value to 0
@@ -395,7 +395,7 @@ class TestNeighborhood(TestController):
                               project_unixname='maxproject2', project_name='Max project2',
                               project_description='', neighborhood='Projects'),
                           antispam=True,
-                          extra_environ=dict(username='root'))
+                          extra_environ=dict(username=str('root')))
         while isinstance(r.response, HTTPFound):
             r = r.follow()
         assert 'You have exceeded the maximum number of projects' in r
@@ -408,7 +408,7 @@ class TestNeighborhood(TestController):
                                   project_unixname='rateproject1', project_name='Rate project1',
                                   project_description='', neighborhood='Projects'),
                               antispam=True,
-                              extra_environ=dict(username='test-user-1'), status=302)
+                              extra_environ=dict(username=str('test-user-1')), status=302)
             assert '/p/rateproject1/admin' in r.location
 
         # Set rate limit to 1 in first hour of user account
@@ -418,7 +418,7 @@ class TestNeighborhood(TestController):
                                   project_unixname='rateproject2', project_name='Rate project2',
                                   project_description='', neighborhood='Projects'),
                               antispam=True,
-                              extra_environ=dict(username='test-user-1'))
+                              extra_environ=dict(username=str('test-user-1')))
             while isinstance(r.response, HTTPFound):
                 r = r.follow()
             assert 'Project creation rate limit exceeded.  Please try again later.' in r
@@ -431,7 +431,7 @@ class TestNeighborhood(TestController):
                                   project_unixname='rateproject1', project_name='Rate project1',
                                   project_description='', neighborhood='Projects'),
                               antispam=True,
-                              extra_environ=dict(username='root'), status=302)
+                              extra_environ=dict(username=str('root')), status=302)
             assert '/p/rateproject1/admin' in r.location
 
         # Set rate limit to 1 in first hour of user account
@@ -441,71 +441,71 @@ class TestNeighborhood(TestController):
                                   project_unixname='rateproject2', project_name='Rate project2',
                                   project_description='', neighborhood='Projects'),
                               antispam=True,
-                              extra_environ=dict(username='root'))
+                              extra_environ=dict(username=str('root')))
             assert '/p/rateproject2/admin' in r.location
 
     def test_invite(self):
         p_nbhd_id = str(M.Neighborhood.query.get(name='Projects')._id)
         r = self.app.get('/adobe/_moderate/',
-                         extra_environ=dict(username='root'))
+                         extra_environ=dict(username=str('root')))
         r = self.app.post('/adobe/_moderate/invite',
                           params=dict(pid='adobe-1', invite='on',
                                       neighborhood_id=p_nbhd_id),
-                          extra_environ=dict(username='root'))
-        r = self.app.get(r.location, extra_environ=dict(username='root'))
+                          extra_environ=dict(username=str('root')))
+        r = self.app.get(r.location, extra_environ=dict(username=str('root')))
         assert 'error' in r
         r = self.app.post('/adobe/_moderate/invite',
                           params=dict(pid='no_such_user',
                                       invite='on', neighborhood_id=p_nbhd_id),
-                          extra_environ=dict(username='root'))
-        r = self.app.get(r.location, extra_environ=dict(username='root'))
+                          extra_environ=dict(username=str('root')))
+        r = self.app.get(r.location, extra_environ=dict(username=str('root')))
         assert 'error' in r
         r = self.app.post('/adobe/_moderate/invite',
                           params=dict(pid='test', invite='on',
                                       neighborhood_id=p_nbhd_id),
-                          extra_environ=dict(username='root'))
-        r = self.app.get(r.location, extra_environ=dict(username='root'))
+                          extra_environ=dict(username=str('root')))
+        r = self.app.get(r.location, extra_environ=dict(username=str('root')))
         assert 'invited' in r, r
         assert 'warning' not in r
         r = self.app.post('/adobe/_moderate/invite',
                           params=dict(pid='test', invite='on',
                                       neighborhood_id=p_nbhd_id),
-                          extra_environ=dict(username='root'))
-        r = self.app.get(r.location, extra_environ=dict(username='root'))
+                          extra_environ=dict(username=str('root')))
+        r = self.app.get(r.location, extra_environ=dict(username=str('root')))
         assert 'warning' in r
         r = self.app.post('/adobe/_moderate/invite',
                           params=dict(pid='test', uninvite='on',
                                       neighborhood_id=p_nbhd_id),
-                          extra_environ=dict(username='root'))
-        r = self.app.get(r.location, extra_environ=dict(username='root'))
+                          extra_environ=dict(username=str('root')))
+        r = self.app.get(r.location, extra_environ=dict(username=str('root')))
         assert 'uninvited' in r
         assert 'warning' not in r
         r = self.app.post('/adobe/_moderate/invite',
                           params=dict(pid='test', uninvite='on',
                                       neighborhood_id=p_nbhd_id),
-                          extra_environ=dict(username='root'))
-        r = self.app.get(r.location, extra_environ=dict(username='root'))
+                          extra_environ=dict(username=str('root')))
+        r = self.app.get(r.location, extra_environ=dict(username=str('root')))
         assert 'warning' in r
         r = self.app.post('/adobe/_moderate/invite',
                           params=dict(pid='test', invite='on',
                                       neighborhood_id=p_nbhd_id),
-                          extra_environ=dict(username='root'))
-        r = self.app.get(r.location, extra_environ=dict(username='root'))
+                          extra_environ=dict(username=str('root')))
+        r = self.app.get(r.location, extra_environ=dict(username=str('root')))
         assert 'invited' in r
         assert 'warning' not in r
 
     def test_evict(self):
         r = self.app.get('/adobe/_moderate/',
-                         extra_environ=dict(username='root'))
+                         extra_environ=dict(username=str('root')))
         r = self.app.post('/adobe/_moderate/evict',
                           params=dict(pid='test'),
-                          extra_environ=dict(username='root'))
-        r = self.app.get(r.location, extra_environ=dict(username='root'))
+                          extra_environ=dict(username=str('root')))
+        r = self.app.get(r.location, extra_environ=dict(username=str('root')))
         assert 'error' in r
         r = self.app.post('/adobe/_moderate/evict',
                           params=dict(pid='adobe-1'),
-                          extra_environ=dict(username='root'))
-        r = self.app.get(r.location, extra_environ=dict(username='root'))
+                          extra_environ=dict(username=str('root')))
+        r = self.app.get(r.location, extra_environ=dict(username=str('root')))
         assert 'adobe-1 evicted to Projects' in r
 
     def test_home(self):
@@ -518,7 +518,7 @@ class TestNeighborhood(TestController):
                               project_unixname='', project_name='Nothing',
                               project_description='', neighborhood='Adobe'),
                           antispam=True,
-                          extra_environ=dict(username='root'))
+                          extra_environ=dict(username=str('root')))
         assert r.html.find('div', {'class': 'error'}
                            ).string == 'Please use 3-15 small letters, numbers, and dashes.'
         r = self.app.post('/adobe/register',
@@ -526,14 +526,14 @@ class TestNeighborhood(TestController):
                               project_unixname='mymoz', project_name='My Moz',
                               project_description='', neighborhood='Adobe'),
                           antispam=True,
-                          extra_environ=dict(username='*anonymous'),
+                          extra_environ=dict(username=str('*anonymous')),
                           status=302)
         r = self.app.post('/adobe/register',
                           params=dict(
                               project_unixname='foo.mymoz', project_name='My Moz',
                               project_description='', neighborhood='Adobe'),
                           antispam=True,
-                          extra_environ=dict(username='root'))
+                          extra_environ=dict(username=str('root')))
         assert r.html.find('div', {'class': 'error'}
                            ).string == 'Please use 3-15 small letters, numbers, and dashes.'
         r = self.app.post('/p/register',
@@ -541,7 +541,7 @@ class TestNeighborhood(TestController):
                               project_unixname='test', project_name='Tester',
                               project_description='', neighborhood='Projects'),
                           antispam=True,
-                          extra_environ=dict(username='root'))
+                          extra_environ=dict(username=str('root')))
         assert r.html.find('div', {'class': 'error'}
                            ).string == 'This project name is taken.'
         r = self.app.post('/adobe/register',
@@ -549,7 +549,7 @@ class TestNeighborhood(TestController):
                               project_unixname='mymoz', project_name='My Moz',
                               project_description='', neighborhood='Adobe'),
                           antispam=True,
-                          extra_environ=dict(username='root'),
+                          extra_environ=dict(username=str('root')),
                           status=302)
 
     def test_register_private_fails_for_anon(self):
@@ -562,7 +562,7 @@ class TestNeighborhood(TestController):
                 neighborhood='Projects',
                 private_project='on'),
             antispam=True,
-            extra_environ=dict(username='*anonymous'),
+            extra_environ=dict(username=str('*anonymous')),
             status=302)
         assert config.get('auth.login_url', '/auth/') in r.location, r.location
 
@@ -576,14 +576,14 @@ class TestNeighborhood(TestController):
                 neighborhood='Projects',
                 private_project='on'),
             antispam=True,
-            extra_environ=dict(username='test-user'),
+            extra_environ=dict(username=str('test-user')),
             status=403)
 
     def test_register_private_fails_for_non_private_neighborhood(self):
         # Turn off private
         neighborhood = M.Neighborhood.query.get(name='Projects')
         neighborhood.features['private_projects'] = False
-        r = self.app.get('/p/add_project', extra_environ=dict(username='root'))
+        r = self.app.get('/p/add_project', extra_environ=dict(username=str('root')))
         assert 'private_project' not in r
 
         r = self.app.post(
@@ -595,7 +595,7 @@ class TestNeighborhood(TestController):
                 neighborhood='Projects',
                 private_project='on'),
             antispam=True,
-            extra_environ=dict(username='root'))
+            extra_environ=dict(username=str('root')))
         cookies = r.headers.getall('Set-Cookie')
         flash_msg_cookies = map(urllib2.unquote, cookies)
 
@@ -608,7 +608,7 @@ class TestNeighborhood(TestController):
         # Turn on private
         neighborhood = M.Neighborhood.query.get(name='Projects')
         neighborhood.features['private_projects'] = True
-        r = self.app.get('/p/add_project', extra_environ=dict(username='root'))
+        r = self.app.get('/p/add_project', extra_environ=dict(username=str('root')))
         assert 'private_project' in r
 
         self.app.post(
@@ -620,7 +620,7 @@ class TestNeighborhood(TestController):
                 neighborhood='Projects',
                 private_project='on'),
             antispam=True,
-            extra_environ=dict(username='root'))
+            extra_environ=dict(username=str('root')))
 
         proj = M.Project.query.get(
             shortname='myprivate2', neighborhood_id=neighborhood._id)
@@ -637,21 +637,21 @@ class TestNeighborhood(TestController):
                 private_project='on',
                 tools='wiki'),
             antispam=True,
-            extra_environ=dict(username='root'),
+            extra_environ=dict(username=str('root')),
             status=302)
         assert config.get('auth.login_url',
                           '/auth/') not in r.location, r.location
         r = self.app.get(
             '/p/mymoz/wiki/',
-            extra_environ=dict(username='root')).follow(extra_environ=dict(username='root'), status=200)
+            extra_environ=dict(username=str('root'))).follow(extra_environ=dict(username=str('root')), status=200)
         r = self.app.get(
             '/p/mymoz/wiki/',
-            extra_environ=dict(username='*anonymous'),
+            extra_environ=dict(username=str('*anonymous')),
             status=302)
         assert config.get('auth.login_url', '/auth/') in r.location, r.location
         self.app.get(
             '/p/mymoz/wiki/',
-            extra_environ=dict(username='test-user'),
+            extra_environ=dict(username=str('test-user')),
             status=403)
 
     def test_project_template(self):
@@ -714,7 +714,7 @@ class TestNeighborhood(TestController):
                 },
                 "groups": %s
                 }""" % (icon_url, json.dumps(test_groups))),
-            extra_environ=dict(username='root'))
+            extra_environ=dict(username=str('root')))
         r = self.app.post(
             '/adobe/register',
             params=dict(
@@ -724,7 +724,7 @@ class TestNeighborhood(TestController):
                 neighborhood='Mozq1',
                 private_project='off'),
             antispam=True,
-            extra_environ=dict(username='root'),
+            extra_environ=dict(username=str('root')),
             status=302).follow()
         p = M.Project.query.get(shortname='testtemp')
         # make sure the correct tools got installed in the right order
@@ -740,10 +740,10 @@ class TestNeighborhood(TestController):
         # make sure project is private
         r = self.app.get(
             '/adobe/testtemp/wiki/',
-            extra_environ=dict(username='root')).follow(extra_environ=dict(username='root'), status=200)
+            extra_environ=dict(username=str('root'))).follow(extra_environ=dict(username=str('root')), status=200)
         r = self.app.get(
             '/adobe/testtemp/wiki/',
-            extra_environ=dict(username='*anonymous'),
+            extra_environ=dict(username=str('*anonymous')),
             status=302)
         # check the labels and trove cats
         r = self.app.get('/adobe/testtemp/admin/trove')
@@ -809,7 +809,7 @@ class TestNeighborhood(TestController):
                 "tool_order":["wiki","admin"],
 
                 }"""),
-                          extra_environ=dict(username='root'))
+                          extra_environ=dict(username=str('root')))
         neighborhood = M.Neighborhood.query.get(name='Adobe')
         neighborhood.anchored_tools = 'wiki:Wiki'
         r = self.app.post(
@@ -821,7 +821,7 @@ class TestNeighborhood(TestController):
                 neighborhood='Adobe',
                 private_project='off'),
             antispam=True,
-            extra_environ=dict(username='root'))
+            extra_environ=dict(username=str('root')))
         r = self.app.get('/adobe/testtemp/admin/overview')
         assert r.html.find('div', id='top_nav').find(
             'a', href='/adobe/testtemp/wiki/'), r.html
@@ -858,7 +858,7 @@ class TestNeighborhood(TestController):
                               project_unixname='test', project_name='Test again',
                               project_description='', neighborhood='Adobe', tools='wiki'),
                           antispam=True,
-                          extra_environ=dict(username='root'))
+                          extra_environ=dict(username=str('root')))
         assert r.status_int == 302, r.html.find(
             'div', {'class': 'error'}).string
         r = self.app.get('/adobe/test/wiki/').follow(status=200)
@@ -871,42 +871,42 @@ class TestNeighborhood(TestController):
         upload = ('icon', file_name, file_data)
 
         r = self.app.get('/adobe/_admin/awards',
-                         extra_environ=dict(username='root'))
+                         extra_environ=dict(username=str('root')))
         r = self.app.post('/adobe/_admin/awards/create',
                           params=dict(short='FOO', full='A basic foo award'),
-                          extra_environ=dict(username='root'), upload_files=[upload])
+                          extra_environ=dict(username=str('root')), upload_files=[upload])
         r = self.app.post('/adobe/_admin/awards/create',
                           params=dict(short='BAR',
                                       full='A basic bar award with no icon'),
-                          extra_environ=dict(username='root'))
+                          extra_environ=dict(username=str('root')))
         foo_id = str(M.Award.query.find(dict(short='FOO')).first()._id)
         bar_id = str(M.Award.query.find(dict(short='BAR')).first()._id)
         r = self.app.post('/adobe/_admin/awards/%s/update' % bar_id,
                           params=dict(short='BAR2',
                                       full='Updated description.'),
-                          extra_environ=dict(username='root')).follow().follow()
+                          extra_environ=dict(username=str('root'))).follow().follow()
         assert 'BAR2' in r
         assert 'Updated description.' in r
         r = self.app.get('/adobe/_admin/awards/%s' %
-                         foo_id, extra_environ=dict(username='root'))
+                         foo_id, extra_environ=dict(username=str('root')))
         r = self.app.get('/adobe/_admin/awards/%s/icon' %
-                         foo_id, extra_environ=dict(username='root'))
+                         foo_id, extra_environ=dict(username=str('root')))
         image = PIL.Image.open(StringIO(r.body))
         assert image.size == (48, 48)
         self.app.post('/adobe/_admin/awards/grant',
                       params=dict(grant='FOO', recipient='adobe-1',
                                   url='http://award.org', comment='Winner!'),
-                      extra_environ=dict(username='root'))
+                      extra_environ=dict(username=str('root')))
         r = self.app.get('/adobe/_admin/accolades',
-                         extra_environ=dict(username='root'))
+                         extra_environ=dict(username=str('root')))
         assert_in('Winner!', r)
         assert_in('http://award.org', r)
         self.app.get('/adobe/_admin/awards/%s/adobe-1' %
-                     foo_id, extra_environ=dict(username='root'))
+                     foo_id, extra_environ=dict(username=str('root')))
         self.app.post('/adobe/_admin/awards/%s/adobe-1/revoke' % foo_id,
-                      extra_environ=dict(username='root'))
+                      extra_environ=dict(username=str('root')))
         self.app.post('/adobe/_admin/awards/%s/delete' % foo_id,
-                      extra_environ=dict(username='root'))
+                      extra_environ=dict(username=str('root')))
 
     def test_add_a_project_link(self):
         from tg import tmpl_context as c
@@ -917,24 +917,24 @@ class TestNeighborhood(TestController):
                 p.install_app('home', 'home', 'Home', ordinal=0)
         r = self.app.get('/p/')
         assert 'Add a Project' in r
-        r = self.app.get('/u/', extra_environ=dict(username='test-user'))
+        r = self.app.get('/u/', extra_environ=dict(username=str('test-user')))
         assert 'Add a Project' not in r
-        r = self.app.get('/adobe/', extra_environ=dict(username='test-user'))
+        r = self.app.get('/adobe/', extra_environ=dict(username=str('test-user')))
         assert 'Add a Project' not in r
-        r = self.app.get('/u/', extra_environ=dict(username='root'))
+        r = self.app.get('/u/', extra_environ=dict(username=str('root')))
         assert 'Add a Project' in r
-        r = self.app.get('/adobe/', extra_environ=dict(username='root'))
+        r = self.app.get('/adobe/', extra_environ=dict(username=str('root')))
         assert 'Add a Project' in r
 
     def test_help(self):
         r = self.app.get('/p/_admin/help/',
-                         extra_environ=dict(username='root'))
+                         extra_environ=dict(username=str('root')))
         assert 'macro' in r
 
     @td.with_user_project('test-user')
     def test_profile_tools(self):
         r = self.app.get('/u/test-user/',
-                         extra_environ=dict(username='test-user')).follow()
+                         extra_environ=dict(username=str('test-user'))).follow()
         assert r.html.find('div', 'profile-section tools').find(
             'a', href='/u/test-user/profile/'), r.html
 
@@ -1086,7 +1086,7 @@ class TestPhoneVerificationOnProjectRegistration(TestController):
                     project_name='Phone Test',
                     project_description='',
                     neighborhood='Projects'),
-                extra_environ=dict(username='test-user'),
+                extra_environ=dict(username=str('test-user')),
                 antispam=True)
             overlay = r.html.find('div', {'id': 'phone_verification_overlay'})
             assert_not_equal(overlay, None)
diff --git a/Allura/allura/tests/functional/test_rest.py b/Allura/allura/tests/functional/test_rest.py
index fc8ba1e..7cbff40 100644
--- a/Allura/allura/tests/functional/test_rest.py
+++ b/Allura/allura/tests/functional/test_rest.py
@@ -201,7 +201,7 @@ class TestRestHome(TestRestApiBase):
 
         # anonymous sees only non-private tool
         r = self.app.get('/rest/p/test/',
-                         extra_environ={'username': '*anonymous'})
+                         extra_environ={'username': str('*anonymous')})
         assert_equal(r.json['shortname'], 'test')
         tool_mounts = [t['mount_point'] for t in r.json['tools']]
         assert_in('bugs', tool_mounts)
@@ -333,10 +333,10 @@ class TestRestHome(TestRestApiBase):
         if auth_read_perm in acl:
             acl.remove(auth_read_perm)
         self.app.get('/rest/p/test/wiki/Home/',
-                     extra_environ={'username': '*anonymous'},
+                     extra_environ={'username': str('*anonymous')},
                      status=401)
         self.app.get('/rest/p/test/wiki/Home/',
-                     extra_environ={'username': 'test-user-0'},
+                     extra_environ={'username': str('test-user-0')},
                      status=403)
 
     def test_index(self):
@@ -368,7 +368,7 @@ class TestRestHome(TestRestApiBase):
     @td.with_wiki
     def test_cors_POST_req_blocked_by_csrf(self):
         # so test-admin isn't automatically logged in for all requests
-        self.app.extra_environ = {'disable_auth_magic': 'True'}
+        self.app.extra_environ = {'disable_auth_magic': str('True')}
 
         # regular login to get a session cookie set up
         r = self.app.get('/auth/')
@@ -380,7 +380,7 @@ class TestRestHome(TestRestApiBase):
         # simulate CORS ajax request withCredentials (cookie headers)
         # make sure we don't allow the cookies to authorize the request (else could be a CSRF attack vector)
         assert self.app.cookies['allura']
-        self.app.post('/rest/p/test/wiki/NewPage', headers={'Origin': 'http://bad.com/'},
+        self.app.post('/rest/p/test/wiki/NewPage', headers={'Origin': str('http://bad.com/')},
                       status=401)
 
     @mock.patch('allura.lib.plugin.ThemeProvider._get_site_notification')
@@ -463,7 +463,7 @@ class TestDoap(TestRestApiBase):
 
         # anonymous sees only non-private tool
         r = self.app.get('/rest/p/test?doap',
-                         extra_environ={'username': '*anonymous'})
+                         extra_environ={'username': str('*anonymous')})
         p = r.xml.find(self.ns + 'Project')
         tools = p.findall(self.ns_sf + 'feature')
         tools = [(t.find(self.ns_sf + 'Feature').find(self.ns + 'name').text,
diff --git a/Allura/allura/tests/functional/test_root.py b/Allura/allura/tests/functional/test_root.py
index 7438a37..b91a97e 100644
--- a/Allura/allura/tests/functional/test_root.py
+++ b/Allura/allura/tests/functional/test_root.py
@@ -56,7 +56,7 @@ class TestRootController(TestController):
         n_adobe.register_project('adobe-2', u_admin)
 
     def test_index(self):
-        response = self.app.get('/', extra_environ=dict(username='*anonymous'))
+        response = self.app.get('/', extra_environ=dict(username=str('*anonymous')))
         assert_equal(response.location, 'http://localhost/neighborhood')
 
         response = self.app.get('/')
@@ -99,7 +99,7 @@ class TestRootController(TestController):
         for hdr in hdrs:
             # malformed headers used to return 500, just make sure they don't
             # now
-            self.app.get('/', headers=dict(Accept=hdr), validate_skip=True)
+            self.app.get('/', headers=dict(Accept=str(hdr)), validate_skip=True)
 
     def test_encoded_urls(self):
         # not valid unicode
@@ -214,7 +214,7 @@ class TestRootWithSSLPattern(TestController):
     def test_no_weird_ssl_redirect_for_error_document(self):
         # test a 404, same functionality as a 500 from an error
         r = self.app.get('/auth/asdfasdf',
-                         extra_environ={'wsgi.url_scheme': 'https'},
+                         extra_environ={'wsgi.url_scheme': str('https')},
                          status=404)
         assert '302 Found' not in r.body, r.body
         assert '/error/document' not in r.body, r.body
diff --git a/Allura/allura/tests/functional/test_search.py b/Allura/allura/tests/functional/test_search.py
index 3d3315b..6a09f5c 100644
--- a/Allura/allura/tests/functional/test_search.py
+++ b/Allura/allura/tests/functional/test_search.py
@@ -70,6 +70,6 @@ class TestSearch(TestController):
         resp.mustcontain('Welcome to your wiki! This is the default page')
         resp.mustcontain('Sample wiki comment')
 
-        resp = self.app.get('/p/test2/search/', params=dict(q='wiki'), extra_environ=dict(username='*anonymous'))
+        resp = self.app.get('/p/test2/search/', params=dict(q='wiki'), extra_environ=dict(username=str('*anonymous')))
         resp.mustcontain(no='Welcome to your wiki! This is the default page')
         resp.mustcontain(no='Sample wiki comment')
diff --git a/Allura/allura/tests/functional/test_site_admin.py b/Allura/allura/tests/functional/test_site_admin.py
index b75f51d..e287f0a 100644
--- a/Allura/allura/tests/functional/test_site_admin.py
+++ b/Allura/allura/tests/functional/test_site_admin.py
@@ -40,21 +40,21 @@ class TestSiteAdmin(TestController):
 
     def test_access(self):
         r = self.app.get('/nf/admin/', extra_environ=dict(
-            username='test-user'), status=403)
+            username=str('test-user')), status=403)
 
         r = self.app.get('/nf/admin/', extra_environ=dict(
-            username='*anonymous'), status=302)
+            username=str('*anonymous')), status=302)
         r = r.follow()
         assert 'Login' in r
 
     def test_home(self):
         r = self.app.get('/nf/admin/', extra_environ=dict(
-            username='root'))
+            username=str('root')))
         assert 'Site Admin Home' in r
 
     def test_stats(self):
         r = self.app.get('/nf/admin/stats/', extra_environ=dict(
-            username='root'))
+            username=str('root')))
         assert 'Forge Site Admin' in r.html.find(
             'h2', {'class': 'dark title'}).contents[0]
         stats_table = r.html.find('table')
@@ -63,18 +63,18 @@ class TestSiteAdmin(TestController):
 
     def test_tickets_access(self):
         self.app.get('/nf/admin/api_tickets', extra_environ=dict(
-            username='test-user'), status=403)
+            username=str('test-user')), status=403)
 
     def test_new_projects_access(self):
         self.app.get('/nf/admin/new_projects', extra_environ=dict(
-            username='test_user'), status=403)
+            username=str('test_user')), status=403)
         r = self.app.get('/nf/admin/new_projects', extra_environ=dict(
-            username='*anonymous'), status=302).follow()
+            username=str('*anonymous')), status=302).follow()
         assert 'Login' in r
 
     def test_new_projects(self):
         r = self.app.get('/nf/admin/new_projects', extra_environ=dict(
-            username='root'))
+            username=str('root')))
         headers = r.html.find('table').findAll('th')
         assert headers[1].contents[0] == 'Created'
         assert headers[2].contents[0] == 'Shortname'
@@ -87,18 +87,18 @@ class TestSiteAdmin(TestController):
     def test_new_projects_deleted_projects(self):
         '''Deleted projects should not be visible here'''
         r = self.app.get('/nf/admin/new_projects', extra_environ=dict(
-            username='root'))
+            username=str('root')))
         count = len(r.html.find('table').findAll('tr'))
         p = M.Project.query.get(shortname='test')
         p.deleted = True
         ThreadLocalORMSession.flush_all()
         r = self.app.get('/nf/admin/new_projects', extra_environ=dict(
-            username='root'))
+            username=str('root')))
         assert_equal(len(r.html.find('table').findAll('tr')), count - 1)
 
     def test_new_projects_daterange_filtering(self):
         r = self.app.get('/nf/admin/new_projects', extra_environ=dict(
-            username='root'))
+            username=str('root')))
         count = len(r.html.find('table').findAll('tr'))
         assert_equal(count, 7)
 
@@ -111,7 +111,7 @@ class TestSiteAdmin(TestController):
 
     def test_reclone_repo_access(self):
         r = self.app.get('/nf/admin/reclone_repo', extra_environ=dict(
-            username='*anonymous'), status=302).follow()
+            username=str('*anonymous')), status=302).follow()
         assert 'Login' in r
 
     def test_reclone_repo(self):
@@ -124,7 +124,7 @@ class TestSiteAdmin(TestController):
 
     def test_task_list(self):
         r = self.app.get('/nf/admin/task_manager',
-                         extra_environ=dict(username='*anonymous'), status=302)
+                         extra_environ=dict(username=str('*anonymous')), status=302)
         import math
         M.MonQTask.post(math.ceil, (12.5,))
         r = self.app.get('/nf/admin/task_manager?page_num=1')
@@ -135,7 +135,7 @@ class TestSiteAdmin(TestController):
         task = M.MonQTask.post(math.ceil, (12.5,))
         url = '/nf/admin/task_manager/view/%s' % task._id
         r = self.app.get(
-            url, extra_environ=dict(username='*anonymous'), status=302)
+            url, extra_environ=dict(username=str('*anonymous')), status=302)
         r = self.app.get(url)
         assert 'math.ceil' in r, r
         assert 'ready' in r, r
@@ -185,9 +185,9 @@ class TestSiteAdminNotifications(TestController):
 
     def test_site_notifications_access(self):
         self.app.get('/nf/admin/site_notifications', extra_environ=dict(
-            username='test_user'), status=403)
+            username=str('test_user')), status=403)
         r = self.app.get('/nf/admin/site_notifications', extra_environ=dict(
-            username='*anonymous'), status=302).follow()
+            username=str('*anonymous')), status=302).follow()
         assert 'Login' in r
 
     def test_site_notifications(self):
@@ -201,7 +201,7 @@ class TestSiteAdminNotifications(TestController):
         assert M.notification.SiteNotification.query.find().count() == 1
 
         r = self.app.get('/nf/admin/site_notifications/', extra_environ=dict(
-            username='root'))
+            username=str('root')))
         table = r.html.find('table')
         headers = table.findAll('th')
         row = table.findAll('td')
@@ -496,7 +496,7 @@ class TestUserDetails(TestController):
     def test_add_audit_trail_entry_access(self):
         self.app.get('/nf/admin/user/add_audit_log_entry', status=404)  # GET is not allowed
         r = self.app.post('/nf/admin/user/add_audit_log_entry',
-                          extra_environ={'username': '*anonymous'},
+                          extra_environ={'username': str('*anonymous')},
                           status=302)
         assert_equal(r.location, 'http://localhost/auth/')
 
@@ -649,7 +649,7 @@ class TestUserDetails(TestController):
                 'new_addr.addr': 'test@example.com',
                 'new_addr.claim': 'Claim Address',
                 'primary_addr': 'test@example.com'},
-                extra_environ=dict(username='test-admin'))
+                extra_environ=dict(username=str('test-admin')))
         r = self.app.get('/nf/admin/user/test-user')
         assert_in('test@example.com', r)
         em = M.EmailAddress.get(email='test@example.com')
@@ -664,7 +664,7 @@ class TestUserDetails(TestController):
                 'new_addr.addr': 'test2@example.com',
                 'new_addr.claim': 'Claim Address',
                 'primary_addr': 'test@example.com'},
-                extra_environ=dict(username='test-admin'))
+                extra_environ=dict(username=str('test-admin')))
         r = self.app.get('/nf/admin/user/test-user')
         assert_in('test2@example.com', r)
         em = M.EmailAddress.get(email='test2@example.com')
@@ -678,7 +678,7 @@ class TestUserDetails(TestController):
                 'username': 'test-user',
                 'new_addr.addr': '',
                 'primary_addr': 'test2@example.com'},
-                extra_environ=dict(username='test-admin'))
+                extra_environ=dict(username=str('test-admin')))
         r = self.app.get('/nf/admin/user/test-user')
         user = M.User.query.get(username='test-user')
         assert_equal(user.get_pref('email_address'), 'test2@example.com')
@@ -693,7 +693,7 @@ class TestUserDetails(TestController):
                 'addr-3.delete': 'on',
                 'new_addr.addr': '',
                 'primary_addr': 'test2@example.com'},
-                extra_environ=dict(username='test-admin'))
+                extra_environ=dict(username=str('test-admin')))
         r = self.app.get('/nf/admin/user/test-user')
         user = M.User.query.get(username='test-user')
         # test@example.com set as primary since test2@example.com is deleted
diff --git a/Allura/allura/tests/functional/test_trovecategory.py b/Allura/allura/tests/functional/test_trovecategory.py
index 850a3bb..8946043 100644
--- a/Allura/allura/tests/functional/test_trovecategory.py
+++ b/Allura/allura/tests/functional/test_trovecategory.py
@@ -63,7 +63,7 @@ class TestTroveCategory(TestController):
     def test_enableediting_setting(self):
         def check_access(username=None, status=None):
             self.app.get('/categories/', status=status,
-                         extra_environ=dict(username=username))
+                         extra_environ=dict(username=str(username)))
 
         cfg = {'trovecategories.enableediting': 'true'}
 
diff --git a/Allura/allura/tests/functional/test_user_profile.py b/Allura/allura/tests/functional/test_user_profile.py
index 6ed96a8..5ef92a1 100644
--- a/Allura/allura/tests/functional/test_user_profile.py
+++ b/Allura/allura/tests/functional/test_user_profile.py
@@ -150,7 +150,7 @@ class TestUserProfile(TestController):
     @td.with_user_project('test-user')
     def test_send_message_for_anonymous(self):
         r = self.app.get('/u/test-user/profile/send_message',
-                         extra_environ={'username': '*anonymous'},
+                         extra_environ={'username': str('*anonymous')},
                          status=302)
         assert 'You must be logged in to send user messages.' in self.webflash(
             r)
@@ -159,7 +159,7 @@ class TestUserProfile(TestController):
                           params={'subject': 'test subject',
                                   'message': 'test message',
                                   'cc': 'on'},
-                          extra_environ={'username': '*anonymous'},
+                          extra_environ={'username': str('*anonymous')},
                           status=302)
         assert 'You must be logged in to send user messages.' in self.webflash(
             r)
diff --git a/Allura/allura/tests/model/test_auth.py b/Allura/allura/tests/model/test_auth.py
index 88df750..57c36f6 100644
--- a/Allura/allura/tests/model/test_auth.py
+++ b/Allura/allura/tests/model/test_auth.py
@@ -339,7 +339,7 @@ def test_user_track_active():
     assert_equal(c.user.last_access['session_ip'], None)
     assert_equal(c.user.last_access['session_ua'], None)
 
-    req = Mock(headers={'User-Agent': 'browser'}, remote_addr='addr')
+    req = Mock(headers={'User-Agent': str('browser')}, remote_addr='addr')
     c.user.track_active(req)
     c.user = M.User.by_username(c.user.username)
     assert_not_equal(c.user.last_access['session_date'], None)
diff --git a/Allura/allura/tests/model/test_filesystem.py b/Allura/allura/tests/model/test_filesystem.py
index f8f1e7c..b36f9a2 100644
--- a/Allura/allura/tests/model/test_filesystem.py
+++ b/Allura/allura/tests/model/test_filesystem.py
@@ -148,7 +148,7 @@ class TestFile(TestCase):
             assert_equal(['test1'], response_body)
             assert_equal(response.content_type, f.content_type)
             assert_equal(response.headers['Content-Disposition'],
-                         'attachment;filename="te s\xe0\xad\xae1.txt"')
+                         'attachment;filename="te%20s%E0%AD%AE1.txt"')
 
     def test_image(self):
         path = os.path.join(
diff --git a/Allura/allura/tests/test_security.py b/Allura/allura/tests/test_security.py
index b57ee05..1acb87f 100644
--- a/Allura/allura/tests/test_security.py
+++ b/Allura/allura/tests/test_security.py
@@ -69,7 +69,7 @@ class TestSecurity(TestController):
                      status=200)
         # This should fail b/c test-user doesn't have the permission
         self.app.get('/security/test-user/needs_artifact_access_fail',
-                     extra_environ=dict(username='test-user'), status=403)
+                     extra_environ=dict(username=str('test-user')), status=403)
         # This should succeed b/c users with the 'admin' permission on a
         # project implicitly have all permissions to everything in the project
         self.app.get(
diff --git a/Allura/allura/tests/test_webhooks.py b/Allura/allura/tests/test_webhooks.py
index a1fad06..9dbba01 100644
--- a/Allura/allura/tests/test_webhooks.py
+++ b/Allura/allura/tests/test_webhooks.py
@@ -182,10 +182,10 @@ class TestWebhookController(TestController):
     def test_access(self):
         self.app.get(self.url + '/repo-push/')
         self.app.get(self.url + '/repo-push/',
-                     extra_environ={'username': 'test-user'},
+                     extra_environ={'username': str('test-user')},
                      status=403)
         r = self.app.get(self.url + '/repo-push/',
-                         extra_environ={'username': '*anonymous'},
+                         extra_environ={'username': str('*anonymous')},
                          status=302)
         assert_equal(r.location,
                      'http://localhost/auth/'
@@ -451,7 +451,7 @@ class TestSendWebhookHelper(TestWebhookBase):
         response = Mock(
             status_code=500,
             text='that is why',
-            headers={'Content-Type': 'application/json'})
+            headers={str('Content-Type'): str('application/json')})
         assert_equal(
             self.h.log_msg('Error', response=response),
             "Error: repo-push http://httpbin.org/post /adobe/adobe-1/src/ 500 "
diff --git a/Allura/allura/tests/unit/test_ldap_auth_provider.py b/Allura/allura/tests/unit/test_ldap_auth_provider.py
index 1500252..f635dd5 100644
--- a/Allura/allura/tests/unit/test_ldap_auth_provider.py
+++ b/Allura/allura/tests/unit/test_ldap_auth_provider.py
@@ -76,7 +76,7 @@ class TestLdapAuthenticationProvider(object):
             'password': 'test-password',
         }
         self.provider.request.method = 'POST'
-        self.provider.request.body = '&'.join(['%s=%s' % (k,v) for k,v in params.iteritems()])
+        self.provider.request.body = '&'.join(['%s=%s' % (k,v) for k,v in params.iteritems()]).encode('utf-8')
         ldap.dn.escape_dn_chars = lambda x: x
 
         self.provider._login()
@@ -95,7 +95,7 @@ class TestLdapAuthenticationProvider(object):
             'password': 'test-password',
         }
         self.provider.request.method = 'POST'
-        self.provider.request.body = '&'.join(['%s=%s' % (k,v) for k,v in params.iteritems()])
+        self.provider.request.body = '&'.join(['%s=%s' % (k,v) for k,v in params.iteritems()]).encode('utf-8')
         ldap.dn.escape_dn_chars = lambda x: x
         dn = 'uid=%s,ou=people,dc=localdomain' % params['username']
         conn = ldap.initialize.return_value
diff --git a/Allura/allura/webhooks.py b/Allura/allura/webhooks.py
index ca46755..6bee064 100644
--- a/Allura/allura/webhooks.py
+++ b/Allura/allura/webhooks.py
@@ -235,7 +235,7 @@ class WebhookRestController(BaseController):
     @expose('json:')
     @require_post()
     def index(self, **kw):
-        response.content_type = 'application/json'
+        response.content_type = str('application/json')
         try:
             params = {'secret': kw.pop('secret', ''),
                       'url': kw.pop('url', None)}
diff --git a/AlluraTest/alluratest/controller.py b/AlluraTest/alluratest/controller.py
index 066bf8c..5273060 100644
--- a/AlluraTest/alluratest/controller.py
+++ b/AlluraTest/alluratest/controller.py
@@ -178,7 +178,7 @@ class TestController(object):
         pkg = self.__module__.split('.')[0]
         self.app = ValidatingTestApp(
             setup_functional_test(app_name=self.application_under_test, current_pkg=pkg))
-        self.app.extra_environ = {'REMOTE_ADDR': '127.0.0.1'}  # remote_addr needed by AntiSpam
+        self.app.extra_environ = {str('REMOTE_ADDR'): str('127.0.0.1')}  # remote_addr needed by AntiSpam
         if self.validate_skip:
             self.app.validate_skip = self.validate_skip
         if asbool(tg.config.get('smtp.mock')):
@@ -269,7 +269,7 @@ class TestRestApiBase(TestController):
 
         token = self.token(user).api_key
         headers = {
-            'Authorization': 'Bearer {}'.format(token)
+            'Authorization': str('Bearer {}'.format(token))
         }
 
         fn = getattr(self.app, method.lower())
diff --git a/ForgeActivity/forgeactivity/main.py b/ForgeActivity/forgeactivity/main.py
index 32ddf03..1e6ba97 100644
--- a/ForgeActivity/forgeactivity/main.py
+++ b/ForgeActivity/forgeactivity/main.py
@@ -152,8 +152,8 @@ class ForgeActivityController(BaseController):
     @expose()
     def feed(self, **kw):
         data = self._get_activities_data(**kw)
-        response.headers['Content-Type'] = ''
-        response.content_type = 'application/xml'
+        response.headers['Content-Type'] = str('')
+        response.content_type = str('application/xml')
         d = {
             'title': 'Activity for %s' % data['followee'].activity_name,
             'link': h.absurl(self.app.url),
@@ -161,7 +161,7 @@ class ForgeActivityController(BaseController):
                 data['followee'].activity_name),
             'language': 'en',
         }
-        if request.environ['PATH_INFO'].endswith('.atom'):
+        if request.environ['PATH_INFO'].endswith(str('.atom')):
             feed = FG.Atom1Feed(**d)
         else:
             feed = FG.Rss201rev2Feed(**d)
diff --git a/ForgeActivity/forgeactivity/tests/functional/test_root.py b/ForgeActivity/forgeactivity/tests/functional/test_root.py
index 2f24766..faaac4d 100644
--- a/ForgeActivity/forgeactivity/tests/functional/test_root.py
+++ b/ForgeActivity/forgeactivity/tests/functional/test_root.py
@@ -55,7 +55,7 @@ class TestActivityController(TestController):
     @td.with_user_project('test-user-1')
     def test_anon_read(self):
         r = self.app.get('/u/test-user-1',
-                extra_environ={'username': '*anonymous'}).follow().follow()
+                extra_environ={'username': str('*anonymous')}).follow().follow()
         assert r.html.find('div', 'profile-section tools').find('a',
                 dict(href='/u/test-user-1/activity/')), \
                         'No Activity tool in top nav'
@@ -165,7 +165,7 @@ class TestActivityController(TestController):
     @td.with_user_project('test-user-1')
     def test_background_aggregation(self):
         self.app.post('/u/test-admin/activity/follow', {'follow': 'true'},
-                      extra_environ=dict(username='test-user-1'))
+                      extra_environ=dict(username=str('test-user-1')))
         # new ticket, creates activity
         d = {'ticket_form.summary': 'New Ticket'}
         self.app.post('/bugs/save_ticket', params=d)
@@ -429,7 +429,7 @@ class TestActivityController(TestController):
     def test_delete_item_gone(self):
         self.app.post('/u/test-user-1/activity/delete_item',
                       {'activity_id': str(ObjectId())},
-                      extra_environ={'username': 'root'},  # nbhd admin
+                      extra_environ={'username': str('root')},  # nbhd admin
                       status=410)
 
     @td.with_tool('u/test-user-1', 'activity')
@@ -475,7 +475,7 @@ class TestActivityController(TestController):
 
         self.app.post('/u/test-user-1/activity/delete_item',
                       {'activity_id': activity_id},
-                      extra_environ={'username': 'root'},  # nbhd admin
+                      extra_environ={'username': str('root')},  # nbhd admin
                       status=200)
         ThreadLocalODMSession.flush_all()
 
diff --git a/ForgeBlog/forgeblog/main.py b/ForgeBlog/forgeblog/main.py
index 30b85c2..e04530d 100644
--- a/ForgeBlog/forgeblog/main.py
+++ b/ForgeBlog/forgeblog/main.py
@@ -319,7 +319,7 @@ class RootController(BaseController, FeedController):
         if attachment is not None:
             post.add_multiple_attachments(attachment)
         notification_tasks.send_usermentions_notification.post(post.index_id(), kw['text'])
-        redirect(h.really_unicode(post.url()).encode('utf-8'))
+        redirect(h.urlquote(h.really_unicode(post.url())))
 
     @with_trailing_slash
     @expose('jinja:allura:templates/markdown_syntax_dialog.html')
@@ -426,7 +426,7 @@ class PostController(BaseController, FeedController):
         if delete:
             self.post.delete()
             flash('Post deleted', 'info')
-            redirect(h.really_unicode(c.app.url).encode('utf-8'))
+            redirect(h.urlquote(h.really_unicode(c.app.url)))
         else:
             g.spam_checker.check(kw['title'] + '\n' + kw['text'], artifact=self.post,
                                  user=c.user, content_type='blog-post')
@@ -593,7 +593,7 @@ class RootRestController(BaseController, AppRestControllerMixin):
                 text=text,
                 labels=labels.split(','),
                 **kw)
-            return exc.HTTPCreated(headers=dict(Location=h.absurl('/rest' + post.url()).encode('utf-8')))
+            return exc.HTTPCreated(headers=dict(Location=str(h.absurl('/rest' + post.url()).encode('utf-8'))))
 
         else:
             result = RootController().index(limit=limit, page=page)
diff --git a/ForgeBlog/forgeblog/tests/functional/test_rest.py b/ForgeBlog/forgeblog/tests/functional/test_rest.py
index a2630e7..b38018b 100644
--- a/ForgeBlog/forgeblog/tests/functional/test_rest.py
+++ b/ForgeBlog/forgeblog/tests/functional/test_rest.py
@@ -105,21 +105,21 @@ class TestBlogApi(TestRestApiBase):
         self.api_post('/rest/p/test/blog/', title='test',
                       text='test text', state='published')
         self.app.get('/rest/p/test/blog/',
-                     extra_environ={'username': '*anonymous'}, status=200)
+                     extra_environ={'username': str('*anonymous')}, status=200)
         p = M.Project.query.get(shortname='test')
         acl = p.app_instance('blog').config.acl
         anon = M.ProjectRole.by_name('*anonymous')._id
         anon_read = M.ACE.allow(anon, 'read')
         acl.remove(anon_read)
         self.app.get('/rest/p/test/blog/',
-                     extra_environ={'username': '*anonymous'},
+                     extra_environ={'username': str('*anonymous')},
                      status=401)
 
     def test_new_post_permissons(self):
         self.app.post('/rest/p/test/blog/',
                       params=dict(title='test', text='test text',
                                   state='published'),
-                      extra_environ={'username': '*anonymous'},
+                      extra_environ={'username': str('*anonymous')},
                       status=401)
         p = M.Project.query.get(shortname='test')
         acl = p.app_instance('blog').config.acl
@@ -129,7 +129,7 @@ class TestBlogApi(TestRestApiBase):
         self.app.post('/rest/p/test/blog/',
                       params=dict(title='test', text='test text',
                                   state='published'),
-                      extra_environ={'username': '*anonymous'},
+                      extra_environ={'username': str('*anonymous')},
                       status=201)
 
     def test_update_post_permissons(self):
@@ -139,7 +139,7 @@ class TestBlogApi(TestRestApiBase):
         self.app.post(url.encode('utf-8'),
                       params=dict(title='test2', text='test text2',
                                   state='published'),
-                      extra_environ={'username': '*anonymous'},
+                      extra_environ={'username': str('*anonymous')},
                       status=401)
         p = M.Project.query.get(shortname='test')
         acl = p.app_instance('blog').config.acl
@@ -149,7 +149,7 @@ class TestBlogApi(TestRestApiBase):
         self.app.post(url.encode('utf-8'),
                       params=dict(title='test2', text='test text2',
                                   state='published'),
-                      extra_environ={'username': '*anonymous'},
+                      extra_environ={'username': str('*anonymous')},
                       status=200)
         r = self.api_get(url)
         assert_equal(r.json['title'], 'test2')
@@ -160,13 +160,13 @@ class TestBlogApi(TestRestApiBase):
         self.api_post('/rest/p/test/blog/', title='test',
                       text='test text', state='draft')
         r = self.app.get('/rest/p/test/blog/',
-                         extra_environ={'username': '*anonymous'})
+                         extra_environ={'username': str('*anonymous')})
         assert_equal(r.json['posts'], [])
         url = '/rest' + BM.BlogPost.query.find().first().url()
         self.app.post(url.encode('utf-8'),
                       params=dict(title='test2', text='test text2',
                                   state='published'),
-                      extra_environ={'username': '*anonymous'},
+                      extra_environ={'username': str('*anonymous')},
                       status=401)
         p = M.Project.query.get(shortname='test')
         acl = p.app_instance('blog').config.acl
@@ -174,19 +174,19 @@ class TestBlogApi(TestRestApiBase):
         anon_write = M.ACE.allow(anon, 'write')
         acl.append(anon_write)
         r = self.app.get('/rest/p/test/blog/',
-                         extra_environ={'username': '*anonymous'})
+                         extra_environ={'username': str('*anonymous')})
         assert_equal(r.json['posts'][0]['title'], 'test')
 
     def test_draft_post(self):
         self.api_post('/rest/p/test/blog/', title='test',
                       text='test text', state='draft')
         r = self.app.get('/rest/p/test/blog/',
-                         extra_environ={'username': '*anonymous'})
+                         extra_environ={'username': str('*anonymous')})
         assert_equal(r.json['posts'], [])
         url = '/rest' + BM.BlogPost.query.find().first().url()
         self.api_post(url, state='published')
         r = self.app.get('/rest/p/test/blog/',
-                         extra_environ={'username': '*anonymous'})
+                         extra_environ={'username': str('*anonymous')})
         assert_equal(r.json['posts'][0]['title'], 'test')
 
     def test_pagination(self):
diff --git a/ForgeBlog/forgeblog/tests/functional/test_root.py b/ForgeBlog/forgeblog/tests/functional/test_root.py
index 9d112c5..8c61cfa 100644
--- a/ForgeBlog/forgeblog/tests/functional/test_root.py
+++ b/ForgeBlog/forgeblog/tests/functional/test_root.py
@@ -69,7 +69,7 @@ class Test(TestController):
         assert 'Nothing to see here' in response
         assert '/blog/%s/my-post/edit' % d in response
         anon_r = self.app.get('/blog/',
-                              extra_environ=dict(username='*anonymous'))
+                              extra_environ=dict(username=str('*anonymous')))
         # anonymous user can't see Edit links
         assert 'Nothing to see here' in anon_r
         assert '/blog/%s/my-post/edit' % d not in anon_r
@@ -83,7 +83,7 @@ class Test(TestController):
         assert 'Draft' in response
         assert '/blog/%s/my-post/edit' % d in response
         anon_r = self.app.get('/blog/',
-                              extra_environ=dict(username='*anonymous'))
+                              extra_environ=dict(username=str('*anonymous')))
         # anonymous user can't see draft posts
         assert 'Nothing to see here' not in anon_r
 
@@ -136,7 +136,7 @@ class Test(TestController):
         assert 'Nothing to see here' in response
         assert '/blog/%s/my-post/edit' % d in response
         anon_r = self.app.get('/blog/%s/my-post/' % d,
-                              extra_environ=dict(username='*anonymous'))
+                              extra_environ=dict(username=str('*anonymous')))
         # anonymous user can't see Edit links
         assert 'Nothing to see here' in anon_r
         assert '/blog/%s/my-post/edit' % d not in anon_r
@@ -150,7 +150,7 @@ class Test(TestController):
         assert 'Draft' in response
         assert '/blog/%s/my-post/edit' % d in response
         anon_r = self.app.get('/blog/%s/my-post/' % d,
-                              extra_environ=dict(username='*anonymous'))
+                              extra_environ=dict(username=str('*anonymous')))
         # anonymous user can't get to draft posts
         assert 'Nothing to see here' not in anon_r
 
@@ -161,7 +161,7 @@ class Test(TestController):
         assert 'Nothing' in response
         # anon users can't edit
         response = self.app.get('/blog/%s/my-post/edit' % d,
-                                extra_environ=dict(username='*anonymous'))
+                                extra_environ=dict(username=str('*anonymous')))
         assert 'Nothing' not in response
 
     def test_post_get_markdown(self):
@@ -183,7 +183,7 @@ class Test(TestController):
             '/blog/%s/my-post/update_markdown' % d,
             params={
                 'text': '- [x] checkbox'},
-            extra_environ=dict(username='*anonymous'))
+            extra_environ=dict(username=str('*anonymous')))
         assert response.json['status'] == 'no_permission'
 
     def test_post_attachments(self):
diff --git a/ForgeDiscussion/forgediscussion/tests/functional/test_forum.py b/ForgeDiscussion/forgediscussion/tests/functional/test_forum.py
index ff736b1..13c1f07 100644
--- a/ForgeDiscussion/forgediscussion/tests/functional/test_forum.py
+++ b/ForgeDiscussion/forgediscussion/tests/functional/test_forum.py
@@ -403,7 +403,7 @@ class TestForum(TestController):
             params[f.find('select')['name']] = 'testforum'
             params[f.find('input', {'style': 'width: 90%'})['name']] = 'Test Zero Posts'
             r = self.app.post('/discussion/save_new_topic', params=params,
-                              extra_environ=dict(username='*anonymous'),
+                              extra_environ=dict(username=str('*anonymous')),
                               status=302)
             assert r.location.startswith(
                 'http://localhost/p/test/discussion/testforum/thread/'), r.location
@@ -551,11 +551,11 @@ class TestForum(TestController):
         params[f.find('select')['name']] = 'testforum'
         params[f.find('input', {'style': 'width: 90%'})['name']] = 'Test Thread'
         thread = self.app.post('/discussion/save_new_topic', params=params,
-                               extra_environ=dict(username='*anonymous')).follow()
+                               extra_environ=dict(username=str('*anonymous'))).follow()
 
         # assert post awaiting moderation
         r = self.app.get(thread.request.url,
-                         extra_environ=dict(username='*anonymous'))
+                         extra_environ=dict(username=str('*anonymous')))
         assert 'Post awaiting moderation' in r
         assert 'name="delete"' not in r
         assert 'name="approve"' not in r
@@ -572,9 +572,9 @@ class TestForum(TestController):
             if field.has_attr('name'):
                 params[field['name']] = field.get('value') or ''
         params[f.find('textarea')['name']] = 'anon reply to anon post content'
-        r = self.app.post(str(rep_url), params=params, extra_environ=dict(username='*anonymous'))
+        r = self.app.post(str(rep_url), params=params, extra_environ=dict(username=str('*anonymous')))
         r = self.app.get(thread.request.url,
-                         extra_environ=dict(username='*anonymous'))
+                         extra_environ=dict(username=str('*anonymous')))
         assert 'anon reply to anon post' not in r
         assert_equal(spam_checker.check.call_args[0][0], 'anon reply to anon post content')
 
@@ -606,7 +606,7 @@ class TestForum(TestController):
 
         # assert anon can't edit their original post
         r = self.app.get(thread.request.url,
-                    extra_environ=dict(username='*anonymous'))
+                    extra_environ=dict(username=str('*anonymous')))
         assert 'Post content' in r
         post_container = r.html.find('div', {'id': post.slug})
         btn_edit = post_container.find('a', {'title': 'Edit'})
@@ -959,7 +959,7 @@ class TestForum(TestController):
         params[f.find('select')['name']] = 'testforum'
         params[f.find('input', {'style': 'width: 90%'})['name']] = 'AAA'
         thread = self.app.post('/discussion/save_new_topic',
-                               params=params).follow(extra_environ=dict(username='*anonymous'))
+                               params=params).follow(extra_environ=dict(username=str('*anonymous')))
         thread_sidebar_menu = str(thread.html.find('div', {'id': 'sidebar'}))
         assert_not_in("flag_as_spam", thread_sidebar_menu)
 
@@ -985,7 +985,7 @@ class TestForum(TestController):
         form.submit()
         r = self.app.get('/admin/discussion/forums')
         assert 'téstforum'.encode('utf-8') in r
-        r = self.app.get('/p/test/discussion/create_topic/téstforum/'.encode('utf-8'))
+        r = self.app.get(h.urlquote('/p/test/discussion/create_topic/téstforum/'))
         assert '<option value="téstforum" selected>Tést Forum</option>' in r
 
     def test_create_topic_attachment(self):
diff --git a/ForgeDiscussion/forgediscussion/tests/functional/test_forum_admin.py b/ForgeDiscussion/forgediscussion/tests/functional/test_forum_admin.py
index 1b9e54d..8f5a337 100644
--- a/ForgeDiscussion/forgediscussion/tests/functional/test_forum_admin.py
+++ b/ForgeDiscussion/forgediscussion/tests/functional/test_forum_admin.py
@@ -169,7 +169,7 @@ class TestForumAdmin(TestController):
         # forum can be viewed by member and non-member
         self.app.get('/discussion/secret')
         self.app.get('/discussion/secret',
-                     extra_environ=dict(username='test-user'))
+                     extra_environ=dict(username=str('test-user')))
         # make a post in the forum and confirm it is also viewable by member
         # and non-member
         r = self.app.get('/discussion/create_topic/')
@@ -187,12 +187,12 @@ class TestForumAdmin(TestController):
         r = self.app.post('/discussion/save_new_topic', params=params).follow()
         thread_url = r.request.url
         self.app.get(thread_url)
-        self.app.get(thread_url, extra_environ=dict(username='test-user'))
+        self.app.get(thread_url, extra_environ=dict(username=str('test-user')))
         # link shows up in app for member and non-member
         r = self.app.get('/discussion/')
         assert '/secret/' in r
         r = self.app.get('/discussion/',
-                         extra_environ=dict(username='test-user'))
+                         extra_environ=dict(username=str('test-user')))
         assert '/secret/' in r
         # make the forum member only viewable
         secret = FM.Forum.query.get(shortname='secret')
@@ -207,16 +207,16 @@ class TestForumAdmin(TestController):
         # member can see the forum, but non-member gets 403
         self.app.get('/discussion/secret')
         self.app.get('/discussion/secret',
-                     extra_environ=dict(username='test-user'), status=403)
+                     extra_environ=dict(username=str('test-user')), status=403)
         # member can see a thread in the forum, but non-member gets 403
         self.app.get(thread_url)
         self.app.get(thread_url,
-                     extra_environ=dict(username='test-user'), status=403)
+                     extra_environ=dict(username=str('test-user')), status=403)
         # link shows up in app for member but not non-member
         r = self.app.get('/discussion/')
         assert '/secret/' in r
         r = self.app.get('/discussion/',
-                         extra_environ=dict(username='test-user'))
+                         extra_environ=dict(username=str('test-user')))
         assert '/secret/' not in r
 
     def test_anon_posts(self):
@@ -239,7 +239,7 @@ class TestForumAdmin(TestController):
         params[f.find('select')['name']] = 'testforum'
         params[f.find('input', {'style': 'width: 90%'})['name']] = 'post topic'
         r = self.app.post('/discussion/save_new_topic',
-                          params=params, extra_environ=dict(username='*anonymous'))
+                          params=params, extra_environ=dict(username=str('*anonymous')))
         assert r.location == 'http://localhost/auth/'
         # allow anon posts in the forum
         testforum = FM.Forum.query.get(shortname='testforum')
diff --git a/ForgeDiscussion/forgediscussion/tests/functional/test_rest.py b/ForgeDiscussion/forgediscussion/tests/functional/test_rest.py
index 7b6a9bc..bca2e08 100644
--- a/ForgeDiscussion/forgediscussion/tests/functional/test_rest.py
+++ b/ForgeDiscussion/forgediscussion/tests/functional/test_rest.py
@@ -222,16 +222,16 @@ class TestRootRestController(TestDiscussionApiBase):
         acl.append(auth_read)
         self.api_get('/rest/p/test/discussion/')
         self.app.get('/rest/p/test/discussion/',
-                     extra_environ={'username': '*anonymous'},
+                     extra_environ={'username': str('*anonymous')},
                      status=401)
         self.api_get('/rest/p/test/discussion/general/')
         self.app.get('/rest/p/test/discussion/general/',
-                     extra_environ={'username': '*anonymous'},
+                     extra_environ={'username': str('*anonymous')},
                      status=401)
         t = ForumThread.query.find({'subject': 'Hi guys'}).first()
         self.api_get('/rest/p/test/discussion/general/thread/%s/' % t._id)
         self.app.get('/rest/p/test/discussion/general/thread/%s/' % t._id,
-                     extra_environ={'username': '*anonymous'},
+                     extra_environ={'username': str('*anonymous')},
                      status=401)
 
     def test_private_forums(self):
@@ -245,7 +245,7 @@ class TestRootRestController(TestDiscussionApiBase):
         r = self.api_get('/rest/p/test/discussion/')
         assert_equal(len(r.json['forums']), 2)
         r = self.app.get('/rest/p/test/discussion/',
-                         extra_environ={'username': '*anonymous'})
+                         extra_environ={'username': str('*anonymous')})
         assert_equal(len(r.json['forums']), 1)
         assert_equal(r.json['forums'][0]['shortname'], 'general')
 
diff --git a/ForgeGit/forgegit/tests/functional/test_controllers.py b/ForgeGit/forgegit/tests/functional/test_controllers.py
index 3c18bcd..88c0338 100644
--- a/ForgeGit/forgegit/tests/functional/test_controllers.py
+++ b/ForgeGit/forgegit/tests/functional/test_controllers.py
@@ -479,7 +479,7 @@ class TestRootController(_TestCase):
         assert_in('refresh queued', r)
         assert_equal(1, M.MonQTask.query.find(dict(task_name='allura.tasks.repo_tasks.refresh')).count())
 
-        r = self.app.get('/p/test/src-git/refresh', extra_environ={'HTTP_REFERER': '/p/test/src-git/'}, status=302)
+        r = self.app.get('/p/test/src-git/refresh', extra_environ={'HTTP_REFERER': str('/p/test/src-git/')}, status=302)
         assert_in('is being refreshed', self.webflash(r))
         assert_equal(2, M.MonQTask.query.find(dict(task_name='allura.tasks.repo_tasks.refresh')).count())
 
@@ -634,7 +634,7 @@ class TestFork(_TestCase):
 
     def test_merge_request_invisible_to_non_admin(self):
         assert 'Request Merge' not in self._fork_page(
-            extra_environ=dict(username='test-user'))
+            extra_environ=dict(username=str('test-user')))
 
     def test_merge_action_available_to_admin(self):
         self.app.get('/p/test2/code/request_merge')
@@ -642,7 +642,7 @@ class TestFork(_TestCase):
     def test_merge_action_unavailable_to_non_admin(self):
         self.app.get(
             '/p/test2/code/request_merge',
-            status=403, extra_environ=dict(username='test-user'))
+            status=403, extra_environ=dict(username=str('test-user')))
 
     def test_merge_request_detail_view(self):
         r, mr_num = self._request_merge()
@@ -798,7 +798,7 @@ class TestFork(_TestCase):
                               'summary': 'changed summary',
                               'description': 'changed description'
                           },
-                          extra_environ=dict(username='*anonymous'),
+                          extra_environ=dict(username=str('*anonymous')),
                           status=302,
                           ).follow()
         assert 'Login' in r
@@ -869,7 +869,7 @@ class TestFork(_TestCase):
             '/p/test/src-git/merge-requests/1/update_markdown',
             params={
                 'text': '- [x] checkbox'},
-            extra_environ=dict(username='*anonymous'))
+            extra_environ=dict(username=str('*anonymous')))
         assert response.json['status'] == 'no_permission'
 
     @patch.object(GM.Repository, 'merge_request_commits', autospec=True)
diff --git a/ForgeImporters/forgeimporters/tests/github/functional/test_github.py b/ForgeImporters/forgeimporters/tests/github/functional/test_github.py
index bfada9e..e1fa14a 100644
--- a/ForgeImporters/forgeimporters/tests/github/functional/test_github.py
+++ b/ForgeImporters/forgeimporters/tests/github/functional/test_github.py
@@ -37,12 +37,12 @@ class TestGitHubImportController(TestController, TestCase):
 
     def test_login_overlay(self):
         r = self.app.get('/p/import_project/github/',
-                         extra_environ=dict(username='*anonymous'))
+                         extra_environ=dict(username=str('*anonymous')))
         self.assertIn('GitHub Project Importer', r)
         self.assertIn('Login Required', r)
 
         r = self.app.post('/p/import_project/github/process',
-                          extra_environ=dict(username='*anonymous'), status=302)
+                          extra_environ=dict(username=str('*anonymous')), status=302)
         self.assertIn('/auth/', r.location)
 
 
diff --git a/ForgeImporters/forgeimporters/trac/tests/functional/test_trac.py b/ForgeImporters/forgeimporters/trac/tests/functional/test_trac.py
index c8237cd..e9ce596 100644
--- a/ForgeImporters/forgeimporters/trac/tests/functional/test_trac.py
+++ b/ForgeImporters/forgeimporters/trac/tests/functional/test_trac.py
@@ -53,6 +53,6 @@ class TestTracImportController(TestController):
 
 
     def test_import_with_phone_validation(self):
-        self.app.extra_environ = {'username': 'test-user'}
+        self.app.extra_environ = {'username': str('test-user')}
         with h.push_config(config, **{'project.verify_phone': 'true'}):
             self.test_submit()
\ No newline at end of file
diff --git a/ForgeLink/forgelink/link_main.py b/ForgeLink/forgelink/link_main.py
index 5e4af9d..acc13df 100644
--- a/ForgeLink/forgelink/link_main.py
+++ b/ForgeLink/forgelink/link_main.py
@@ -129,6 +129,8 @@ class RootController(BaseController):
         path = "/".join(remainder)
         url = c.app.config.options.get('url')
         if url:
+            # h.urlquote is better than utf8 encoding for Location headers, but in this case the url can be a full
+            # http://... url and we don't want to urlquote/urlencode that part
             redirect(url + h.really_unicode(path).encode('utf-8'))
         return dict()
 
diff --git a/ForgeLink/forgelink/tests/functional/test_rest.py b/ForgeLink/forgelink/tests/functional/test_rest.py
index 9678042..ced7978 100644
--- a/ForgeLink/forgelink/tests/functional/test_rest.py
+++ b/ForgeLink/forgelink/tests/functional/test_rest.py
@@ -54,19 +54,19 @@ class TestLinkApi(TestRestApiBase):
 
     def test_rest_link_get_permissions(self):
         self.app.get('/rest/p/test/link',
-                     extra_environ={'username': '*anonymous'}, status=200)
+                     extra_environ={'username': str('*anonymous')}, status=200)
         p = M.Project.query.get(shortname='test')
         acl = p.app_instance('link').config.acl
         anon = M.ProjectRole.by_name('*anonymous')._id
         anon_read = M.ACE.allow(anon, 'read')
         acl.remove(anon_read)
         self.app.get('/rest/p/test/link',
-                     extra_environ={'username': '*anonymous'}, status=401)
+                     extra_environ={'username': str('*anonymous')}, status=401)
 
     def test_rest_link_post_permissions(self):
         self.app.post('/rest/p/test/link',
                       params={'url': 'http://yahoo.com'},
-                      extra_environ={'username': '*anonymous'},
+                      extra_environ={'username': str('*anonymous')},
                       status=401)
         p = M.Project.query.get(shortname='test')
         acl = p.app_instance('link').config.acl
@@ -75,7 +75,7 @@ class TestLinkApi(TestRestApiBase):
         acl.append(anon_configure)
         self.app.post('/rest/p/test/link',
                       params={'url': 'http://yahoo.com'},
-                      extra_environ={'username': '*anonymous'},
+                      extra_environ={'username': str('*anonymous')},
                       status=200)
         r = self.api_get('/rest/p/test/link'.encode('utf-8'))
         assert_equal(r.json['url'], 'http://yahoo.com')
diff --git a/ForgeShortUrl/forgeshorturl/tests/functional/test.py b/ForgeShortUrl/forgeshorturl/tests/functional/test.py
index 059a348..a93ab53 100644
--- a/ForgeShortUrl/forgeshorturl/tests/functional/test.py
+++ b/ForgeShortUrl/forgeshorturl/tests/functional/test.py
@@ -91,7 +91,7 @@ class TestRootController(TestController):
         assert 'http://www.amazone.com/' in r
         assert '<td><small>yes</small></td>' in r
         self.app.get('/url/test_private',
-                     extra_environ=dict(username='*anonymous'),
+                     extra_environ=dict(username=str('*anonymous')),
                      status=404)
         self.app.get('/url/test_private',
                      status=302)
@@ -133,9 +133,9 @@ class TestRootController(TestController):
         self.app.post('/admin/url/add',
                       params=dict(short_url='g',
                                   full_url='http://google.com/'),
-                      extra_environ=dict(username='test-user'), status=403)
+                      extra_environ=dict(username=str('test-user')), status=403)
         self.app.post('/admin/url/remove', params=dict(shorturl='g'),
-                      extra_environ=dict(username='test-user'), status=403)
+                      extra_environ=dict(username=str('test-user')), status=403)
 
     def test_build_short_url(self):
         with h.push_config(config, **{
diff --git a/ForgeTracker/forgetracker/tests/functional/test_root.py b/ForgeTracker/forgetracker/tests/functional/test_root.py
index ede3637..75256f2 100644
--- a/ForgeTracker/forgetracker/tests/functional/test_root.py
+++ b/ForgeTracker/forgetracker/tests/functional/test_root.py
@@ -246,7 +246,7 @@ class TestSubprojectTrackerController(TrackerTestController):
         """Test that non-admin users can see tickets created by admins."""
         self.new_ticket(summary="my ticket", mount_point="/sub1/tickets/")
         response = self.app.get('/p/test/sub1/tickets/',
-                                extra_environ=dict(username='*anonymous'))
+                                extra_environ=dict(username=str('*anonymous')))
         assert 'my ticket' in response
 
     @td.with_tool('test/sub1', 'Tickets', 'tickets')
@@ -257,7 +257,7 @@ class TestSubprojectTrackerController(TrackerTestController):
         M.MonQTask.run_ready()
         ThreadLocalORMSession.flush_all()
         response = self.app.get('/p/test/sub1/tickets/search/?q=my',
-                                extra_environ=dict(username='*anonymous'))
+                                extra_environ=dict(username=str('*anonymous')))
         assert 'my ticket' in response, response.showbrowser()
 
     @td.with_tool('test/sub1', 'Tickets', 'tickets')
@@ -319,7 +319,7 @@ class TestFunctionalController(TrackerTestController):
             '/bugs/1/update_markdown',
             params={
                 'text': '- [x] checkbox'},
-            extra_environ=dict(username='*anonymous'))
+            extra_environ=dict(username=str('*anonymous')))
         assert response.json['status'] == 'no_permission'
 
     def test_labels(self):
@@ -348,7 +348,7 @@ class TestFunctionalController(TrackerTestController):
         # Private tickets shouldn't be included in counts if user doesn't
         # have read access to private tickets.
         r = self.app.get('/bugs/milestone_counts',
-                         extra_environ=dict(username='*anonymous'))
+                         extra_environ=dict(username=str('*anonymous')))
         counts['milestone_counts'][0]['count'] = 1
         assert_equal(r.body, json.dumps(counts))
 
@@ -371,7 +371,7 @@ class TestFunctionalController(TrackerTestController):
         
         # Private tickets shouldn't be included in counts if user doesn't
         # have read access to private tickets.
-        r = self.app.get('/bugs/bin_counts', extra_environ=dict(username='*anonymous'))
+        r = self.app.get('/bugs/bin_counts', extra_environ=dict(username=str('*anonymous')))
         assert_equal(r.json, {"bin_counts": [{"count": 1, "label": "Changes"},
                                              {"count": 0, "label": "Closed Tickets"},
                                              {"count": 1, "label": "Open Tickets"}]})
@@ -387,7 +387,7 @@ class TestFunctionalController(TrackerTestController):
         # Private tickets shouldn't be included in counts if user doesn't
         # have read access to private tickets.
         r = self.app.get('/bugs/milestone/1.0/',
-                         extra_environ=dict(username='*anonymous'))
+                         extra_environ=dict(username=str('*anonymous')))
         assert '0 / 1' in r
 
     def test_new_ticket_form(self):
@@ -658,7 +658,7 @@ class TestFunctionalController(TrackerTestController):
         assert '2 results' in search_response
         assert 'Private Ticket' in search_response
         # Unauthorized user doesn't see private ticket on list page...
-        env = dict(username='*anonymous')
+        env = dict(username=str('*anonymous'))
         r = self.app.get('/p/test/bugs/', extra_environ=env)
         assert '1 results' in r
         assert 'Private Ticket' not in r
@@ -718,7 +718,7 @@ class TestFunctionalController(TrackerTestController):
         assert_in('edit_post_form reply', response)  # Make sure admin can still comment
 
         # Unauthorized user cannot comment or even see form fields
-        env = dict(username='*anonymous')
+        env = dict(username=str('*anonymous'))
         r = self.app.get('/p/test/bugs/1', extra_environ=env)
         assert_not_in('edit_post_form reply', r)
 
@@ -782,7 +782,7 @@ class TestFunctionalController(TrackerTestController):
         assert 'Create Ticket' in index_view
 
         # Make sure the 'Create Ticket' button is disabled for user without 'create' perm
-        r = self.app.get('/bugs/', extra_environ=dict(username='*anonymous'))
+        r = self.app.get('/bugs/', extra_environ=dict(username=str('*anonymous')))
         create_button = r.html.find('a', attrs={'href': '/p/test/bugs/new/'})
         assert_equal(create_button['class'], ['icon', 'sidebar-disabled'])
 
@@ -1416,11 +1416,11 @@ class TestFunctionalController(TrackerTestController):
         M.MonQTask.run_ready()
         ThreadLocalORMSession.flush_all()
         response = self.app.get('/p/test/bugs/search/?q=reported_by_s:$USER',
-                                extra_environ={'username': 'test-user-0'})
+                                extra_environ={'username': str('test-user-0')})
         assert '1 result' in response, response.showbrowser()
         assert 'test first ticket' in response, response.showbrowser()
         response = self.app.get('/p/test/bugs/search/?q=reported_by_s:$USER',
-                                extra_environ={'username': 'test-user-1'})
+                                extra_environ={'username': str('test-user-1')})
         assert '1 result' in response, response.showbrowser()
         assert 'test second ticket' in response, response.showbrowser()
 
@@ -1959,7 +1959,7 @@ class TestFunctionalController(TrackerTestController):
         assert_true(r.html.find('div', {'id': 'vote'}))
 
         # test vote form not visible to anon user
-        r = self.app.get('/bugs/1/', extra_environ=dict(username='*anonymous'))
+        r = self.app.get('/bugs/1/', extra_environ=dict(username=str('*anonymous')))
         assert_false(r.html.find('div', {'id': 'vote'}))
 
         r = self.app.get('/bugs/1/')
@@ -1980,7 +1980,7 @@ class TestFunctionalController(TrackerTestController):
 
         # vote down by another user
         r = self.app.post('/bugs/1/vote', dict(vote='d'),
-                          extra_environ=dict(username='test-user-0'))
+                          extra_environ=dict(username=str('test-user-0')))
 
         expected_resp = json.dumps(dict(status='ok', votes_up=1, votes_down=1, votes_percent=50))
         assert r.response.content == expected_resp
@@ -2007,15 +2007,15 @@ class TestFunctionalController(TrackerTestController):
         but can't edit it without `update` permission.
         """
         response = self.app.get('/p/test/tracker/',
-                                extra_environ=dict(username='test-user-0'))
+                                extra_environ=dict(username=str('test-user-0')))
         assert 'Create Ticket' in response
 
         response = self.new_ticket(summary='test create, not update',
                                    mount_point='/tracker/',
-                                   extra_environ=dict(username='test-user-0'))
+                                   extra_environ=dict(username=str('test-user-0')))
         ticket_url = response.headers['Location']
         response = self.app.get(ticket_url,
-                                extra_environ=dict(username='test-user-0'))
+                                extra_environ=dict(username=str('test-user-0')))
         assert not response.html.find('div', {'class': 'error'})
         assert not response.html.find('a', {'class': 'edit_ticket'})
 
@@ -2023,13 +2023,13 @@ class TestFunctionalController(TrackerTestController):
                   post_install_hook=post_install_update_ticket_permission)
     def test_update_permission(self):
         r = self.app.get('/p/test/tracker/',
-                         extra_environ=dict(username='*anonymous'))
+                         extra_environ=dict(username=str('*anonymous')))
         assert 'Create Ticket' in r
 
         r = self.new_ticket(summary='test', mount_point='/tracker/',
-                            extra_environ=dict(username='*anonymous'))
+                            extra_environ=dict(username=str('*anonymous')))
         ticket_url = r.headers['Location']
-        r = self.app.get(ticket_url, extra_environ=dict(username='*anonymous'))
+        r = self.app.get(ticket_url, extra_environ=dict(username=str('*anonymous')))
         a = r.html.find('a', {'class': 'icon edit_ticket'})
         assert_equal(a.text, '\xa0Edit')
 
@@ -2045,7 +2045,7 @@ class TestFunctionalController(TrackerTestController):
         if update_permission in acl:
             acl.remove(update_permission)
         # test-user creates private ticket
-        env = {'username': 'test-user'}
+        env = {'username': str('test-user')}
         post_data = {
             'ticket_form.summary': 'Private ticket title',
             'ticket_form.private': True
@@ -2088,12 +2088,12 @@ class TestFunctionalController(TrackerTestController):
     def test_ticket_delete_without_permission(self):
         self.new_ticket(summary='Test ticket')
         self.app.post('/bugs/1/delete',
-                      extra_environ=dict(username='*anonymous'))
+                      extra_environ=dict(username=str('*anonymous')))
         r = self.app.get('/bugs/')
         assert '<a href="/p/test/bugs/1/">Test ticket</a>' in r
         self.app.post('/bugs/1/delete')
         self.app.post('/bugs/1/undelete',
-                      extra_environ=dict(username='*anonymous'))
+                      extra_environ=dict(username=str('*anonymous')))
         r = self.app.get('/bugs/')
         assert 'No open tickets found.' in r
 
@@ -2103,14 +2103,14 @@ class TestFunctionalController(TrackerTestController):
         r = self.app.get('/p/test/bugs/1/')
         assert '#1 test' in r
         self.app.get('/p/test/bugs/1/',
-                     extra_environ=dict(username='*anonymous'), status=404)
+                     extra_environ=dict(username=str('*anonymous')), status=404)
         r = self.app.get('/p/test/bugs/',
                          params=dict(q='test', deleted='True'))
         assert '<td><a href="/p/test/bugs/1/">test' in r
         assert '<tr class=" deleted">' in r
         r = self.app.get(
             '/p/test/bugs/', params=dict(q='test', deleted='True'),
-            extra_environ=dict(username='*anonymous'))
+            extra_environ=dict(username=str('*anonymous')))
         assert 'No open tickets found.' in r
 
     def test_show_hide_deleted_tickets(self):
@@ -2208,26 +2208,26 @@ class TestFunctionalController(TrackerTestController):
 
     def test_move_ticket_bad_data(self):
         self.new_ticket(summary='test')
-        r = self.app.post('/p/test/bugs/1/move', extra_environ={'HTTP_REFERER': '/p/test/bugs/1/'}).follow()  # empty POST
+        r = self.app.post('/p/test/bugs/1/move', extra_environ={'HTTP_REFERER': str('/p/test/bugs/1/')}).follow()  # empty POST
         assert 'Select valid tracker' in r, r
         r = self.app.post('/p/test/bugs/1/move',
                           params={'tracker': 'invalid tracker id'},
-                          extra_environ={'HTTP_REFERER': '/p/test/bugs/1/'}).follow()
+                          extra_environ={'HTTP_REFERER': str('/p/test/bugs/1/')}).follow()
         assert 'Select valid tracker' in r, r
         p = M.Project.query.get(shortname='test')
         tracker = p.app_instance('bugs')
         r = self.app.post('/p/test/bugs/1/move',
                           params={'tracker': str(tracker.config._id)},
-                          extra_environ={'HTTP_REFERER': '/p/test/bugs/1/'}).follow()
+                          extra_environ={'HTTP_REFERER': str('/p/test/bugs/1/')}).follow()
         assert 'Ticket already in a selected tracker' in r, r
 
     def test_move_ticket_access(self):
         self.new_ticket(summary='test')
         self.app.get('/p/test/bugs/1/move',
-                     extra_environ={'username': 'test-user'},
+                     extra_environ={'username': str('test-user')},
                      status=403)
         self.app.post('/p/test/bugs/1/move',
-                      extra_environ={'username': 'test-user'},
+                      extra_environ={'username': str('test-user')},
                       status=403)
 
     @td.with_tool('test', 'Tickets', 'dummy')
@@ -2319,7 +2319,7 @@ class TestFunctionalController(TrackerTestController):
 
         # subscribe test-user to ticket #2
         self.app.post('/p/test/bugs/2/subscribe', {'subscribe': True},
-                      extra_environ={'username': 'test-user'})
+                      extra_environ={'username': str('test-user')})
         assert M.Mailbox.query.get(user_id=user._id,
                                    project_id=p._id,
                                    app_config_id=bugs.config._id,
@@ -2558,7 +2558,7 @@ class TestFunctionalController(TrackerTestController):
         Credentials.get().clear()
         # make a ticket created by & assigned to test-user
         self.new_ticket(summary='foo bar', assigned_to='test-user', private=True,
-                        extra_environ={'username': 'test-user'})
+                        extra_environ={'username': str('test-user')})
         # but then remove the user
         M.User.query.remove({'username': 'test-user'})
 
@@ -2737,7 +2737,7 @@ class TestEmailMonitoring(TrackerTestController):
         self.app.post('/doc-bugs/1/update_ticket', {
             'summary': 'test moderation',
             'comment': 'test unmoderated post'
-        }, extra_environ=dict(username='*anonymous'))
+        }, extra_environ=dict(username=str('*anonymous')))
         send_direct.assert_called_with(
             str(M.User.query.get(username='test-admin')._id))
 
@@ -2751,7 +2751,7 @@ class TestEmailMonitoring(TrackerTestController):
         self.app.post('/doc-bugs/1/update_ticket', {
             'summary': 'test moderation',
             'comment': 'test unmoderated post'
-        }, extra_environ=dict(username='*anonymous'))
+        }, extra_environ=dict(username=str('*anonymous')))
         assert not send_direct.called
 
     @patch('forgetracker.model.ticket.Notification.send_simple')
@@ -2983,13 +2983,13 @@ class TestBulkMove(TrackerTestController):
     def test_access_restriction(self):
         self.app.get('/bugs/move/', status=200)
         self.app.get('/bugs/move/',
-                     extra_environ={'username': 'test-user-0'},
+                     extra_environ={'username': str('test-user-0')},
                      status=403)
         self.app.get('/bugs/move/',
-                     extra_environ={'username': '*anonymous'},
+                     extra_environ={'username': str('*anonymous')},
                      status=302)
         self.app.post('/bugs/move_tickets',
-                      extra_environ={'username': 'test-user-0'},
+                      extra_environ={'username': str('test-user-0')},
                       status=403)
 
     def test_ticket_list(self):
diff --git a/ForgeTracker/forgetracker/tests/unit/test_milestone_controller.py b/ForgeTracker/forgetracker/tests/unit/test_milestone_controller.py
index 579b805..00d2382 100644
--- a/ForgeTracker/forgetracker/tests/unit/test_milestone_controller.py
+++ b/ForgeTracker/forgetracker/tests/unit/test_milestone_controller.py
@@ -40,7 +40,7 @@ def test_unicode_lookup():
         root = None
         field = 'milestone'
         # u'Перспектива'
-        milestone_urlparam = '%D0%9F%D0%B5%D1%80%D1%81%D0%BF%D0%B5%D0%BA%D1%82%D0%B8%D0%B2%D0%B0'
+        milestone_urlparam = str('%D0%9F%D0%B5%D1%80%D1%81%D0%BF%D0%B5%D0%BA%D1%82%D0%B8%D0%B2%D0%B0')
         mc = MilestoneController(root, field, milestone_urlparam)
 
     assert mc.milestone  # check that it is found
diff --git a/ForgeTracker/forgetracker/tracker_main.py b/ForgeTracker/forgetracker/tracker_main.py
index 02cfbaf..50c5db8 100644
--- a/ForgeTracker/forgetracker/tracker_main.py
+++ b/ForgeTracker/forgetracker/tracker_main.py
@@ -855,11 +855,11 @@ class RootController(BaseController, FeedController):
             q = query
         result = TM.Ticket.paged_search(
             c.app.config, c.user, q, page=page, sort=sort, show_deleted=deleted, **kw)
-        response.headers['Content-Type'] = ''
-        response.content_type = 'application/xml'
+        response.headers['Content-Type'] = str('')
+        response.content_type = str('application/xml')
         d = dict(title='Ticket search results', link=h.absurl(c.app.url),
                  description='You searched for %s' % q, language='en')
-        if request.environ['PATH_INFO'].endswith('.atom'):
+        if request.environ['PATH_INFO'].endswith(str('.atom')):
             feed = FG.Atom1Feed(**d)
         else:
             feed = FG.Rss201rev2Feed(**d)
diff --git a/ForgeWiki/forgewiki/tests/functional/test_root.py b/ForgeWiki/forgewiki/tests/functional/test_root.py
index f2fd42c..871aabc 100644
--- a/ForgeWiki/forgewiki/tests/functional/test_root.py
+++ b/ForgeWiki/forgewiki/tests/functional/test_root.py
@@ -59,7 +59,7 @@ class TestRootController(TestController):
         assert 'Create Page' in r
         # No 'Create Page' button if user doesn't have 'create' perm
         r = self.app.get('/wiki/Home',
-                         extra_environ=dict(username='*anonymous'))
+                         extra_environ=dict(username=str('*anonymous')))
         assert 'Create Page' not in r, r
 
     def test_create_wiki_page(self):
@@ -82,7 +82,7 @@ class TestRootController(TestController):
 
     def test_root_new_page(self):
         response = self.app.get('/wiki/new_page?title=' + h.urlquote('tést'))
-        assert 'tést' in response
+        assert_equal(response.location, 'http://localhost/wiki/t%C3%A9st/')
 
     def test_root_new_search(self):
         self.app.get(h.urlquote('/wiki/tést/'))
@@ -151,17 +151,17 @@ class TestRootController(TestController):
         assert_in('To search for an exact phrase', div.text)
 
     def test_nonexistent_page_edit(self):
-        resp = self.app.get('/wiki/tést/')
+        resp = self.app.get(h.urlquote('/wiki/tést/'))
         assert resp.location.endswith(h.urlquote('/wiki/tést/edit')), resp.location
         resp = resp.follow()
         assert 'tést' in resp
 
     def test_nonexistent_page_noedit(self):
-        self.app.get('/wiki/tést/',
-                     extra_environ=dict(username='*anonymous'),
+        self.app.get(h.urlquote('/wiki/tést/'),
+                     extra_environ=dict(username=str('*anonymous')),
                      status=404)
-        self.app.get('/wiki/tést/',
-                     extra_environ=dict(username='test-user'),
+        self.app.get(h.urlquote('/wiki/tést/'),
+                     extra_environ=dict(username=str('test-user')),
                      status=404)
 
     @patch('forgewiki.wiki_main.g.director.create_activity')
@@ -215,34 +215,34 @@ class TestRootController(TestController):
         assert 'page.dot' in r
 
     def test_subpage_attempt(self):
-        self.app.get('/wiki/tést/')
+        self.app.get(h.urlquote('/wiki/tést/'))
         self.app.post(
-            '/wiki/tést/update',
+            h.urlquote('/wiki/tést/update'),
             params={
-                'title': 'tést',
+                'title': 'tést'.encode('utf-8'),
                 'text': 'text1',
                 'labels': '',
                 })
-        assert '/p/test/wiki/Home/' in self.app.get('/wiki/tést/Home/')
-        self.app.get('/wiki/tést/notthere/', status=404)
+        assert '/p/test/wiki/Home/' in self.app.get(h.urlquote('/wiki/tést/Home/'))
+        self.app.get(h.urlquote('/wiki/tést/notthere/'), status=404)
 
     def test_page_history(self):
-        self.app.get('/wiki/tést/')
+        self.app.get(h.urlquote('/wiki/tést/'))
         self.app.post(
-            '/wiki/tést/update',
+            h.urlquote('/wiki/tést/update'),
             params={
-                'title': 'tést',
+                'title': 'tést'.encode('utf-8'),
                 'text': 'text1',
                 'labels': '',
                 })
         self.app.post(
-            '/wiki/tést/update',
+            h.urlquote('/wiki/tést/update'),
             params={
-                'title': 'tést',
+                'title': 'tést'.encode('utf-8'),
                 'text': 'text2',
                 'labels': '',
                 })
-        response = self.app.get('/wiki/tést/history')
+        response = self.app.get(h.urlquote('/wiki/tést/history'))
         assert 'tést' in response
         # two revisions are shown
         assert '2 by Test Admin' in response
@@ -250,8 +250,8 @@ class TestRootController(TestController):
         # you can revert to an old revison, but not the current one
         assert response.html.find('a', {'data-dialog-id': '1'}), response.html
         assert not response.html.find('a', {'data-dialog-id': '2'})
-        response = self.app.get('/wiki/tést/history',
-                                extra_environ=dict(username='*anonymous'))
+        response = self.app.get(h.urlquote('/wiki/tést/history'),
+                                extra_environ=dict(username=str('*anonymous')))
         # two revisions are shown
         assert '2 by Test Admin' in response
         assert '1 by Test Admin' in response
@@ -260,20 +260,20 @@ class TestRootController(TestController):
         assert not response.html.find('a', {'data-dialog-id': '2'})
 
         # view an older version
-        response = self.app.get('/wiki/tést/?version=1')
+        response = self.app.get(h.urlquote('/wiki/tést/') + '?version=1')
         response.mustcontain('text1')
         response.mustcontain(no='text2')
 
     def test_page_diff(self):
         self.app.post(
-            '/wiki/tést/update',
+            h.urlquote('/wiki/tést/update'),
             params={
-                'title': 'tést',
+                'title': 'tést'.encode('utf-8'),
                 'text': 'sometext',
                 'labels': '',
                 })
-        self.app.post('/wiki/tést/revert', params=dict(version='1'))
-        response = self.app.get('/wiki/tést/diff?v1=0&v2=0')
+        self.app.post(h.urlquote('/wiki/tést/revert'), params=dict(version='1'))
+        response = self.app.get(h.urlquote('/wiki/tést/diff') + '?v1=0&v2=0')
         assert 'tést' in response
         d = dict(title='testdiff', text="""**Optionally**, you may also want to remove all the unused accounts that have accumulated (one was created for *every* logged in SF-user who has visited your MediaWiki hosted app):
 
@@ -371,104 +371,105 @@ class TestRootController(TestController):
 
     def test_page_revert_no_text(self):
         self.app.post(
-            '/wiki/tést/update',
+            h.urlquote('/wiki/tést/update'),
             params={
-                'title': 'tést',
+                'title': 'tést'.encode('utf-8'),
                 'text': '',
                 'labels': '',
                 })
-        response = self.app.post('/wiki/tést/revert', params=dict(version='1'))
+        response = self.app.post(h.urlquote('/wiki/tést/revert'), params=dict(version='1'))
         assert '.' in response.json['location']
-        response = self.app.get('/wiki/tést/')
+        response = self.app.get(h.urlquote('/wiki/tést/'))
         assert 'tést' in response
 
     def test_page_revert_with_text(self):
-        self.app.get('/wiki/tést/')
+        self.app.get(h.urlquote('/wiki/tést/'))
         self.app.post(
-            '/wiki/tést/update',
+            h.urlquote('/wiki/tést/update'),
             params={
-                'title': 'tést',
+                'title': 'tést'.encode('utf-8'),
                 'text': 'sometext',
                 'labels': '',
                 })
-        response = self.app.post('/wiki/tést/revert', params=dict(version='1'))
+        response = self.app.post(h.urlquote('/wiki/tést/revert'), params=dict(version='1'))
         assert '.' in response.json['location']
-        response = self.app.get('/wiki/tést/')
+        response = self.app.get(h.urlquote('/wiki/tést/'))
         assert 'tést' in response
 
     @patch('forgewiki.wiki_main.g.spam_checker')
     def test_page_update(self, spam_checker):
-        self.app.get('/wiki/tést/')
+        self.app.get(h.urlquote('/wiki/tést/'))
         response = self.app.post(
-            '/wiki/tést/update',
+            h.urlquote('/wiki/tést/update'),
             params={
-                'title': 'tést',
+                'title': 'tést'.encode('utf-8'),
                 'text': 'sometext',
                 'labels': '',
                 })
         assert_equal(spam_checker.check.call_args[0][0], 'tést\nsometext')
-        assert 'tést' in response
+        assert_equal(response.location, 'http://localhost/wiki/t%C3%A9st/')
 
     def test_page_get_markdown(self):
         self.app.post(
-            '/wiki/tést/update',
+            h.urlquote('/wiki/tést/update'),
             params={
-                'title': 'tést',
+                'title': 'tést'.encode('utf-8'),
                 'text': '- [ ] checkbox',
                 'labels': '',
                 })
-        response = self.app.get('/wiki/tést/get_markdown')
+        response = self.app.get(h.urlquote('/wiki/tést/get_markdown'))
         assert '- [ ] checkbox' in response
 
 
     def test_page_update_markdown(self):
         self.app.post(
-            '/wiki/tést/update',
+            h.urlquote('/wiki/tést/update'),
             params={
-                'title': 'tést',
+                'title': 'tést'.encode('utf-8'),
                 'text': '- [ ] checkbox',
                 'labels': '',
                 })
         response = self.app.post(
-            '/wiki/tést/update_markdown',
+            h.urlquote('/wiki/tést/update_markdown'),
             params={
                 'text': '- [x] checkbox'})
+        print(response)
         assert response.json['status'] == 'success'
         # anon users can't edit markdown
         response = self.app.post(
-            '/wiki/tést/update_markdown',
+            h.urlquote('/wiki/tést/update_markdown'),
             params={
                 'text': '- [x] checkbox'},
-            extra_environ=dict(username='*anonymous'))
+            extra_environ=dict(username=str('*anonymous')))
         assert response.json['status'] == 'no_permission'
 
     def test_page_label_unlabel(self):
-        self.app.get('/wiki/tést/')
+        self.app.get(h.urlquote('/wiki/tést/'))
         response = self.app.post(
-            '/wiki/tést/update',
+            h.urlquote('/wiki/tést/update'),
             params={
-                'title': 'tést',
+                'title': 'tést'.encode('utf-8'),
                 'text': 'sometext',
                 'labels': 'yellow,green',
                 })
-        assert 'tést' in response
+        assert_equal(response.location, 'http://localhost/wiki/t%C3%A9st/')
         response = self.app.post(
-            '/wiki/tést/update',
+            h.urlquote('/wiki/tést/update'),
             params={
-                'title': 'tést',
+                'title': 'tést'.encode('utf-8'),
                 'text': 'sometext',
                 'labels': 'yellow',
                 })
-        assert 'tést' in response
+        assert_equal(response.location, 'http://localhost/wiki/t%C3%A9st/')
 
     def test_page_label_count(self):
         labels = "label"
         for i in range(1, 100):
             labels += ',label%s' % i
         self.app.post(
-            '/wiki/tést/update',
+            h.urlquote('/wiki/tést/update'),
             params={
-                'title': 'tést',
+                'title': 'tést'.encode('utf-8'),
                 'text': 'sometext',
                 'labels': labels,
                 })
@@ -484,46 +485,46 @@ class TestRootController(TestController):
 
     def test_new_attachment(self):
         self.app.post(
-            '/wiki/tést/update',
+            h.urlquote('/wiki/tést/update'),
             params={
-                'title': 'tést',
+                'title': 'tést'.encode('utf-8'),
                 'text': 'sometext',
                 'labels': '',
                 })
         content = file(__file__).read()
-        self.app.post('/wiki/tést/attach',
+        self.app.post(h.urlquote('/wiki/tést/attach'),
                       upload_files=[('file_info', 'test_root.py', content)])
-        response = self.app.get('/wiki/tést/')
+        response = self.app.get(h.urlquote('/wiki/tést/'))
         assert 'test_root.py' in response
 
     def test_attach_two_files(self):
         self.app.post(
-            '/wiki/tést/update',
+            h.urlquote('/wiki/tést/update'),
             params={
-                'title': 'tést',
+                'title': 'tést'.encode('utf-8'),
                 'text': 'sometext',
                 'labels': '',
                 })
         content = file(__file__).read()
-        self.app.post('/wiki/tést/attach',
+        self.app.post(h.urlquote('/wiki/tést/attach'),
                       upload_files=[('file_info', 'test1.py', content), ('file_info', 'test2.py', content)])
-        response = self.app.get('/wiki/tést/')
+        response = self.app.get(h.urlquote('/wiki/tést/'))
         assert 'test1.py' in response
         assert 'test2.py' in response
 
     def test_new_text_attachment_content(self):
         self.app.post(
-            '/wiki/tést/update',
+            h.urlquote('/wiki/tést/update'),
             params={
-                'title': 'tést',
+                'title': 'tést'.encode('utf-8'),
                 'text': 'sometext',
                 'labels': '',
                 })
         file_name = 'test_root.py'
         file_data = file(__file__).read()
         upload = ('file_info', file_name, file_data)
-        self.app.post('/wiki/tést/attach', upload_files=[upload])
-        page_editor = self.app.get('/wiki/tést/edit')
+        self.app.post(h.urlquote('/wiki/tést/attach'), upload_files=[upload])
+        page_editor = self.app.get(h.urlquote('/wiki/tést/edit'))
         download = page_editor.click(description=file_name)
         assert_true(download.body == file_data)
 
@@ -559,7 +560,7 @@ class TestRootController(TestController):
                 filename) not in img_srcs, img_srcs
 
     def test_sidebar_static_page(self):
-        response = self.app.get('/wiki/tést/')
+        response = self.app.get(h.urlquote('/wiki/tést/'))
         assert 'Edit this page' not in response
         assert 'Related Pages' not in response
 
@@ -599,12 +600,12 @@ class TestRootController(TestController):
         assert 'bbb' in response
 
     def test_show_discussion(self):
-        self.app.post('/wiki/tést/update', params={
-            'title': 'tést',
+        self.app.post(h.urlquote('/wiki/tést/update'), params={
+            'title': 'tést'.encode('utf-8'),
             'text': 'sometext',
             'labels': '',
             })
-        wiki_page = self.app.get('/wiki/tést/')
+        wiki_page = self.app.get(h.urlquote('/wiki/tést/'))
         assert wiki_page.html.find('div', {'id': 'new_post_holder'})
         options_admin = self.app.get(
             '/admin/wiki/options', validate_chunk=True)
@@ -614,16 +615,16 @@ class TestRootController(TestController):
         options_admin2 = self.app.get(
             '/admin/wiki/options', validate_chunk=True)
         assert not options_admin2.form['show_discussion'].checked
-        wiki_page2 = self.app.get('/wiki/tést/')
+        wiki_page2 = self.app.get(h.urlquote('/wiki/tést/'))
         assert not wiki_page2.html.find('div', {'id': 'new_post_holder'})
 
     def test_show_left_bar(self):
-        self.app.post('/wiki/tést/update', params={
-            'title': 'tést',
+        self.app.post(h.urlquote('/wiki/tést/update'), params={
+            'title': 'tést'.encode('utf-8'),
             'text': 'sometext',
             'labels': '',
             })
-        wiki_page = self.app.get('/wiki/tést/')
+        wiki_page = self.app.get(h.urlquote('/wiki/tést/'))
         assert wiki_page.html.find('ul', {'class': 'sidebarmenu'})
         options_admin = self.app.get(
             '/admin/wiki/options', validate_chunk=True)
@@ -634,18 +635,18 @@ class TestRootController(TestController):
             '/admin/wiki/options', validate_chunk=True)
         assert not options_admin2.form['show_left_bar'].checked
         wiki_page2 = self.app.get(
-            '/wiki/tést/', extra_environ=dict(username='*anonymous'))
+            h.urlquote('/wiki/tést/'), extra_environ=dict(username=str('*anonymous')))
         assert not wiki_page2.html.find('ul', {'class': 'sidebarmenu'})
-        wiki_page3 = self.app.get('/wiki/tést/')
+        wiki_page3 = self.app.get(h.urlquote('/wiki/tést/'))
         assert not wiki_page3.html.find('ul', {'class': 'sidebarmenu'})
 
     def test_show_metadata(self):
-        self.app.post('/wiki/tést/update', params={
-            'title': 'tést',
+        self.app.post(h.urlquote('/wiki/tést/update'), params={
+            'title': 'tést'.encode('utf-8'),
             'text': 'sometext',
             'labels': '',
             })
-        wiki_page = self.app.get('/wiki/tést/')
+        wiki_page = self.app.get(h.urlquote('/wiki/tést/'))
         assert wiki_page.html.find('div', {'class': 'editbox'})
         options_admin = self.app.get(
             '/admin/wiki/options', validate_chunk=True)
@@ -655,12 +656,12 @@ class TestRootController(TestController):
         options_admin2 = self.app.get(
             '/admin/wiki/options', validate_chunk=True)
         assert not options_admin2.form['show_right_bar'].checked
-        wiki_page2 = self.app.get('/wiki/tést/')
+        wiki_page2 = self.app.get(h.urlquote('/wiki/tést/'))
         assert not wiki_page2.html.find('div', {'class': 'editbox'})
 
     def test_change_home_page(self):
-        self.app.post('/wiki/tést/update', params={
-            'title': 'our_néw_home',
+        self.app.post(h.urlquote('/wiki/tést/update'), params={
+            'title': 'our_néw_home'.encode('utf-8'),
             'text': 'sometext',
             'labels': '',
             })
@@ -669,7 +670,7 @@ class TestRootController(TestController):
         homepage_admin.form['new_home'].value = 'our_néw_home'
         homepage_admin.form.submit()
         root_path = self.app.get('/wiki/', status=302)
-        assert root_path.location.endswith('/wiki/our_néw_home/'), root_path.location
+        assert root_path.location.endswith('/wiki/our_n%C3%A9w_home/'), root_path.location
 
     def test_edit_mount_label(self):
         r = self.app.get('/admin/wiki/edit_label', validate_chunk=True)
@@ -944,7 +945,7 @@ class TestRootController(TestController):
 
     def test_sidebar_admin_menu_invisible_to_not_admin(self):
         def assert_invisible_for(username):
-            env = {'username': username}
+            env = {'username': str(username)}
             r = self.app.get('/p/test/wiki/Home/', extra_environ=env)
             menu = r.html.find('div', {'id': 'sidebar-admin-menu'})
             assert_equal(menu, None)
diff --git a/ForgeWiki/forgewiki/wiki_main.py b/ForgeWiki/forgewiki/wiki_main.py
index d089974..5b550af 100644
--- a/ForgeWiki/forgewiki/wiki_main.py
+++ b/ForgeWiki/forgewiki/wiki_main.py
@@ -388,7 +388,7 @@ class RootController(BaseController, DispatchIndex, FeedController):
     @with_trailing_slash
     @expose()
     def index(self, **kw):
-        redirect(h.really_unicode(c.app.root_page_name).encode('utf-8') + '/')
+        redirect(h.urlquote(h.really_unicode(c.app.root_page_name)+ '/'))
 
     @expose()
     def _lookup(self, pname, *remainder):
@@ -397,7 +397,7 @@ class RootController(BaseController, DispatchIndex, FeedController):
 
     @expose()
     def new_page(self, title):
-        redirect(h.really_unicode(title).encode('utf-8') + '/')
+        redirect(h.urlquote(h.really_unicode(title) + '/'))
 
     @with_trailing_slash
     @expose('jinja:forgewiki:templates/wiki/search.html')
@@ -674,7 +674,7 @@ class PageController(BaseController, FeedController):
         return dict(p1=p1, p2=p2, edits=result)
 
     @without_trailing_slash
-    @expose(content_type='text/plain')
+    @expose(content_type=str('text/plain'))
     def raw(self):
         if not self.page:
             raise exc.HTTPNotFound
@@ -756,8 +756,7 @@ class PageController(BaseController, FeedController):
             notification_tasks.send_usermentions_notification.post(self.page.index_id(), text, old_text)
         g.director.create_activity(c.user, activity_verb, self.page,
                                    related_nodes=[c.project], tags=['wiki'])
-        redirect('../' + h.really_unicode(self.page.title)
-                 .encode('utf-8') + ('/' if not name_conflict else '/edit'))
+        redirect('../' + h.urlquote(h.really_unicode(self.page.title)) + ('/' if not name_conflict else '/edit'))
 
     @without_trailing_slash
     @expose('json:')
@@ -927,9 +926,8 @@ class WikiAdminController(DefaultAdminController):
         flash('Home updated')
         mount_base = c.project.url() + \
             self.app.config.options.mount_point + '/'
-        url = h.really_unicode(mount_base).encode('utf-8') + \
-            h.really_unicode(new_home).encode('utf-8') + '/'
-        redirect(url)
+        url = h.really_unicode(mount_base) + h.really_unicode(new_home) + '/'
+        redirect(h.urlquote(url))
 
     @without_trailing_slash
     @expose()
diff --git a/scripts/perf/call_count.py b/scripts/perf/call_count.py
index 669e95e..c424d82 100755
--- a/scripts/perf/call_count.py
+++ b/scripts/perf/call_count.py
@@ -116,7 +116,7 @@ def generate_wiki_thread(test):
 def count_page(test, url, verbose=False, debug_html=False):
 
     with LogCapture('stats') as stats, LogCapture('timermiddleware') as calls:
-        resp = test.app.get(url, extra_environ=dict(username='*anonymous'))
+        resp = test.app.get(url, extra_environ=dict(username=str('*anonymous')))
         print url, resp.status
         if debug_html:
             debug_filename = 'call-{}.html'.format(''.join([random.choice(string.ascii_letters + string.digits)


[allura] 02/12: [#7878] run: python-modernize --future-unicode -n -w --no-diffs -f unicode_future .

Posted by br...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

brondsem pushed a commit to branch db/7878
in repository https://gitbox.apache.org/repos/asf/allura.git

commit 4e8c766f5e1c43fbaa21e535d58707c1ed31d73b
Author: Dave Brondsema <db...@slashdotmedia.com>
AuthorDate: Thu Jan 16 18:08:23 2020 +0000

    [#7878] run: python-modernize --future-unicode -n -w --no-diffs -f unicode_future .
---
 Allura/allura/__init__.py                          |   1 +
 Allura/allura/app.py                               |   5 +-
 Allura/allura/command/__init__.py                  |   1 +
 Allura/allura/command/base.py                      |   1 +
 Allura/allura/command/create_neighborhood.py       |   1 +
 Allura/allura/command/create_trove_categories.py   |   1 +
 Allura/allura/command/reclone_repo.py              |   1 +
 Allura/allura/command/script.py                    |   1 +
 Allura/allura/command/set_neighborhood_features.py |   1 +
 Allura/allura/command/show_models.py               |   1 +
 Allura/allura/command/smtp_server.py               |   1 +
 Allura/allura/command/taskd.py                     |   1 +
 Allura/allura/command/taskd_cleanup.py             |   1 +
 Allura/allura/config/app_cfg.py                    |   1 +
 Allura/allura/config/environment.py                |   1 +
 Allura/allura/config/middleware.py                 |   1 +
 Allura/allura/config/resources.py                  |   1 +
 Allura/allura/controllers/__init__.py              |   1 +
 Allura/allura/controllers/attachments.py           |   1 +
 Allura/allura/controllers/auth.py                  |  11 +-
 Allura/allura/controllers/base.py                  |   1 +
 Allura/allura/controllers/basetest_project_root.py |   1 +
 Allura/allura/controllers/discuss.py               |   3 +-
 Allura/allura/controllers/error.py                 |   1 +
 Allura/allura/controllers/feed.py                  |   1 +
 Allura/allura/controllers/project.py               |   3 +-
 Allura/allura/controllers/repository.py            |   7 +-
 Allura/allura/controllers/rest.py                  |   1 +
 Allura/allura/controllers/root.py                  |   1 +
 Allura/allura/controllers/search.py                |   1 +
 Allura/allura/controllers/site_admin.py            |  23 +--
 Allura/allura/controllers/static.py                |   1 +
 Allura/allura/controllers/task.py                  |   1 +
 Allura/allura/controllers/trovecategories.py       |   7 +-
 Allura/allura/eventslistener.py                    |   1 +
 Allura/allura/ext/admin/admin_main.py              |   5 +-
 Allura/allura/ext/admin/widgets.py                 |   1 +
 .../ext/personal_dashboard/dashboard_main.py       |   1 +
 Allura/allura/ext/project_home/project_main.py     |   1 +
 Allura/allura/ext/search/search_main.py            |   1 +
 Allura/allura/ext/user_profile/user_main.py        |   1 +
 Allura/allura/lib/AsciiDammit.py                   |   1 +
 Allura/allura/lib/app_globals.py                   |  17 +-
 Allura/allura/lib/base.py                          |   1 +
 Allura/allura/lib/custom_middleware.py             |   1 +
 Allura/allura/lib/decorators.py                    |   1 +
 Allura/allura/lib/diff.py                          |   1 +
 Allura/allura/lib/exceptions.py                    |   1 +
 Allura/allura/lib/gravatar.py                      |   1 +
 Allura/allura/lib/helpers.py                       |   5 +-
 Allura/allura/lib/import_api.py                    |   1 +
 Allura/allura/lib/macro.py                         |   3 +-
 Allura/allura/lib/mail_util.py                     |   7 +-
 Allura/allura/lib/markdown_extensions.py           |   3 +-
 Allura/allura/lib/multifactor.py                   |   1 +
 Allura/allura/lib/package_path_loader.py           |   1 +
 Allura/allura/lib/patches.py                       |   1 +
 Allura/allura/lib/phone/__init__.py                |   1 +
 Allura/allura/lib/phone/nexmo.py                   |   1 +
 Allura/allura/lib/plugin.py                        |  27 ++--
 Allura/allura/lib/repository.py                    |   1 +
 Allura/allura/lib/search.py                        |   7 +-
 Allura/allura/lib/solr.py                          |   1 +
 Allura/allura/lib/spam/__init__.py                 |   1 +
 Allura/allura/lib/spam/akismetfilter.py            |   1 +
 Allura/allura/lib/spam/stopforumspamfilter.py      |   1 +
 Allura/allura/lib/utils.py                         |   3 +-
 Allura/allura/lib/validators.py                    |   1 +
 Allura/allura/lib/widgets/__init__.py              |   1 +
 Allura/allura/lib/widgets/analytics.py             |   1 +
 Allura/allura/lib/widgets/auth_widgets.py          |   1 +
 Allura/allura/lib/widgets/discuss.py               |   1 +
 Allura/allura/lib/widgets/form_fields.py           |   1 +
 Allura/allura/lib/widgets/forms.py                 |   5 +-
 Allura/allura/lib/widgets/macros.py                |   1 +
 Allura/allura/lib/widgets/oauth_widgets.py         |   1 +
 Allura/allura/lib/widgets/project_list.py          |   1 +
 Allura/allura/lib/widgets/repo.py                  |   1 +
 Allura/allura/lib/widgets/search.py                |   1 +
 Allura/allura/lib/widgets/subscriptions.py         |   1 +
 Allura/allura/lib/widgets/user_profile.py          |   1 +
 Allura/allura/lib/widgets/vote.py                  |   1 +
 Allura/allura/model/__init__.py                    |   1 +
 Allura/allura/model/artifact.py                    |   9 +-
 Allura/allura/model/attachments.py                 |   1 +
 Allura/allura/model/auth.py                        |  17 +-
 Allura/allura/model/discuss.py                     |   5 +-
 Allura/allura/model/filesystem.py                  |   1 +
 Allura/allura/model/index.py                       |   1 +
 Allura/allura/model/monq_model.py                  |   1 +
 Allura/allura/model/multifactor.py                 |   1 +
 Allura/allura/model/neighborhood.py                |   1 +
 Allura/allura/model/notification.py                |   1 +
 Allura/allura/model/oauth.py                       |   1 +
 Allura/allura/model/project.py                     |   7 +-
 Allura/allura/model/repo.py                        |   1 +
 Allura/allura/model/repo_refresh.py                |   9 +-
 Allura/allura/model/repository.py                  |  11 +-
 Allura/allura/model/session.py                     |   1 +
 Allura/allura/model/stats.py                       |   1 +
 Allura/allura/model/timeline.py                    |   1 +
 Allura/allura/model/types.py                       |   1 +
 Allura/allura/model/webhook.py                     |   3 +-
 Allura/allura/scripts/create_sitemap_files.py      |   1 +
 Allura/allura/scripts/delete_projects.py           |   3 +-
 Allura/allura/scripts/disable_users.py             |   1 +
 Allura/allura/scripts/refresh_last_commits.py      |   1 +
 Allura/allura/scripts/refreshrepo.py               |   1 +
 Allura/allura/scripts/reindex_projects.py          |   1 +
 Allura/allura/scripts/reindex_users.py             |   1 +
 Allura/allura/scripts/scripttask.py                |   1 +
 Allura/allura/scripts/trac_export.py               |   1 +
 Allura/allura/tasks/activity_tasks.py              |   1 +
 Allura/allura/tasks/admin_tasks.py                 |   1 +
 Allura/allura/tasks/export_tasks.py                |   3 +-
 Allura/allura/tasks/index_tasks.py                 |   1 +
 Allura/allura/tasks/mail_tasks.py                  |   3 +-
 Allura/allura/tasks/notification_tasks.py          |   1 +
 Allura/allura/tasks/repo_tasks.py                  |   1 +
 Allura/allura/templates/__init__.py                |   2 +
 .../templates_responsive/responsive_overrides.py   |   1 +
 Allura/allura/tests/__init__.py                    |   1 +
 Allura/allura/tests/decorators.py                  |   1 +
 Allura/allura/tests/functional/__init__.py         |   2 +
 Allura/allura/tests/functional/test_admin.py       | 109 ++++++-------
 Allura/allura/tests/functional/test_auth.py        |   9 +-
 Allura/allura/tests/functional/test_discuss.py     |   1 +
 Allura/allura/tests/functional/test_feeds.py       |   3 +-
 Allura/allura/tests/functional/test_gravatar.py    |   3 +-
 Allura/allura/tests/functional/test_home.py        |  11 +-
 Allura/allura/tests/functional/test_nav.py         |   1 +
 .../allura/tests/functional/test_neighborhood.py   |   9 +-
 .../tests/functional/test_personal_dashboard.py    |   1 +
 Allura/allura/tests/functional/test_rest.py        |   1 +
 Allura/allura/tests/functional/test_root.py        |   1 +
 Allura/allura/tests/functional/test_search.py      |   1 +
 Allura/allura/tests/functional/test_site_admin.py  |  39 ++---
 Allura/allura/tests/functional/test_static.py      |   1 +
 Allura/allura/tests/functional/test_subscriber.py  |   1 +
 Allura/allura/tests/functional/test_tool_list.py   |   1 +
 .../allura/tests/functional/test_trovecategory.py  |   1 +
 .../allura/tests/functional/test_user_profile.py   |  13 +-
 Allura/allura/tests/model/__init__.py              |   2 +
 Allura/allura/tests/model/test_artifact.py         |   1 +
 Allura/allura/tests/model/test_auth.py             |   1 +
 Allura/allura/tests/model/test_discussion.py       |   9 +-
 Allura/allura/tests/model/test_filesystem.py       |  11 +-
 Allura/allura/tests/model/test_monq.py             |   1 +
 Allura/allura/tests/model/test_neighborhood.py     |  11 +-
 Allura/allura/tests/model/test_notification.py     |   1 +
 Allura/allura/tests/model/test_oauth.py            |   1 +
 Allura/allura/tests/model/test_project.py          |   5 +-
 Allura/allura/tests/model/test_repo.py             |   5 +-
 Allura/allura/tests/model/test_timeline.py         |   1 +
 .../tests/scripts/test_create_sitemap_files.py     |   1 +
 .../allura/tests/scripts/test_delete_projects.py   |   5 +-
 Allura/allura/tests/scripts/test_reindexes.py      |   1 +
 .../tests/templates/jinja_master/test_lib.py       |   1 +
 Allura/allura/tests/test_app.py                    |   3 +-
 Allura/allura/tests/test_commands.py               |   1 +
 Allura/allura/tests/test_decorators.py             |   1 +
 Allura/allura/tests/test_diff.py                   |   3 +-
 Allura/allura/tests/test_dispatch.py               |   1 +
 Allura/allura/tests/test_globals.py                |  91 +++++------
 Allura/allura/tests/test_helpers.py                |  39 ++---
 Allura/allura/tests/test_mail_util.py              |  35 ++--
 Allura/allura/tests/test_markdown.py               |   1 +
 Allura/allura/tests/test_middlewares.py            |   1 +
 Allura/allura/tests/test_multifactor.py            |   3 +-
 Allura/allura/tests/test_patches.py                |   1 +
 Allura/allura/tests/test_plugin.py                 |  15 +-
 Allura/allura/tests/test_scripttask.py             |   1 +
 Allura/allura/tests/test_security.py               |   1 +
 Allura/allura/tests/test_tasks.py                  |  71 +++++----
 Allura/allura/tests/test_utils.py                  |  15 +-
 Allura/allura/tests/test_validators.py             |   1 +
 Allura/allura/tests/test_webhooks.py               | 111 ++++++-------
 Allura/allura/tests/tscript.py                     |   1 +
 Allura/allura/tests/tscript_error.py               |   1 +
 Allura/allura/tests/unit/controllers/test_auth.py  |   1 +
 .../test_discussion_moderation_controller.py       |   1 +
 .../tests/unit/controllers/test_dispatch_index.py  |   1 +
 Allura/allura/tests/unit/factories.py              |   1 +
 Allura/allura/tests/unit/patches.py                |   1 +
 Allura/allura/tests/unit/phone/test_nexmo.py       |   1 +
 .../allura/tests/unit/phone/test_phone_service.py  |   1 +
 Allura/allura/tests/unit/spam/test_akismet.py      |  15 +-
 Allura/allura/tests/unit/spam/test_spam_filter.py  |   1 +
 .../allura/tests/unit/spam/test_stopforumspam.py   |   3 +-
 Allura/allura/tests/unit/test_app.py               |   1 +
 Allura/allura/tests/unit/test_artifact.py          |   1 +
 Allura/allura/tests/unit/test_discuss.py           |   1 +
 Allura/allura/tests/unit/test_helpers/test_ago.py  |   1 +
 .../tests/unit/test_helpers/test_set_context.py    |   1 +
 .../allura/tests/unit/test_ldap_auth_provider.py   |  11 +-
 Allura/allura/tests/unit/test_mixins.py            |   1 +
 .../allura/tests/unit/test_package_path_loader.py  |   1 +
 Allura/allura/tests/unit/test_post_model.py        |   1 +
 Allura/allura/tests/unit/test_project.py           |  19 +--
 Allura/allura/tests/unit/test_repo.py              |   1 +
 Allura/allura/tests/unit/test_session.py           |   1 +
 Allura/allura/tests/unit/test_sitemapentry.py      |   1 +
 Allura/allura/tests/unit/test_solr.py              |   1 +
 Allura/allura/version.py                           |   1 +
 Allura/allura/webhooks.py                          |  11 +-
 Allura/allura/websetup/__init__.py                 |   1 +
 Allura/allura/websetup/bootstrap.py                |   1 +
 Allura/allura/websetup/schema.py                   |   1 +
 Allura/docs/conf.py                                |   5 +-
 Allura/ldap-setup.py                               |   1 +
 Allura/ldap-userconfig.py                          |   1 +
 Allura/setup.py                                    |   1 +
 AlluraTest/alluratest/controller.py                |   3 +-
 AlluraTest/alluratest/pylint_checkers.py           |   1 +
 AlluraTest/alluratest/smtp_debug.py                |   1 +
 AlluraTest/alluratest/test_syntax.py               |   1 +
 AlluraTest/alluratest/validation.py                |   3 +-
 AlluraTest/setup.py                                |   1 +
 ForgeActivity/forgeactivity/config/resources.py    |   1 +
 ForgeActivity/forgeactivity/main.py                |   5 +-
 .../forgeactivity/tests/functional/test_rest.py    |   1 +
 .../forgeactivity/tests/functional/test_root.py    |   1 +
 ForgeActivity/forgeactivity/widgets/follow.py      |   3 +-
 ForgeActivity/setup.py                             |   1 +
 ForgeBlog/forgeblog/command/base.py                |   1 +
 ForgeBlog/forgeblog/command/rssfeeds.py            |   5 +-
 ForgeBlog/forgeblog/main.py                        |   5 +-
 ForgeBlog/forgeblog/model/blog.py                  |   1 +
 ForgeBlog/forgeblog/tests/functional/test_feeds.py |   1 +
 ForgeBlog/forgeblog/tests/functional/test_rest.py  |   1 +
 ForgeBlog/forgeblog/tests/functional/test_root.py  |   1 +
 ForgeBlog/forgeblog/tests/test_app.py              |   1 +
 ForgeBlog/forgeblog/tests/test_commands.py         |  11 +-
 ForgeBlog/forgeblog/tests/test_roles.py            |   1 +
 ForgeBlog/forgeblog/tests/unit/__init__.py         |   1 +
 ForgeBlog/forgeblog/tests/unit/test_blog_post.py   |   1 +
 ForgeBlog/forgeblog/version.py                     |   1 +
 ForgeBlog/forgeblog/widgets.py                     |   1 +
 ForgeBlog/setup.py                                 |   1 +
 ForgeChat/forgechat/command.py                     |   1 +
 ForgeChat/forgechat/main.py                        |   1 +
 ForgeChat/forgechat/model/chat.py                  |   1 +
 ForgeChat/forgechat/tests/functional/test_root.py  |   1 +
 ForgeChat/forgechat/version.py                     |   1 +
 ForgeChat/setup.py                                 |   1 +
 .../forgediscussion/controllers/forum.py           |   1 +
 .../forgediscussion/controllers/root.py            |   1 +
 ForgeDiscussion/forgediscussion/forum_main.py      |   1 +
 ForgeDiscussion/forgediscussion/import_support.py  |   1 +
 ForgeDiscussion/forgediscussion/model/forum.py     |   1 +
 ForgeDiscussion/forgediscussion/site_stats.py      |   1 +
 ForgeDiscussion/forgediscussion/tasks.py           |   1 +
 .../forgediscussion/tests/functional/test_forum.py |  19 +--
 .../tests/functional/test_forum_admin.py           |   1 +
 .../tests/functional/test_import.py                |   1 +
 .../forgediscussion/tests/functional/test_rest.py  |  11 +-
 ForgeDiscussion/forgediscussion/tests/test_app.py  |  21 +--
 .../forgediscussion/tests/test_forum_roles.py      |   1 +
 ForgeDiscussion/forgediscussion/utils.py           |   1 +
 ForgeDiscussion/forgediscussion/version.py         |   1 +
 .../forgediscussion/widgets/__init__.py            |   1 +
 ForgeDiscussion/forgediscussion/widgets/admin.py   |   3 +-
 .../forgediscussion/widgets/forum_widgets.py       |   1 +
 ForgeDiscussion/setup.py                           |   1 +
 ForgeFeedback/forgefeedback/feedback_main.py       |   1 +
 ForgeFeedback/forgefeedback/model/feedback.py      |   1 +
 .../forgefeedback/tests/functional/test_root.py    |   1 +
 .../forgefeedback/tests/test_feedback_roles.py     |   1 +
 ForgeFeedback/forgefeedback/tests/unit/__init__.py |   1 +
 .../forgefeedback/tests/unit/test_feedback.py      |   1 +
 .../tests/unit/test_root_controller.py             |   1 +
 ForgeFeedback/forgefeedback/version.py             |   1 +
 ForgeFeedback/setup.py                             |   1 +
 ForgeGit/forgegit/controllers.py                   |   1 +
 ForgeGit/forgegit/git_main.py                      |   1 +
 ForgeGit/forgegit/model/git_repo.py                |   5 +-
 ForgeGit/forgegit/tests/__init__.py                |   1 +
 ForgeGit/forgegit/tests/functional/test_auth.py    |   1 +
 .../forgegit/tests/functional/test_controllers.py  |  23 +--
 ForgeGit/forgegit/tests/model/test_repository.py   | 169 ++++++++++----------
 ForgeGit/forgegit/tests/test_git_app.py            |   1 +
 ForgeGit/forgegit/tests/test_tasks.py              |   1 +
 ForgeGit/forgegit/version.py                       |   1 +
 ForgeGit/setup.py                                  |   1 +
 ForgeImporters/docs/conf.py                        |   5 +-
 ForgeImporters/forgeimporters/base.py              |   1 +
 ForgeImporters/forgeimporters/forge/tracker.py     |   1 +
 ForgeImporters/forgeimporters/github/__init__.py   |   1 +
 ForgeImporters/forgeimporters/github/code.py       |   1 +
 ForgeImporters/forgeimporters/github/project.py    |   1 +
 ForgeImporters/forgeimporters/github/tasks.py      |   1 +
 .../forgeimporters/github/tests/test_code.py       |   9 +-
 .../forgeimporters/github/tests/test_oauth.py      |   1 +
 .../forgeimporters/github/tests/test_tracker.py    |   9 +-
 .../forgeimporters/github/tests/test_utils.py      |  17 +-
 .../forgeimporters/github/tests/test_wiki.py       | 177 +++++++++++----------
 ForgeImporters/forgeimporters/github/tracker.py    |  13 +-
 ForgeImporters/forgeimporters/github/utils.py      |   7 +-
 ForgeImporters/forgeimporters/github/wiki.py       |  13 +-
 .../forgeimporters/tests/forge/test_tracker.py     |   5 +-
 .../tests/github/functional/test_github.py         |   1 +
 .../forgeimporters/tests/github/test_extractor.py  |  29 ++--
 .../forgeimporters/tests/github/test_tasks.py      |   1 +
 .../forgeimporters/tests/github/test_tracker.py    |   5 +-
 ForgeImporters/forgeimporters/tests/test_base.py   |   1 +
 ForgeImporters/forgeimporters/trac/__init__.py     |   1 +
 ForgeImporters/forgeimporters/trac/project.py      |   1 +
 .../trac/tests/functional/test_trac.py             |   1 +
 .../forgeimporters/trac/tests/test_tickets.py      |   7 +-
 ForgeImporters/forgeimporters/trac/tickets.py      |   1 +
 ForgeImporters/setup.py                            |   1 +
 ForgeLink/forgelink/link_main.py                   |   1 +
 ForgeLink/forgelink/tests/functional/test_rest.py  |  15 +-
 ForgeLink/forgelink/tests/functional/test_root.py  |   3 +-
 ForgeLink/forgelink/tests/test_app.py              |   1 +
 ForgeLink/forgelink/version.py                     |   1 +
 ForgeLink/setup.py                                 |   1 +
 ForgeSVN/forgesvn/controllers.py                   |   1 +
 ForgeSVN/forgesvn/model/svn.py                     |   1 +
 ForgeSVN/forgesvn/svn_main.py                      |   1 +
 ForgeSVN/forgesvn/tests/__init__.py                |   1 +
 ForgeSVN/forgesvn/tests/functional/test_auth.py    |   1 +
 .../forgesvn/tests/functional/test_controllers.py  |   5 +-
 ForgeSVN/forgesvn/tests/model/test_repository.py   | 141 ++++++++--------
 .../forgesvn/tests/model/test_svnimplementation.py |   1 +
 ForgeSVN/forgesvn/tests/test_svn_app.py            |   1 +
 ForgeSVN/forgesvn/tests/test_tasks.py              |   1 +
 ForgeSVN/forgesvn/version.py                       |   1 +
 ForgeSVN/forgesvn/widgets.py                       |   1 +
 ForgeSVN/setup.py                                  |   1 +
 ForgeShortUrl/forgeshorturl/main.py                |   1 +
 ForgeShortUrl/forgeshorturl/model/shorturl.py      |   1 +
 .../forgeshorturl/tests/functional/test.py         |   1 +
 ForgeShortUrl/forgeshorturl/widgets/short_url.py   |   1 +
 ForgeShortUrl/setup.py                             |   1 +
 .../forgetracker/command/fix_discussion.py         |   1 +
 ForgeTracker/forgetracker/config/resources.py      |   1 +
 ForgeTracker/forgetracker/import_support.py        |   9 +-
 ForgeTracker/forgetracker/model/ticket.py          |   3 +-
 ForgeTracker/forgetracker/search.py                |   1 +
 ForgeTracker/forgetracker/site_stats.py            |   1 +
 ForgeTracker/forgetracker/tasks.py                 |   1 +
 .../tests/command/test_fix_discussion.py           |   1 +
 .../forgetracker/tests/functional/test_rest.py     |   1 +
 .../forgetracker/tests/functional/test_root.py     |  53 +++---
 ForgeTracker/forgetracker/tests/test_app.py        |   1 +
 .../forgetracker/tests/test_tracker_roles.py       |   1 +
 ForgeTracker/forgetracker/tests/unit/__init__.py   |   1 +
 .../forgetracker/tests/unit/test_globals_model.py  |   1 +
 .../tests/unit/test_milestone_controller.py        |   5 +-
 .../tests/unit/test_root_controller.py             |   1 +
 .../forgetracker/tests/unit/test_search.py         |   1 +
 .../tests/unit/test_ticket_custom_fields_form.py   |   1 +
 .../forgetracker/tests/unit/test_ticket_form.py    |   1 +
 .../forgetracker/tests/unit/test_ticket_model.py   |   1 +
 ForgeTracker/forgetracker/tracker_main.py          |  11 +-
 ForgeTracker/forgetracker/version.py               |   1 +
 ForgeTracker/forgetracker/widgets/admin.py         |   1 +
 .../forgetracker/widgets/admin_custom_fields.py    |   1 +
 ForgeTracker/forgetracker/widgets/bin_form.py      |   1 +
 ForgeTracker/forgetracker/widgets/ticket_form.py   |   1 +
 ForgeTracker/forgetracker/widgets/ticket_search.py |   1 +
 ForgeTracker/setup.py                              |   1 +
 .../forgeuserstats/controllers/userstats.py        |   1 +
 ForgeUserStats/forgeuserstats/main.py              |   1 +
 ForgeUserStats/forgeuserstats/model/stats.py       |   1 +
 ForgeUserStats/forgeuserstats/tests/test_model.py  |   1 +
 ForgeUserStats/forgeuserstats/tests/test_stats.py  |   1 +
 ForgeUserStats/forgeuserstats/version.py           |   1 +
 ForgeUserStats/forgeuserstats/widgets/forms.py     |   1 +
 ForgeUserStats/setup.py                            |   1 +
 ForgeWiki/forgewiki/converters.py                  |   1 +
 ForgeWiki/forgewiki/model/wiki.py                  |   1 +
 ForgeWiki/forgewiki/tests/functional/test_rest.py  |  11 +-
 ForgeWiki/forgewiki/tests/functional/test_root.py  |  25 +--
 ForgeWiki/forgewiki/tests/test_app.py              |   1 +
 ForgeWiki/forgewiki/tests/test_converters.py       |   1 +
 ForgeWiki/forgewiki/tests/test_models.py           |   1 +
 ForgeWiki/forgewiki/tests/test_wiki_roles.py       |   1 +
 ForgeWiki/forgewiki/version.py                     |   1 +
 ForgeWiki/forgewiki/wiki_main.py                   |   5 +-
 ForgeWiki/setup.py                                 |   1 +
 fuse/accessfs.py                                   |   1 +
 scripts/ApacheAccessHandler.py                     |   1 +
 scripts/add_user_to_group.py                       |   1 +
 scripts/changelog.py                               |   1 +
 scripts/create-allura-sitemap.py                   |   1 +
 scripts/migrations/013-update-ordinals.py          |   1 +
 .../015-add-neighborhood_id-to-blog-posts.py       |   1 +
 scripts/migrations/018-add-svn-checkout-url.py     |   1 +
 .../migrations/020-remove-wiki-title-slashes.py    |   1 +
 scripts/migrations/022-change-anon-display-name.py |   1 +
 .../migrations/024-migrate-custom-profile-text.py  |   1 +
 scripts/migrations/025-add-is-nbhd-project.py      |   1 +
 scripts/migrations/026-install-activity-tool.py    |   1 +
 .../027-change-ticket-write-permissions.py         |   1 +
 scripts/migrations/028-remove-svn-trees.py         |   1 +
 scripts/migrations/029-set-mailbox-queue_empty.py  |   1 +
 .../migrations/031-set-user-pending-to-false.py    |   1 +
 .../032-subscribe-merge-request-submitters.py      |   1 +
 .../033-change-comment-anon-permissions.py         |   1 +
 ...34-update_subscriptions_ticket_and_mr_titles.py |   1 +
 scripts/new_ticket.py                              |   1 +
 scripts/perf/benchmark-scm.py                      |   1 +
 scripts/perf/call_count.py                         |   1 +
 scripts/perf/generate-projects.py                  |   1 +
 scripts/perf/load-up-forum.py                      |   1 +
 scripts/perf/md_perf.py                            |   1 +
 scripts/perf/parse_timings.py                      |   1 +
 scripts/perf/sstress.py                            |   1 +
 scripts/perf/test_git_lcd.py                       |   1 +
 scripts/project-import.py                          |   1 +
 scripts/publicize-neighborhood.py                  |   1 +
 scripts/rethumb.py                                 |   1 +
 scripts/scrub-allura-data.py                       |   1 +
 scripts/teamforge-import.py                        |   1 +
 scripts/trac_export.py                             |   1 +
 scripts/trac_export_wiki.py                        |   1 +
 scripts/trac_import.py                             |   1 +
 scripts/wiki-copy.py                               |   1 +
 420 files changed, 1261 insertions(+), 838 deletions(-)

diff --git a/Allura/allura/__init__.py b/Allura/allura/__init__.py
index 15c3ab2..87deecd 100644
--- a/Allura/allura/__init__.py
+++ b/Allura/allura/__init__.py
@@ -18,6 +18,7 @@
 #       under the License.
 
 """The allura package"""
+from __future__ import unicode_literals
 from tg.support.registry import StackedObjectProxy
 
 credentials = StackedObjectProxy(name='credentials')
diff --git a/Allura/allura/app.py b/Allura/allura/app.py
index b238f67..478b48f 100644
--- a/Allura/allura/app.py
+++ b/Allura/allura/app.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import os
 import logging
 from urllib import basejoin
@@ -655,7 +656,7 @@ class Application(object):
     def admin_menu_collapse_button(self):
         """Returns button for showing/hiding admin sidebar menu"""
         return SitemapEntry(
-            label=u'Admin - {}'.format(self.config.options.mount_label),
+            label='Admin - {}'.format(self.config.options.mount_label),
             extra_html_attrs={
                 'id': 'sidebar-admin-menu-trigger',
             })
@@ -960,7 +961,7 @@ class DefaultAdminController(BaseController, AdminControllerMixin):
                 try:
                     val = opt.validate(val)
                 except fev.Invalid as e:
-                    flash(u'{}: {}'.format(opt.name, str(e)), 'error')
+                    flash('{}: {}'.format(opt.name, str(e)), 'error')
                     continue
                 self.app.config.options[opt.name] = val
             if is_admin:
diff --git a/Allura/allura/command/__init__.py b/Allura/allura/command/__init__.py
index 0221b7b..d6d0c92 100644
--- a/Allura/allura/command/__init__.py
+++ b/Allura/allura/command/__init__.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 from base import Command
 from show_models import ShowModelsCommand, ReindexCommand, EnsureIndexCommand
 from script import ScriptCommand, SetToolAccessCommand
diff --git a/Allura/allura/command/base.py b/Allura/allura/command/base.py
index 9a7689a..470a509 100644
--- a/Allura/allura/command/base.py
+++ b/Allura/allura/command/base.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import os
 import logging
 import shlex
diff --git a/Allura/allura/command/create_neighborhood.py b/Allura/allura/command/create_neighborhood.py
index dc4b2cd..e1dc85a 100644
--- a/Allura/allura/command/create_neighborhood.py
+++ b/Allura/allura/command/create_neighborhood.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import logging
 
 from . import base
diff --git a/Allura/allura/command/create_trove_categories.py b/Allura/allura/command/create_trove_categories.py
index 288117c..da28728 100644
--- a/Allura/allura/command/create_trove_categories.py
+++ b/Allura/allura/command/create_trove_categories.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import re
 import sys
 import logging
diff --git a/Allura/allura/command/reclone_repo.py b/Allura/allura/command/reclone_repo.py
index 51e5ca6..abbf9fb 100644
--- a/Allura/allura/command/reclone_repo.py
+++ b/Allura/allura/command/reclone_repo.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import warnings
 
 from tg import tmpl_context as c
diff --git a/Allura/allura/command/script.py b/Allura/allura/command/script.py
index 7bc3b8c..de77136 100644
--- a/Allura/allura/command/script.py
+++ b/Allura/allura/command/script.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import sys
 import os.path
 import cProfile
diff --git a/Allura/allura/command/set_neighborhood_features.py b/Allura/allura/command/set_neighborhood_features.py
index 36ba22b..b10f9fd 100644
--- a/Allura/allura/command/set_neighborhood_features.py
+++ b/Allura/allura/command/set_neighborhood_features.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 from ast import literal_eval
 
 from allura.command import base
diff --git a/Allura/allura/command/show_models.py b/Allura/allura/command/show_models.py
index 86180d4..e082f9b 100644
--- a/Allura/allura/command/show_models.py
+++ b/Allura/allura/command/show_models.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import sys
 from collections import defaultdict
 from contextlib import contextmanager
diff --git a/Allura/allura/command/smtp_server.py b/Allura/allura/command/smtp_server.py
index 40008d7..4036084 100644
--- a/Allura/allura/command/smtp_server.py
+++ b/Allura/allura/command/smtp_server.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import smtpd
 import asyncore
 
diff --git a/Allura/allura/command/taskd.py b/Allura/allura/command/taskd.py
index d305c74..5eb2559 100644
--- a/Allura/allura/command/taskd.py
+++ b/Allura/allura/command/taskd.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import logging
 import os
 import time
diff --git a/Allura/allura/command/taskd_cleanup.py b/Allura/allura/command/taskd_cleanup.py
index a796e6c..3ce1421 100644
--- a/Allura/allura/command/taskd_cleanup.py
+++ b/Allura/allura/command/taskd_cleanup.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import os
 import time
 import signal
diff --git a/Allura/allura/config/app_cfg.py b/Allura/allura/config/app_cfg.py
index f5b1464..547403b 100644
--- a/Allura/allura/config/app_cfg.py
+++ b/Allura/allura/config/app_cfg.py
@@ -30,6 +30,7 @@ convert them into boolean, for example, you should use the
     setting = asbool(global_conf.get('the_setting'))
 
 """
+from __future__ import unicode_literals
 import logging
 from functools import partial
 
diff --git a/Allura/allura/config/environment.py b/Allura/allura/config/environment.py
index bf0f5b6..e6ea993 100644
--- a/Allura/allura/config/environment.py
+++ b/Allura/allura/config/environment.py
@@ -19,6 +19,7 @@
 
 """WSGI environment setup for allura."""
 
+from __future__ import unicode_literals
 from allura.config.app_cfg import base_config
 
 __all__ = ['load_environment']
diff --git a/Allura/allura/config/middleware.py b/Allura/allura/config/middleware.py
index 6ddd6fc..1834cc6 100644
--- a/Allura/allura/config/middleware.py
+++ b/Allura/allura/config/middleware.py
@@ -18,6 +18,7 @@
 #       under the License.
 
 """WSGI middleware initialization for the allura application."""
+from __future__ import unicode_literals
 import importlib
 import mimetypes
 
diff --git a/Allura/allura/config/resources.py b/Allura/allura/config/resources.py
index e8717fe..cf683d3 100644
--- a/Allura/allura/config/resources.py
+++ b/Allura/allura/config/resources.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import os
 import logging
 
diff --git a/Allura/allura/controllers/__init__.py b/Allura/allura/controllers/__init__.py
index 417b8ad..1e5cd2c 100644
--- a/Allura/allura/controllers/__init__.py
+++ b/Allura/allura/controllers/__init__.py
@@ -18,6 +18,7 @@
 #       under the License.
 
 """Controllers for the allura application."""
+from __future__ import unicode_literals
 from .discuss import DiscussionController, AppDiscussionController, ThreadController, PostController
 from .discuss import ModerationController, AppDiscussionRestController
 from .base import BaseController, DispatchIndex
diff --git a/Allura/allura/controllers/attachments.py b/Allura/allura/controllers/attachments.py
index 11bf24d..15419f2 100644
--- a/Allura/allura/controllers/attachments.py
+++ b/Allura/allura/controllers/attachments.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 from urllib import unquote
 from webob import exc
 
diff --git a/Allura/allura/controllers/auth.py b/Allura/allura/controllers/auth.py
index 2ca07c7..b6e21c5 100644
--- a/Allura/allura/controllers/auth.py
+++ b/Allura/allura/controllers/auth.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import logging
 import os
 from datetime import datetime, timedelta
@@ -676,7 +677,7 @@ class PreferencesController(BaseController):
             user=c.user,
             config=config,
         ))
-        send_system_mail_to_user(c.user, u'Password Changed', email_body)
+        send_system_mail_to_user(c.user, 'Password Changed', email_body)
         redirect('.')
 
     @expose()
@@ -775,7 +776,7 @@ class PreferencesController(BaseController):
                 user=c.user,
                 config=config,
             ))
-            send_system_mail_to_user(c.user, u'Two-Factor Authentication Enabled', email_body)
+            send_system_mail_to_user(c.user, 'Two-Factor Authentication Enabled', email_body)
             redirect('/auth/preferences/multifactor_recovery')
 
     @expose()
@@ -797,7 +798,7 @@ class PreferencesController(BaseController):
             user=c.user,
             config=config,
         ))
-        send_system_mail_to_user(c.user, u'Two-Factor Authentication Disabled', email_body)
+        send_system_mail_to_user(c.user, 'Two-Factor Authentication Disabled', email_body)
         redirect('.')
 
     @expose()
@@ -810,7 +811,7 @@ class PreferencesController(BaseController):
             user=c.user,
             config=config,
         ))
-        send_system_mail_to_user(c.user, u'Two-Factor Authentication Apps', email_body)
+        send_system_mail_to_user(c.user, 'Two-Factor Authentication Apps', email_body)
 
     @expose('jinja:allura:templates/user_recovery_codes.html')
     @reconfirm_auth
@@ -850,7 +851,7 @@ class PreferencesController(BaseController):
             config=config,
         ))
         h.auditlog_user('Regenerated multifactor recovery codes')
-        send_system_mail_to_user(c.user, u'Two-Factor Recovery Codes Regenerated', email_body)
+        send_system_mail_to_user(c.user, 'Two-Factor Recovery Codes Regenerated', email_body)
         tg.flash('Your recovery codes have been regenerated.  Save the new codes!')
         redirect('/auth/preferences/multifactor_recovery')
 
diff --git a/Allura/allura/controllers/base.py b/Allura/allura/controllers/base.py
index ef91a32..902ffa7 100644
--- a/Allura/allura/controllers/base.py
+++ b/Allura/allura/controllers/base.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import logging
 
 from tg import expose
diff --git a/Allura/allura/controllers/basetest_project_root.py b/Allura/allura/controllers/basetest_project_root.py
index 12b86c9..acdaf3e 100644
--- a/Allura/allura/controllers/basetest_project_root.py
+++ b/Allura/allura/controllers/basetest_project_root.py
@@ -18,6 +18,7 @@
 #       under the License.
 
 """Main Controller"""
+from __future__ import unicode_literals
 import logging
 from urllib import unquote
 
diff --git a/Allura/allura/controllers/discuss.py b/Allura/allura/controllers/discuss.py
index 69ea86e..0ec6a1a 100644
--- a/Allura/allura/controllers/discuss.py
+++ b/Allura/allura/controllers/discuss.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 from urllib import unquote
 from datetime import datetime
 import logging
@@ -563,7 +564,7 @@ class ModerationController(BaseController):
                     posted.approve()
                     g.spam_checker.submit_ham(posted.text, artifact=posted, user=posted.author())
                     posted.thread.post_to_feed(posted)
-        flash(u'{} {}'.format(h.text.plural(count, 'post', 'posts'),
+        flash('{} {}'.format(h.text.plural(count, 'post', 'posts'),
                               'deleted' if delete else 'marked as spam' if spam else 'approved'))
         redirect(request.referer or '/')
 
diff --git a/Allura/allura/controllers/error.py b/Allura/allura/controllers/error.py
index 5cb26d0..599a8eb 100644
--- a/Allura/allura/controllers/error.py
+++ b/Allura/allura/controllers/error.py
@@ -19,6 +19,7 @@
 
 """Error controller"""
 
+from __future__ import unicode_literals
 from tg import request, expose
 
 __all__ = ['ErrorController']
diff --git a/Allura/allura/controllers/feed.py b/Allura/allura/controllers/feed.py
index 9a9c2ae..b426717 100644
--- a/Allura/allura/controllers/feed.py
+++ b/Allura/allura/controllers/feed.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 from tg import expose, validate, request, response
 from tg.decorators import without_trailing_slash
 from formencode import validators as V
diff --git a/Allura/allura/controllers/project.py b/Allura/allura/controllers/project.py
index 58c902e..bdbad3c 100644
--- a/Allura/allura/controllers/project.py
+++ b/Allura/allura/controllers/project.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import re
 import logging
 from datetime import datetime, timedelta
@@ -153,7 +154,7 @@ class NeighborhoodController(object):
     def add_project(self, **form_data):
         with h.login_overlay():
             require_access(self.neighborhood, 'register')
-        verify = c.form_errors == {'_the_form': u'phone-verification'}
+        verify = c.form_errors == {'_the_form': 'phone-verification'}
         c.show_phone_verification_overlay = verify
         c.add_project = W.add_project
         form_data.setdefault('tools', W.add_project.default_tools)
diff --git a/Allura/allura/controllers/repository.py b/Allura/allura/controllers/repository.py
index cc05da3..b4d5a33 100644
--- a/Allura/allura/controllers/repository.py
+++ b/Allura/allura/controllers/repository.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import os
 import logging
 import difflib
@@ -904,8 +905,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', '')
@@ -916,7 +917,7 @@ class FileBrowser(BaseController):
             if max(a.size, b.size) > asint(tg.config.get('scm.view.max_syntax_highlight_bytes', 500000)):
                 # have to check the original file size, not diff size, because difflib._mdiff inside HtmlSideBySideDiff
                 # can take an extremely long time on large files (and its even a generator)
-                diff = u'<em>File too large for side-by-side view</em>'
+                diff = '<em>File too large for side-by-side view</em>'
             else:
                 hd = HtmlSideBySideDiff()
                 diff = hd.make_table(la, lb, adesc, bdesc)
diff --git a/Allura/allura/controllers/rest.py b/Allura/allura/controllers/rest.py
index 04dac71..8b81d99 100644
--- a/Allura/allura/controllers/rest.py
+++ b/Allura/allura/controllers/rest.py
@@ -18,6 +18,7 @@
 #       under the License.
 
 """REST Controller"""
+from __future__ import unicode_literals
 import logging
 from urllib import unquote
 
diff --git a/Allura/allura/controllers/root.py b/Allura/allura/controllers/root.py
index be3d5bb..9eb4603 100644
--- a/Allura/allura/controllers/root.py
+++ b/Allura/allura/controllers/root.py
@@ -18,6 +18,7 @@
 #       under the License.
 
 """Main Controller"""
+from __future__ import unicode_literals
 import logging
 from string import Template
 
diff --git a/Allura/allura/controllers/search.py b/Allura/allura/controllers/search.py
index 3fe6f8a..3039ba2 100644
--- a/Allura/allura/controllers/search.py
+++ b/Allura/allura/controllers/search.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 from tg import expose, validate, request
 from tg.decorators import with_trailing_slash, without_trailing_slash
 from formencode import validators as V
diff --git a/Allura/allura/controllers/site_admin.py b/Allura/allura/controllers/site_admin.py
index aa7e877..6674133 100644
--- a/Allura/allura/controllers/site_admin.py
+++ b/Allura/allura/controllers/site_admin.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import re
 import logging
 from datetime import datetime, timedelta
@@ -359,12 +360,12 @@ class DeleteProjectsController(object):
         return parsed_projects
 
     def format_parsed_projects(self, projects):
-        template = u'{}    # {}'
+        template = '{}    # {}'
         lines = []
         for input, p, error in projects:
-            comment = u'OK: {}'.format(p.url()) if p else error
+            comment = 'OK: {}'.format(p.url()) if p else error
             lines.append(template.format(input, comment))
-        return u'\n'.join(lines)
+        return '\n'.join(lines)
 
     @with_trailing_slash
     @expose('jinja:allura:templates/site_admin_delete_projects.html')
@@ -380,11 +381,11 @@ class DeleteProjectsController(object):
     @validate(validators=delete_form_validators)
     def confirm(self, projects=None, reason=None, disable_users=False, **kw):
         if not projects:
-            flash(u'No projects specified', 'warning')
+            flash('No projects specified', 'warning')
             redirect('.')
         parsed_projects = self.parse_projects(projects)
         projects = self.format_parsed_projects(parsed_projects)
-        edit_link = u'./?projects={}&reason={}&disable_users={}'.format(
+        edit_link = './?projects={}&reason={}&disable_users={}'.format(
             h.urlquoteplus(projects),
             h.urlquoteplus(reason or ''),
             h.urlquoteplus(disable_users))
@@ -400,20 +401,20 @@ class DeleteProjectsController(object):
     @validate(validators=delete_form_validators)
     def really_delete(self, projects=None, reason=None, disable_users=False, **kw):
         if not projects:
-            flash(u'No projects specified', 'warning')
+            flash('No projects specified', 'warning')
             redirect('.')
         projects = self.parse_projects(projects)
         task_params = [p.url().strip('/') for (_, p, _) in projects if p]
         if not task_params:
-            flash(u'Unable to parse at least one project from your input', 'warning')
+            flash('Unable to parse at least one project from your input', 'warning')
             redirect('.')
-        task_params = u' '.join(task_params)
+        task_params = ' '.join(task_params)
         if reason:
-            task_params = u'-r {} {}'.format(pipes.quote(reason), task_params)
+            task_params = '-r {} {}'.format(pipes.quote(reason), task_params)
         if disable_users:
-            task_params = u'--disable-users {}'.format(task_params)
+            task_params = '--disable-users {}'.format(task_params)
         DeleteProjects.post(task_params)
-        flash(u'Delete scheduled', 'ok')
+        flash('Delete scheduled', 'ok')
         redirect('.')
 
 
diff --git a/Allura/allura/controllers/static.py b/Allura/allura/controllers/static.py
index 15b638c..9cddb41 100644
--- a/Allura/allura/controllers/static.py
+++ b/Allura/allura/controllers/static.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 from cStringIO import StringIO
 
 from tg import expose
diff --git a/Allura/allura/controllers/task.py b/Allura/allura/controllers/task.py
index e8669d7..f7651f4 100644
--- a/Allura/allura/controllers/task.py
+++ b/Allura/allura/controllers/task.py
@@ -16,6 +16,7 @@
 #       under the License.
 
 
+from __future__ import unicode_literals
 class TaskController(object):
 
     '''WSGI app providing web-like RPC
diff --git a/Allura/allura/controllers/trovecategories.py b/Allura/allura/controllers/trovecategories.py
index 91450fd..81132da 100644
--- a/Allura/allura/controllers/trovecategories.py
+++ b/Allura/allura/controllers/trovecategories.py
@@ -14,6 +14,7 @@
 #       KIND, either express or implied.  See the License for the
 #       specific language governing permissions and limitations
 #       under the License.
+from __future__ import unicode_literals
 import re
 from collections import OrderedDict
 
@@ -139,7 +140,7 @@ class TroveCategoryController(BaseController):
         if oldcat:
             raise TroveAdminException(
                 ('A category with shortname "%s" already exists (%s).  Try a different, unique shortname' % (shortname, oldcat.fullpath), "error"),
-                u'?categoryname={}&shortname={}'.format(name, shortname),
+                '?categoryname={}&shortname={}'.format(name, shortname),
                 upper
             )
         else:
@@ -170,9 +171,9 @@ class TroveCategoryController(BaseController):
         flash(*flash_args)
 
         if upper:
-            redirect(u'/categories/{}/{}'.format(upper.trove_cat_id, redir_params))
+            redirect('/categories/{}/{}'.format(upper.trove_cat_id, redir_params))
         else:
-            redirect(u'/categories/{}'.format(redir_params))
+            redirect('/categories/{}'.format(redir_params))
 
     @expose()
     @require_post()
diff --git a/Allura/allura/eventslistener.py b/Allura/allura/eventslistener.py
index f9f7431..c4f077c 100644
--- a/Allura/allura/eventslistener.py
+++ b/Allura/allura/eventslistener.py
@@ -21,6 +21,7 @@ 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 unicode_literals
 class EventsListener:
 
     def newArtifact(self, art_type, art_datetime, project, user):
diff --git a/Allura/allura/ext/admin/admin_main.py b/Allura/allura/ext/admin/admin_main.py
index b7a9ce8..622aafd 100644
--- a/Allura/allura/ext/admin/admin_main.py
+++ b/Allura/allura/ext/admin/admin_main.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import logging
 import re
 import os
@@ -628,13 +629,13 @@ class ProjectAdminController(BaseController):
     @expose()
     @require_post()
     def update_mounts(self, subproject=None, tool=None, new=None, page=0, limit=200, **kw):
-        if new and new['ep_name'] == u'subproject':
+        if new and new['ep_name'] == 'subproject':
             new['ep_name'] = ""
         try:
             new_app = self._update_mounts(subproject, tool, new, **kw)
             if new_app:
                 if getattr(new_app, 'tool_label', '') == 'External Link':
-                    flash(u'{} installed successfully.'.format(new_app.tool_label))
+                    flash('{} installed successfully.'.format(new_app.tool_label))
                 else:
                     new_url = new_app.url
                     if callable(new_url):  # subprojects have a method instead of property
diff --git a/Allura/allura/ext/admin/widgets.py b/Allura/allura/ext/admin/widgets.py
index d27f8fa..3666bb8 100644
--- a/Allura/allura/ext/admin/widgets.py
+++ b/Allura/allura/ext/admin/widgets.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 from tg import tmpl_context as c
 
 import ew as ew_core
diff --git a/Allura/allura/ext/personal_dashboard/dashboard_main.py b/Allura/allura/ext/personal_dashboard/dashboard_main.py
index fb83cb7..20fed9d 100644
--- a/Allura/allura/ext/personal_dashboard/dashboard_main.py
+++ b/Allura/allura/ext/personal_dashboard/dashboard_main.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import logging
 
 from tg import tmpl_context as c, app_globals as g
diff --git a/Allura/allura/ext/project_home/project_main.py b/Allura/allura/ext/project_home/project_main.py
index 0710ea6..37cbd2e 100644
--- a/Allura/allura/ext/project_home/project_main.py
+++ b/Allura/allura/ext/project_home/project_main.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import logging
 
 import pkg_resources
diff --git a/Allura/allura/ext/search/search_main.py b/Allura/allura/ext/search/search_main.py
index 0c54021..177354b 100644
--- a/Allura/allura/ext/search/search_main.py
+++ b/Allura/allura/ext/search/search_main.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import logging
 
 import pkg_resources
diff --git a/Allura/allura/ext/user_profile/user_main.py b/Allura/allura/ext/user_profile/user_main.py
index 4a30276..f99b608 100644
--- a/Allura/allura/ext/user_profile/user_main.py
+++ b/Allura/allura/ext/user_profile/user_main.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import logging
 
 import pkg_resources
diff --git a/Allura/allura/lib/AsciiDammit.py b/Allura/allura/lib/AsciiDammit.py
index d86c26d..95d27dd 100644
--- a/Allura/allura/lib/AsciiDammit.py
+++ b/Allura/allura/lib/AsciiDammit.py
@@ -17,6 +17,7 @@ the copyright holder hereby grants irrevocably to every recipient
 all rights in this work otherwise reserved under copyright.
 """
 
+from __future__ import unicode_literals
 __author__ = "Leonard Richardson (leonardr@segfault.org)"
 __version__ = "$Revision: 1.3 $"
 __date__ = "$Date: 2009/04/28 10:45:03 $"
diff --git a/Allura/allura/lib/app_globals.py b/Allura/allura/lib/app_globals.py
index 8770a5c..5f783e4 100644
--- a/Allura/allura/lib/app_globals.py
+++ b/Allura/allura/lib/app_globals.py
@@ -20,6 +20,7 @@
 
 """The application's Globals object"""
 
+from __future__ import unicode_literals
 import logging
 import cgi
 import hashlib
@@ -81,7 +82,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:
@@ -89,7 +90,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):
@@ -434,7 +435,7 @@ class Globals(object):
             # no highlighting, but we should escape, encode, and wrap it in
             # a <pre>
             text = cgi.escape(text)
-            return h.html.literal(u'<pre>' + text + u'</pre>')
+            return h.html.literal('<pre>' + text + '</pre>')
         else:
             return h.html.literal(pygments.highlight(text, lexer, formatter))
 
@@ -632,7 +633,7 @@ class Icon(object):
 
     def __init__(self, css, title=None):
         self.css = css
-        self.title = title or u''
+        self.title = title or ''
 
     def render(self, show_title=False, extra_css=None, closing_tag=True, tag='a', **kw):
         title = kw.get('title') or self.title
@@ -644,9 +645,9 @@ class Icon(object):
             attrs['href'] = '#'
         attrs.update(kw)
         attrs = ew._Jinja2Widget().j2_attrs(attrs)
-        visible_title = u''
+        visible_title = ''
         if show_title:
-            visible_title = u'&nbsp;{}'.format(Markup.escape(title))
-        closing_tag = u'</{}>'.format(tag) if closing_tag else u''
-        icon = u'<{} {}><i class="{}"></i>{}{}'.format(tag, attrs, self.css, visible_title, closing_tag)
+            visible_title = '&nbsp;{}'.format(Markup.escape(title))
+        closing_tag = '</{}>'.format(tag) if closing_tag else ''
+        icon = '<{} {}><i class="{}"></i>{}{}'.format(tag, attrs, self.css, visible_title, closing_tag)
         return Markup(icon)
diff --git a/Allura/allura/lib/base.py b/Allura/allura/lib/base.py
index 4c7a2d8..5cff792 100644
--- a/Allura/allura/lib/base.py
+++ b/Allura/allura/lib/base.py
@@ -18,6 +18,7 @@
 #       under the License.
 
 """The base Controller API."""
+from __future__ import unicode_literals
 from tg import TGController
 
 __all__ = ['WsgiDispatchController']
diff --git a/Allura/allura/lib/custom_middleware.py b/Allura/allura/lib/custom_middleware.py
index cc87d44..79092fa 100644
--- a/Allura/allura/lib/custom_middleware.py
+++ b/Allura/allura/lib/custom_middleware.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import os
 import re
 import logging
diff --git a/Allura/allura/lib/decorators.py b/Allura/allura/lib/decorators.py
index 80c8d63..eed03b7 100644
--- a/Allura/allura/lib/decorators.py
+++ b/Allura/allura/lib/decorators.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import inspect
 import sys
 import json
diff --git a/Allura/allura/lib/diff.py b/Allura/allura/lib/diff.py
index 2691b65..6c54673 100644
--- a/Allura/allura/lib/diff.py
+++ b/Allura/allura/lib/diff.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import difflib
 from allura.lib import helpers as h
 
diff --git a/Allura/allura/lib/exceptions.py b/Allura/allura/lib/exceptions.py
index 32be05d..6b3ba28 100644
--- a/Allura/allura/lib/exceptions.py
+++ b/Allura/allura/lib/exceptions.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import webob.exc
 from formencode import Invalid
 
diff --git a/Allura/allura/lib/gravatar.py b/Allura/allura/lib/gravatar.py
index a0fb1de..b678d14 100644
--- a/Allura/allura/lib/gravatar.py
+++ b/Allura/allura/lib/gravatar.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import re
 import urllib
 import hashlib
diff --git a/Allura/allura/lib/helpers.py b/Allura/allura/lib/helpers.py
index 1ea60ef..c204aaf 100644
--- a/Allura/allura/lib/helpers.py
+++ b/Allura/allura/lib/helpers.py
@@ -16,6 +16,7 @@
 #       KIND, either express or implied.  See the License for the
 #       specific language governing permissions and limitations
 #       under the License.
+from __future__ import unicode_literals
 import base64
 import sys
 import os
@@ -153,7 +154,7 @@ def make_safe_path_portion(ustr, relaxed=True):
 
 
 def escape_json(data):
-    return json.dumps(data).replace('<', '\u003C')
+    return json.dumps(data).replace('<', '\\u003C')
 
 
 def monkeypatch(*objs):
@@ -179,7 +180,7 @@ def urlquoteplus(url, safe=""):
 
 def _attempt_encodings(s, encodings):
     if s is None:
-        return u''
+        return ''
     for enc in encodings:
         try:
             if enc is None:
diff --git a/Allura/allura/lib/import_api.py b/Allura/allura/lib/import_api.py
index 60754a6..71080ba 100644
--- a/Allura/allura/lib/import_api.py
+++ b/Allura/allura/lib/import_api.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import urllib
 import urllib2
 import urlparse
diff --git a/Allura/allura/lib/macro.py b/Allura/allura/lib/macro.py
index ae467cd..4a2ec43 100644
--- a/Allura/allura/lib/macro.py
+++ b/Allura/allura/lib/macro.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import cgi
 import random
 import shlex
@@ -80,7 +81,7 @@ 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:
             raise
diff --git a/Allura/allura/lib/mail_util.py b/Allura/allura/lib/mail_util.py
index c6c6aac..7e09526 100644
--- a/Allura/allura/lib/mail_util.py
+++ b/Allura/allura/lib/mail_util.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import re
 import logging
 import smtplib
@@ -264,7 +265,7 @@ 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 + '>')
         message['Date'] = email.utils.formatdate()
         if sender:
             message['Sender'] = AddrHeader(sender)
@@ -274,11 +275,11 @@ class SMTPClient(object):
         if in_reply_to:
             if not isinstance(in_reply_to, basestring):
                 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)
diff --git a/Allura/allura/lib/markdown_extensions.py b/Allura/allura/lib/markdown_extensions.py
index f27e7a3..59233fd 100644
--- a/Allura/allura/lib/markdown_extensions.py
+++ b/Allura/allura/lib/markdown_extensions.py
@@ -17,6 +17,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import re
 import logging
 from urlparse import urljoin
@@ -271,7 +272,7 @@ class ForgeExtension(markdown.Extension):
 
 class EmojiExtension(markdown.Extension):
 
-    EMOJI_RE = u'(%s[a-zA-Z0-9\+\-_&.ô’Åéãíç()!#*]+%s)' % (':', ':')
+    EMOJI_RE = '(%s[a-zA-Z0-9\+\-_&.ô’Åéãíç()!#*]+%s)' % (':', ':')
 
     def __init__(self, **kwargs):
         markdown.Extension.__init__(self)
diff --git a/Allura/allura/lib/multifactor.py b/Allura/allura/lib/multifactor.py
index ec0249d..adcb0eb 100644
--- a/Allura/allura/lib/multifactor.py
+++ b/Allura/allura/lib/multifactor.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import os
 import logging
 import random
diff --git a/Allura/allura/lib/package_path_loader.py b/Allura/allura/lib/package_path_loader.py
index be28fed..6b6a8c1 100644
--- a/Allura/allura/lib/package_path_loader.py
+++ b/Allura/allura/lib/package_path_loader.py
@@ -122,6 +122,7 @@ The positioners are:
 **TODO:** Support multiple partial themes
 
 """
+from __future__ import unicode_literals
 import pkg_resources
 import os
 
diff --git a/Allura/allura/lib/patches.py b/Allura/allura/lib/patches.py
index 977f5d1..a7a4dcd 100644
--- a/Allura/allura/lib/patches.py
+++ b/Allura/allura/lib/patches.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import re
 
 import webob
diff --git a/Allura/allura/lib/phone/__init__.py b/Allura/allura/lib/phone/__init__.py
index dd46f2f..acb567c 100644
--- a/Allura/allura/lib/phone/__init__.py
+++ b/Allura/allura/lib/phone/__init__.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import logging
 
 log = logging.getLogger(__name__)
diff --git a/Allura/allura/lib/phone/nexmo.py b/Allura/allura/lib/phone/nexmo.py
index 9c1eb61..f621da9 100644
--- a/Allura/allura/lib/phone/nexmo.py
+++ b/Allura/allura/lib/phone/nexmo.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import logging
 from urlparse import urljoin
 import cgi
diff --git a/Allura/allura/lib/plugin.py b/Allura/allura/lib/plugin.py
index 33c61c7..904ddca 100644
--- a/Allura/allura/lib/plugin.py
+++ b/Allura/allura/lib/plugin.py
@@ -18,6 +18,7 @@
 '''
 Allura plugins for authentication and project registration
 '''
+from __future__ import unicode_literals
 import re
 import os
 import logging
@@ -242,14 +243,14 @@ class AuthenticationProvider(object):
 
             if trusted:
                 # current user must change password
-                h.auditlog_user(u'Successful login with password in HIBP breach database, '
-                                u'from trusted source (reason: {})'.format(trusted), user=user)
+                h.auditlog_user('Successful login with password in HIBP breach database, '
+                                'from trusted source (reason: {})'.format(trusted), user=user)
                 return 'hibp'  # reason
             else:
                 # current user may not continue, must reset password via email
                 h.auditlog_user('Attempted login from untrusted location with password in HIBP breach database',
                                 user=user)
-                user.send_password_reset_email(subject_tmpl=u'Update your {site_name} password')
+                user.send_password_reset_email(subject_tmpl='Update your {site_name} password')
                 raise exc.HTTPBadRequest('To ensure account security, you must reset your password via email.'
                                          '\n'
                                          'Please check your email to continue.')
@@ -519,13 +520,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
@@ -1180,7 +1181,7 @@ class ProjectRegistrationProvider(object):
                 log.info('User %s is already disabled', user.username)
                 continue
             provider.disable_user(user, audit=False)
-            msg = u'Account disabled because project {}{} is deleted. Reason: {}'.format(
+            msg = 'Account disabled because project {}{} is deleted. Reason: {}'.format(
                 project.neighborhood.url_prefix,
                 project.shortname,
                 reason)
@@ -1223,29 +1224,29 @@ class ProjectRegistrationProvider(object):
         '''
         from allura.model import Project, Neighborhood
         if url is None:
-            return None, u'Empty url'
+            return None, 'Empty url'
         url = urlparse(url)
         url = [u for u in url.path.split('/') if u]
         if len(url) == 0:
-            return None, u'Empty url'
+            return None, 'Empty url'
         if len(url) == 1:
             q = Project.query.find(dict(shortname=url[0]))
             cnt = q.count()
             if cnt == 0:
-                return None, u'Project not found'
+                return None, 'Project not found'
             if cnt == 1:
                 return q.first(), None
-            return None, u'Too many matches for project: {}'.format(cnt)
-        n = Neighborhood.query.get(url_prefix=u'/{}/'.format(url[0]))
+            return None, 'Too many matches for project: {}'.format(cnt)
+        n = Neighborhood.query.get(url_prefix='/{}/'.format(url[0]))
         if not n:
-            return None, u'Neighborhood not found'
+            return None, 'Neighborhood not found'
         p = Project.query.get(neighborhood_id=n._id, shortname=n.shortname_prefix + url[1])
         if len(url) > 2:
             # Maybe subproject
             subp = Project.query.get(neighborhood_id=n._id, shortname='{}/{}'.format(*url[1:3]))
             if subp:
                 return (subp, None)
-        return (p, u'Project not found' if p is None else None)
+        return (p, 'Project not found' if p is None else None)
 
 
 class ThemeProvider(object):
diff --git a/Allura/allura/lib/repository.py b/Allura/allura/lib/repository.py
index 6250026..f05206c 100644
--- a/Allura/allura/lib/repository.py
+++ b/Allura/allura/lib/repository.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import logging
 from urllib import quote
 
diff --git a/Allura/allura/lib/search.py b/Allura/allura/lib/search.py
index fdad56e..887e8c2 100644
--- a/Allura/allura/lib/search.py
+++ b/Allura/allura/lib/search.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import re
 import socket
 from logging import getLogger
@@ -143,7 +144,7 @@ def search(q, short_timeout=False, ignore_errors=True, **kw):
         log.exception('Error in solr search')
         if not ignore_errors:
             match = re.search(r'<pre>(.*)</pre>', six.text_type(e))
-            raise SearchError(u'Error running search query: %s' %
+            raise SearchError('Error running search query: %s' %
                               (match.group(1) if match else e))
 
 
@@ -204,9 +205,9 @@ def site_admin_search(model, q, field, **kw):
         # escaping spaces with '\ ' isn't sufficient for display_name_t since its stored as text_general (why??)
         # and wouldn't handle foo@bar.com split on @ either
         # This should work, but doesn't for unknown reasons: q = u'{!term f=%s}%s' % (field, q)
-        q = obj.translate_query(u'%s:(%s)' % (field, q), fields)
+        q = obj.translate_query('%s:(%s)' % (field, q), fields)
         kw['q.op'] = 'AND'  # so that all terms within the () are required
-    fq = [u'type_s:%s' % model.type_s]
+    fq = ['type_s:%s' % model.type_s]
     return search(q, fq=fq, ignore_errors=False, **kw)
 
 
diff --git a/Allura/allura/lib/solr.py b/Allura/allura/lib/solr.py
index 3df3a6f..307a4ab 100644
--- a/Allura/allura/lib/solr.py
+++ b/Allura/allura/lib/solr.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import shlex
 import logging
 
diff --git a/Allura/allura/lib/spam/__init__.py b/Allura/allura/lib/spam/__init__.py
index b51f20d..793999d 100644
--- a/Allura/allura/lib/spam/__init__.py
+++ b/Allura/allura/lib/spam/__init__.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import logging
 from copy import copy
 
diff --git a/Allura/allura/lib/spam/akismetfilter.py b/Allura/allura/lib/spam/akismetfilter.py
index e30f05e..a6e2115 100644
--- a/Allura/allura/lib/spam/akismetfilter.py
+++ b/Allura/allura/lib/spam/akismetfilter.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import logging
 
 from tg import request
diff --git a/Allura/allura/lib/spam/stopforumspamfilter.py b/Allura/allura/lib/spam/stopforumspamfilter.py
index 9499fec..b8af127 100644
--- a/Allura/allura/lib/spam/stopforumspamfilter.py
+++ b/Allura/allura/lib/spam/stopforumspamfilter.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import logging
 import csv
 from sys import getsizeof
diff --git a/Allura/allura/lib/utils.py b/Allura/allura/lib/utils.py
index 2554af3..2e6ea3b 100644
--- a/Allura/allura/lib/utils.py
+++ b/Allura/allura/lib/utils.py
@@ -14,6 +14,7 @@
 #       KIND, either express or implied.  See the License for the
 #       specific language governing permissions and limitations
 #       under the License.
+from __future__ import unicode_literals
 from contextlib import contextmanager
 import time
 import string
@@ -498,7 +499,7 @@ def serve_file(fp, filename, content_type, last_modified=None,
                cache_expires=None, size=None, embed=True, etag=None):
     '''Sets the response headers and serves as a wsgi iter'''
     if not etag and filename and last_modified:
-        etag = u'{0}?{1}'.format(filename, last_modified).encode('utf-8')
+        etag = '{0}?{1}'.format(filename, last_modified).encode('utf-8')
     if etag:
         etag_cache(etag)
     tg.response.headers['Content-Type'] = ''
diff --git a/Allura/allura/lib/validators.py b/Allura/allura/lib/validators.py
index b130634..7bcbbb4 100644
--- a/Allura/allura/lib/validators.py
+++ b/Allura/allura/lib/validators.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import json
 import re
 from bson import ObjectId
diff --git a/Allura/allura/lib/widgets/__init__.py b/Allura/allura/lib/widgets/__init__.py
index 3e8e798..b08a310 100644
--- a/Allura/allura/lib/widgets/__init__.py
+++ b/Allura/allura/lib/widgets/__init__.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 from .discuss import Post, Thread
 from .subscriptions import SubscriptionForm
 from .oauth_widgets import OAuthApplicationForm, OAuthRevocationForm
diff --git a/Allura/allura/lib/widgets/analytics.py b/Allura/allura/lib/widgets/analytics.py
index c2e85c5..08f5cb0 100644
--- a/Allura/allura/lib/widgets/analytics.py
+++ b/Allura/allura/lib/widgets/analytics.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import ew
 
 
diff --git a/Allura/allura/lib/widgets/auth_widgets.py b/Allura/allura/lib/widgets/auth_widgets.py
index 47c6764..28b916a 100644
--- a/Allura/allura/lib/widgets/auth_widgets.py
+++ b/Allura/allura/lib/widgets/auth_widgets.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import ew as ew_core
 import ew.jinja2_ew as ew
 from ew.core import validator
diff --git a/Allura/allura/lib/widgets/discuss.py b/Allura/allura/lib/widgets/discuss.py
index 5d9be46..703718c 100644
--- a/Allura/allura/lib/widgets/discuss.py
+++ b/Allura/allura/lib/widgets/discuss.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 from formencode import validators as fev
 
 import json
diff --git a/Allura/allura/lib/widgets/form_fields.py b/Allura/allura/lib/widgets/form_fields.py
index 7a5110a..893f1fa 100644
--- a/Allura/allura/lib/widgets/form_fields.py
+++ b/Allura/allura/lib/widgets/form_fields.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 from tg import tmpl_context as c
 from tg import request, url
 import json
diff --git a/Allura/allura/lib/widgets/forms.py b/Allura/allura/lib/widgets/forms.py
index 930b6bc..0c8fc80 100644
--- a/Allura/allura/lib/widgets/forms.py
+++ b/Allura/allura/lib/widgets/forms.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import logging
 import warnings
 
@@ -970,7 +971,7 @@ class NeighborhoodAddProjectForm(ForgeForm):
         submit_text='Start',
         neighborhood=None)
     # tools installed by default
-    default_tools = [u'wiki', u'git', u'tickets', u'discussion']
+    default_tools = ['wiki', 'git', 'tickets', 'discussion']
 
     @property
     def fields(self):
@@ -1007,7 +1008,7 @@ class NeighborhoodAddProjectForm(ForgeForm):
         value = super(NeighborhoodAddProjectForm, self).validate(value, state)
         provider = plugin.ProjectRegistrationProvider.get()
         if not provider.phone_verified(c.user, c.project.neighborhood):
-            raise formencode.Invalid(u'phone-verification', value, None)
+            raise formencode.Invalid('phone-verification', value, None)
         return value
 
     def resources(self):
diff --git a/Allura/allura/lib/widgets/macros.py b/Allura/allura/lib/widgets/macros.py
index 77c329a..a709ac4 100644
--- a/Allura/allura/lib/widgets/macros.py
+++ b/Allura/allura/lib/widgets/macros.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import ew
 
 
diff --git a/Allura/allura/lib/widgets/oauth_widgets.py b/Allura/allura/lib/widgets/oauth_widgets.py
index f04fed8..b73c92c 100644
--- a/Allura/allura/lib/widgets/oauth_widgets.py
+++ b/Allura/allura/lib/widgets/oauth_widgets.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import ew as ew_core
 import ew.jinja2_ew as ew
 
diff --git a/Allura/allura/lib/widgets/project_list.py b/Allura/allura/lib/widgets/project_list.py
index ad9d063..ad472f5 100644
--- a/Allura/allura/lib/widgets/project_list.py
+++ b/Allura/allura/lib/widgets/project_list.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import ew as ew_core
 import ew.jinja2_ew as ew
 
diff --git a/Allura/allura/lib/widgets/repo.py b/Allura/allura/lib/widgets/repo.py
index d73ef0c..ecf5b4b 100644
--- a/Allura/allura/lib/widgets/repo.py
+++ b/Allura/allura/lib/widgets/repo.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import ew as ew_core
 import ew.jinja2_ew as ew
 
diff --git a/Allura/allura/lib/widgets/search.py b/Allura/allura/lib/widgets/search.py
index b923618..9ed6bf7 100644
--- a/Allura/allura/lib/widgets/search.py
+++ b/Allura/allura/lib/widgets/search.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import ew as ew_core
 import ew.jinja2_ew as ew
 import jinja2
diff --git a/Allura/allura/lib/widgets/subscriptions.py b/Allura/allura/lib/widgets/subscriptions.py
index 83d3037..7b3829b 100644
--- a/Allura/allura/lib/widgets/subscriptions.py
+++ b/Allura/allura/lib/widgets/subscriptions.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import ew as ew_core
 import ew.jinja2_ew as ew
 from tg import tmpl_context as c
diff --git a/Allura/allura/lib/widgets/user_profile.py b/Allura/allura/lib/widgets/user_profile.py
index 3c51f94..a4089c1 100644
--- a/Allura/allura/lib/widgets/user_profile.py
+++ b/Allura/allura/lib/widgets/user_profile.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import logging
 import re
 
diff --git a/Allura/allura/lib/widgets/vote.py b/Allura/allura/lib/widgets/vote.py
index 4a8cae9..39ea618 100644
--- a/Allura/allura/lib/widgets/vote.py
+++ b/Allura/allura/lib/widgets/vote.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import ew as ew_core
 import ew.jinja2_ew as ew
 
diff --git a/Allura/allura/model/__init__.py b/Allura/allura/model/__init__.py
index b5bbe1b..50bc8e1 100644
--- a/Allura/allura/model/__init__.py
+++ b/Allura/allura/model/__init__.py
@@ -19,6 +19,7 @@
 
 """The application's model objects"""
 
+from __future__ import unicode_literals
 from .neighborhood import Neighborhood, NeighborhoodFile
 from .project import Project, ProjectCategory, TroveCategory, ProjectFile, AppConfig
 from .index import ArtifactReference, Shortlink
diff --git a/Allura/allura/model/artifact.py b/Allura/allura/model/artifact.py
index 873b9e5..89f2159 100644
--- a/Allura/allura/model/artifact.py
+++ b/Allura/allura/model/artifact.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import logging
 from collections import defaultdict
 from datetime import datetime
@@ -468,7 +469,7 @@ class Artifact(MappedClass, SearchIndexable):
         False otherwise
         """
         pkg = cls.__module__.split('.', 1)[0]
-        opt = u'{}.rate_limits'.format(pkg)
+        opt = '{}.rate_limits'.format(pkg)
 
         def count_in_app():
             return cls.query.find(dict(app_config_id=app_config._id)).count()
@@ -896,11 +897,11 @@ class Feed(MappedClass):
         self.author_name = ""
         self.author_link = ""
         title_parts = self.title.partition(" modified by ")
-        self.title = u"".join(title_parts[0:2]) + (u"<REDACTED>" if title_parts[2] else '')
+        self.title = "".join(title_parts[0:2]) + ("<REDACTED>" if title_parts[2] else '')
 
     @classmethod
     def from_username(cls, username):
-        return cls.query.find({'author_link': u"/u/{}/".format(username)}).all()
+        return cls.query.find({'author_link': "/u/{}/".format(username)}).all()
 
     @classmethod
     def has_access(cls, artifact):
@@ -960,7 +961,7 @@ class Feed(MappedClass):
              since=None, until=None, page=None, limit=None):
         "Produces webhelper.feedgenerator Feed"
         d = dict(title=title, link=h.absurl(link),
-                 description=description, language=u'en',
+                 description=description, language='en',
                  feed_url=request.url)
         if feed_type == 'atom':
             feed = FG.Atom1Feed(**d)
diff --git a/Allura/allura/model/attachments.py b/Allura/allura/model/attachments.py
index fa67134..ad5549b 100644
--- a/Allura/allura/model/attachments.py
+++ b/Allura/allura/model/attachments.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 from tg import tmpl_context as c
 from ming.orm import FieldProperty
 from ming import schema as S
diff --git a/Allura/allura/model/auth.py b/Allura/allura/model/auth.py
index 356355e..12571a9 100644
--- a/Allura/allura/model/auth.py
+++ b/Allura/allura/model/auth.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import logging
 import calendar
 from urlparse import urlparse
@@ -144,7 +145,7 @@ class EmailAddress(MappedClass):
             ))
 
             allura.tasks.mail_tasks.send_system_mail_to_user(self.email,
-                                                             u'%s - Email address claim attempt' % config['site_name'],
+                                                             '%s - Email address claim attempt' % config['site_name'],
                                                              text)
 
     def set_nonce_hash(self):
@@ -168,7 +169,7 @@ please visit the following URL:
             fromaddr=g.noreply,
             reply_to=g.noreply,
             toaddr=self.email,
-            subject=u'%s - Email address verification' % config['site_name'],
+            subject='%s - Email address verification' % config['site_name'],
             message_id=h.gen_message_id(),
             text=text)
 
@@ -300,8 +301,8 @@ class User(MappedClass, ActivityNode, ActivityObject, SearchIndexable):
         session_ua=str))
 
     def __repr__(self):
-        return (u'<User username={s.username!r} display_name={s.display_name!r} _id={s._id!r} '
-                u'disabled={s.disabled!r} pending={s.pending!r}>'.format(s=self))
+        return ('<User username={s.username!r} display_name={s.display_name!r} _id={s._id!r} '
+                'disabled={s.disabled!r} pending={s.pending!r}>'.format(s=self))
 
     def index(self):
         provider = plugin.AuthenticationProvider.get(None)  # no need in request here
@@ -385,7 +386,7 @@ class User(MappedClass, ActivityNode, ActivityObject, SearchIndexable):
             if login_detail:
                 self.add_login_detail(login_detail)
 
-    def send_password_reset_email(self, email_address=None, subject_tmpl=u'{site_name} Password recovery'):
+    def send_password_reset_email(self, email_address=None, subject_tmpl='{site_name} Password recovery'):
         if email_address is None:
             email_address = self.get_pref('email_address')
         reset_url = self.make_password_reset_url()
@@ -787,8 +788,8 @@ class User(MappedClass, ActivityNode, ActivityObject, SearchIndexable):
 
     def email_address_header(self):
         h = header.Header()
-        h.append(u'"%s" ' % self.get_pref('display_name'))
-        h.append(u'<%s>' % self.get_pref('email_address'))
+        h.append('"%s" ' % self.get_pref('display_name'))
+        h.append('<%s>' % self.get_pref('email_address'))
         return h
 
     def update_notifications(self):
@@ -1019,7 +1020,7 @@ class AuditLog(object):
 
     @classmethod
     def comment_user(cls, by, message, *args, **kwargs):
-        message = u'Comment by %s: %s' % (by.username, message)
+        message = 'Comment by %s: %s' % (by.username, message)
         return cls.log_user(message, *args, **kwargs)
 
 
diff --git a/Allura/allura/model/discuss.py b/Allura/allura/model/discuss.py
index 973d196..ed3db2f 100644
--- a/Allura/allura/model/discuss.py
+++ b/Allura/allura/model/discuss.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import os
 import logging
 from datetime import datetime
@@ -293,7 +294,7 @@ class Thread(Artifact, ActivityObject):
         roles = [r.name for r in c.project.named_roles]
         spam_check_text = post.text
         if self.include_subject_in_spam_check(post):
-            spam_check_text = self.subject + u'\n' + spam_check_text
+            spam_check_text = self.subject + '\n' + spam_check_text
         spammy = g.spam_checker.check(spam_check_text, artifact=post, user=c.user)
         if c.user in c.project.users_with_role(*roles):
             # always run the check, so it's logged.  But don't act on it for admins/developers of their own project
@@ -772,7 +773,7 @@ class Post(Message, VersionedArtifact, ActivityObject, ReactableArtifact):
             # this means artifact was not auto approved, and all the
             # subscribers did not receive notification. Now, moderator approved
             # artifact/post, so we should re-send actual notification
-            msg_id = u'approved-' + msg_id
+            msg_id = 'approved-' + msg_id
             n = Notification.query.get(_id=msg_id)
             if n:
                 # 'approved' notification also exists, re-send
diff --git a/Allura/allura/model/filesystem.py b/Allura/allura/model/filesystem.py
index 523904c..e1c1bed 100644
--- a/Allura/allura/model/filesystem.py
+++ b/Allura/allura/model/filesystem.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import os
 import re
 from cStringIO import StringIO
diff --git a/Allura/allura/model/index.py b/Allura/allura/model/index.py
index 300b9ba..f468608 100644
--- a/Allura/allura/model/index.py
+++ b/Allura/allura/model/index.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import re
 import logging
 from itertools import groupby
diff --git a/Allura/allura/model/monq_model.py b/Allura/allura/model/monq_model.py
index a759c99..97db4e2 100644
--- a/Allura/allura/model/monq_model.py
+++ b/Allura/allura/model/monq_model.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import sys
 import time
 import traceback
diff --git a/Allura/allura/model/multifactor.py b/Allura/allura/model/multifactor.py
index cf88ce6..5f6c6ee 100644
--- a/Allura/allura/model/multifactor.py
+++ b/Allura/allura/model/multifactor.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import logging
 
 from ming import schema as S
diff --git a/Allura/allura/model/neighborhood.py b/Allura/allura/model/neighborhood.py
index 38356bf..ac40a13 100644
--- a/Allura/allura/model/neighborhood.py
+++ b/Allura/allura/model/neighborhood.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import re
 import json
 import logging
diff --git a/Allura/allura/model/notification.py b/Allura/allura/model/notification.py
index 1e1a707..4ea53eb 100644
--- a/Allura/allura/model/notification.py
+++ b/Allura/allura/model/notification.py
@@ -34,6 +34,7 @@ Periodically:
 
 '''
 
+from __future__ import unicode_literals
 import logging
 from bson import ObjectId
 from datetime import datetime, timedelta
diff --git a/Allura/allura/model/oauth.py b/Allura/allura/model/oauth.py
index 9d93d37..b1d324d 100644
--- a/Allura/allura/model/oauth.py
+++ b/Allura/allura/model/oauth.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import logging
 
 import oauth2 as oauth
diff --git a/Allura/allura/model/project.py b/Allura/allura/model/project.py
index 5a6f896..5d0b2a7 100644
--- a/Allura/allura/model/project.py
+++ b/Allura/allura/model/project.py
@@ -16,6 +16,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import logging
 from calendar import timegm
 from collections import Counter, OrderedDict
@@ -155,7 +156,7 @@ class TroveCategory(MappedClass):
     @property
     def fullpath_within_type(self):
         'remove first section of full path, and use nicer separator'
-        return u' » '.join(self.fullpath.split(' :: ')[1:])
+        return ' » '.join(self.fullpath.split(' :: ')[1:])
 
     def __json__(self):
         return dict(
@@ -684,7 +685,7 @@ class Project(SearchIndexable, MappedClass, ActivityNode, ActivityObject):
                 if tool_name not in grouped_nav:
                     child = deepcopy(e)
                     # change label to be the tool name (type)
-                    e.label = g.entry_points['tool'][tool_name].tool_label + u' \u25be'
+                    e.label = g.entry_points['tool'][tool_name].tool_label + ' \u25be'
                     # add tool url to list of urls that will match this nav entry
                     # have to do this before changing the url to the list page
                     e.matching_urls.append(e.url)
@@ -782,7 +783,7 @@ class Project(SearchIndexable, MappedClass, ActivityNode, ActivityObject):
         try:
             return opt.validate(value)
         except fe.Invalid as e:
-            raise exceptions.ToolError(u'{}: {}'.format(opt.name, str(e)))
+            raise exceptions.ToolError('{}: {}'.format(opt.name, str(e)))
 
     def last_ordinal_value(self):
         last_menu_item = self.ordered_mounts(include_hidden=True)[-1]
diff --git a/Allura/allura/model/repo.py b/Allura/allura/model/repo.py
index 6231ba4..298fcaf 100644
--- a/Allura/allura/model/repo.py
+++ b/Allura/allura/model/repo.py
@@ -19,6 +19,7 @@
 # For backwards compatibility.
 # These used to be separate in this repo module, now all in repository module
 
+from __future__ import unicode_literals
 from .repository import SUser, SObjType
 from .repository import QSIZE, README_RE, VIEWABLE_EXTENSIONS, PYPELINE_EXTENSIONS, DIFF_SIMILARITY_THRESHOLD
 from .repository import CommitDoc, TreeDoc, LastCommitDoc
diff --git a/Allura/allura/model/repo_refresh.py b/Allura/allura/model/repo_refresh.py
index cf7615a..790cba3 100644
--- a/Allura/allura/model/repo_refresh.py
+++ b/Allura/allura/model/repo_refresh.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import logging
 from itertools import chain
 from cPickle import dumps
@@ -105,10 +106,10 @@ def refresh_repo(repo, all_commits=False, notify=True, new_clone=False, commits_
         by_branches, by_tags = _group_commits(repo, commit_ids)
         params = []
         for b, commits in by_branches.iteritems():
-            ref = u'refs/heads/{}'.format(b) if b != '__default__' else None
+            ref = 'refs/heads/{}'.format(b) if b != '__default__' else None
             params.append(dict(commit_ids=commits, ref=ref))
         for t, commits in by_tags.iteritems():
-            ref = u'refs/tags/{}'.format(t)
+            ref = 'refs/tags/{}'.format(t)
             params.append(dict(commit_ids=commits, ref=ref))
         if params:
             RepoPushWebhookSender().send(params)
@@ -236,10 +237,10 @@ def send_notifications(repo, commit_ids):
 
     if commit_msgs:
         if len(commit_msgs) > 1:
-            subject = u"{} new commits to {}".format(len(commit_msgs), repo.app.config.options.mount_label)
+            subject = "{} new commits to {}".format(len(commit_msgs), repo.app.config.options.mount_label)
         else:
             commit = commit_msgs[0]
-            subject = u'New commit {} by {}'.format(commit['shorthand_id'], commit['author'])
+            subject = 'New commit {} by {}'.format(commit['shorthand_id'], commit['author'])
         text = g.jinja2_env.get_template("allura:templates/mail/commits.md").render(
             commit_msgs=commit_msgs,
             max_num_commits=asint(tg.config.get('scm.notify.max_commits', 100)),
diff --git a/Allura/allura/model/repository.py b/Allura/allura/model/repository.py
index 71c1569..512dd68 100644
--- a/Allura/allura/model/repository.py
+++ b/Allura/allura/model/repository.py
@@ -14,6 +14,7 @@
 #       KIND, either express or implied.  See the License for the
 #       specific language governing permissions and limitations
 #       under the License.
+from __future__ import unicode_literals
 import json
 import os
 import stat
@@ -592,14 +593,14 @@ class Repository(Artifact, ActivityObject):
 
     @property
     def email_address(self):
-        return u'noreply@%s%s' % (self.email_domain, config.common_suffix)
+        return 'noreply@%s%s' % (self.email_domain, config.common_suffix)
 
     def index(self):
         result = Artifact.index(self)
         result.update(
             name_s=self.name,
             type_s=self.type_s,
-            title=u'{} {} repository'.format(self.project.name, self.app.tool_label))
+            title='{} {} repository'.format(self.project.name, self.app.tool_label))
         return result
 
     @property
@@ -892,7 +893,7 @@ class MergeRequest(VersionedArtifact, ActivityObject):
 
     @property
     def email_subject(self):
-        return u'Merge request: ' + self.summary
+        return 'Merge request: ' + self.summary
 
     def merge_allowed(self, user):
         """
@@ -1287,12 +1288,12 @@ class Commit(RepoObject, ActivityObject):
             'author': {
                 'name': self.authored.name,
                 'email': self.authored.email,
-                'username': self.authored_user.username if self.authored_user else u'',
+                'username': self.authored_user.username if self.authored_user else '',
             },
             'committer': {
                 'name': self.committed.name,
                 'email': self.committed.email,
-                'username': self.committed_user.username if self.committed_user else u'',
+                'username': self.committed_user.username if self.committed_user else '',
             },
             'added': self.diffs.added,
             'removed': self.diffs.removed,
diff --git a/Allura/allura/model/session.py b/Allura/allura/model/session.py
index bdd89ac..bff64e9 100644
--- a/Allura/allura/model/session.py
+++ b/Allura/allura/model/session.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import logging
 import pymongo
 from collections import defaultdict
diff --git a/Allura/allura/model/stats.py b/Allura/allura/model/stats.py
index 0928f51..726f08a 100644
--- a/Allura/allura/model/stats.py
+++ b/Allura/allura/model/stats.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 from datetime import datetime
 from tg import config
 from paste.deploy.converters import asbool
diff --git a/Allura/allura/model/timeline.py b/Allura/allura/model/timeline.py
index ee50462..2a1a138 100644
--- a/Allura/allura/model/timeline.py
+++ b/Allura/allura/model/timeline.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import bson
 import logging
 
diff --git a/Allura/allura/model/types.py b/Allura/allura/model/types.py
index 833e685..60bb225 100644
--- a/Allura/allura/model/types.py
+++ b/Allura/allura/model/types.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 from ming.base import Object
 from ming import schema as S
 
diff --git a/Allura/allura/model/webhook.py b/Allura/allura/model/webhook.py
index 96a9ded..b4810d2 100644
--- a/Allura/allura/model/webhook.py
+++ b/Allura/allura/model/webhook.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import datetime as dt
 import json
 
@@ -66,7 +67,7 @@ class Webhook(Artifact):
     def __json__(self):
         return {
             '_id': six.text_type(self._id),
-            'url': h.absurl(u'/rest' + self.url()),
+            'url': h.absurl('/rest' + self.url()),
             'type': six.text_type(self.type),
             'hook_url': six.text_type(self.hook_url),
             'mod_date': self.mod_date,
diff --git a/Allura/allura/scripts/create_sitemap_files.py b/Allura/allura/scripts/create_sitemap_files.py
index 40e4642..2bdbb86 100644
--- a/Allura/allura/scripts/create_sitemap_files.py
+++ b/Allura/allura/scripts/create_sitemap_files.py
@@ -28,6 +28,7 @@ things that would make it faster, if we need/want to.
 2. Use multiprocessing to distribute the offsets to n subprocesses.
 """
 
+from __future__ import unicode_literals
 import os
 from datetime import datetime
 import argparse
diff --git a/Allura/allura/scripts/delete_projects.py b/Allura/allura/scripts/delete_projects.py
index 9b28f64..330fe01 100644
--- a/Allura/allura/scripts/delete_projects.py
+++ b/Allura/allura/scripts/delete_projects.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import argparse
 import logging
 
@@ -43,7 +44,7 @@ class DeleteProjects(ScriptTask):
                     user = proj.user_project_of
                     if user:
                         auth_provider.disable_user(user, audit=False)
-                        msg = u'Account disabled because user-project was specified for deletion. Reason: {}'.format(
+                        msg = 'Account disabled because user-project was specified for deletion. Reason: {}'.format(
                             options.reason)
                         log_entry = h.auditlog_user(msg, user=user)
                         session(log_entry).flush(log_entry)
diff --git a/Allura/allura/scripts/disable_users.py b/Allura/allura/scripts/disable_users.py
index 6c6f8b3..63a91a5 100644
--- a/Allura/allura/scripts/disable_users.py
+++ b/Allura/allura/scripts/disable_users.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import argparse
 import logging
 import sys
diff --git a/Allura/allura/scripts/refresh_last_commits.py b/Allura/allura/scripts/refresh_last_commits.py
index 84399d8..01e2b8f 100644
--- a/Allura/allura/scripts/refresh_last_commits.py
+++ b/Allura/allura/scripts/refresh_last_commits.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import argparse
 import logging
 from datetime import datetime
diff --git a/Allura/allura/scripts/refreshrepo.py b/Allura/allura/scripts/refreshrepo.py
index 8556f87..e1e9bf9 100644
--- a/Allura/allura/scripts/refreshrepo.py
+++ b/Allura/allura/scripts/refreshrepo.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import argparse
 import logging
 import faulthandler
diff --git a/Allura/allura/scripts/reindex_projects.py b/Allura/allura/scripts/reindex_projects.py
index 37ff222..f14b973 100644
--- a/Allura/allura/scripts/reindex_projects.py
+++ b/Allura/allura/scripts/reindex_projects.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import argparse
 import logging
 
diff --git a/Allura/allura/scripts/reindex_users.py b/Allura/allura/scripts/reindex_users.py
index e2acd3b..427cd09 100644
--- a/Allura/allura/scripts/reindex_users.py
+++ b/Allura/allura/scripts/reindex_users.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import argparse
 import logging
 
diff --git a/Allura/allura/scripts/scripttask.py b/Allura/allura/scripts/scripttask.py
index fb97c2a..ec5e4b2 100644
--- a/Allura/allura/scripts/scripttask.py
+++ b/Allura/allura/scripts/scripttask.py
@@ -44,6 +44,7 @@ To call as a task::
 
 """
 
+from __future__ import unicode_literals
 import argparse
 import logging
 import shlex
diff --git a/Allura/allura/scripts/trac_export.py b/Allura/allura/scripts/trac_export.py
index 5560fd0..d05b68b 100644
--- a/Allura/allura/scripts/trac_export.py
+++ b/Allura/allura/scripts/trac_export.py
@@ -17,6 +17,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import logging
 import sys
 import csv
diff --git a/Allura/allura/tasks/activity_tasks.py b/Allura/allura/tasks/activity_tasks.py
index f6bf8b6..3dc5f77 100644
--- a/Allura/allura/tasks/activity_tasks.py
+++ b/Allura/allura/tasks/activity_tasks.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 from tg import app_globals as g
 from activitystream.storage.mingstorage import Activity
 
diff --git a/Allura/allura/tasks/admin_tasks.py b/Allura/allura/tasks/admin_tasks.py
index 7a71444..c3b6c26 100644
--- a/Allura/allura/tasks/admin_tasks.py
+++ b/Allura/allura/tasks/admin_tasks.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import inspect
 
 from tg import tmpl_context as c
diff --git a/Allura/allura/tasks/export_tasks.py b/Allura/allura/tasks/export_tasks.py
index 49771ae..56a19d5 100644
--- a/Allura/allura/tasks/export_tasks.py
+++ b/Allura/allura/tasks/export_tasks.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import os
 import os.path
 import logging
@@ -89,7 +90,7 @@ class BulkExport(object):
         }
 
         mail_tasks.send_system_mail_to_user(user,
-                                            u'Bulk export for project %s completed' % project.shortname,
+                                            'Bulk export for project %s completed' % project.shortname,
                                             tmpl.render(tmpl_context))
 
     def filter_exportable(self, apps):
diff --git a/Allura/allura/tasks/index_tasks.py b/Allura/allura/tasks/index_tasks.py
index 631ec14..ef4b80f 100644
--- a/Allura/allura/tasks/index_tasks.py
+++ b/Allura/allura/tasks/index_tasks.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import sys
 import logging
 from contextlib import contextmanager
diff --git a/Allura/allura/tasks/mail_tasks.py b/Allura/allura/tasks/mail_tasks.py
index bab4d69..eaa730e 100644
--- a/Allura/allura/tasks/mail_tasks.py
+++ b/Allura/allura/tasks/mail_tasks.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import logging
 import HTMLParser
 import re
@@ -262,7 +263,7 @@ def send_system_mail_to_user(user_or_emailaddr, subject, text):
 
     email = {
         'toaddr': toaddr,
-        'fromaddr': u'"{}" <{}>'.format(
+        'fromaddr': '"{}" <{}>'.format(
             config['site_name'],
             config['forgemail.return_path']
         ),
diff --git a/Allura/allura/tasks/notification_tasks.py b/Allura/allura/tasks/notification_tasks.py
index f82063d..c74dd2c 100644
--- a/Allura/allura/tasks/notification_tasks.py
+++ b/Allura/allura/tasks/notification_tasks.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 from allura.lib.decorators import task
 from allura.lib import utils
 from tg import tmpl_context as c
diff --git a/Allura/allura/tasks/repo_tasks.py b/Allura/allura/tasks/repo_tasks.py
index c84977f..b4fb977 100644
--- a/Allura/allura/tasks/repo_tasks.py
+++ b/Allura/allura/tasks/repo_tasks.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import shutil
 import logging
 import traceback
diff --git a/Allura/allura/templates/__init__.py b/Allura/allura/templates/__init__.py
index 8109045..4ab9cb7 100644
--- a/Allura/allura/templates/__init__.py
+++ b/Allura/allura/templates/__init__.py
@@ -18,3 +18,5 @@
 #       under the License.
 
 """Templates package for the application."""
+
+from __future__ import unicode_literals
\ No newline at end of file
diff --git a/Allura/allura/templates_responsive/responsive_overrides.py b/Allura/allura/templates_responsive/responsive_overrides.py
index 0478620..bcdb910 100644
--- a/Allura/allura/templates_responsive/responsive_overrides.py
+++ b/Allura/allura/templates_responsive/responsive_overrides.py
@@ -16,6 +16,7 @@
 #       under the License.
 
 
+from __future__ import unicode_literals
 class ResponsiveOverrides(object):
     '''
     Placeholder to trigger usage of template overrides in the /override/ dir
diff --git a/Allura/allura/tests/__init__.py b/Allura/allura/tests/__init__.py
index 8047c6b..d557387 100644
--- a/Allura/allura/tests/__init__.py
+++ b/Allura/allura/tests/__init__.py
@@ -19,6 +19,7 @@
 
 """Unit and functional test suite for allura."""
 
+from __future__ import unicode_literals
 import alluratest.controller
 
 # HACK: prevents test suite from crashing when running under the nose
diff --git a/Allura/allura/tests/decorators.py b/Allura/allura/tests/decorators.py
index f044c0e..c1bbcd7 100644
--- a/Allura/allura/tests/decorators.py
+++ b/Allura/allura/tests/decorators.py
@@ -14,6 +14,7 @@
 #       KIND, either express or implied.  See the License for the
 #       specific language governing permissions and limitations
 #       under the License.
+from __future__ import unicode_literals
 import logging
 import sys
 import re
diff --git a/Allura/allura/tests/functional/__init__.py b/Allura/allura/tests/functional/__init__.py
index 40a4394..9055977 100644
--- a/Allura/allura/tests/functional/__init__.py
+++ b/Allura/allura/tests/functional/__init__.py
@@ -18,3 +18,5 @@
 #       under the License.
 
 """Functional test suite for the controllers of the application."""
+
+from __future__ import unicode_literals
\ No newline at end of file
diff --git a/Allura/allura/tests/functional/test_admin.py b/Allura/allura/tests/functional/test_admin.py
index 534863e..66e6d32 100644
--- a/Allura/allura/tests/functional/test_admin.py
+++ b/Allura/allura/tests/functional/test_admin.py
@@ -16,6 +16,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import os
 import allura
 import pkg_resources
@@ -53,12 +54,12 @@ class TestProjectAdmin(TestController):
         with audits(
                 'change summary to Milkshakes are for crazy monkeys',
                 'change project name to My Test Project',
-                u'change short description to (\u00bf A Test Project \?){45}'):
+                'change short description to (\u00bf A Test Project \?){45}'):
             self.app.post('/admin/update', params=dict(
                 name='My Test Project',
                 shortname='test',
                 summary='Milkshakes are for crazy monkeys',
-                short_description=u'\u00bf A Test Project ?'.encode(
+                short_description='\u00bf A Test Project ?'.encode(
                         'utf-8') * 45,
                 labels='aaa,bbb'))
         r = self.app.get('/admin/overview')
@@ -164,7 +165,7 @@ class TestProjectAdmin(TestController):
     def test_features(self):
         proj = M.Project.query.get(shortname='test')
         assert_equals(proj.features, [])
-        with audits(u"change project features to \[u'One', u'Two'\]"):
+        with audits("change project features to \[u'One', u'Two'\]"):
             resp = self.app.post('/admin/update', params={
                 'features-0.feature': 'One',
                 'features-1.feature': '  ',
@@ -181,10 +182,10 @@ class TestProjectAdmin(TestController):
         features = features.findAll('input', {'type': 'text'})
         # two features + extra empty input + stub hidden input for js
         assert_equals(len(features), 2+1+1)
-        assert_equals(features[0]['value'], u'One')
-        assert_equals(features[1]['value'], u'Two')
+        assert_equals(features[0]['value'], 'One')
+        assert_equals(features[1]['value'], 'Two')
         proj = M.Project.query.get(shortname='test')
-        assert_equals(proj.features, [u'One', u'Two'])
+        assert_equals(proj.features, ['One', 'Two'])
 
     @td.with_wiki
     def test_block_user_empty_data(self):
@@ -354,17 +355,17 @@ class TestProjectAdmin(TestController):
 
     def test_install_tool_form(self):
         r = self.app.get('/admin/install_tool?tool_name=wiki')
-        assert_in(u'Installing Wiki', r)
+        assert_in('Installing Wiki', r)
 
     def test_install_tool_form_options(self):
         opts = ['AllowEmailPosting']
         with mock.patch.object(ForgeWikiApp, 'config_on_install', new=opts):
             r = self.app.get('/admin/install_tool?tool_name=wiki')
-            assert_in(u'<input id="AllowEmailPosting" name="AllowEmailPosting"', r)
+            assert_in('<input id="AllowEmailPosting" name="AllowEmailPosting"', r)
 
     def test_install_tool_form_subproject(self):
         r = self.app.get('/admin/install_tool?tool_name=subproject')
-        assert_in(u'Installing Sub Project', r)
+        assert_in('Installing Sub Project', r)
 
     def test_project_icon(self):
         file_name = 'neo-icon-set-454545-256x350.png'
@@ -552,13 +553,13 @@ class TestProjectAdmin(TestController):
             r = form.submit().follow()
         # make sure it worked
         assert 'No Database Environment categories have been selected.' not in r
-        assert u'<span class="trove_fullpath">Database API » Python Database API</span>' in r
+        assert '<span class="trove_fullpath">Database API » Python Database API</span>' in r
         # delete the cat
         with audits('remove trove root_database: Database Environment :: Database API'):
             r = r.forms['delete_trove_root_database_506'].submit().follow()
         # make sure it worked
         assert 'No Database Environment categories have been selected.' in r
-        assert u'<span class="trove_fullpath">Database API » Python Database API</span>' not in r
+        assert '<span class="trove_fullpath">Database API » Python Database API</span>' not in r
 
     def test_add_remove_label(self):
         setup_trove_categories()
@@ -879,43 +880,43 @@ class TestProjectAdmin(TestController):
             'role_id': anon_id,
             'permission': 'create',
             'allow': 'true'})
-        assert {u'text': u'Inherited permission create from Anonymous',
-                u'has': u'inherit', u'name': u'create'} in r.json[admin_id]
-        assert {u'text': u'Inherited permission create from Anonymous',
-                u'has': u'inherit', u'name': u'create'} in r.json[mem_id]
-        assert {u'text': u'Has permission create', u'has':
-                u'yes', u'name': u'create'} in r.json[anon_id]
+        assert {'text': 'Inherited permission create from Anonymous',
+                'has': 'inherit', 'name': 'create'} in r.json[admin_id]
+        assert {'text': 'Inherited permission create from Anonymous',
+                'has': 'inherit', 'name': 'create'} in r.json[mem_id]
+        assert {'text': 'Has permission create', 'has':
+                'yes', 'name': 'create'} in r.json[anon_id]
         r = self.app.post('/admin/groups/change_perm', params={
             'role_id': anon_id,
             'permission': 'create',
             'allow': 'false'})
-        assert {u'text': u'Does not have permission create',
-                u'has': u'no', u'name': u'create'} in r.json[admin_id]
-        assert {u'text': u'Does not have permission create',
-                u'has': u'no', u'name': u'create'} in r.json[mem_id]
-        assert {u'text': u'Does not have permission create',
-                u'has': u'no', u'name': u'create'} in r.json[anon_id]
+        assert {'text': 'Does not have permission create',
+                'has': 'no', 'name': 'create'} in r.json[admin_id]
+        assert {'text': 'Does not have permission create',
+                'has': 'no', 'name': 'create'} in r.json[mem_id]
+        assert {'text': 'Does not have permission create',
+                'has': 'no', 'name': 'create'} in r.json[anon_id]
         # updates to Member inherit up
         r = self.app.post('/admin/groups/change_perm', params={
             'role_id': mem_id,
             'permission': 'create',
             'allow': 'true'})
-        assert {u'text': u'Inherited permission create from Member',
-                u'has': u'inherit', u'name': u'create'} in r.json[admin_id]
-        assert {u'text': u'Has permission create', u'has':
-                u'yes', u'name': u'create'} in r.json[mem_id]
-        assert {u'text': u'Does not have permission create',
-                u'has': u'no', u'name': u'create'} in r.json[anon_id]
+        assert {'text': 'Inherited permission create from Member',
+                'has': 'inherit', 'name': 'create'} in r.json[admin_id]
+        assert {'text': 'Has permission create', 'has':
+                'yes', 'name': 'create'} in r.json[mem_id]
+        assert {'text': 'Does not have permission create',
+                'has': 'no', 'name': 'create'} in r.json[anon_id]
         r = self.app.post('/admin/groups/change_perm', params={
             'role_id': mem_id,
             'permission': 'create',
             'allow': 'false'})
-        assert {u'text': u'Does not have permission create',
-                u'has': u'no', u'name': u'create'} in r.json[admin_id]
-        assert {u'text': u'Does not have permission create',
-                u'has': u'no', u'name': u'create'} in r.json[mem_id]
-        assert {u'text': u'Does not have permission create',
-                u'has': u'no', u'name': u'create'} in r.json[anon_id]
+        assert {'text': 'Does not have permission create',
+                'has': 'no', 'name': 'create'} in r.json[admin_id]
+        assert {'text': 'Does not have permission create',
+                'has': 'no', 'name': 'create'} in r.json[mem_id]
+        assert {'text': 'Does not have permission create',
+                'has': 'no', 'name': 'create'} in r.json[anon_id]
 
     def test_admin_extension_sidebar(self):
 
@@ -972,7 +973,7 @@ class TestExport(TestController):
         exportable_tools = AdminApp.exportable_tools_for(project)
         exportable_mount_points = [
             t.options.mount_point for t in exportable_tools]
-        assert_equals(exportable_mount_points, [u'admin', u'wiki', u'wiki2'])
+        assert_equals(exportable_mount_points, ['admin', 'wiki', 'wiki2'])
 
     def test_access(self):
         r = self.app.get('/admin/export',
@@ -1016,29 +1017,29 @@ class TestExport(TestController):
             exportable_mount_points = [
                 t.options.mount_point for t in exportable_tools]
             assert_equals(exportable_mount_points,
-                          [u'admin', u'search', u'wiki', u'wiki2'])
+                          ['admin', 'search', 'wiki', 'wiki2'])
 
     def test_tools_not_selected(self):
         r = self.app.post('/admin/export')
         assert_in('error', self.webflash(r))
 
     def test_bad_tool(self):
-        r = self.app.post('/admin/export', {'tools': u'search'})
+        r = self.app.post('/admin/export', {'tools': 'search'})
         assert_in('error', self.webflash(r))
 
     @mock.patch('allura.ext.admin.admin_main.export_tasks')
     def test_selected_one_tool(self, export_tasks):
-        r = self.app.post('/admin/export', {'tools': u'wiki'})
+        r = self.app.post('/admin/export', {'tools': 'wiki'})
         assert_in('ok', self.webflash(r))
         export_tasks.bulk_export.post.assert_called_once_with(
-            [u'wiki'], 'test.zip', send_email=True, with_attachments=False)
+            ['wiki'], 'test.zip', send_email=True, with_attachments=False)
 
     @mock.patch('allura.ext.admin.admin_main.export_tasks')
     def test_selected_multiple_tools(self, export_tasks):
-        r = self.app.post('/admin/export', {'tools': [u'wiki', u'wiki2']})
+        r = self.app.post('/admin/export', {'tools': ['wiki', 'wiki2']})
         assert_in('ok', self.webflash(r))
         export_tasks.bulk_export.post.assert_called_once_with(
-            [u'wiki', u'wiki2'], 'test.zip', send_email=True, with_attachments=False)
+            ['wiki', 'wiki2'], 'test.zip', send_email=True, with_attachments=False)
 
     @mock.patch('allura.model.session.project_doc_session')
     def test_export_in_progress(self, session):
@@ -1343,23 +1344,23 @@ class TestRestMountOrder(TestRestApiBase):
     @td.with_wiki
     def test_reorder(self):
         d1 = {
-            '0': u'sub1',
-            '1': u'wiki',
-            '2': u'admin'
+            '0': 'sub1',
+            '1': 'wiki',
+            '2': 'admin'
         }
         d2 = {
-            '0': u'wiki',
-            '1': u'sub1',
-            '2': u'admin'
+            '0': 'wiki',
+            '1': 'sub1',
+            '2': 'admin'
         }
 
         tool = {
-            u'icon': u'tool-admin',
-            u'is_anchored': False,
-            u'mount_point': u'sub1',
-            u'name': u'A Subproject',
-            u'tool_name': u'sub',
-            u'url': u'/p/test/sub1/'
+            'icon': 'tool-admin',
+            'is_anchored': False,
+            'mount_point': 'sub1',
+            'name': 'A Subproject',
+            'tool_name': 'sub',
+            'url': '/p/test/sub1/'
         }
 
         # Set initial order to d1
diff --git a/Allura/allura/tests/functional/test_auth.py b/Allura/allura/tests/functional/test_auth.py
index 307460f..653609e 100644
--- a/Allura/allura/tests/functional/test_auth.py
+++ b/Allura/allura/tests/functional/test_auth.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import calendar
 from datetime import datetime, time, timedelta
 from time import time as time_time
@@ -157,7 +158,7 @@ class TestAuth(TestController):
 
         args, kwargs = sendsimplemail.post.call_args
         assert_equal(sendsimplemail.post.call_count, 1)
-        assert_equal(kwargs['subject'], u'Update your %s password' % config['site_name'])
+        assert_equal(kwargs['subject'], 'Update your %s password' % config['site_name'])
         assert_in('/auth/forgotten_password/', kwargs['text'])
 
         assert_equal([], M.UserLoginDetails.query.find().all())  # no records created
@@ -371,7 +372,7 @@ class TestAuth(TestController):
 
         assert sendsimplemail.post.call_count == 1
         assert kwargs['toaddr'] == email_address
-        assert kwargs['subject'] == u'%s - Email address claim attempt' % config['site_name']
+        assert kwargs['subject'] == '%s - Email address claim attempt' % config['site_name']
         assert "You tried to add %s to your Allura account, " \
                "but it is already claimed by your %s account." % (email_address, user.username) in kwargs['text']
 
@@ -475,7 +476,7 @@ class TestAuth(TestController):
         args, kwargs = sendsimplemail.post.call_args
         assert sendsimplemail.post.call_count == 1
         assert kwargs['toaddr'] == email_address
-        assert kwargs['subject'] == u'%s - Email address claim attempt' % config['site_name']
+        assert kwargs['subject'] == '%s - Email address claim attempt' % config['site_name']
         assert "You tried to add %s to your Allura account, " \
                "but it is already claimed by your %s account." % (email_address, user.username) in kwargs['text']
 
@@ -1611,7 +1612,7 @@ To update your password on %s, please visit the following URL:
         sendmail.post.assert_called_once_with(
             sender='noreply@localhost',
             toaddr=email.email,
-            fromaddr=u'"{}" <{}>'.format(config['site_name'], config['forgemail.return_path']),
+            fromaddr='"{}" <{}>'.format(config['site_name'], config['forgemail.return_path']),
             reply_to=config['forgemail.return_path'],
             subject='Allura Password recovery',
             message_id=gen_message_id(),
diff --git a/Allura/allura/tests/functional/test_discuss.py b/Allura/allura/tests/functional/test_discuss.py
index 978ec33..e94bf53 100644
--- a/Allura/allura/tests/functional/test_discuss.py
+++ b/Allura/allura/tests/functional/test_discuss.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import os
 from mock import patch
 from nose.tools import assert_in, assert_not_in, assert_equal, assert_false, assert_true, assert_raises
diff --git a/Allura/allura/tests/functional/test_feeds.py b/Allura/allura/tests/functional/test_feeds.py
index e1e54d5..7558db9 100644
--- a/Allura/allura/tests/functional/test_feeds.py
+++ b/Allura/allura/tests/functional/test_feeds.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 from formencode.variabledecode import variable_encode
 
 from allura.tests import TestController
@@ -44,7 +45,7 @@ class TestFeeds(TestController):
                     status='open',
                     description='This is a description'))),
             status=302)
-        title = u'Descri\xe7\xe3o e Arquitetura'.encode('utf-8')
+        title = 'Descri\xe7\xe3o e Arquitetura'.encode('utf-8')
         self.app.post(
             '/wiki/%s/update' % title,
             params=dict(
diff --git a/Allura/allura/tests/functional/test_gravatar.py b/Allura/allura/tests/functional/test_gravatar.py
index 72e0b99..b07e4cd 100644
--- a/Allura/allura/tests/functional/test_gravatar.py
+++ b/Allura/allura/tests/functional/test_gravatar.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 from urlparse import urlparse, parse_qs
 
 import tg
@@ -34,7 +35,7 @@ class TestGravatar(TestController):
         assert expected_id == actual_id
 
     def test_unicode_id(self):
-        email = u'Vin\u00EDcius@example.com'
+        email = 'Vin\u00EDcius@example.com'
         expected_id = 'e00968255d68523b034a6a39c522efdb'
         actual_id = gravatar.id(email)
         assert expected_id == actual_id, 'Expected gravitar ID %s, got %s' % (
diff --git a/Allura/allura/tests/functional/test_home.py b/Allura/allura/tests/functional/test_home.py
index 768a973..4ab8147 100644
--- a/Allura/allura/tests/functional/test_home.py
+++ b/Allura/allura/tests/functional/test_home.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import json
 import re
 import os
@@ -61,7 +62,7 @@ class TestProjectHome(TestController):
                                }])
                 break
         else:
-            raise AssertionError(u'Did not find sub1 subproject in menu results: {}'.format(r.json['menu']))
+            raise AssertionError('Did not find sub1 subproject in menu results: {}'.format(r.json['menu']))
         for m in r.json['menu']:
             if m['mount_point'] == 'wiki':
                 assert_in({'className': 'admin_modal',
@@ -78,7 +79,7 @@ class TestProjectHome(TestController):
                            }, m['admin_options'])
                 break
         else:
-            raise AssertionError(u'Did not find wiki in menu results: {}'.format(r.json['menu']))
+            raise AssertionError('Did not find wiki in menu results: {}'.format(r.json['menu']))
 
     @td.with_wiki
     def test_project_group_nav(self):
@@ -92,7 +93,7 @@ class TestProjectHome(TestController):
         menu = response.json['menu']
         wiki_group = menu[-2]
         wikis = wiki_group.pop('children')
-        assert_equal({'url': '/p/test/_list/wiki', 'name': u'Wiki \u25be', 'mount_point': None,
+        assert_equal({'url': '/p/test/_list/wiki', 'name': 'Wiki \u25be', 'mount_point': None,
                       'icon': 'tool-wiki', 'tool_name': 'wiki', 'is_anchored': False}, wiki_group)
         assert_equal(len(wikis), 2)
         assert_in({'url': '/p/test/wiki/', 'name': 'Wiki', 'mount_point': 'wiki',
@@ -187,8 +188,8 @@ class TestProjectHome(TestController):
         r = self.app.get('/p/test/users', status=200)
         j = json.loads(r.body)
         expected = [{
-            'value': u'test-admin',
-            'label': u'Test Admin (test-admin)'
+            'value': 'test-admin',
+            'label': 'Test Admin (test-admin)'
         }]
         assert_equal(j['options'], expected)
 
diff --git a/Allura/allura/tests/functional/test_nav.py b/Allura/allura/tests/functional/test_nav.py
index 8fb2f78..a50579f 100644
--- a/Allura/allura/tests/functional/test_nav.py
+++ b/Allura/allura/tests/functional/test_nav.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import mock
 import json
 from tg import config
diff --git a/Allura/allura/tests/functional/test_neighborhood.py b/Allura/allura/tests/functional/test_neighborhood.py
index 211b745..2e7d65a 100644
--- a/Allura/allura/tests/functional/test_neighborhood.py
+++ b/Allura/allura/tests/functional/test_neighborhood.py
@@ -16,6 +16,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import json
 import os
 from cStringIO import StringIO
@@ -747,7 +748,7 @@ class TestNeighborhood(TestController):
         # check the labels and trove cats
         r = self.app.get('/adobe/testtemp/admin/trove')
         assert 'mmi' in r
-        assert u'Communications » Telephony' in r
+        assert 'Communications » Telephony' in r
         assert '5 - Production/Stable' in r
         # check the wiki text
         r = self.app.get('/adobe/testtemp/wiki/').follow()
@@ -1003,7 +1004,7 @@ class TestPhoneVerificationOnProjectRegistration(TestController):
             r = self.app.get('/p/verify_phone', {'number': '555-444-3333'})
         expected = {
             'status': 'error',
-            'error': u'&lt;script&gt;alert(&#34;hacked&#34;);&lt;/script&gt;',
+            'error': '&lt;script&gt;alert(&#34;hacked&#34;);&lt;/script&gt;',
         }
         assert_equal(r.json, expected)
 
@@ -1017,7 +1018,7 @@ class TestPhoneVerificationOnProjectRegistration(TestController):
             r = self.app.get('/p/verify_phone', {'number': '1-555-444-9999'})
             assert_equal(r.json, {
                 'status': 'error',
-                'error': u'That phone number has already been used.'
+                'error': 'That phone number has already been used.'
             })
 
     def test_check_phone_verification_no_params(self):
@@ -1072,7 +1073,7 @@ class TestPhoneVerificationOnProjectRegistration(TestController):
             r = self.app.get('/p/check_phone_verification', {'pin': '1234'})
         expected = {
             'status': 'error',
-            'error': u'&lt;script&gt;alert(&#34;hacked&#34;);&lt;/script&gt;',
+            'error': '&lt;script&gt;alert(&#34;hacked&#34;);&lt;/script&gt;',
         }
         assert_equal(r.json, expected)
 
diff --git a/Allura/allura/tests/functional/test_personal_dashboard.py b/Allura/allura/tests/functional/test_personal_dashboard.py
index 217c470..01e3e8f 100644
--- a/Allura/allura/tests/functional/test_personal_dashboard.py
+++ b/Allura/allura/tests/functional/test_personal_dashboard.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import pkg_resources
 import mock
 import tg
diff --git a/Allura/allura/tests/functional/test_rest.py b/Allura/allura/tests/functional/test_rest.py
index 1ba033b..fc8ba1e 100644
--- a/Allura/allura/tests/functional/test_rest.py
+++ b/Allura/allura/tests/functional/test_rest.py
@@ -17,6 +17,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 from tg import app_globals as g
 import mock
 from nose.tools import assert_equal, assert_in, assert_not_in
diff --git a/Allura/allura/tests/functional/test_root.py b/Allura/allura/tests/functional/test_root.py
index e6a6bc7..7438a37 100644
--- a/Allura/allura/tests/functional/test_root.py
+++ b/Allura/allura/tests/functional/test_root.py
@@ -28,6 +28,7 @@ functional tests exercise the whole application and its WSGI stack.
 Please read http://pythonpaste.org/webtest/ for more information.
 
 """
+from __future__ import unicode_literals
 import os
 from urllib import quote
 
diff --git a/Allura/allura/tests/functional/test_search.py b/Allura/allura/tests/functional/test_search.py
index 3a1a72b..3d3315b 100644
--- a/Allura/allura/tests/functional/test_search.py
+++ b/Allura/allura/tests/functional/test_search.py
@@ -14,6 +14,7 @@
 #       KIND, either express or implied.  See the License for the
 #       specific language governing permissions and limitations
 #       under the License.
+from __future__ import unicode_literals
 from mock import patch
 
 from tg import tmpl_context as c
diff --git a/Allura/allura/tests/functional/test_site_admin.py b/Allura/allura/tests/functional/test_site_admin.py
index 8b23c1e..b75f51d 100644
--- a/Allura/allura/tests/functional/test_site_admin.py
+++ b/Allura/allura/tests/functional/test_site_admin.py
@@ -16,6 +16,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import json
 import datetime as dt
 import bson
@@ -471,9 +472,9 @@ class TestUserDetails(TestController):
         # list of projects
         projects = r.html.findAll('fieldset')[-1]
         projects = [e.getText() for e in projects.findAll('li')]
-        assert_in(u'Test 2\n\u2013\nAdmin\n', projects)
-        assert_in(u'Test Project\n\u2013\nAdmin\n', projects)
-        assert_in(u'Adobe project 1\n\u2013\nAdmin\n', projects)
+        assert_in('Test 2\n\u2013\nAdmin\n', projects)
+        assert_in('Test Project\n\u2013\nAdmin\n', projects)
+        assert_in('Adobe project 1\n\u2013\nAdmin\n', projects)
 
     @patch('allura.model.auth.request')
     @patch('allura.lib.helpers.request')
@@ -501,14 +502,14 @@ class TestUserDetails(TestController):
 
     def test_add_comment(self):
         r = self.app.get('/nf/admin/user/test-user')
-        assert_not_in(u'Comment by test-admin: I was hêre!', r)
+        assert_not_in('Comment by test-admin: I was hêre!', r)
         form = [f for f in r.forms.itervalues() if f.action.endswith('add_audit_trail_entry')][0]
         assert_equal(form['username'].value, 'test-user')
-        form['comment'] = u'I was hêre!'
+        form['comment'] = 'I was hêre!'
         r = form.submit()
-        assert_in(u'Comment added', self.webflash(r))
+        assert_in('Comment added', self.webflash(r))
         r = self.app.get('/nf/admin/user/test-user')
-        assert_in(u'Comment by test-admin: I was hêre!', r)
+        assert_in('Comment by test-admin: I was hêre!', r)
 
     def test_disable_user(self):
         # user was not pending
@@ -522,7 +523,7 @@ class TestUserDetails(TestController):
         with td.audits('Account disabled', user=True):
             r = form.submit()
             assert_equal(M.AuditLog.query.find().count(), 1)
-        assert_in(u'User disabled', self.webflash(r))
+        assert_in('User disabled', self.webflash(r))
         assert_equal(M.User.by_username('test-user-3').disabled, True)
         assert_equal(M.User.by_username('test-user-3').pending, False)
 
@@ -541,7 +542,7 @@ class TestUserDetails(TestController):
         with td.audits('Account disabled', user=True):
             r = form.submit()
             assert_equal(M.AuditLog.query.find().count(), 1)
-        assert_in(u'User disabled', self.webflash(r))
+        assert_in('User disabled', self.webflash(r))
         assert_equal(M.User.by_username('test-user-3').disabled, True)
         assert_equal(M.User.by_username('test-user-3').pending, True)
 
@@ -560,7 +561,7 @@ class TestUserDetails(TestController):
         with td.audits('Account enabled', user=True):
             r = form.submit()
             assert_equal(M.AuditLog.query.find().count(), 1)
-        assert_in(u'User enabled', self.webflash(r))
+        assert_in('User enabled', self.webflash(r))
         assert_equal(M.User.by_username('test-user-3').disabled, False)
         assert_equal(M.User.by_username('test-user-3').pending, False)
 
@@ -579,7 +580,7 @@ class TestUserDetails(TestController):
         with td.audits('Account enabled', user=True):
             r = form.submit()
             assert_equal(M.AuditLog.query.find().count(), 1)
-        assert_in(u'User enabled', self.webflash(r))
+        assert_in('User enabled', self.webflash(r))
         assert_equal(M.User.by_username('test-user-3').disabled, False)
         assert_equal(M.User.by_username('test-user-3').pending, False)
 
@@ -598,7 +599,7 @@ class TestUserDetails(TestController):
         with td.audits('Account enabled', user=True):
             r = form.submit()
             assert_equal(M.AuditLog.query.find().count(), 1)
-        assert_in(u'User enabled', self.webflash(r))
+        assert_in('User enabled', self.webflash(r))
         assert_equal(M.User.by_username('test-user-3').disabled, False)
         assert_equal(M.User.by_username('test-user-3').pending, False)
 
@@ -617,7 +618,7 @@ class TestUserDetails(TestController):
         with td.audits('Account changed to pending', user=True):
             r = form.submit()
             assert_equal(M.AuditLog.query.find().count(), 1)
-        assert_in(u'Set user status to pending', self.webflash(r))
+        assert_in('Set user status to pending', self.webflash(r))
         assert_equal(M.User.by_username('test-user-3').disabled, False)
         assert_equal(M.User.by_username('test-user-3').pending, True)
 
@@ -636,7 +637,7 @@ class TestUserDetails(TestController):
         with td.audits('Account changed to pending', user=True):
             r = form.submit()
             assert_equal(M.AuditLog.query.find().count(), 1)
-        assert_in(u'Set user status to pending', self.webflash(r))
+        assert_in('Set user status to pending', self.webflash(r))
         assert_equal(M.User.by_username('test-user-3').disabled, False)
         assert_equal(M.User.by_username('test-user-3').pending, True)
 
@@ -723,7 +724,7 @@ To update your password on %s, please visit the following URL:
         sendmail.post.assert_called_once_with(
             sender='noreply@localhost',
             toaddr='test-user@example.org',
-            fromaddr=u'"{}" <{}>'.format(config['site_name'], config['forgemail.return_path']),
+            fromaddr='"{}" <{}>'.format(config['site_name'], config['forgemail.return_path']),
             reply_to=config['forgemail.return_path'],
             subject='Allura Password recovery',
             message_id=gen_message_id(),
@@ -747,14 +748,14 @@ class TestDeleteProjects(TestController):
 
     def test_projects_populated_from_get_params(self):
         r = self.app.get('/nf/admin/delete_projects/')
-        assert_equal(self.delete_form(r)['projects'].value, u'')
+        assert_equal(self.delete_form(r)['projects'].value, '')
         link = '/nf/admin/delete_projects/?projects=/p/test/++++%23+comment%0A/adobe/adobe-1/%0A/p/test2/'
         link += '&reason=The%0AReason&disable_users=True'
         r = self.app.get(link)
         form = self.delete_form(r)
-        assert_equal(form['projects'].value, u'/p/test/    # comment\n/adobe/adobe-1/\n/p/test2/')
-        assert_equal(form['reason'].value, u'The\nReason')
-        assert_equal(form['disable_users'].value, u'on')
+        assert_equal(form['projects'].value, '/p/test/    # comment\n/adobe/adobe-1/\n/p/test2/')
+        assert_equal(form['reason'].value, 'The\nReason')
+        assert_equal(form['disable_users'].value, 'on')
 
     def test_confirm_step_values(self):
         r = self.app.get('/nf/admin/delete_projects/')
diff --git a/Allura/allura/tests/functional/test_static.py b/Allura/allura/tests/functional/test_static.py
index 6d66d33..392af13 100644
--- a/Allura/allura/tests/functional/test_static.py
+++ b/Allura/allura/tests/functional/test_static.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 from allura.tests import TestController
 
 
diff --git a/Allura/allura/tests/functional/test_subscriber.py b/Allura/allura/tests/functional/test_subscriber.py
index 93b822e..aae83a3 100644
--- a/Allura/allura/tests/functional/test_subscriber.py
+++ b/Allura/allura/tests/functional/test_subscriber.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 from allura.tests import TestController
 from allura.tests import decorators as td
 from allura.model.notification import Mailbox
diff --git a/Allura/allura/tests/functional/test_tool_list.py b/Allura/allura/tests/functional/test_tool_list.py
index 08607cf..1bf57db 100644
--- a/Allura/allura/tests/functional/test_tool_list.py
+++ b/Allura/allura/tests/functional/test_tool_list.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 from allura.tests import TestController
 from allura.tests import decorators as td
 
diff --git a/Allura/allura/tests/functional/test_trovecategory.py b/Allura/allura/tests/functional/test_trovecategory.py
index 2dc438f..850a3bb 100644
--- a/Allura/allura/tests/functional/test_trovecategory.py
+++ b/Allura/allura/tests/functional/test_trovecategory.py
@@ -14,6 +14,7 @@
 #       KIND, either express or implied.  See the License for the
 #       specific language governing permissions and limitations
 #       under the License.
+from __future__ import unicode_literals
 from bs4 import BeautifulSoup
 import mock
 
diff --git a/Allura/allura/tests/functional/test_user_profile.py b/Allura/allura/tests/functional/test_user_profile.py
index 8f8f027..6ed96a8 100644
--- a/Allura/allura/tests/functional/test_user_profile.py
+++ b/Allura/allura/tests/functional/test_user_profile.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import mock
 import tg
 from nose.tools import assert_equal, assert_in, assert_not_in
@@ -121,12 +122,12 @@ class TestUserProfile(TestController):
 
         sendsimplemail.post.assert_called_once_with(
             cc=User.by_username('test-admin').get_pref('email_address'),
-            text=u'test message\n\n---\n\nThis message was sent to you via the Allura web mail form.  You may reply to this message directly, or send a message to Test Admin at http://localhost/u/test-admin/profile/send_message\n',
+            text='test message\n\n---\n\nThis message was sent to you via the Allura web mail form.  You may reply to this message directly, or send a message to Test Admin at http://localhost/u/test-admin/profile/send_message\n',
             toaddr=User.by_username('test-user').get_pref('email_address'),
             fromaddr=User.by_username('test-admin').get_pref('email_address'),
             reply_to=User.by_username('test-admin').get_pref('email_address'),
-            message_id=u'id',
-            subject=u'test subject')
+            message_id='id',
+            subject='test subject')
         sendsimplemail.reset_mock()
         self.app.post('/u/test-user/profile/send_user_message',
                       params={'subject': 'test subject',
@@ -134,12 +135,12 @@ class TestUserProfile(TestController):
 
         sendsimplemail.post.assert_called_once_with(
             cc=None,
-            text=u'test message\n\n---\n\nThis message was sent to you via the Allura web mail form.  You may reply to this message directly, or send a message to Test Admin at http://localhost/u/test-admin/profile/send_message\n',
+            text='test message\n\n---\n\nThis message was sent to you via the Allura web mail form.  You may reply to this message directly, or send a message to Test Admin at http://localhost/u/test-admin/profile/send_message\n',
             toaddr=User.by_username('test-user').get_pref('email_address'),
             fromaddr=User.by_username('test-admin').get_pref('email_address'),
             reply_to=User.by_username('test-admin').get_pref('email_address'),
-            message_id=u'id',
-            subject=u'test subject')
+            message_id='id',
+            subject='test subject')
 
         check.return_value = False
         response = self.app.get(
diff --git a/Allura/allura/tests/model/__init__.py b/Allura/allura/tests/model/__init__.py
index c194980..b7d0eab 100644
--- a/Allura/allura/tests/model/__init__.py
+++ b/Allura/allura/tests/model/__init__.py
@@ -18,3 +18,5 @@
 #       under the License.
 
 """Model test suite for the models of the application."""
+
+from __future__ import unicode_literals
\ No newline at end of file
diff --git a/Allura/allura/tests/model/test_artifact.py b/Allura/allura/tests/model/test_artifact.py
index 1e63813..06b2b2a 100644
--- a/Allura/allura/tests/model/test_artifact.py
+++ b/Allura/allura/tests/model/test_artifact.py
@@ -20,6 +20,7 @@
 """
 Model tests for artifact
 """
+from __future__ import unicode_literals
 import re
 from datetime import datetime
 
diff --git a/Allura/allura/tests/model/test_auth.py b/Allura/allura/tests/model/test_auth.py
index 47dc8b6..88df750 100644
--- a/Allura/allura/tests/model/test_auth.py
+++ b/Allura/allura/tests/model/test_auth.py
@@ -20,6 +20,7 @@
 """
 Model tests for auth
 """
+from __future__ import unicode_literals
 from nose.tools import (
     with_setup,
     assert_equal,
diff --git a/Allura/allura/tests/model/test_discussion.py b/Allura/allura/tests/model/test_discussion.py
index d470c87..162c235 100644
--- a/Allura/allura/tests/model/test_discussion.py
+++ b/Allura/allura/tests/model/test_discussion.py
@@ -20,6 +20,7 @@
 """
 Model tests for artifact
 """
+from __future__ import unicode_literals
 from cStringIO import StringIO
 import time
 from datetime import datetime, timedelta
@@ -200,10 +201,10 @@ def test_attachment_methods():
     fs.filename = 'fake.txt'
     fs.type = 'text/plain'
     fs.file = StringIO('this is the content of the fake file\n')
-    p = t.post(text=u'test message', forum=None, subject='', file_info=fs)
+    p = t.post(text='test message', forum=None, subject='', file_info=fs)
     ThreadLocalORMSession.flush_all()
     n = M.Notification.query.get(
-        subject=u'[test:wiki] Test comment notification')
+        subject='[test:wiki] Test comment notification')
     url = h.absurl('{}attachment/{}'.format(p.url(), fs.filename))
     assert_in(
         '\nAttachments:\n\n'
@@ -265,10 +266,10 @@ def test_notification_two_attaches():
     fs2.filename = 'fake2.txt'
     fs2.type = 'text/plain'
     fs2.file = StringIO('this is the content of the fake file\n')
-    p = t.post(text=u'test message', forum=None, subject='', file_info=[fs1, fs2])
+    p = t.post(text='test message', forum=None, subject='', file_info=[fs1, fs2])
     ThreadLocalORMSession.flush_all()
     n = M.Notification.query.get(
-        subject=u'[test:wiki] Test comment notification')
+        subject='[test:wiki] Test comment notification')
     base_url = h.absurl('{}attachment/'.format(p.url()))
     assert_in(
         '\nAttachments:\n\n'
diff --git a/Allura/allura/tests/model/test_filesystem.py b/Allura/allura/tests/model/test_filesystem.py
index 30ab9e9..665ec22 100644
--- a/Allura/allura/tests/model/test_filesystem.py
+++ b/Allura/allura/tests/model/test_filesystem.py
@@ -17,6 +17,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import os
 from unittest import TestCase
 from cStringIO import StringIO
@@ -123,26 +124,26 @@ class TestFile(TestCase):
         self._assert_content(f, 'test2')
 
     def test_serve_embed(self):
-        f = File.from_data(u'te s\u0b6e1.txt', 'test1')
+        f = File.from_data('te s\u0b6e1.txt', 'test1')
         self.session.flush()
         with patch('allura.lib.utils.tg.request', Request.blank('/')), \
                 patch('allura.lib.utils.tg.response', Response()) as response, \
                 patch('allura.lib.utils.etag_cache') as etag_cache:
             response_body = list(f.serve())
-            etag_cache.assert_called_once_with(u'{}?{}'.format(f.filename,
+            etag_cache.assert_called_once_with('{}?{}'.format(f.filename,
                                                                f._id.generation_time).encode('utf-8'))
             assert_equal(['test1'], response_body)
             assert_equal(response.content_type, f.content_type)
             assert 'Content-Disposition' not in response.headers
 
     def test_serve_embed_false(self):
-        f = File.from_data(u'te s\u0b6e1.txt', 'test1')
+        f = File.from_data('te s\u0b6e1.txt', 'test1')
         self.session.flush()
         with patch('allura.lib.utils.tg.request', Request.blank('/')), \
                 patch('allura.lib.utils.tg.response', Response()) as response, \
                 patch('allura.lib.utils.etag_cache') as etag_cache:
             response_body = list(f.serve(embed=False))
-            etag_cache.assert_called_once_with(u'{}?{}'.format(f.filename,
+            etag_cache.assert_called_once_with('{}?{}'.format(f.filename,
                                                                f._id.generation_time).encode('utf-8'))
             assert_equal(['test1'], response_body)
             assert_equal(response.content_type, f.content_type)
@@ -209,7 +210,7 @@ class TestFile(TestCase):
             b'Strukturpr\xfcfung.dvi', fp,
             save_original=True)
         assert type(attachment) != tuple   # tuple is for (img, thumb) pairs
-        assert_equal(attachment.filename, u'Strukturpr\xfcfung.dvi')
+        assert_equal(attachment.filename, 'Strukturpr\xfcfung.dvi')
 
     def _assert_content(self, f, content):
         result = f.rfile().read()
diff --git a/Allura/allura/tests/model/test_monq.py b/Allura/allura/tests/model/test_monq.py
index 4ba2f0b..e42c940 100644
--- a/Allura/allura/tests/model/test_monq.py
+++ b/Allura/allura/tests/model/test_monq.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import pprint
 from nose.tools import with_setup
 
diff --git a/Allura/allura/tests/model/test_neighborhood.py b/Allura/allura/tests/model/test_neighborhood.py
index b1ba8c3..bb62787 100644
--- a/Allura/allura/tests/model/test_neighborhood.py
+++ b/Allura/allura/tests/model/test_neighborhood.py
@@ -20,6 +20,7 @@
 """
 Model tests for neighborhood
 """
+from __future__ import unicode_literals
 from nose.tools import with_setup
 
 from allura import model as M
@@ -56,11 +57,11 @@ def test_neighborhood():
     assert neighborhood.get_max_projects() == 500
 
     # Check picker css styles
-    test_css_dict = {'barontop': u'#444',
-                     'titlebarbackground': u'#555',
-                     'projecttitlefont': u'arial,sans-serif',
-                     'projecttitlecolor': u'#333',
-                     'titlebarcolor': u'#666'}
+    test_css_dict = {'barontop': '#444',
+                     'titlebarbackground': '#555',
+                     'projecttitlefont': 'arial,sans-serif',
+                     'projecttitlecolor': '#333',
+                     'titlebarcolor': '#666'}
     css_text = neighborhood.compile_css_for_picker(test_css_dict)
     assert '#333' in css_text
     assert '#444' in css_text
diff --git a/Allura/allura/tests/model/test_notification.py b/Allura/allura/tests/model/test_notification.py
index d154bd7..684d3be 100644
--- a/Allura/allura/tests/model/test_notification.py
+++ b/Allura/allura/tests/model/test_notification.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import unittest
 from datetime import timedelta
 import collections
diff --git a/Allura/allura/tests/model/test_oauth.py b/Allura/allura/tests/model/test_oauth.py
index c565f5d..d7e4ee6 100644
--- a/Allura/allura/tests/model/test_oauth.py
+++ b/Allura/allura/tests/model/test_oauth.py
@@ -18,6 +18,7 @@
 #       under the License.
 
 
+from __future__ import unicode_literals
 from nose.tools import with_setup, assert_equal, assert_not_equal
 
 from ming.odm import ThreadLocalORMSession
diff --git a/Allura/allura/tests/model/test_project.py b/Allura/allura/tests/model/test_project.py
index 84eb0af..ea06c69 100644
--- a/Allura/allura/tests/model/test_project.py
+++ b/Allura/allura/tests/model/test_project.py
@@ -20,6 +20,7 @@
 """
 Model tests for project
 """
+from __future__ import unicode_literals
 from nose import with_setup
 from nose.tools import assert_equals, assert_in
 from tg import tmpl_context as c
@@ -187,7 +188,7 @@ def test_project_disabled_users():
 
 def test_screenshot_unicode_serialization():
     p = M.Project.query.get(shortname='test')
-    screenshot_unicode = M.ProjectFile(project_id=p._id, category='screenshot', caption=u"ConSelección", filename=u'ConSelección.jpg')
+    screenshot_unicode = M.ProjectFile(project_id=p._id, category='screenshot', caption="ConSelección", filename='ConSelección.jpg')
     screenshot_ascii = M.ProjectFile(project_id=p._id, category='screenshot', caption='test-screenshot', filename='test_file.jpg')
     ThreadLocalORMSession.flush_all()
 
@@ -196,7 +197,7 @@ def test_screenshot_unicode_serialization():
 
     assert len(screenshots) == 2
     assert screenshots[0]['url'] == 'http://localhost/p/test/screenshot/ConSelecci%C3%B3n.jpg'
-    assert screenshots[0]['caption'] == u"ConSelección"
+    assert screenshots[0]['caption'] == "ConSelección"
     assert screenshots[0]['thumbnail_url'] == 'http://localhost/p/test/screenshot/ConSelecci%C3%B3n.jpg/thumb'
 
     assert screenshots[1]['url'] == 'http://localhost/p/test/screenshot/test_file.jpg'
diff --git a/Allura/allura/tests/model/test_repo.py b/Allura/allura/tests/model/test_repo.py
index 5007d28..4d77e67 100644
--- a/Allura/allura/tests/model/test_repo.py
+++ b/Allura/allura/tests/model/test_repo.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 from datetime import datetime
 from collections import defaultdict, OrderedDict
 
@@ -99,12 +100,12 @@ class RepoTestBase(unittest.TestCase):
         repo = M.repository.Repository(tool='git')
         cmd_cats = repo.clone_command_categories(anon=False)
         assert_equal(cmd_cats, [
-            {'key': 'file', 'name': 'File', 'title': u'Filesystem'}
+            {'key': 'file', 'name': 'File', 'title': 'Filesystem'}
         ])
 
         cmd_cats = repo.clone_command_categories(anon=True)
         assert_equal(cmd_cats, [
-            {'key': 'file', 'name': 'File', 'title': u'Filesystem'}
+            {'key': 'file', 'name': 'File', 'title': 'Filesystem'}
         ])
 
         repo = M.repository.Repository(tool='something-else')  # no "something-else" in config so will use defaults
diff --git a/Allura/allura/tests/model/test_timeline.py b/Allura/allura/tests/model/test_timeline.py
index 6f7ae94..0ca22d5 100644
--- a/Allura/allura/tests/model/test_timeline.py
+++ b/Allura/allura/tests/model/test_timeline.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 from nose.tools import assert_equal
 
 from allura import model as M
diff --git a/Allura/allura/tests/scripts/test_create_sitemap_files.py b/Allura/allura/tests/scripts/test_create_sitemap_files.py
index 6f5f1b9..e8be071 100644
--- a/Allura/allura/tests/scripts/test_create_sitemap_files.py
+++ b/Allura/allura/tests/scripts/test_create_sitemap_files.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import os
 from shutil import rmtree
 import xml.etree.ElementTree as ET
diff --git a/Allura/allura/tests/scripts/test_delete_projects.py b/Allura/allura/tests/scripts/test_delete_projects.py
index c128f8c..cbb4767 100644
--- a/Allura/allura/tests/scripts/test_delete_projects.py
+++ b/Allura/allura/tests/scripts/test_delete_projects.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 from ming.odm import session, Mapper, ThreadLocalODMSession
 from mock import patch
 from tg import app_globals as g
@@ -133,8 +134,8 @@ class TestDeleteProjects(TestController):
         self.proj.add_user(dev, ['Developer'])
         ThreadLocalODMSession.flush_all()
         g.credentials.clear()
-        proj = u'p/{}'.format(self.p_shortname)
-        msg = u'Account disabled because project /{} is deleted. Reason: The Reason'.format(proj)
+        proj = 'p/{}'.format(self.p_shortname)
+        msg = 'Account disabled because project /{} is deleted. Reason: The Reason'.format(proj)
         opts = ['-r', 'The Reason', proj]
         if disable:
             opts.insert(0, '--disable-users')
diff --git a/Allura/allura/tests/scripts/test_reindexes.py b/Allura/allura/tests/scripts/test_reindexes.py
index d6dbeb0..90ede90 100644
--- a/Allura/allura/tests/scripts/test_reindexes.py
+++ b/Allura/allura/tests/scripts/test_reindexes.py
@@ -14,6 +14,7 @@
 #       KIND, either express or implied.  See the License for the
 #       specific language governing permissions and limitations
 #       under the License.
+from __future__ import unicode_literals
 from nose.tools import assert_in, assert_equal
 from testfixtures import LogCapture
 
diff --git a/Allura/allura/tests/templates/jinja_master/test_lib.py b/Allura/allura/tests/templates/jinja_master/test_lib.py
index 9421fe4..8005fbc 100644
--- a/Allura/allura/tests/templates/jinja_master/test_lib.py
+++ b/Allura/allura/tests/templates/jinja_master/test_lib.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 from tg import config, app_globals as g
 from mock import Mock
 from nose.tools import assert_equal
diff --git a/Allura/allura/tests/test_app.py b/Allura/allura/tests/test_app.py
index 3ddbd04..25e39c2 100644
--- a/Allura/allura/tests/test_app.py
+++ b/Allura/allura/tests/test_app.py
@@ -17,6 +17,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 from tg import tmpl_context as c
 import mock
 from ming.base import Object
@@ -138,7 +139,7 @@ def test_handle_artifact_unicode(qg):
 
     a = app.Application(c.project, c.app.config)
 
-    msg = dict(payload=u'foo ƒ†©¥˙¨ˆ', message_id=1, headers={})
+    msg = dict(payload='foo ƒ†©¥˙¨ˆ', message_id=1, headers={})
     a.handle_artifact_message(ticket, msg)
     assert_equal(post.attach.call_args[0][1].getvalue(), 'foo ƒ†©¥˙¨ˆ')
 
diff --git a/Allura/allura/tests/test_commands.py b/Allura/allura/tests/test_commands.py
index 3ad18b6..22925b7 100644
--- a/Allura/allura/tests/test_commands.py
+++ b/Allura/allura/tests/test_commands.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 from nose.tools import assert_raises, assert_in
 from testfixtures import OutputCapture
 
diff --git a/Allura/allura/tests/test_decorators.py b/Allura/allura/tests/test_decorators.py
index 7261711..74777ab 100644
--- a/Allura/allura/tests/test_decorators.py
+++ b/Allura/allura/tests/test_decorators.py
@@ -14,6 +14,7 @@
 #       KIND, either express or implied.  See the License for the
 #       specific language governing permissions and limitations
 #       under the License.
+from __future__ import unicode_literals
 import inspect
 from unittest import TestCase
 from mock import patch
diff --git a/Allura/allura/tests/test_diff.py b/Allura/allura/tests/test_diff.py
index 67c0807..6b61cec 100644
--- a/Allura/allura/tests/test_diff.py
+++ b/Allura/allura/tests/test_diff.py
@@ -16,6 +16,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import unittest
 
 from allura.lib.diff import HtmlSideBySideDiff
@@ -143,4 +144,4 @@ class TestHtmlSideBySideDiff(unittest.TestCase):
         a = ['строка']
         b = ['измененная строка']
         html = self.diff.make_table(a, b, 'file a', 'file b')
-        assert u'строка' in html
+        assert 'строка' in html
diff --git a/Allura/allura/tests/test_dispatch.py b/Allura/allura/tests/test_dispatch.py
index 19f9652..b80cead 100644
--- a/Allura/allura/tests/test_dispatch.py
+++ b/Allura/allura/tests/test_dispatch.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 from allura.tests import TestController
 
 app = None
diff --git a/Allura/allura/tests/test_globals.py b/Allura/allura/tests/test_globals.py
index f7b2be4..6e3a702 100644
--- a/Allura/allura/tests/test_globals.py
+++ b/Allura/allura/tests/test_globals.py
@@ -18,6 +18,7 @@
 #       under the License.
 
 
+from __future__ import unicode_literals
 import re
 import os
 import allura
@@ -172,8 +173,8 @@ def test_macro_gittip_button():
         r = g.markdown_wiki.convert('[[gittip_button username=test]]')
     assert_equal(
         r,
-        u'<div class="markdown_content"><p><iframe height="22pt" src="https://www.gittip.com/test/widget.html" '
-        u'style="border: 0; margin: 0; padding: 0;" width="48pt"></iframe></p>\n</div>')
+        '<div class="markdown_content"><p><iframe height="22pt" src="https://www.gittip.com/test/widget.html" '
+        'style="border: 0; margin: 0; padding: 0;" width="48pt"></iframe></p>\n</div>')
 
 
 def test_macro_neighborhood_feeds():
@@ -231,26 +232,26 @@ def test_macro_members():
 @with_setup(setUp)
 def test_macro_members_escaping():
     user = M.User.by_username('test-admin')
-    user.display_name = u'Test Admin <script>'
+    user.display_name = 'Test Admin <script>'
     r = g.markdown_wiki.convert('[[members]]')
     assert_equal(r.replace('\n', '').replace('\t', ''),
-                 u'<div class="markdown_content"><h6>Project Members:</h6>'
-                 u'<ul class="md-users-list">'
-                 u'<li><a href="/u/test-admin/">Test Admin &lt;script&gt;</a> (admin)</li>'
-                 u'</ul></div>')
+                 '<div class="markdown_content"><h6>Project Members:</h6>'
+                 '<ul class="md-users-list">'
+                 '<li><a href="/u/test-admin/">Test Admin &lt;script&gt;</a> (admin)</li>'
+                 '</ul></div>')
 
 
 @with_setup(setUp)
 def test_macro_project_admins():
     user = M.User.by_username('test-admin')
-    user.display_name = u'Test Ådmin <script>'
+    user.display_name = 'Test Ådmin <script>'
     with h.push_context('test', neighborhood='Projects'):
         r = g.markdown_wiki.convert('[[project_admins]]')
     assert_equal(r.replace('\n', ''),
-                 u'<div class="markdown_content"><h6>Project Admins:</h6>'
-                 u'<ul class="md-users-list">'
-                 u'    <li><a href="/u/test-admin/">Test \xc5dmin &lt;script&gt;</a></li>'
-                 u'</ul></div>')
+                 '<div class="markdown_content"><h6>Project Admins:</h6>'
+                 '<ul class="md-users-list">'
+                 '    <li><a href="/u/test-admin/">Test \xc5dmin &lt;script&gt;</a></li>'
+                 '</ul></div>')
 
 
 @with_setup(setUp)
@@ -618,12 +619,12 @@ def test_hideawards_macro():
 
     app_config_id = ObjectId()
     award = M.Award(app_config_id=app_config_id)
-    award.short = u'Award short'
-    award.full = u'Award full'
+    award.short = 'Award short'
+    award.full = 'Award full'
     award.created_by_neighborhood_id = p_nbhd._id
 
     project = M.Project.query.get(
-        neighborhood_id=p_nbhd._id, shortname=u'test')
+        neighborhood_id=p_nbhd._id, shortname='test')
 
     M.AwardGrant(
         award=award,
@@ -705,8 +706,8 @@ class TestCachedMarkdown(unittest.TestCase):
     def setUp(self):
         self.md = ForgeMarkdown()
         self.post = M.Post()
-        self.post.text = u'**bold**'
-        self.expected_html = u'<p><strong>bold</strong></p>'
+        self.post.text = '**bold**'
+        self.expected_html = '<p><strong>bold</strong></p>'
 
     def test_bad_source_field_name(self):
         self.assertRaises(AttributeError, self.md.cached_convert,
@@ -719,8 +720,8 @@ class TestCachedMarkdown(unittest.TestCase):
 
     @patch.dict('allura.lib.app_globals.config', markdown_cache_threshold='-0.01')
     def test_non_ascii(self):
-        self.post.text = u'å∫ç'
-        expected = u'<p>å∫ç</p>'
+        self.post.text = 'å∫ç'
+        expected = '<p>å∫ç</p>'
         # test with empty cache
         self.assertEqual(expected, self.md.cached_convert(self.post, 'text'))
         # test with primed cache
@@ -738,7 +739,7 @@ class TestCachedMarkdown(unittest.TestCase):
     @patch.dict('allura.lib.app_globals.config', markdown_cache_threshold='-0.01')
     def test_stale_cache(self):
         old = self.md.cached_convert(self.post, 'text')
-        self.post.text = u'new, different source text'
+        self.post.text = 'new, different source text'
         html = self.md.cached_convert(self.post, 'text')
         self.assertNotEqual(old, html)
         self.assertEqual(html, self.post.text_cache.html)
@@ -755,7 +756,7 @@ class TestCachedMarkdown(unittest.TestCase):
             self.assertEqual(html, self.expected_html)
             self.assertIsInstance(html, Markup)
             self.assertFalse(convert_func.called)
-            self.post.text = u"text [[macro]] pass"
+            self.post.text = "text [[macro]] pass"
             html = self.md.cached_convert(self.post, 'text')
             self.assertTrue(convert_func.called)
 
@@ -795,33 +796,33 @@ class TestEmojis(unittest.TestCase):
 
     def test_markdown_emoji_atomic(self):
         output = g.markdown.convert(':+1:')
-        assert u'<p>\U0001F44D</p>' in output
+        assert '<p>\U0001F44D</p>' in output
         output = g.markdown.convert(':Bosnia_&_Herzegovina:')
-        assert u'<p>\U0001F1E7\U0001F1E6</p>' in output
-        output = g.markdown.convert(u':Åland_Islands:') # emoji code with non-asciii charactor
-        assert u'<p>\U0001F1E6\U0001F1FD</p>' in output
+        assert '<p>\U0001F1E7\U0001F1E6</p>' in output
+        output = g.markdown.convert(':Åland_Islands:') # emoji code with non-asciii charactor
+        assert '<p>\U0001F1E6\U0001F1FD</p>' in output
 
     def test_markdown_emoji_with_text(self):
         output = g.markdown.convert('Thumbs up emoji :+1: wow!')
-        assert u'<p>Thumbs up emoji \U0001F44D wow!</p>' in output
-        output = g.markdown.convert(u'More emojis :+1::camel::three_o’clock: wow!')
-        assert u'<p>More emojis \U0001F44D\U0001F42B\U0001F552 wow!</p>' in output
+        assert '<p>Thumbs up emoji \U0001F44D wow!</p>' in output
+        output = g.markdown.convert('More emojis :+1::camel::three_o’clock: wow!')
+        assert '<p>More emojis \U0001F44D\U0001F42B\U0001F552 wow!</p>' in output
         output = g.markdown.convert(':man_bouncing_ball_medium-light_skin_tone:emoji:+1:')
-        assert u'<p>\U000026F9\U0001F3FC\U0000200D\U00002642\U0000FE0Femoji\U0001F44D</p>' in output
+        assert '<p>\U000026F9\U0001F3FC\U0000200D\U00002642\U0000FE0Femoji\U0001F44D</p>' in output
 
     def test_markdown_emoji_in_code(self):
         output = g.markdown.convert('This will not become an emoji `:+1:`')
-        assert_in(u'<p>This will not become an emoji <code>:+1:</code></p>', output)
-        output = g.markdown.convert(u'```html\n<p>:camel:</p>\n```')
-        assert_in(u':camel:', output)
-        output = g.markdown.convert(u'~~~\n:camel:\n~~~')
-        assert_in(u'<span class="p">:</span><span class="n">camel</span><span class="p">:</span>', output)
+        assert_in('<p>This will not become an emoji <code>:+1:</code></p>', output)
+        output = g.markdown.convert('```html\n<p>:camel:</p>\n```')
+        assert_in(':camel:', output)
+        output = g.markdown.convert('~~~\n:camel:\n~~~')
+        assert_in('<span class="p">:</span><span class="n">camel</span><span class="p">:</span>', output)
 
     def test_markdown_commit_with_emojis(self):
         output = g.markdown_commit.convert('Thumbs up emoji :+1: wow!')
-        assert u'Thumbs up emoji \U0001F44D wow!' in output
-        output = g.markdown.convert(u'More emojis :+1::camel::three_o’clock: wow!')
-        assert u'More emojis \U0001F44D\U0001F42B\U0001F552 wow!' in output
+        assert 'Thumbs up emoji \U0001F44D wow!' in output
+        output = g.markdown.convert('More emojis :+1::camel::three_o’clock: wow!')
+        assert 'More emojis \U0001F44D\U0001F42B\U0001F552 wow!' in output
 
 class TestUserMentions(unittest.TestCase):
 
@@ -918,32 +919,32 @@ class TestIconRender(object):
         self.i = g.icons['edit']
 
     def test_default(self):
-        html = u'<a class="icon" href="#" title="Edit"><i class="fa fa-edit"></i></a>'
+        html = '<a class="icon" href="#" title="Edit"><i class="fa fa-edit"></i></a>'
         assert_equal(html, self.i.render())
 
     def test_show_title(self):
-        html = u'<a class="icon" href="#" title="Edit"><i class="fa fa-edit"></i>&nbsp;Edit</a>'
+        html = '<a class="icon" href="#" title="Edit"><i class="fa fa-edit"></i>&nbsp;Edit</a>'
         assert_equal(html, self.i.render(show_title=True))
 
-        html = u'<a class="icon" href="#" title="&lt;script&gt;"><i class="fa fa-edit"></i>&nbsp;&lt;script&gt;</a>'
+        html = '<a class="icon" href="#" title="&lt;script&gt;"><i class="fa fa-edit"></i>&nbsp;&lt;script&gt;</a>'
         assert_equal(html, self.i.render(show_title=True, title="<script>"))
 
     def test_extra_css(self):
-        html = u'<a class="icon reply btn" href="#" title="Edit"><i class="fa fa-edit"></i></a>'
+        html = '<a class="icon reply btn" href="#" title="Edit"><i class="fa fa-edit"></i></a>'
         assert_equal(html, self.i.render(extra_css='reply btn'))
 
     def test_no_closing_tag(self):
-        html = u'<a class="icon" href="#" title="Edit"><i class="fa fa-edit"></i>'
+        html = '<a class="icon" href="#" title="Edit"><i class="fa fa-edit"></i>'
         assert_equal(html, self.i.render(closing_tag=False))
 
     def test_tag(self):
-        html = u'<div class="icon" title="Edit"><i class="fa fa-edit"></i></div>'
+        html = '<div class="icon" title="Edit"><i class="fa fa-edit"></i></div>'
         assert_equal(html, self.i.render(tag='div'))
 
     def test_kwargs(self):
-        html = u'<a class="icon" data-id="123" href="#" title="Edit"><i class="fa fa-edit"></i></a>'
+        html = '<a class="icon" data-id="123" href="#" title="Edit"><i class="fa fa-edit"></i></a>'
         assert_equal(html, self.i.render(**{'data-id': '123'}))
 
     def test_escaping(self):
-        html = u'<a class="icon &#34;" data-url="&gt;" href="#" title="Edit"><i class="fa fa-edit"></i></a>'
+        html = '<a class="icon &#34;" data-url="&gt;" href="#" title="Edit"><i class="fa fa-edit"></i></a>'
         assert_equal(html, self.i.render(extra_css='"', **{'data-url': '>'}))
diff --git a/Allura/allura/tests/test_helpers.py b/Allura/allura/tests/test_helpers.py
index dd3e264..73f8fe4 100644
--- a/Allura/allura/tests/test_helpers.py
+++ b/Allura/allura/tests/test_helpers.py
@@ -17,6 +17,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 from unittest import TestCase
 from os import path
 from datetime import datetime, timedelta
@@ -55,7 +56,7 @@ class TestMakeSafePathPortion(TestCase):
         self.f = h.make_safe_path_portion
 
     def test_no_latin1_chars(self):
-        s = self.f(u'Задачи', relaxed=False)
+        s = self.f('Задачи', relaxed=False)
         self.assertEqual(s, '')
 
     def test_some_latin1_chars(self):
@@ -81,7 +82,7 @@ class TestMakeSafePathPortion(TestCase):
 
 def test_escape_json():
     inputdata = {"foo": "bar</script><img src=foobar onerror=alert(1)>"}
-    outputsample = '{"foo": "bar\u003C/script>\u003Cimg src=foobar onerror=alert(1)>"}'
+    outputsample = '{"foo": "bar\\u003C/script>\\u003Cimg src=foobar onerror=alert(1)>"}'
     outputdata = h.escape_json(inputdata)
     assert_equals(outputdata, outputsample)
 
@@ -89,12 +90,12 @@ def test_escape_json():
 def test_really_unicode():
     here_dir = path.dirname(__file__)
     s = h.really_unicode('\xef\xbb\xbf<?xml version="1.0" encoding="utf-8" ?>')
-    assert s.startswith(u'\ufeff')
+    assert s.startswith('\ufeff')
     s = h.really_unicode(
         open(path.join(here_dir, 'data/unicode_test.txt')).read())
     assert isinstance(s, six.text_type)
     # try non-ascii string in legacy 8bit encoding
-    h.really_unicode(u'\u0410\u0401'.encode('cp1251'))
+    h.really_unicode('\u0410\u0401'.encode('cp1251'))
     # ensure invalid encodings are handled gracefully
     s = h._attempt_encodings('foo', ['LKDJFLDK'])
     assert isinstance(s, six.text_type)
@@ -181,7 +182,7 @@ def test_context_setters():
 
 
 def test_encode_keys():
-    kw = h.encode_keys({u'foo': 5})
+    kw = h.encode_keys({'foo': 5})
     assert type(kw.keys()[0]) != six.text_type
 
 
@@ -201,8 +202,8 @@ def test_ago():
 
 def test_urlquote_unicode():
     # No exceptions please
-    h.urlquote(u'\u0410')
-    h.urlquoteplus(u'\u0410')
+    h.urlquote('\u0410')
+    h.urlquoteplus('\u0410')
 
 
 def test_sharded_path():
@@ -251,8 +252,8 @@ def test_render_any_markup_formatting():
 
 def test_render_any_markdown_encoding():
     # send encoded content in, make sure it converts it to actual unicode object which Markdown lib needs
-    assert_equals(h.render_any_markup('README.md', u'Müller'.encode('utf8')),
-                  u'<div class="markdown_content"><p>Müller</p></div>')
+    assert_equals(h.render_any_markup('README.md', 'Müller'.encode('utf8')),
+                  '<div class="markdown_content"><p>Müller</p></div>')
 
 
 class AuditLogMock(Mock):
@@ -319,7 +320,7 @@ def test_datetimeformat():
 
 def test_nl2br_jinja_filter():
     assert_equals(h.nl2br_jinja_filter('foo<script>alert(1)</script>\nbar\nbaz'),
-            Markup(u'foo&lt;script&gt;alert(1)&lt;/script&gt;<br>\nbar<br>\nbaz'))
+            Markup('foo&lt;script&gt;alert(1)&lt;/script&gt;<br>\nbar<br>\nbaz'))
 
 
 def test_split_select_field_options():
@@ -598,14 +599,14 @@ def test_base64uri_text():
 
 
 def test_slugify():
-    assert_equals(h.slugify(u'Foo Bar Bat')[0], 'Foo-Bar-Bat')
-    assert_equals(h.slugify(u'Foo_Bar')[0], 'Foo_Bar')
-    assert_equals(h.slugify(u'Foo   ')[0], 'Foo')
-    assert_equals(h.slugify(u'    Foo   ')[0], 'Foo')
-    assert_equals(h.slugify(u'"    Foo   ')[0], 'Foo')
-    assert_equals(h.slugify(u'Fôö')[0], 'Foo')
-    assert_equals(h.slugify(u'Foo.Bar')[0], 'Foo-Bar')
-    assert_equals(h.slugify(u'Foo.Bar', True)[0], 'Foo.Bar')
+    assert_equals(h.slugify('Foo Bar Bat')[0], 'Foo-Bar-Bat')
+    assert_equals(h.slugify('Foo_Bar')[0], 'Foo_Bar')
+    assert_equals(h.slugify('Foo   ')[0], 'Foo')
+    assert_equals(h.slugify('    Foo   ')[0], 'Foo')
+    assert_equals(h.slugify('"    Foo   ')[0], 'Foo')
+    assert_equals(h.slugify('Fôö')[0], 'Foo')
+    assert_equals(h.slugify('Foo.Bar')[0], 'Foo-Bar')
+    assert_equals(h.slugify('Foo.Bar', True)[0], 'Foo.Bar')
 
 
 class TestRateLimit(TestCase):
@@ -651,4 +652,4 @@ def test_hide_private_info():
 
 
 def test_emojize():
-    assert_equals(h.emojize(':smile:'), u'😄')
+    assert_equals(h.emojize(':smile:'), '😄')
diff --git a/Allura/allura/tests/test_mail_util.py b/Allura/allura/tests/test_mail_util.py
index deb16e0..eba9b8b 100644
--- a/Allura/allura/tests/test_mail_util.py
+++ b/Allura/allura/tests/test_mail_util.py
@@ -17,6 +17,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import unittest
 from email.MIMEMultipart import MIMEMultipart
 from email.MIMEText import MIMEText
@@ -87,7 +88,7 @@ class TestReactor(unittest.TestCase):
 
     def test_unicode_simple_message(self):
         charset = 'utf-8'
-        msg1 = MIMEText(u'''По оживлённым берегам
+        msg1 = MIMEText('''По оживлённым берегам
 Громады стройные теснятся
 Дворцов и башен; корабли
 Толпой со всех концов земли
@@ -98,11 +99,11 @@ class TestReactor(unittest.TestCase):
         s_msg = msg1.as_string()
         msg2 = parse_message(s_msg)
         assert isinstance(msg2['payload'], six.text_type)
-        assert_in(u'всех', msg2['payload'])
+        assert_in('всех', msg2['payload'])
 
     def test_more_encodings(self):
         # these are unicode strings to reflect behavior after loading 'route_email' tasks from mongo
-        s_msg = u"""Date: Sat, 25 May 2019 09:32:00 +1000
+        s_msg = """Date: Sat, 25 May 2019 09:32:00 +1000
 From: <fo...@bar.com>
 To: <38...@bugs.proj.localhost>
 Subject: bugs
@@ -117,9 +118,9 @@ IGZ0cCx0ZWxuZXQscGluZyBjYW4ndCB3b3JrICEKCgpXaHk/
 """
         msg = parse_message(s_msg)
         assert isinstance(msg['payload'], six.text_type)
-        assert_in(u'The Snap7 application', msg['payload'])
+        assert_in('The Snap7 application', msg['payload'])
 
-        s_msg = u"""Date: Sat, 25 May 2019 09:32:00 +1000
+        s_msg = """Date: Sat, 25 May 2019 09:32:00 +1000
 From: <fo...@bar.com>
 To: <38...@bugs.proj.localhost>
 Subject: bugs
@@ -136,9 +137,9 @@ Content-Transfer-Encoding: 8bit
 """
         msg = parse_message(s_msg)
         assert isinstance(msg['payload'], six.text_type)
-        assert_in(u'• foo', msg['payload'])
+        assert_in('• foo', msg['payload'])
 
-        s_msg = u"""Date: Sat, 25 May 2019 09:32:00 +1000
+        s_msg = """Date: Sat, 25 May 2019 09:32:00 +1000
 From: <fo...@bar.com>
 To: <38...@bugs.proj.localhost>
 Subject: bugs
@@ -149,11 +150,11 @@ programmed or èrogrammed ?
 """
         msg = parse_message(s_msg)
         assert isinstance(msg['payload'], six.text_type)
-        assert_in(u'èrogrammed', msg['payload'])
+        assert_in('èrogrammed', msg['payload'])
 
     def test_more_encodings_multipart(self):
         # these are unicode strings to reflect behavior after loading 'route_email' tasks from mongo
-        s_msg = u"""Date: Sat, 25 May 2019 09:32:00 +1000
+        s_msg = """Date: Sat, 25 May 2019 09:32:00 +1000
 From: <fo...@bar.com>
 To: <38...@bugs.proj.localhost>
 Subject: bugs
@@ -180,19 +181,19 @@ Content-Type: text/html; charset="utf-8"
         msg = parse_message(s_msg)
         assert isinstance(msg['parts'][1]['payload'], six.text_type)
         assert isinstance(msg['parts'][2]['payload'], six.text_type)
-        assert_in(u'• foo', msg['parts'][1]['payload'])
-        assert_in(u'• foo', msg['parts'][2]['payload'])
+        assert_in('• foo', msg['parts'][1]['payload'])
+        assert_in('• foo', msg['parts'][2]['payload'])
 
     def test_unicode_complex_message(self):
         charset = 'utf-8'
-        p1 = MIMEText(u'''По оживлённым берегам
+        p1 = MIMEText('''По оживлённым берегам
 Громады стройные теснятся
 Дворцов и башен; корабли
 Толпой со всех концов земли
 К богатым пристаням стремятся;'''.encode(charset),
                       'plain',
                       charset)
-        p2 = MIMEText(u'''<p>По оживлённым берегам
+        p2 = MIMEText('''<p>По оживлённым берегам
 Громады стройные теснятся
 Дворцов и башен; корабли
 Толпой со всех концов земли
@@ -219,15 +220,15 @@ class TestHeader(object):
         assert_equal(str(our_header), '[asdf2:wiki] Discussion for Home page')
 
     def test_ascii(self):
-        our_header = Header(u'[asdf2:wiki] Discussion for Home page')
+        our_header = Header('[asdf2:wiki] Discussion for Home page')
         assert_equal(str(our_header), '[asdf2:wiki] Discussion for Home page')
 
     def test_utf8(self):
-        our_header = Header(u'теснятся')
+        our_header = Header('теснятся')
         assert_equal(str(our_header), '=?utf-8?b?0YLQtdGB0L3Rj9GC0YHRjw==?=')
 
     def test_name_addr(self):
-        our_header = Header(u'"теснятся"', u'<da...@b.com>')
+        our_header = Header('"теснятся"', '<da...@b.com>')
         assert_equal(str(our_header),
                      '=?utf-8?b?ItGC0LXRgdC90Y/RgtGB0Y8i?= <da...@b.com>')
 
@@ -337,6 +338,6 @@ class TestMailServer(object):
         listen_port = ('0.0.0.0', 8825)
         mailserver = MailServer(listen_port, None)
         mailserver.process_message('127.0.0.1', 'foo@bar.com', ['1234@tickets.test.p.localhost'],
-                                   u'this is the email body with headers and everything Ο'.encode('utf-8'))
+                                   'this is the email body with headers and everything Ο'.encode('utf-8'))
         assert_equal([], log.exception.call_args_list)
         log.info.assert_called_with('Msg passed along')
diff --git a/Allura/allura/tests/test_markdown.py b/Allura/allura/tests/test_markdown.py
index e122a25..10f6a52 100644
--- a/Allura/allura/tests/test_markdown.py
+++ b/Allura/allura/tests/test_markdown.py
@@ -17,6 +17,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import unittest
 import mock
 
diff --git a/Allura/allura/tests/test_middlewares.py b/Allura/allura/tests/test_middlewares.py
index 58dc515..67914b0 100644
--- a/Allura/allura/tests/test_middlewares.py
+++ b/Allura/allura/tests/test_middlewares.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 from mock import MagicMock, patch
 from datadiff.tools import assert_equal
 from nose.tools import assert_not_equal
diff --git a/Allura/allura/tests/test_multifactor.py b/Allura/allura/tests/test_multifactor.py
index ad6dbc2..34bc53f 100644
--- a/Allura/allura/tests/test_multifactor.py
+++ b/Allura/allura/tests/test_multifactor.py
@@ -14,6 +14,7 @@
 #       KIND, either express or implied.  See the License for the
 #       specific language governing permissions and limitations
 #       under the License.
+from __future__ import unicode_literals
 import shutil
 import tempfile
 import textwrap
@@ -95,7 +96,7 @@ class TestTotpService(object):
         time.return_value = self.sample_time
         srv = GenericTotpService()
         totp = srv.Totp(key=self.sample_key)
-        srv.verify(totp, u'283 397', None)
+        srv.verify(totp, '283 397', None)
         srv.verify(totp, b'283397', None)
 
     @patch('allura.lib.multifactor.time')
diff --git a/Allura/allura/tests/test_patches.py b/Allura/allura/tests/test_patches.py
index a1fc9bc..8c19737 100644
--- a/Allura/allura/tests/test_patches.py
+++ b/Allura/allura/tests/test_patches.py
@@ -17,6 +17,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import webob
 from mock import patch
 from nose.tools import (
diff --git a/Allura/allura/tests/test_plugin.py b/Allura/allura/tests/test_plugin.py
index c3ae26d..30d24e2 100644
--- a/Allura/allura/tests/test_plugin.py
+++ b/Allura/allura/tests/test_plugin.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import datetime as dt
 import calendar
 
@@ -94,16 +95,16 @@ class TestProjectRegistrationProviderParseProjectFromUrl(object):
         self.parse = self.provider.project_from_url
 
     def test_empty_url(self):
-        assert_equal((None, u'Empty url'), self.parse(None))
-        assert_equal((None, u'Empty url'), self.parse(''))
-        assert_equal((None, u'Empty url'), self.parse('/'))
+        assert_equal((None, 'Empty url'), self.parse(None))
+        assert_equal((None, 'Empty url'), self.parse(''))
+        assert_equal((None, 'Empty url'), self.parse('/'))
 
     def test_neighborhood_not_found(self):
-        assert_equal((None, u'Neighborhood not found'), self.parse('/nbhd/project'))
+        assert_equal((None, 'Neighborhood not found'), self.parse('/nbhd/project'))
 
     def test_project_not_found(self):
-        assert_equal((None, u'Project not found'), self.parse('/p/project'))
-        assert_equal((None, u'Project not found'), self.parse('project'))
+        assert_equal((None, 'Project not found'), self.parse('/p/project'))
+        assert_equal((None, 'Project not found'), self.parse('project'))
 
     def test_ok_full(self):
         p = M.Project.query.get(shortname='test')
@@ -118,7 +119,7 @@ class TestProjectRegistrationProviderParseProjectFromUrl(object):
         adobe_n = M.Neighborhood.query.get(url_prefix='/adobe/')
         M.Project(shortname='test', neighborhood_id=adobe_n._id)
         ThreadLocalORMSession.flush_all()
-        assert_equal((None, u'Too many matches for project: 2'), self.parse('test'))
+        assert_equal((None, 'Too many matches for project: 2'), self.parse('test'))
 
     def test_only_shortname_ok(self):
         p = M.Project.query.get(shortname='test')
diff --git a/Allura/allura/tests/test_scripttask.py b/Allura/allura/tests/test_scripttask.py
index 6273ee0..58b8888 100644
--- a/Allura/allura/tests/test_scripttask.py
+++ b/Allura/allura/tests/test_scripttask.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import unittest
 import mock
 
diff --git a/Allura/allura/tests/test_security.py b/Allura/allura/tests/test_security.py
index 940388c..b57ee05 100644
--- a/Allura/allura/tests/test_security.py
+++ b/Allura/allura/tests/test_security.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 from tg import tmpl_context as c
 from nose.tools import assert_equal
 
diff --git a/Allura/allura/tests/test_tasks.py b/Allura/allura/tests/test_tasks.py
index 6a03abb..e9c966b 100644
--- a/Allura/allura/tests/test_tasks.py
+++ b/Allura/allura/tests/test_tasks.py
@@ -17,6 +17,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import operator
 import shutil
 import sys
@@ -259,9 +260,9 @@ class TestMailTasks(unittest.TestCase):
             mail_tasks.sendmail(
                 fromaddr=str(c.user._id),
                 destinations=[str(c.user._id)],
-                text=u'This is a test',
+                text='This is a test',
                 reply_to=g.noreply,
-                subject=u'Test subject',
+                subject='Test subject',
                 message_id=h.gen_message_id())
             assert_equal(_client.sendmail.call_count, 1)
             return_path, rcpts, body = _client.sendmail.call_args[0]
@@ -280,11 +281,11 @@ class TestMailTasks(unittest.TestCase):
     def test_send_email_nonascii(self):
         with mock.patch.object(mail_tasks.smtp_client, '_client') as _client:
             mail_tasks.sendmail(
-                fromaddr=u'"По" <fo...@bar.com>',
+                fromaddr='"По" <fo...@bar.com>',
                 destinations=['blah@blah.com'],
-                text=u'Громады стройные теснятся',
+                text='Громады стройные теснятся',
                 reply_to=g.noreply,
-                subject=u'По оживлённым берегам',
+                subject='По оживлённым берегам',
                 message_id=h.gen_message_id())
             assert_equal(_client.sendmail.call_count, 1)
             return_path, rcpts, body = _client.sendmail.call_args[0]
@@ -303,7 +304,7 @@ class TestMailTasks(unittest.TestCase):
             assert_in('Content-Type: text/plain; charset="utf-8"', body)
             assert_in('Content-Transfer-Encoding: base64', body)
             assert_in(
-                b64encode(u'Громады стройные теснятся'.encode('utf-8')), body)
+                b64encode('Громады стройные теснятся'.encode('utf-8')), body)
 
     def test_send_email_with_disabled_user(self):
         c.user = M.User.by_username('test-admin')
@@ -315,9 +316,9 @@ class TestMailTasks(unittest.TestCase):
             mail_tasks.sendmail(
                 fromaddr=str(c.user._id),
                 destinations=[str(destination_user._id)],
-                text=u'This is a test',
+                text='This is a test',
                 reply_to=g.noreply,
-                subject=u'Test subject',
+                subject='Test subject',
                 message_id=h.gen_message_id())
             assert_equal(_client.sendmail.call_count, 1)
             return_path, rcpts, body = _client.sendmail.call_args[0]
@@ -334,9 +335,9 @@ class TestMailTasks(unittest.TestCase):
             mail_tasks.sendmail(
                 fromaddr=str(c.user._id),
                 destinations=[str(destination_user._id)],
-                text=u'This is a test',
+                text='This is a test',
                 reply_to=g.noreply,
-                subject=u'Test subject',
+                subject='Test subject',
                 message_id=h.gen_message_id())
             assert_equal(_client.sendmail.call_count, 0)
 
@@ -346,9 +347,9 @@ class TestMailTasks(unittest.TestCase):
             mail_tasks.sendsimplemail(
                 fromaddr=str(c.user._id),
                 toaddr='test@mail.com',
-                text=u'This is a test',
+                text='This is a test',
                 reply_to=g.noreply,
-                subject=u'Test subject',
+                subject='Test subject',
                 message_id=h.gen_message_id())
             assert_equal(_client.sendmail.call_count, 1)
             return_path, rcpts, body = _client.sendmail.call_args[0]
@@ -360,9 +361,9 @@ class TestMailTasks(unittest.TestCase):
             mail_tasks.sendsimplemail(
                 fromaddr=str(c.user._id),
                 toaddr='test@mail.com',
-                text=u'This is a test',
+                text='This is a test',
                 reply_to=g.noreply,
-                subject=u'Test subject',
+                subject='Test subject',
                 message_id=h.gen_message_id())
             assert_equal(_client.sendmail.call_count, 2)
             return_path, rcpts, body = _client.sendmail.call_args[0]
@@ -375,10 +376,10 @@ class TestMailTasks(unittest.TestCase):
             mail_tasks.sendsimplemail(
                 fromaddr=str(c.user._id),
                 toaddr='test@mail.com',
-                text=u'This is a test',
+                text='This is a test',
                 reply_to=g.noreply,
-                subject=u'Test subject',
-                sender=u'tickets@test.p.domain.net',
+                subject='Test subject',
+                sender='tickets@test.p.domain.net',
                 message_id=h.gen_message_id())
             assert_equal(_client.sendmail.call_count, 1)
             return_path, rcpts, body = _client.sendmail.call_args[0]
@@ -391,10 +392,10 @@ class TestMailTasks(unittest.TestCase):
             mail_tasks.sendmail(
                 fromaddr=str(c.user._id),
                 destinations=[str(c.user._id)],
-                text=u'This is a test',
-                reply_to=u'123@tickets.test.p.domain.net',
-                subject=u'Test subject',
-                sender=u'tickets@test.p.domain.net',
+                text='This is a test',
+                reply_to='123@tickets.test.p.domain.net',
+                subject='Test subject',
+                sender='tickets@test.p.domain.net',
                 message_id=h.gen_message_id())
             assert_equal(_client.sendmail.call_count, 1)
             return_path, rcpts, body = _client.sendmail.call_args[0]
@@ -409,9 +410,9 @@ class TestMailTasks(unittest.TestCase):
             mail_tasks.sendsimplemail(
                 fromaddr=str(c.user._id),
                 toaddr='test@mail.com',
-                text=u'This is a test',
+                text='This is a test',
                 reply_to=g.noreply,
-                subject=u'Test subject',
+                subject='Test subject',
                 references=['a', 'b', 'c'],
                 message_id=h.gen_message_id())
             assert_equal(_client.sendmail.call_count, 1)
@@ -424,10 +425,10 @@ class TestMailTasks(unittest.TestCase):
             mail_tasks.sendmail(
                 fromaddr=str(c.user._id),
                 destinations=[str(c.user._id)],
-                text=u'This is a test',
+                text='This is a test',
                 reply_to=g.noreply,
-                subject=u'Test subject',
-                references=u'ref',
+                subject='Test subject',
+                references='ref',
                 message_id=h.gen_message_id())
             assert_equal(_client.sendmail.call_count, 1)
             return_path, rcpts, body = _client.sendmail.call_args[0]
@@ -441,10 +442,10 @@ class TestMailTasks(unittest.TestCase):
             mail_tasks.sendsimplemail(
                 fromaddr=str(c.user._id),
                 toaddr='test@mail.com',
-                text=u'This is a test',
+                text='This is a test',
                 reply_to=g.noreply,
-                subject=u'Test subject',
-                cc=u'someone@example.com',
+                subject='Test subject',
+                cc='someone@example.com',
                 message_id=h.gen_message_id())
             assert_equal(_client.sendmail.call_count, 1)
             return_path, rcpts, body = _client.sendmail.call_args[0]
@@ -457,9 +458,9 @@ class TestMailTasks(unittest.TestCase):
             mail_tasks.sendsimplemail(
                 fromaddr=c.user._id,
                 toaddr='test@mail.com',
-                text=u'This is a test',
+                text='This is a test',
                 reply_to=g.noreply,
-                subject=u'Test subject',
+                subject='Test subject',
                 message_id=h.gen_message_id())
             assert_equal(_client.sendmail.call_count, 1)
             return_path, rcpts, body = _client.sendmail.call_args[0]
@@ -468,11 +469,11 @@ class TestMailTasks(unittest.TestCase):
     def test_send_email_long_lines_use_quoted_printable(self):
         with mock.patch.object(mail_tasks.smtp_client, '_client') as _client:
             mail_tasks.sendsimplemail(
-                fromaddr=u'"По" <fo...@bar.com>',
+                fromaddr='"По" <fo...@bar.com>',
                 toaddr='blah@blah.com',
-                text=(u'0123456789' * 100) + u'\n\n' + (u'Громады стро ' * 100),
+                text=('0123456789' * 100) + '\n\n' + ('Громады стро ' * 100),
                 reply_to=g.noreply,
-                subject=u'По оживлённым берегам',
+                subject='По оживлённым берегам',
                 message_id=h.gen_message_id())
             return_path, rcpts, body = _client.sendmail.call_args[0]
             body = body.split('\n')
@@ -642,7 +643,7 @@ class TestExportTasks(unittest.TestCase):
     @td.with_wiki
     def test_bulk_export(self, wiki_bulk_export, zipdir, shutil):
         M.MonQTask.query.remove()
-        export_tasks.bulk_export([u'wiki'])
+        export_tasks.bulk_export(['wiki'])
         assert_equal(wiki_bulk_export.call_count, 1)
         temp = '/tmp/bulk_export/p/test/test'
         zipfn = '/tmp/bulk_export/p/test/test.zip'
diff --git a/Allura/allura/tests/test_utils.py b/Allura/allura/tests/test_utils.py
index 12017e2..feaf059 100644
--- a/Allura/allura/tests/test_utils.py
+++ b/Allura/allura/tests/test_utils.py
@@ -17,6 +17,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import json
 import time
 import unittest
@@ -342,7 +343,7 @@ def test_empty_cursor():
 
 
 def test_DateJSONEncoder():
-    data = {'message': u'Hi!',
+    data = {'message': 'Hi!',
             'date': dt.datetime(2015, 01, 30, 13, 13, 13)}
     result = json.dumps(data, cls=utils.DateJSONEncoder)
     assert_equal(result, '{"date": "2015-01-30T13:13:13Z", "message": "Hi!"}')
@@ -373,7 +374,7 @@ class FakeAttachment(object):
         self._id = ObjectId()
         self.filename = filename
     def __repr__(self):
-        return u'%s %s' % (self._id, self.filename)
+        return '%s %s' % (self._id, self.filename)
 
 
 def unique_attachments():
@@ -417,13 +418,13 @@ def test_lsub_utf8():
     assert_equal(b'asdf',
                  utils.lsub_utf8(h.really_unicode('asdf').encode('utf-8'), 40))
     assert_equal(b'as\xf0\x9f\x98\x84\xc2\xb6\xc2\xba\xc2\xb6',
-                 utils.lsub_utf8(h.really_unicode(u'as😄¶º¶').encode('utf-8'), 40))
+                 utils.lsub_utf8(h.really_unicode('as😄¶º¶').encode('utf-8'), 40))
     assert_equal(b'as\xf0\x9f\x98\x84',
-                 utils.lsub_utf8(h.really_unicode(u'as😄¶º¶').encode('utf-8'), 6))
+                 utils.lsub_utf8(h.really_unicode('as😄¶º¶').encode('utf-8'), 6))
     # these would truncate the smiley:
     assert_equal(b'as',
-                 utils.lsub_utf8(h.really_unicode(u'as😄¶º¶').encode('utf-8'), 5))
+                 utils.lsub_utf8(h.really_unicode('as😄¶º¶').encode('utf-8'), 5))
     assert_equal(b'as',
-                 utils.lsub_utf8(h.really_unicode(u'as😄¶º¶').encode('utf-8'), 4))
+                 utils.lsub_utf8(h.really_unicode('as😄¶º¶').encode('utf-8'), 4))
     assert_equal(b'as',
-                 utils.lsub_utf8(h.really_unicode(u'as😄¶º¶').encode('utf-8'), 3))
+                 utils.lsub_utf8(h.really_unicode('as😄¶º¶').encode('utf-8'), 3))
diff --git a/Allura/allura/tests/test_validators.py b/Allura/allura/tests/test_validators.py
index f482efb..3f9d27a 100644
--- a/Allura/allura/tests/test_validators.py
+++ b/Allura/allura/tests/test_validators.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import unittest
 import formencode as fe
 from mock import Mock, patch
diff --git a/Allura/allura/tests/test_webhooks.py b/Allura/allura/tests/test_webhooks.py
index 60853be..a1fad06 100644
--- a/Allura/allura/tests/test_webhooks.py
+++ b/Allura/allura/tests/test_webhooks.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import json
 import hmac
 import hashlib
@@ -99,10 +100,10 @@ class TestValidators(TestWebhookBase):
         v = WebhookValidator(sender=sender, app=app, not_empty=True)
         with assert_raises(Invalid) as cm:
             v.to_python(None)
-        assert_equal(cm.exception.msg, u'Please enter a value')
+        assert_equal(cm.exception.msg, 'Please enter a value')
         with assert_raises(Invalid) as cm:
             v.to_python('invalid id')
-        assert_equal(cm.exception.msg, u'Invalid webhook')
+        assert_equal(cm.exception.msg, 'Invalid webhook')
 
         wh = M.Webhook(type='invalid type',
                        app_config_id=invalid_app.config._id,
@@ -112,14 +113,14 @@ class TestValidators(TestWebhookBase):
         # invalid type
         with assert_raises(Invalid) as cm:
             v.to_python(wh._id)
-        assert_equal(cm.exception.msg, u'Invalid webhook')
+        assert_equal(cm.exception.msg, 'Invalid webhook')
 
         wh.type = 'repo-push'
         session(wh).flush(wh)
         # invalild app
         with assert_raises(Invalid) as cm:
             v.to_python(wh._id)
-        assert_equal(cm.exception.msg, u'Invalid webhook')
+        assert_equal(cm.exception.msg, 'Invalid webhook')
 
         wh.app_config_id = app.config._id
         session(wh).flush(wh)
@@ -198,7 +199,7 @@ class TestWebhookController(TestController):
         r = self.app.get(self.url)
         assert_in('<h1>repo-push</h1>', r)
         assert_not_in('http://httpbin.org/post', r)
-        data = {'url': u'http://httpbin.org/post',
+        data = {'url': 'http://httpbin.org/post',
                 'secret': ''}
         msg = 'add webhook repo-push {} {}'.format(
             data['url'], self.git.config.url())
@@ -224,7 +225,7 @@ class TestWebhookController(TestController):
         assert_equal(M.Webhook.query.find().count(), 0)
         limit = json.dumps({'git': 1})
         with h.push_config(config, **{'webhook.repo_push.max_hooks': limit}):
-            data = {'url': u'http://httpbin.org/post',
+            data = {'url': 'http://httpbin.org/post',
                     'secret': ''}
             r = self.create_webhook(data).follow()
             assert_equal(M.Webhook.query.find().count(), 1)
@@ -264,10 +265,10 @@ class TestWebhookController(TestController):
 
         Maybe something to do with WebhookControllerMeta setup of `validate` decorators?
         """
-        data1 = {'url': u'http://httpbin.org/post',
-                 'secret': u'secret'}
-        data2 = {'url': u'http://example.com/hook',
-                 'secret': u'secret2'}
+        data1 = {'url': 'http://httpbin.org/post',
+                 'secret': 'secret'}
+        data2 = {'url': 'http://example.com/hook',
+                 'secret': 'secret2'}
         self.create_webhook(data1).follow()
         self.create_webhook(data2).follow()
         assert_equal(M.Webhook.query.find().count(), 2)
@@ -299,7 +300,7 @@ class TestWebhookController(TestController):
         form['url'] = data2['url']
         r = form.submit()
         self.find_error(r, '_the_form',
-                        u'"repo-push" webhook already exists for Git http://example.com/hook',
+                        '"repo-push" webhook already exists for Git http://example.com/hook',
                         form_type='edit')
 
     def test_edit_validation(self):
@@ -311,8 +312,8 @@ class TestWebhookController(TestController):
         session(invalid).flush(invalid)
         self.app.get(self.url + '/repo-push/%s' % invalid._id, status=404)
 
-        data = {'url': u'http://httpbin.org/post',
-                'secret': u'secret'}
+        data = {'url': 'http://httpbin.org/post',
+                'secret': 'secret'}
         self.create_webhook(data).follow()
         wh = M.Webhook.query.get(hook_url=data['url'], type='repo-push')
 
@@ -333,8 +334,8 @@ class TestWebhookController(TestController):
                         'You must provide a full domain name (like qwe.com)', 'edit')
 
     def test_delete(self):
-        data = {'url': u'http://httpbin.org/post',
-                'secret': u'secret'}
+        data = {'url': 'http://httpbin.org/post',
+                'secret': 'secret'}
         self.create_webhook(data).follow()
         assert_equal(M.Webhook.query.find().count(), 1)
         wh = M.Webhook.query.get(hook_url=data['url'])
@@ -366,11 +367,11 @@ class TestWebhookController(TestController):
     def test_list_webhooks(self):
         git2 = self.project.app_instance('src2')
         url2 = str(git2.admin_url + 'webhooks')
-        data1 = {'url': u'http://httpbin.org/post',
+        data1 = {'url': 'http://httpbin.org/post',
                  'secret': 'secret'}
-        data2 = {'url': u'http://another-host.org/',
+        data2 = {'url': 'http://another-host.org/',
                  'secret': 'secret2'}
-        data3 = {'url': u'http://another-app.org/',
+        data3 = {'url': 'http://another-app.org/',
                  'secret': 'secret3'}
         self.create_webhook(data1).follow()
         self.create_webhook(data2).follow()
@@ -387,20 +388,20 @@ class TestWebhookController(TestController):
             [{'text': wh1.hook_url},
              {'text': wh1.secret},
              {'href': self.url + '/repo-push/' + str(wh1._id),
-              'text': u'Edit'},
+              'text': 'Edit'},
              {'href': self.url + '/repo-push/delete',
               'data-id': str(wh1._id)}],
             [{'text': wh2.hook_url},
              {'text': wh2.secret},
              {'href': self.url + '/repo-push/' + str(wh2._id),
-              'text': u'Edit'},
+              'text': 'Edit'},
              {'href': self.url + '/repo-push/delete',
               'data-id': str(wh2._id)}],
         ])
         assert_equal(rows, expected_rows)
         # make sure webhooks for another app is not visible
-        assert_not_in(u'http://another-app.org/', r)
-        assert_not_in(u'secret3', r)
+        assert_not_in('http://another-app.org/', r)
+        assert_not_in('secret3', r)
 
     def _format_row(self, row):
         def link(td):
@@ -575,13 +576,13 @@ class TestRepoPushWebhookSender(TestWebhookBase):
         expected_result = {
             'size': 3,
             'commits': [{'id': '1'}, {'id': '2'}, {'id': '3'}],
-            'ref': u'ref',
-            'after': u'1',
-            'before': u'0',
+            'ref': 'ref',
+            'after': '1',
+            'before': '0',
             'repository': {
-                'full_name': u'/adobe/adobe-1/src/',
-                'name': u'Git',
-                'url': u'http://localhost/adobe/adobe-1/src/',
+                'full_name': '/adobe/adobe-1/src/',
+                'name': 'Git',
+                'url': 'http://localhost/adobe/adobe-1/src/',
             },
         }
         assert_equal(result, expected_result)
@@ -660,10 +661,10 @@ class TestModels(TestWebhookBase):
     def test_json(self):
         expected = {
             '_id': six.text_type(self.wh._id),
-            'url': u'http://localhost/rest/adobe/adobe-1/admin'
-                   u'/src/webhooks/repo-push/{}'.format(self.wh._id),
-            'type': u'repo-push',
-            'hook_url': u'http://httpbin.org/post',
+            'url': 'http://localhost/rest/adobe/adobe-1/admin'
+                   '/src/webhooks/repo-push/{}'.format(self.wh._id),
+            'type': 'repo-push',
+            'hook_url': 'http://httpbin.org/post',
             'mod_date': self.wh.mod_date,
         }
         dd.assert_equal(self.wh.__json__(), expected)
@@ -748,17 +749,17 @@ class TestWebhookRestController(TestRestApiBase):
 
         r = self.api_post(self.url + '/repo-push', status=400)
         expected = {
-            u'result': u'error',
-            u'error': {u'url': u'Please enter a value'},
+            'result': 'error',
+            'error': {'url': 'Please enter a value'},
         }
         assert_equal(r.json, expected)
 
         data = {'url': 'qwer', 'secret': 'qwe'}
         r = self.api_post(self.url + '/repo-push', status=400, **data)
         expected = {
-            u'result': u'error',
-            u'error': {
-                u'url': u'You must provide a full domain name (like qwer.com)'
+            'result': 'error',
+            'error': {
+                'url': 'You must provide a full domain name (like qwer.com)'
             },
         }
         assert_equal(r.json, expected)
@@ -766,7 +767,7 @@ class TestWebhookRestController(TestRestApiBase):
 
     def test_create(self):
         assert_equal(M.Webhook.query.find().count(), len(self.webhooks))
-        data = {u'url': u'http://hook.slack.com/abcd'}
+        data = {'url': 'http://hook.slack.com/abcd'}
         limit = json.dumps({'git': 10})
         with h.push_config(config, **{'webhook.repo_push.max_hooks': limit}):
             msg = 'add webhook repo-push {} {}'.format(
@@ -788,37 +789,37 @@ class TestWebhookRestController(TestRestApiBase):
 
     def test_create_duplicates(self):
         assert_equal(M.Webhook.query.find().count(), len(self.webhooks))
-        data = {u'url': self.webhooks[0].hook_url}
+        data = {'url': self.webhooks[0].hook_url}
         limit = json.dumps({'git': 10})
         with h.push_config(config, **{'webhook.repo_push.max_hooks': limit}):
             r = self.api_post(self.url + '/repo-push', status=400, **data)
-        expected = {u'result': u'error',
-                    u'error': u'_the_form: "repo-push" webhook already '
-                              u'exists for Git http://httpbin.org/post/0'}
+        expected = {'result': 'error',
+                    'error': '_the_form: "repo-push" webhook already '
+                              'exists for Git http://httpbin.org/post/0'}
         assert_equal(r.json, expected)
         assert_equal(M.Webhook.query.find().count(), len(self.webhooks))
 
     def test_create_limit_reached(self):
         assert_equal(M.Webhook.query.find().count(), len(self.webhooks))
-        data = {u'url': u'http://hook.slack.com/abcd'}
+        data = {'url': 'http://hook.slack.com/abcd'}
         r = self.api_post(self.url + '/repo-push', status=400, **data)
         expected = {
-            u'result': u'error',
-            u'limits': {u'max': 3, u'used': 3},
-            u'error': u'You have exceeded the maximum number of webhooks '
-                      u'you are allowed to create for this project/app'}
+            'result': 'error',
+            'limits': {'max': 3, 'used': 3},
+            'error': 'You have exceeded the maximum number of webhooks '
+                      'you are allowed to create for this project/app'}
         assert_equal(r.json, expected)
         assert_equal(M.Webhook.query.find().count(), len(self.webhooks))
 
     def test_edit_validation(self):
         webhook = self.webhooks[0]
-        url = u'{}/repo-push/{}'.format(self.url, webhook._id)
+        url = '{}/repo-push/{}'.format(self.url, webhook._id)
         data = {'url': 'qwe', 'secret': 'qwe'}
         r = self.api_post(url, status=400, **data)
         expected = {
-            u'result': u'error',
-            u'error': {
-                u'url': u'You must provide a full domain name (like qwe.com)'
+            'result': 'error',
+            'error': {
+                'url': 'You must provide a full domain name (like qwe.com)'
             },
         }
         assert_equal(r.json, expected)
@@ -870,9 +871,9 @@ class TestWebhookRestController(TestRestApiBase):
         url = '{}/repo-push/{}'.format(self.url, webhook._id)
         data = {'url': 'http://httpbin.org/post/1'}
         r = self.api_post(url, status=400, **data)
-        expected = {u'result': u'error',
-                    u'error': u'_the_form: "repo-push" webhook already '
-                              u'exists for Git http://httpbin.org/post/1'}
+        expected = {'result': 'error',
+                    'error': '_the_form: "repo-push" webhook already '
+                              'exists for Git http://httpbin.org/post/1'}
         assert_equal(r.json, expected)
 
     def test_delete_validation(self):
@@ -887,7 +888,7 @@ class TestWebhookRestController(TestRestApiBase):
             webhook.hook_url, self.git.config.url())
         with td.audits(msg):
             r = self.api_delete(url, status=200)
-        dd.assert_equal(r.json, {u'result': u'ok'})
+        dd.assert_equal(r.json, {'result': 'ok'})
         assert_equal(M.Webhook.query.find().count(), 2)
         assert_equal(M.Webhook.query.get(_id=webhook._id), None)
 
diff --git a/Allura/allura/tests/tscript.py b/Allura/allura/tests/tscript.py
index 6691331..022a710 100644
--- a/Allura/allura/tests/tscript.py
+++ b/Allura/allura/tests/tscript.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import logging
 
 from allura import model as M
diff --git a/Allura/allura/tests/tscript_error.py b/Allura/allura/tests/tscript_error.py
index ddba357..6006b88 100644
--- a/Allura/allura/tests/tscript_error.py
+++ b/Allura/allura/tests/tscript_error.py
@@ -16,4 +16,5 @@
 #       under the License.
 
 '''Script that raises ValueError'''
+from __future__ import unicode_literals
 raise ValueError
diff --git a/Allura/allura/tests/unit/controllers/test_auth.py b/Allura/allura/tests/unit/controllers/test_auth.py
index 8fb8e46..6b83d2d 100644
--- a/Allura/allura/tests/unit/controllers/test_auth.py
+++ b/Allura/allura/tests/unit/controllers/test_auth.py
@@ -16,6 +16,7 @@
 #       under the License.
 
 
+from __future__ import unicode_literals
 from mock import patch
 from allura.controllers.auth import AuthController
 
diff --git a/Allura/allura/tests/unit/controllers/test_discussion_moderation_controller.py b/Allura/allura/tests/unit/controllers/test_discussion_moderation_controller.py
index e6d0353..7141df8 100644
--- a/Allura/allura/tests/unit/controllers/test_discussion_moderation_controller.py
+++ b/Allura/allura/tests/unit/controllers/test_discussion_moderation_controller.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 from nose.tools import assert_equal
 from mock import Mock, patch
 from ming.orm import ThreadLocalORMSession, session
diff --git a/Allura/allura/tests/unit/controllers/test_dispatch_index.py b/Allura/allura/tests/unit/controllers/test_dispatch_index.py
index 20f3eb8..48df41d 100644
--- a/Allura/allura/tests/unit/controllers/test_dispatch_index.py
+++ b/Allura/allura/tests/unit/controllers/test_dispatch_index.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 from mock import Mock
 from allura.controllers.base import DispatchIndex, BaseController
 
diff --git a/Allura/allura/tests/unit/factories.py b/Allura/allura/tests/unit/factories.py
index 6acb54f..6e66e4e 100644
--- a/Allura/allura/tests/unit/factories.py
+++ b/Allura/allura/tests/unit/factories.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 from functools import wraps
 
 from ming.orm import ThreadLocalORMSession
diff --git a/Allura/allura/tests/unit/patches.py b/Allura/allura/tests/unit/patches.py
index eaa6404..700f1e0 100644
--- a/Allura/allura/tests/unit/patches.py
+++ b/Allura/allura/tests/unit/patches.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 from mock import Mock, patch
 from tg import tmpl_context as c
 
diff --git a/Allura/allura/tests/unit/phone/test_nexmo.py b/Allura/allura/tests/unit/phone/test_nexmo.py
index cf0d5cf..9f4a165 100644
--- a/Allura/allura/tests/unit/phone/test_nexmo.py
+++ b/Allura/allura/tests/unit/phone/test_nexmo.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import json
 from mock import patch
 from datadiff.tools import assert_equal
diff --git a/Allura/allura/tests/unit/phone/test_phone_service.py b/Allura/allura/tests/unit/phone/test_phone_service.py
index aa6bd51..7039285 100644
--- a/Allura/allura/tests/unit/phone/test_phone_service.py
+++ b/Allura/allura/tests/unit/phone/test_phone_service.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 from nose.tools import assert_true
 from datadiff.tools import assert_equal
 
diff --git a/Allura/allura/tests/unit/spam/test_akismet.py b/Allura/allura/tests/unit/spam/test_akismet.py
index f089303..b8a0315 100644
--- a/Allura/allura/tests/unit/spam/test_akismet.py
+++ b/Allura/allura/tests/unit/spam/test_akismet.py
@@ -18,6 +18,7 @@
 #       under the License.
 
 
+from __future__ import unicode_literals
 import mock
 import unittest
 import urllib
@@ -56,13 +57,13 @@ class TestAkismet(unittest.TestCase):
                 )
             ),
         })
-        self.fake_user = mock.Mock(display_name=u'Søme User',
+        self.fake_user = mock.Mock(display_name='Søme User',
                                    email_addresses=['user@domain'],
                                    _id=ObjectId())
         self.fake_headers = dict(
             USER_AGENT='some browser',
             REFERER='some url')
-        self.content = u'spåm text'
+        self.content = 'spåm text'
         self.expected_data = dict(
             comment_content=self.content.encode('utf8'),
             comment_type='comment',
@@ -111,7 +112,7 @@ class TestAkismet(unittest.TestCase):
         c.user = None
         self.akismet.check(self.content, user=self.fake_user)
         expected_data = self.expected_data
-        expected_data.update(comment_author=u'Søme User'.encode('utf8'),
+        expected_data.update(comment_author='Søme User'.encode('utf8'),
                              comment_author_email='user@domain')
         self.akismet.service.comment_check.assert_called_once_with(**expected_data)
 
@@ -123,7 +124,7 @@ class TestAkismet(unittest.TestCase):
         c.user = self.fake_user
         self.akismet.check(self.content)
         expected_data = self.expected_data
-        expected_data.update(comment_author=u'Søme User'.encode('utf8'),
+        expected_data.update(comment_author='Søme User'.encode('utf8'),
                              comment_author_email='user@domain')
         self.akismet.service.comment_check.assert_called_once_with(**expected_data)
 
@@ -134,7 +135,7 @@ class TestAkismet(unittest.TestCase):
         self.akismet.submit_spam(self.content)
 
         # no IP addr, UA, etc, since this isn't the original request
-        expected_data = dict(comment_content=u'spåm text'.encode('utf8'),
+        expected_data = dict(comment_content='spåm text'.encode('utf8'),
                              comment_type='comment',
                              user_ip='',
                              user_agent='',
@@ -148,7 +149,7 @@ class TestAkismet(unittest.TestCase):
         self.akismet.submit_ham(self.content)
 
         # no IP addr, UA, etc, since this isn't the original request
-        expected_data = dict(comment_content=u'spåm text'.encode('utf8'),
+        expected_data = dict(comment_content='spåm text'.encode('utf8'),
                              comment_type='comment',
                              user_ip='',
                              user_agent='',
@@ -161,7 +162,7 @@ class TestAkismet(unittest.TestCase):
 
         self.akismet.submit_ham(self.content, artifact=self.fake_artifact)
 
-        expected_data = dict(comment_content=u'spåm text'.encode('utf8'),
+        expected_data = dict(comment_content='spåm text'.encode('utf8'),
                              comment_type='comment',
                              user_ip='33.4.5.66',
                              user_agent='',
diff --git a/Allura/allura/tests/unit/spam/test_spam_filter.py b/Allura/allura/tests/unit/spam/test_spam_filter.py
index 607273d..2770121 100644
--- a/Allura/allura/tests/unit/spam/test_spam_filter.py
+++ b/Allura/allura/tests/unit/spam/test_spam_filter.py
@@ -17,6 +17,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import mock
 import unittest
 
diff --git a/Allura/allura/tests/unit/spam/test_stopforumspam.py b/Allura/allura/tests/unit/spam/test_stopforumspam.py
index 77f0e89..b0d15f8 100644
--- a/Allura/allura/tests/unit/spam/test_stopforumspam.py
+++ b/Allura/allura/tests/unit/spam/test_stopforumspam.py
@@ -16,6 +16,7 @@
 #       KIND, either express or implied.  See the License for the
 #       specific language governing permissions and limitations
 #       under the License.
+from __future__ import unicode_literals
 import tempfile
 
 import mock
@@ -29,7 +30,7 @@ from allura.lib.spam.stopforumspamfilter import StopForumSpamSpamFilter
 class TestStopForumSpam(object):
 
     def setUp(self):
-        self.content = u'spåm text'
+        self.content = 'spåm text'
 
         self.artifact = mock.Mock()
         self.artifact.project_id = ObjectId()
diff --git a/Allura/allura/tests/unit/test_app.py b/Allura/allura/tests/unit/test_app.py
index 667dc09..719f125 100644
--- a/Allura/allura/tests/unit/test_app.py
+++ b/Allura/allura/tests/unit/test_app.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 from unittest import TestCase
 
 from nose.tools import assert_equal
diff --git a/Allura/allura/tests/unit/test_artifact.py b/Allura/allura/tests/unit/test_artifact.py
index 895eb74..1337b38 100644
--- a/Allura/allura/tests/unit/test_artifact.py
+++ b/Allura/allura/tests/unit/test_artifact.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import unittest
 
 from allura import model as M
diff --git a/Allura/allura/tests/unit/test_discuss.py b/Allura/allura/tests/unit/test_discuss.py
index 07c9525..665240f 100644
--- a/Allura/allura/tests/unit/test_discuss.py
+++ b/Allura/allura/tests/unit/test_discuss.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 from nose.tools import assert_false, assert_true
 
 from allura import model as M
diff --git a/Allura/allura/tests/unit/test_helpers/test_ago.py b/Allura/allura/tests/unit/test_helpers/test_ago.py
index 039bb00..f89c6aa 100644
--- a/Allura/allura/tests/unit/test_helpers/test_ago.py
+++ b/Allura/allura/tests/unit/test_helpers/test_ago.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 from datetime import datetime
 
 from mock import patch
diff --git a/Allura/allura/tests/unit/test_helpers/test_set_context.py b/Allura/allura/tests/unit/test_helpers/test_set_context.py
index 89e5977..ba29a32 100644
--- a/Allura/allura/tests/unit/test_helpers/test_set_context.py
+++ b/Allura/allura/tests/unit/test_helpers/test_set_context.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 from nose.tools import assert_raises
 from tg import tmpl_context as c
 from bson import ObjectId
diff --git a/Allura/allura/tests/unit/test_ldap_auth_provider.py b/Allura/allura/tests/unit/test_ldap_auth_provider.py
index c131be4..1500252 100644
--- a/Allura/allura/tests/unit/test_ldap_auth_provider.py
+++ b/Allura/allura/tests/unit/test_ldap_auth_provider.py
@@ -17,6 +17,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import calendar
 import platform
 from datetime import datetime, timedelta
@@ -98,21 +99,21 @@ class TestLdapAuthenticationProvider(object):
         ldap.dn.escape_dn_chars = lambda x: x
         dn = 'uid=%s,ou=people,dc=localdomain' % params['username']
         conn = ldap.initialize.return_value
-        conn.search_s.return_value = [(dn, {'cn': [u'åℒƒ'.encode('utf-8')]})]
+        conn.search_s.return_value = [(dn, {'cn': ['åℒƒ'.encode('utf-8')]})]
 
         self.provider._login()
 
         user = M.User.query.get(username=params['username'])
         assert user
-        assert_equal(user.display_name, u'åℒƒ')
+        assert_equal(user.display_name, 'åℒƒ')
 
     @patch('allura.lib.plugin.modlist')
     @patch('allura.lib.plugin.ldap')
     def test_register_user(self, ldap, modlist):
         user_doc = {
-            'username': u'new-user',
-            'display_name': u'New User',
-            'password': u'new-password',
+            'username': 'new-user',
+            'display_name': 'New User',
+            'password': 'new-password',
         }
         ldap.dn.escape_dn_chars = lambda x: x
         self.provider._encode_password = Mock(return_value='new-password-hash')
diff --git a/Allura/allura/tests/unit/test_mixins.py b/Allura/allura/tests/unit/test_mixins.py
index cdc3aaa..a29d013 100644
--- a/Allura/allura/tests/unit/test_mixins.py
+++ b/Allura/allura/tests/unit/test_mixins.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 from mock import Mock
 from allura.model import VotableArtifact
 
diff --git a/Allura/allura/tests/unit/test_package_path_loader.py b/Allura/allura/tests/unit/test_package_path_loader.py
index 781ec83..2422aab 100644
--- a/Allura/allura/tests/unit/test_package_path_loader.py
+++ b/Allura/allura/tests/unit/test_package_path_loader.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 from unittest import TestCase
 
 import jinja2
diff --git a/Allura/allura/tests/unit/test_post_model.py b/Allura/allura/tests/unit/test_post_model.py
index 2f9a4d3..031168a 100644
--- a/Allura/allura/tests/unit/test_post_model.py
+++ b/Allura/allura/tests/unit/test_post_model.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 from tg import tmpl_context as c
 
 from allura.lib import helpers as h
diff --git a/Allura/allura/tests/unit/test_project.py b/Allura/allura/tests/unit/test_project.py
index 28ba7fa..4eaef6c 100644
--- a/Allura/allura/tests/unit/test_project.py
+++ b/Allura/allura/tests/unit/test_project.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import unittest
 from mock import Mock
 
@@ -40,10 +41,10 @@ class TestProject(unittest.TestCase):
         p.sitemap = Mock(return_value=sitemap_entries)
         entries = p.grouped_navbar_entries()
         expected = [
-            (u'Tickets \u25be', 'proj_url/_list/tickets', 3),
-            (u'wiki', 'wiki url', 0),
-            (u'Discussion \u25be', 'proj_url/_list/discussion', 2),
-            (u'subproject', 'subproject url', 0),
+            ('Tickets \u25be', 'proj_url/_list/tickets', 3),
+            ('wiki', 'wiki url', 0),
+            ('Discussion \u25be', 'proj_url/_list/discussion', 2),
+            ('subproject', 'subproject url', 0),
         ]
         expected_ticket_urls = ['bugs url', 'features url', 'support url']
         actual = [(e.label, e.url, len(e.matching_urls)) for e in entries]
@@ -67,11 +68,11 @@ class TestProject(unittest.TestCase):
         p.tool_data['allura'] = {'grouping_threshold': 2}
         entries = p.grouped_navbar_entries()
         expected = [
-            (u'Tickets \u25be', 'proj_url/_list/tickets', 3),
-            (u'wiki', 'wiki url', 0),
-            (u'discuss', 'discuss url', 0),
-            (u'subproject', 'subproject url', 0),
-            (u'help', 'help url', 0),
+            ('Tickets \u25be', 'proj_url/_list/tickets', 3),
+            ('wiki', 'wiki url', 0),
+            ('discuss', 'discuss url', 0),
+            ('subproject', 'subproject url', 0),
+            ('help', 'help url', 0),
         ]
         expected_ticket_urls = ['bugs url', 'features url', 'support url']
         actual = [(e.label, e.url, len(e.matching_urls)) for e in entries]
diff --git a/Allura/allura/tests/unit/test_repo.py b/Allura/allura/tests/unit/test_repo.py
index 786d413..1e20c59 100644
--- a/Allura/allura/tests/unit/test_repo.py
+++ b/Allura/allura/tests/unit/test_repo.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import datetime
 import unittest
 from mock import patch, Mock, MagicMock, call
diff --git a/Allura/allura/tests/unit/test_session.py b/Allura/allura/tests/unit/test_session.py
index dc9711d..3d8ce08 100644
--- a/Allura/allura/tests/unit/test_session.py
+++ b/Allura/allura/tests/unit/test_session.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import pymongo
 import mock
 
diff --git a/Allura/allura/tests/unit/test_sitemapentry.py b/Allura/allura/tests/unit/test_sitemapentry.py
index bdd6399..af294f5 100644
--- a/Allura/allura/tests/unit/test_sitemapentry.py
+++ b/Allura/allura/tests/unit/test_sitemapentry.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import unittest
 from mock import Mock
 
diff --git a/Allura/allura/tests/unit/test_solr.py b/Allura/allura/tests/unit/test_solr.py
index 5ef3492..e1fdcf0 100644
--- a/Allura/allura/tests/unit/test_solr.py
+++ b/Allura/allura/tests/unit/test_solr.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import unittest
 
 import mock
diff --git a/Allura/allura/version.py b/Allura/allura/version.py
index e880449..10bcc51 100644
--- a/Allura/allura/version.py
+++ b/Allura/allura/version.py
@@ -15,5 +15,6 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 __version_info__ = (0, 1)
 __version__ = '.'.join(map(str, __version_info__))
diff --git a/Allura/allura/webhooks.py b/Allura/allura/webhooks.py
index d07268d..ca46755 100644
--- a/Allura/allura/webhooks.py
+++ b/Allura/allura/webhooks.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import logging
 import json
 import hmac
@@ -66,7 +67,7 @@ class WebhookValidator(fev.FancyValidator):
                 pass
         if wh and wh.type == self.sender.type and wh.app_config_id == self.app.config._id:
             return wh
-        raise Invalid(u'Invalid webhook', value, state)
+        raise Invalid('Invalid webhook', value, state)
 
 
 class WebhookCreateForm(schema.Schema):
@@ -121,7 +122,7 @@ class WebhookController(BaseController, AdminControllerMixin):
             session(wh).flush(wh)
         except DuplicateKeyError:
             session(wh).expunge(wh)
-            msg = u'_the_form: "{}" webhook already exists for {} {}'.format(
+            msg = '_the_form: "{}" webhook already exists for {} {}'.format(
                 wh.type, self.app.config.options.mount_label, url)
             raise Invalid(msg, None, None)
 
@@ -470,16 +471,16 @@ class RepoPushWebhookSender(WebhookSender):
                 # Merge commit will have multiple parents. As far as I can tell
                 # the last one will be the branch head before merge
                 return self._convert_id(parents[-1])
-        return u''
+        return ''
 
     def _after(self, commit_ids):
         if len(commit_ids) > 0:
             return self._convert_id(commit_ids[0])
-        return u''
+        return ''
 
     def _convert_id(self, _id):
         if ':' in _id:
-            _id = u'r' + _id.rsplit(':', 1)[1]
+            _id = 'r' + _id.rsplit(':', 1)[1]
         return _id
 
     def get_payload(self, commit_ids, **kw):
diff --git a/Allura/allura/websetup/__init__.py b/Allura/allura/websetup/__init__.py
index 37b1586..1d5a386 100644
--- a/Allura/allura/websetup/__init__.py
+++ b/Allura/allura/websetup/__init__.py
@@ -19,6 +19,7 @@
 
 """Setup the allura application"""
 
+from __future__ import unicode_literals
 import logging
 
 from allura.config.environment import load_environment
diff --git a/Allura/allura/websetup/bootstrap.py b/Allura/allura/websetup/bootstrap.py
index 14f5660..80bfe16 100644
--- a/Allura/allura/websetup/bootstrap.py
+++ b/Allura/allura/websetup/bootstrap.py
@@ -18,6 +18,7 @@
 #       under the License.
 
 """Setup the allura application"""
+from __future__ import unicode_literals
 import os
 import sys
 import logging
diff --git a/Allura/allura/websetup/schema.py b/Allura/allura/websetup/schema.py
index 86b0f39..3d4df41 100644
--- a/Allura/allura/websetup/schema.py
+++ b/Allura/allura/websetup/schema.py
@@ -19,6 +19,7 @@
 
 """Setup the allura application"""
 
+from __future__ import unicode_literals
 import logging
 
 import activitystream
diff --git a/Allura/docs/conf.py b/Allura/docs/conf.py
index 07c07d0..99ced2c 100644
--- a/Allura/docs/conf.py
+++ b/Allura/docs/conf.py
@@ -37,6 +37,7 @@
 
 # Add any Sphinx extension module names here, as strings. They can be extensions
 # coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
+from __future__ import unicode_literals
 import os
 
 extensions = ['sphinx.ext.autodoc', 'sphinx.ext.intersphinx',
@@ -202,8 +203,8 @@ htmlhelp_basename = 'alluradoc'
 # Grouping the document tree into LaTeX files. List of tuples
 # (source start file, target name, title, author, documentclass [howto/manual]).
 latex_documents = [
-    ('index', 'allura.tex', u'allura Documentation',
-     u'Mark Ramm, Wolf, Rick Copeland, Jonathan Beard', 'manual'),
+    ('index', 'allura.tex', 'allura Documentation',
+     'Mark Ramm, Wolf, Rick Copeland, Jonathan Beard', 'manual'),
 ]
 
 # The name of an image file (relative to this directory) to place at the top of
diff --git a/Allura/ldap-setup.py b/Allura/ldap-setup.py
index 4f58342..b8b3cb7 100644
--- a/Allura/ldap-setup.py
+++ b/Allura/ldap-setup.py
@@ -17,6 +17,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import os
 import shutil
 import string
diff --git a/Allura/ldap-userconfig.py b/Allura/ldap-userconfig.py
index bc36c1f..9f37660 100644
--- a/Allura/ldap-userconfig.py
+++ b/Allura/ldap-userconfig.py
@@ -17,6 +17,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import os
 import sys
 import pwd
diff --git a/Allura/setup.py b/Allura/setup.py
index 47bd1bd..629ff12 100644
--- a/Allura/setup.py
+++ b/Allura/setup.py
@@ -17,6 +17,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 from setuptools import setup, find_packages
 
 exec open('allura/version.py').read()
diff --git a/AlluraTest/alluratest/controller.py b/AlluraTest/alluratest/controller.py
index b9bc9e7..066bf8c 100644
--- a/AlluraTest/alluratest/controller.py
+++ b/AlluraTest/alluratest/controller.py
@@ -16,6 +16,7 @@
 #       under the License.
 
 """Unit and functional test suite for allura."""
+from __future__ import unicode_literals
 import os
 import urllib
 import json
@@ -68,7 +69,7 @@ def get_config_file(config=None, current_pkg=None):
         conf_file = os.path.join(conf_dir, config)
 
     if not os.path.exists(conf_file.split('#')[0]):
-        raise EnvironmentError(u'Cannot find .ini config file {}'.format(conf_file))
+        raise EnvironmentError('Cannot find .ini config file {}'.format(conf_file))
     else:
         return conf_file
 
diff --git a/AlluraTest/alluratest/pylint_checkers.py b/AlluraTest/alluratest/pylint_checkers.py
index fa2766f..7442aeb 100644
--- a/AlluraTest/alluratest/pylint_checkers.py
+++ b/AlluraTest/alluratest/pylint_checkers.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import astroid
 
 from pylint.checkers import BaseChecker, utils
diff --git a/AlluraTest/alluratest/smtp_debug.py b/AlluraTest/alluratest/smtp_debug.py
index 08a0108..3019e9f 100644
--- a/AlluraTest/alluratest/smtp_debug.py
+++ b/AlluraTest/alluratest/smtp_debug.py
@@ -17,6 +17,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 from smtpd import DebuggingServer
 
 
diff --git a/AlluraTest/alluratest/test_syntax.py b/AlluraTest/alluratest/test_syntax.py
index 36fa733..7b06d62 100644
--- a/AlluraTest/alluratest/test_syntax.py
+++ b/AlluraTest/alluratest/test_syntax.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import os.path
 from subprocess import Popen, PIPE
 import sys
diff --git a/AlluraTest/alluratest/validation.py b/AlluraTest/alluratest/validation.py
index 5076cbe..3972cd4 100644
--- a/AlluraTest/alluratest/validation.py
+++ b/AlluraTest/alluratest/validation.py
@@ -20,6 +20,7 @@
 """
 Functions to syntax-validate output content
 """
+from __future__ import unicode_literals
 from os import path
 import os
 import sys
@@ -144,7 +145,7 @@ def validate_html5(html_or_response):
                 sys.stderr.write('WARNING: ' + resp + '\n')
                 break
 
-    resp = resp.replace(u'“', u'"').replace(u'”', u'"').replace(u'–', u'-')
+    resp = resp.replace('“', '"').replace('”', '"').replace('–', '-')
 
     ignored_errors = [
         'Required attributes missing on element "object"',
diff --git a/AlluraTest/setup.py b/AlluraTest/setup.py
index 056c61d..9286f8b 100644
--- a/AlluraTest/setup.py
+++ b/AlluraTest/setup.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 from setuptools import setup, find_packages
 
 setup(name='AlluraTest',
diff --git a/ForgeActivity/forgeactivity/config/resources.py b/ForgeActivity/forgeactivity/config/resources.py
index 5d98bd3..7370f46 100644
--- a/ForgeActivity/forgeactivity/config/resources.py
+++ b/ForgeActivity/forgeactivity/config/resources.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import pkg_resources
 
 
diff --git a/ForgeActivity/forgeactivity/main.py b/ForgeActivity/forgeactivity/main.py
index d66a9b2..32ddf03 100644
--- a/ForgeActivity/forgeactivity/main.py
+++ b/ForgeActivity/forgeactivity/main.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import logging
 import calendar
 from datetime import timedelta
@@ -158,7 +159,7 @@ class ForgeActivityController(BaseController):
             'link': h.absurl(self.app.url),
             'description': 'Recent activity for %s' % (
                 data['followee'].activity_name),
-            'language': u'en',
+            'language': 'en',
         }
         if request.environ['PATH_INFO'].endswith('.atom'):
             feed = FG.Atom1Feed(**d)
@@ -166,7 +167,7 @@ class ForgeActivityController(BaseController):
             feed = FG.Rss201rev2Feed(**d)
         for t in data['timeline']:
             url = h.absurl(t.obj.activity_url.encode('utf-8'))
-            feed.add_item(title=u'%s %s %s%s' % (
+            feed.add_item(title='%s %s %s%s' % (
                                 t.actor.activity_name,
                 t.verb,
                 t.obj.activity_name,
diff --git a/ForgeActivity/forgeactivity/tests/functional/test_rest.py b/ForgeActivity/forgeactivity/tests/functional/test_rest.py
index 1c99d5f..257b8b7 100644
--- a/ForgeActivity/forgeactivity/tests/functional/test_rest.py
+++ b/ForgeActivity/forgeactivity/tests/functional/test_rest.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 from datadiff.tools import assert_equal
 
 from tg import config
diff --git a/ForgeActivity/forgeactivity/tests/functional/test_root.py b/ForgeActivity/forgeactivity/tests/functional/test_root.py
index 89a3528..2f24766 100644
--- a/ForgeActivity/forgeactivity/tests/functional/test_root.py
+++ b/ForgeActivity/forgeactivity/tests/functional/test_root.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 from textwrap import dedent
 
 from mock import patch
diff --git a/ForgeActivity/forgeactivity/widgets/follow.py b/ForgeActivity/forgeactivity/widgets/follow.py
index 97e2f97..907c125 100644
--- a/ForgeActivity/forgeactivity/widgets/follow.py
+++ b/ForgeActivity/forgeactivity/widgets/follow.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 from tg import tmpl_context as c
 from formencode import validators as fev
 import ew as ew_core
@@ -50,7 +51,7 @@ class FollowToggle(ew.SimpleForm):
 
     def success_message(self, following):
         context = self.prepare_context({})
-        return u'You are {state} {action}ing {thing}.'.format(
+        return 'You are {state} {action}ing {thing}.'.format(
             state='now' if following else 'no longer',
             action=context['action_label'],
             thing=context['thing'],
diff --git a/ForgeActivity/setup.py b/ForgeActivity/setup.py
index 9795dfb..82db329 100644
--- a/ForgeActivity/setup.py
+++ b/ForgeActivity/setup.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 from setuptools import setup, find_packages
 
 setup(name='ForgeActivity',
diff --git a/ForgeBlog/forgeblog/command/base.py b/ForgeBlog/forgeblog/command/base.py
index 8e67cc9..4bcc0fa 100644
--- a/ForgeBlog/forgeblog/command/base.py
+++ b/ForgeBlog/forgeblog/command/base.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 from allura.command.base import Command
 
 
diff --git a/ForgeBlog/forgeblog/command/rssfeeds.py b/ForgeBlog/forgeblog/command/rssfeeds.py
index 5ae5fee..49cb12b 100644
--- a/ForgeBlog/forgeblog/command/rssfeeds.py
+++ b/ForgeBlog/forgeblog/command/rssfeeds.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import calendar
 from datetime import datetime
 
@@ -118,7 +119,7 @@ class RssFeedsCommand(base.BlogCommand):
         parsed_content = filter(
             None, e.get('content') or [e.get('summary_detail')])
         if parsed_content:
-            content = u''
+            content = ''
             for ct in parsed_content:
                 if ct.type != 'text/html':
                     content += plain2markdown(ct.value)
@@ -132,7 +133,7 @@ class RssFeedsCommand(base.BlogCommand):
                                              getattr(e, 'subtitle',
                                                      getattr(e, 'title'))))
 
-        content += u' [link](%s)' % e.link
+        content += ' [link](%s)' % e.link
         updated = datetime.utcfromtimestamp(calendar.timegm(e.updated_parsed))
 
         base_slug = BM.BlogPost.make_base_slug(title, updated)
diff --git a/ForgeBlog/forgeblog/main.py b/ForgeBlog/forgeblog/main.py
index d867c1b..30b85c2 100644
--- a/ForgeBlog/forgeblog/main.py
+++ b/ForgeBlog/forgeblog/main.py
@@ -16,6 +16,7 @@
 #       under the License.
 
 #-*- python -*-
+from __future__ import unicode_literals
 import logging
 import urllib2
 import json
@@ -313,7 +314,7 @@ class RootController(BaseController, FeedController):
         self.rate_limit(BM.BlogPost, 'Create/edit', c.app.config.url())
         attachment = kw.pop('attachment', None)
         post = BM.BlogPost.new(**kw)
-        g.spam_checker.check(kw['title'] + u'\n' + kw['text'], artifact=post,
+        g.spam_checker.check(kw['title'] + '\n' + kw['text'], artifact=post,
                              user=c.user, content_type='blog-post')
         if attachment is not None:
             post.add_multiple_attachments(attachment)
@@ -427,7 +428,7 @@ class PostController(BaseController, FeedController):
             flash('Post deleted', 'info')
             redirect(h.really_unicode(c.app.url).encode('utf-8'))
         else:
-            g.spam_checker.check(kw['title'] + u'\n' + kw['text'], artifact=self.post,
+            g.spam_checker.check(kw['title'] + '\n' + kw['text'], artifact=self.post,
                                  user=c.user, content_type='blog-post')
         attachment = kw.pop('attachment', None)
         old_text = self.post.text
diff --git a/ForgeBlog/forgeblog/model/blog.py b/ForgeBlog/forgeblog/model/blog.py
index 5fafd43..e2c536d 100644
--- a/ForgeBlog/forgeblog/model/blog.py
+++ b/ForgeBlog/forgeblog/model/blog.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import difflib
 import functools
 import re
diff --git a/ForgeBlog/forgeblog/tests/functional/test_feeds.py b/ForgeBlog/forgeblog/tests/functional/test_feeds.py
index 39a090c..a90fa68 100644
--- a/ForgeBlog/forgeblog/tests/functional/test_feeds.py
+++ b/ForgeBlog/forgeblog/tests/functional/test_feeds.py
@@ -16,6 +16,7 @@
 #       under the License.
 
 
+from __future__ import unicode_literals
 import datetime
 
 from nose.tools import assert_in, assert_not_in
diff --git a/ForgeBlog/forgeblog/tests/functional/test_rest.py b/ForgeBlog/forgeblog/tests/functional/test_rest.py
index 157b387..a2630e7 100644
--- a/ForgeBlog/forgeblog/tests/functional/test_rest.py
+++ b/ForgeBlog/forgeblog/tests/functional/test_rest.py
@@ -16,6 +16,7 @@
 #       KIND, either express or implied.  See the License for the
 #       specific language governing permissions and limitations
 #       under the License.
+from __future__ import unicode_literals
 from datetime import date
 
 import tg
diff --git a/ForgeBlog/forgeblog/tests/functional/test_root.py b/ForgeBlog/forgeblog/tests/functional/test_root.py
index 310f690..9d112c5 100644
--- a/ForgeBlog/forgeblog/tests/functional/test_root.py
+++ b/ForgeBlog/forgeblog/tests/functional/test_root.py
@@ -17,6 +17,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import datetime
 import json
 
diff --git a/ForgeBlog/forgeblog/tests/test_app.py b/ForgeBlog/forgeblog/tests/test_app.py
index 24e6bfa..d03460c 100644
--- a/ForgeBlog/forgeblog/tests/test_app.py
+++ b/ForgeBlog/forgeblog/tests/test_app.py
@@ -17,6 +17,7 @@
 
 #-*- python -*-
 
+from __future__ import unicode_literals
 import tempfile
 import json
 import os
diff --git a/ForgeBlog/forgeblog/tests/test_commands.py b/ForgeBlog/forgeblog/tests/test_commands.py
index 87f9c2b..b7531ec 100644
--- a/ForgeBlog/forgeblog/tests/test_commands.py
+++ b/ForgeBlog/forgeblog/tests/test_commands.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 from datetime import datetime, timedelta
 from tg import app_globals as g
 from datadiff.tools import assert_equal
@@ -103,12 +104,12 @@ def test_pull_rss_feeds(parsefeed):
 
     base_app = M.AppConfig.query.find().all()[0]
     tmp_app = M.AppConfig(
-        tool_name=u'Blog', discussion_id=base_app.discussion_id,
+        tool_name='Blog', discussion_id=base_app.discussion_id,
         project_id=base_app.project_id,
-        options={u'ordinal': 0, u'show_right_bar': True,
-                 u'project_name': base_app.project.name,
-                 u'mount_point': u'blog',
-                 u'mount_label': u'Blog'})
+        options={'ordinal': 0, 'show_right_bar': True,
+                 'project_name': base_app.project.name,
+                 'mount_point': 'blog',
+                 'mount_label': 'Blog'})
     new_external_feeds = ['http://example.com/news/feed/']
     BM.Globals(app_config_id=tmp_app._id, external_feeds=new_external_feeds)
     ThreadLocalORMSession.flush_all()
diff --git a/ForgeBlog/forgeblog/tests/test_roles.py b/ForgeBlog/forgeblog/tests/test_roles.py
index 3fe6a64..13b82e7 100644
--- a/ForgeBlog/forgeblog/tests/test_roles.py
+++ b/ForgeBlog/forgeblog/tests/test_roles.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 from tg import tmpl_context as c, app_globals as g
 
 from alluratest.controller import setup_basic_test, setup_global_objects
diff --git a/ForgeBlog/forgeblog/tests/unit/__init__.py b/ForgeBlog/forgeblog/tests/unit/__init__.py
index 565758a..b42e51b 100644
--- a/ForgeBlog/forgeblog/tests/unit/__init__.py
+++ b/ForgeBlog/forgeblog/tests/unit/__init__.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 from tg import tmpl_context as c
 from ming.orm.ormsession import ThreadLocalORMSession
 
diff --git a/ForgeBlog/forgeblog/tests/unit/test_blog_post.py b/ForgeBlog/forgeblog/tests/unit/test_blog_post.py
index b62447e..69e3a6d 100644
--- a/ForgeBlog/forgeblog/tests/unit/test_blog_post.py
+++ b/ForgeBlog/forgeblog/tests/unit/test_blog_post.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 from datetime import datetime
 from nose.tools import assert_equal, assert_true
 from tg import tmpl_context as c
diff --git a/ForgeBlog/forgeblog/version.py b/ForgeBlog/forgeblog/version.py
index 1b493f8..192acec 100644
--- a/ForgeBlog/forgeblog/version.py
+++ b/ForgeBlog/forgeblog/version.py
@@ -15,5 +15,6 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 __version_info__ = (0, 0)
 __version__ = '.'.join(map(str, __version_info__))
diff --git a/ForgeBlog/forgeblog/widgets.py b/ForgeBlog/forgeblog/widgets.py
index 0110b40..1255553 100644
--- a/ForgeBlog/forgeblog/widgets.py
+++ b/ForgeBlog/forgeblog/widgets.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import ew as ew_core
 import ew.jinja2_ew as ew
 
diff --git a/ForgeBlog/setup.py b/ForgeBlog/setup.py
index 7eda917..df75380 100644
--- a/ForgeBlog/setup.py
+++ b/ForgeBlog/setup.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 from setuptools import setup, find_packages
 
 from forgeblog.version import __version__
diff --git a/ForgeChat/forgechat/command.py b/ForgeChat/forgechat/command.py
index da2faf8..b82cc05 100644
--- a/ForgeChat/forgechat/command.py
+++ b/ForgeChat/forgechat/command.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import time
 import logging
 import socket
diff --git a/ForgeChat/forgechat/main.py b/ForgeChat/forgechat/main.py
index 981af26..3b65b68 100644
--- a/ForgeChat/forgechat/main.py
+++ b/ForgeChat/forgechat/main.py
@@ -18,6 +18,7 @@
 '''IRC Chatbot Plugin
 '''
 #-*- python -*-
+from __future__ import unicode_literals
 import logging
 from datetime import date, time, datetime, timedelta
 
diff --git a/ForgeChat/forgechat/model/chat.py b/ForgeChat/forgechat/model/chat.py
index 3907101..7513f8e 100644
--- a/ForgeChat/forgechat/model/chat.py
+++ b/ForgeChat/forgechat/model/chat.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 from datetime import datetime
 
 from ming import schema as S
diff --git a/ForgeChat/forgechat/tests/functional/test_root.py b/ForgeChat/forgechat/tests/functional/test_root.py
index 96426c1..1ea95af 100644
--- a/ForgeChat/forgechat/tests/functional/test_root.py
+++ b/ForgeChat/forgechat/tests/functional/test_root.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import json
 from datadiff.tools import assert_equal
 
diff --git a/ForgeChat/forgechat/version.py b/ForgeChat/forgechat/version.py
index 1b493f8..192acec 100644
--- a/ForgeChat/forgechat/version.py
+++ b/ForgeChat/forgechat/version.py
@@ -15,5 +15,6 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 __version_info__ = (0, 0)
 __version__ = '.'.join(map(str, __version_info__))
diff --git a/ForgeChat/setup.py b/ForgeChat/setup.py
index 242841b..b326d14 100644
--- a/ForgeChat/setup.py
+++ b/ForgeChat/setup.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 from setuptools import setup, find_packages
 
 from forgechat.version import __version__
diff --git a/ForgeDiscussion/forgediscussion/controllers/forum.py b/ForgeDiscussion/forgediscussion/controllers/forum.py
index 8fc2d6b..c427d68 100644
--- a/ForgeDiscussion/forgediscussion/controllers/forum.py
+++ b/ForgeDiscussion/forgediscussion/controllers/forum.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import logging
 import re
 
diff --git a/ForgeDiscussion/forgediscussion/controllers/root.py b/ForgeDiscussion/forgediscussion/controllers/root.py
index b5a459a..0b7d940 100644
--- a/ForgeDiscussion/forgediscussion/controllers/root.py
+++ b/ForgeDiscussion/forgediscussion/controllers/root.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import json
 import logging
 from urllib import unquote
diff --git a/ForgeDiscussion/forgediscussion/forum_main.py b/ForgeDiscussion/forgediscussion/forum_main.py
index 804d349..c7b3d4d 100644
--- a/ForgeDiscussion/forgediscussion/forum_main.py
+++ b/ForgeDiscussion/forgediscussion/forum_main.py
@@ -16,6 +16,7 @@
 #       under the License.
 
 #-*- python -*-
+from __future__ import unicode_literals
 import logging
 import urllib
 import json
diff --git a/ForgeDiscussion/forgediscussion/import_support.py b/ForgeDiscussion/forgediscussion/import_support.py
index a5a3cc0..d31b3a1 100644
--- a/ForgeDiscussion/forgediscussion/import_support.py
+++ b/ForgeDiscussion/forgediscussion/import_support.py
@@ -16,6 +16,7 @@
 #       under the License.
 
 #-*- python -*-
+from __future__ import unicode_literals
 import logging
 from datetime import datetime
 
diff --git a/ForgeDiscussion/forgediscussion/model/forum.py b/ForgeDiscussion/forgediscussion/model/forum.py
index d65e7e2..e4e1b98 100644
--- a/ForgeDiscussion/forgediscussion/model/forum.py
+++ b/ForgeDiscussion/forgediscussion/model/forum.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import re
 import logging
 from itertools import chain
diff --git a/ForgeDiscussion/forgediscussion/site_stats.py b/ForgeDiscussion/forgediscussion/site_stats.py
index cabb5dd..53fef07 100644
--- a/ForgeDiscussion/forgediscussion/site_stats.py
+++ b/ForgeDiscussion/forgediscussion/site_stats.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 from datetime import datetime, timedelta
 
 from . import model as DM
diff --git a/ForgeDiscussion/forgediscussion/tasks.py b/ForgeDiscussion/forgediscussion/tasks.py
index cfd5320..265a0db 100644
--- a/ForgeDiscussion/forgediscussion/tasks.py
+++ b/ForgeDiscussion/forgediscussion/tasks.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import logging
 
 from tg import tmpl_context as c
diff --git a/ForgeDiscussion/forgediscussion/tests/functional/test_forum.py b/ForgeDiscussion/forgediscussion/tests/functional/test_forum.py
index 262d3a0..ff736b1 100644
--- a/ForgeDiscussion/forgediscussion/tests/functional/test_forum.py
+++ b/ForgeDiscussion/forgediscussion/tests/functional/test_forum.py
@@ -17,6 +17,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import mock
 import random
 import logging
@@ -352,11 +353,11 @@ class TestForum(TestController):
     def test_unicode_name(self):
         r = self.app.get('/admin/discussion/forums')
         form = r.forms['add-forum']
-        form['add_forum.shortname'] = u'téstforum'.encode('utf-8')
-        form['add_forum.name'] = u'Tést Forum'.encode('utf-8')
+        form['add_forum.shortname'] = 'téstforum'.encode('utf-8')
+        form['add_forum.name'] = 'Tést Forum'.encode('utf-8')
         form.submit()
         r = self.app.get('/admin/discussion/forums')
-        assert u'téstforum'.encode('utf-8') in r
+        assert 'téstforum'.encode('utf-8') in r
 
     def test_markdown_description(self):
         r = self.app.get('/admin/discussion/forums')
@@ -467,7 +468,7 @@ class TestForum(TestController):
         r = self.app.post('/discussion/testforum/moderate/save_moderation_bulk_user', params={
             'username': 'test-admin',
             'spam': '1'})
-        assert_in(u'5 posts marked as spam', self.webflash(r))
+        assert_in('5 posts marked as spam', self.webflash(r))
         assert_equal(5, FM.ForumPost.query.find({'status': 'spam'}).count())
 
     def test_posting(self):
@@ -979,13 +980,13 @@ class TestForum(TestController):
     def test_create_topic_unicode(self):
         r = self.app.get('/admin/discussion/forums')
         form = r.forms['add-forum']
-        form['add_forum.shortname'] = u'téstforum'.encode('utf-8')
-        form['add_forum.name'] = u'Tést Forum'.encode('utf-8')
+        form['add_forum.shortname'] = 'téstforum'.encode('utf-8')
+        form['add_forum.name'] = 'Tést Forum'.encode('utf-8')
         form.submit()
         r = self.app.get('/admin/discussion/forums')
-        assert u'téstforum'.encode('utf-8') in r
-        r = self.app.get(u'/p/test/discussion/create_topic/téstforum/'.encode('utf-8'))
-        assert u'<option value="téstforum" selected>Tést Forum</option>' in r
+        assert 'téstforum'.encode('utf-8') in r
+        r = self.app.get('/p/test/discussion/create_topic/téstforum/'.encode('utf-8'))
+        assert '<option value="téstforum" selected>Tést Forum</option>' in r
 
     def test_create_topic_attachment(self):
         r = self.app.get('/discussion/create_topic/')
diff --git a/ForgeDiscussion/forgediscussion/tests/functional/test_forum_admin.py b/ForgeDiscussion/forgediscussion/tests/functional/test_forum_admin.py
index 2d84ada..1b9e54d 100644
--- a/ForgeDiscussion/forgediscussion/tests/functional/test_forum_admin.py
+++ b/ForgeDiscussion/forgediscussion/tests/functional/test_forum_admin.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import os
 import allura
 from StringIO import StringIO
diff --git a/ForgeDiscussion/forgediscussion/tests/functional/test_import.py b/ForgeDiscussion/forgediscussion/tests/functional/test_import.py
index 879b21c..5167448 100644
--- a/ForgeDiscussion/forgediscussion/tests/functional/test_import.py
+++ b/ForgeDiscussion/forgediscussion/tests/functional/test_import.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import os
 import json
 from datetime import datetime, timedelta
diff --git a/ForgeDiscussion/forgediscussion/tests/functional/test_rest.py b/ForgeDiscussion/forgediscussion/tests/functional/test_rest.py
index 97995d5..7b6a9bc 100644
--- a/ForgeDiscussion/forgediscussion/tests/functional/test_rest.py
+++ b/ForgeDiscussion/forgediscussion/tests/functional/test_rest.py
@@ -17,6 +17,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 from nose.tools import assert_equal, assert_in
 
 from allura.lib import helpers as h
@@ -79,8 +80,8 @@ class TestRootRestController(TestDiscussionApiBase):
         assert_equal(forums[0]['last_post']['subject'], 'Hi guys')
         assert_equal(forums[0]['last_post']['author'], 'test-admin')
         assert_equal(forums[0]['last_post']['text'], 'Hi boys and girls')
-        assert_equal(forums[1]['name'], u'Say Héllo')
-        assert_equal(forums[1]['description'], u'Say héllo here')
+        assert_equal(forums[1]['name'], 'Say Héllo')
+        assert_equal(forums[1]['description'], 'Say héllo here')
         assert_equal(forums[1]['num_topics'], 0)
         assert_equal(
             forums[1]['url'], 'http://localhost/rest/p/test/discussion/h%C3%A9llo/')
@@ -120,7 +121,7 @@ class TestRootRestController(TestDiscussionApiBase):
         self.create_topic('general', 'Hi again', 'It should not be shown')
         t = ForumThread.query.find({'subject': 'Hi again'}).first()
         first_post = t.first_post
-        first_post.status = u'pending'
+        first_post.status = 'pending'
         first_post.commit()
         forum = self.api_get('/rest/p/test/discussion/general/')
         forum = forum.json['forum']
@@ -155,7 +156,7 @@ class TestRootRestController(TestDiscussionApiBase):
         resp = self.app.get('/rest/p/test/discussion/?limit=1&page=1')
         forums = resp.json['forums']
         assert_equal(len(forums), 1)
-        assert_equal(forums[0]['name'], u'Say Héllo')
+        assert_equal(forums[0]['name'], 'Say Héllo')
         assert_equal(resp.json['count'], 2)
         assert_equal(resp.json['page'], 1)
         assert_equal(resp.json['limit'], 1)
@@ -236,7 +237,7 @@ class TestRootRestController(TestDiscussionApiBase):
     def test_private_forums(self):
         r = self.app.get('/p/test/admin/discussion/forums')
         form = r.forms['edit-forums']
-        if form['forum-0.shortname'].value == u'héllo':
+        if form['forum-0.shortname'].value == 'héllo':
             form['forum-0.members_only'] = True
         else:
             form['forum-1.members_only'] = True
diff --git a/ForgeDiscussion/forgediscussion/tests/test_app.py b/ForgeDiscussion/forgediscussion/tests/test_app.py
index e980fc1..d7493d1 100644
--- a/ForgeDiscussion/forgediscussion/tests/test_app.py
+++ b/ForgeDiscussion/forgediscussion/tests/test_app.py
@@ -19,6 +19,7 @@
 
 #-*- python -*-
 
+from __future__ import unicode_literals
 import tempfile
 import json
 import os
@@ -68,22 +69,22 @@ class TestBulkExport(TestDiscussionApiBase):
         discussion = json.loads(f.read())
         forums = sorted(discussion['forums'], key=lambda x: x['name'])
 
-        assert_equal(forums[0]['shortname'], u'general')
+        assert_equal(forums[0]['shortname'], 'general')
         assert_equal(
-            forums[0]['description'], u'Forum about anything you want to talk about.')
-        assert_equal(forums[0]['name'], u'General Discussion')
+            forums[0]['description'], 'Forum about anything you want to talk about.')
+        assert_equal(forums[0]['name'], 'General Discussion')
         forums[0]['threads'] = sorted(forums[0]['threads'],
                                       key=lambda x: x['posts'][0]['subject'])
         assert_equal(
-            forums[0]['threads'][0]['posts'][0]['text'], u'Hi boys and girls')
+            forums[0]['threads'][0]['posts'][0]['text'], 'Hi boys and girls')
         assert_equal(
-            forums[0]['threads'][0]['posts'][0]['subject'], u'Hi guys')
-        assert_equal(forums[0]['threads'][1]['posts'][0]['text'], u'1st post')
+            forums[0]['threads'][0]['posts'][0]['subject'], 'Hi guys')
+        assert_equal(forums[0]['threads'][1]['posts'][0]['text'], '1st post')
         assert_equal(
-            forums[0]['threads'][1]['posts'][0]['subject'], u"Let's talk")
-        assert_equal(forums[1]['shortname'], u'héllo')
-        assert_equal(forums[1]['description'], u'Say héllo here')
-        assert_equal(forums[1]['name'], u'Say Héllo')
+            forums[0]['threads'][1]['posts'][0]['subject'], "Let's talk")
+        assert_equal(forums[1]['shortname'], 'héllo')
+        assert_equal(forums[1]['description'], 'Say héllo here')
+        assert_equal(forums[1]['name'], 'Say Héllo')
 
     def test_export_with_attachments(self):
         project = M.Project.query.get(shortname='test')
diff --git a/ForgeDiscussion/forgediscussion/tests/test_forum_roles.py b/ForgeDiscussion/forgediscussion/tests/test_forum_roles.py
index 4bc0eef..7e688f1 100644
--- a/ForgeDiscussion/forgediscussion/tests/test_forum_roles.py
+++ b/ForgeDiscussion/forgediscussion/tests/test_forum_roles.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 from tg import tmpl_context as c
 
 from alluratest.controller import setup_basic_test, setup_global_objects
diff --git a/ForgeDiscussion/forgediscussion/utils.py b/ForgeDiscussion/forgediscussion/utils.py
index 1169caf..76cd473 100644
--- a/ForgeDiscussion/forgediscussion/utils.py
+++ b/ForgeDiscussion/forgediscussion/utils.py
@@ -17,6 +17,7 @@
 
 """ ForgeDiscussion utilities. """
 
+from __future__ import unicode_literals
 from bson import ObjectId
 from tg import flash
 from allura.lib import helpers as h
diff --git a/ForgeDiscussion/forgediscussion/version.py b/ForgeDiscussion/forgediscussion/version.py
index 1b493f8..192acec 100644
--- a/ForgeDiscussion/forgediscussion/version.py
+++ b/ForgeDiscussion/forgediscussion/version.py
@@ -15,5 +15,6 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 __version_info__ = (0, 0)
 __version__ = '.'.join(map(str, __version_info__))
diff --git a/ForgeDiscussion/forgediscussion/widgets/__init__.py b/ForgeDiscussion/forgediscussion/widgets/__init__.py
index 02bbb9e..cbe2c73 100644
--- a/ForgeDiscussion/forgediscussion/widgets/__init__.py
+++ b/ForgeDiscussion/forgediscussion/widgets/__init__.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 from forum_widgets import ThreadSubscriptionForm, AnnouncementsTable
 from forum_widgets import ModerateThread, ForumHeader, ThreadHeader
 from forum_widgets import Post, Thread, Forum
diff --git a/ForgeDiscussion/forgediscussion/widgets/admin.py b/ForgeDiscussion/forgediscussion/widgets/admin.py
index 7de847e..af1d079 100644
--- a/ForgeDiscussion/forgediscussion/widgets/admin.py
+++ b/ForgeDiscussion/forgediscussion/widgets/admin.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 from formencode import validators as fev
 from formencode import All
 import formencode
@@ -67,7 +68,7 @@ class AddForum(ff.AdminForm):
                          validator=fev.UnicodeString()),
             ew.TextField(name='shortname', label='Short Name',
                          validator=All(
-                             fev.Regex(ur"^[^\s\/\.]*$", not_empty=True, messages={
+                             fev.Regex(r"^[^\s\/\.]*$", not_empty=True, messages={
                                  'invalid': 'Shortname cannot contain space . or /',
                                  'empty': 'You must create a short name for the forum.'}),
                              UniqueForumShortnameValidator())),
diff --git a/ForgeDiscussion/forgediscussion/widgets/forum_widgets.py b/ForgeDiscussion/forgediscussion/widgets/forum_widgets.py
index 046557f..7707e5c 100644
--- a/ForgeDiscussion/forgediscussion/widgets/forum_widgets.py
+++ b/ForgeDiscussion/forgediscussion/widgets/forum_widgets.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 from tg import tmpl_context as c
 from formencode import validators as fev
 
diff --git a/ForgeDiscussion/setup.py b/ForgeDiscussion/setup.py
index 165ce4b..f6a12f8 100644
--- a/ForgeDiscussion/setup.py
+++ b/ForgeDiscussion/setup.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 from setuptools import setup, find_packages
 
 from forgediscussion.version import __version__
diff --git a/ForgeFeedback/forgefeedback/feedback_main.py b/ForgeFeedback/forgefeedback/feedback_main.py
index 889c0f1..bd02001 100755
--- a/ForgeFeedback/forgefeedback/feedback_main.py
+++ b/ForgeFeedback/forgefeedback/feedback_main.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import logging
 import pymongo
 
diff --git a/ForgeFeedback/forgefeedback/model/feedback.py b/ForgeFeedback/forgefeedback/model/feedback.py
index 9498f70..b3e566f 100755
--- a/ForgeFeedback/forgefeedback/model/feedback.py
+++ b/ForgeFeedback/forgefeedback/model/feedback.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import logging
 import urllib
 # Non-stdlib imports
diff --git a/ForgeFeedback/forgefeedback/tests/functional/test_root.py b/ForgeFeedback/forgefeedback/tests/functional/test_root.py
index e99fdb5..45113d8 100755
--- a/ForgeFeedback/forgefeedback/tests/functional/test_root.py
+++ b/ForgeFeedback/forgefeedback/tests/functional/test_root.py
@@ -14,6 +14,7 @@
 #       KIND, either express or implied.  See the License for the
 #       specific language governing permissions and limitations
 #       under the License.
+from __future__ import unicode_literals
 from tg import tmpl_context as c
 from tg import config
 
diff --git a/ForgeFeedback/forgefeedback/tests/test_feedback_roles.py b/ForgeFeedback/forgefeedback/tests/test_feedback_roles.py
index a8ea087..11c1e72 100755
--- a/ForgeFeedback/forgefeedback/tests/test_feedback_roles.py
+++ b/ForgeFeedback/forgefeedback/tests/test_feedback_roles.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 from tg import tmpl_context as c, app_globals as g
 
 from nose.tools import assert_equal
diff --git a/ForgeFeedback/forgefeedback/tests/unit/__init__.py b/ForgeFeedback/forgefeedback/tests/unit/__init__.py
index 193f766..91e95d4 100755
--- a/ForgeFeedback/forgefeedback/tests/unit/__init__.py
+++ b/ForgeFeedback/forgefeedback/tests/unit/__init__.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 from tg import tmpl_context as c
 from ming.orm.ormsession import ThreadLocalORMSession
 
diff --git a/ForgeFeedback/forgefeedback/tests/unit/test_feedback.py b/ForgeFeedback/forgefeedback/tests/unit/test_feedback.py
index 03f6944..b7e4154 100755
--- a/ForgeFeedback/forgefeedback/tests/unit/test_feedback.py
+++ b/ForgeFeedback/forgefeedback/tests/unit/test_feedback.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 from datetime import datetime
 from nose.tools import assert_equal, assert_true
 from tg import tmpl_context as c
diff --git a/ForgeFeedback/forgefeedback/tests/unit/test_root_controller.py b/ForgeFeedback/forgefeedback/tests/unit/test_root_controller.py
index 4b000b1..3acfaa8 100755
--- a/ForgeFeedback/forgefeedback/tests/unit/test_root_controller.py
+++ b/ForgeFeedback/forgefeedback/tests/unit/test_root_controller.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import unittest
 
 from mock import Mock, patch
diff --git a/ForgeFeedback/forgefeedback/version.py b/ForgeFeedback/forgefeedback/version.py
index 3ea72c5..13480d0 100755
--- a/ForgeFeedback/forgefeedback/version.py
+++ b/ForgeFeedback/forgefeedback/version.py
@@ -15,5 +15,6 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 __version_info__ = (0, 0)
 __version__ = '.'.join(map(str, __version_info__))
diff --git a/ForgeFeedback/setup.py b/ForgeFeedback/setup.py
index cce91c5..0edfbb2 100755
--- a/ForgeFeedback/setup.py
+++ b/ForgeFeedback/setup.py
@@ -16,6 +16,7 @@
 #       under the License.
 
 
+from __future__ import unicode_literals
 from setuptools import setup, find_packages
 
 from forgefeedback.version import __version__
diff --git a/ForgeGit/forgegit/controllers.py b/ForgeGit/forgegit/controllers.py
index d939576..9164b28 100644
--- a/ForgeGit/forgegit/controllers.py
+++ b/ForgeGit/forgegit/controllers.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 from tg import expose, redirect
 from tg.decorators import with_trailing_slash
 from tg import tmpl_context as c
diff --git a/ForgeGit/forgegit/git_main.py b/ForgeGit/forgegit/git_main.py
index 27abc42..455efa7 100644
--- a/ForgeGit/forgegit/git_main.py
+++ b/ForgeGit/forgegit/git_main.py
@@ -16,6 +16,7 @@
 #       under the License.
 
 #-*- python -*-
+from __future__ import unicode_literals
 import logging
 
 # Non-stdlib imports
diff --git a/ForgeGit/forgegit/model/git_repo.py b/ForgeGit/forgegit/model/git_repo.py
index c7ed9f0..7cb4bcf 100644
--- a/ForgeGit/forgegit/model/git_repo.py
+++ b/ForgeGit/forgegit/model/git_repo.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import os
 import shutil
 import string
@@ -130,7 +131,7 @@ class Repository(M.Repository):
             author = h.really_unicode(c.user.display_name or c.user.username)
             tmp_repo.git.config('user.name', author.encode('utf8'))
             tmp_repo.git.config('user.email', 'allura@localhost')  # a public email alias could be nice here
-            msg = u'Merge {} branch {} into {}\n\n{}'.format(
+            msg = 'Merge {} branch {} into {}\n\n{}'.format(
                 mr.downstream_repo.url(),
                 mr.source_branch,
                 mr.target_branch,
@@ -563,7 +564,7 @@ class GitImplementation(M.RepositoryImplementation):
             try:
                 hex_sha = ref.commit.hexsha
             except (ValueError, AssertionError) as e:
-                log.debug(u"Found invalid sha: {}".format(ref), exc_info=e)
+                log.debug("Found invalid sha: {}".format(ref), exc_info=e)
                 continue
             refs.append(Object(name=ref.name, object_id=hex_sha))
         time_taken = time() - start_time
diff --git a/ForgeGit/forgegit/tests/__init__.py b/ForgeGit/forgegit/tests/__init__.py
index b693039..16153e2 100644
--- a/ForgeGit/forgegit/tests/__init__.py
+++ b/ForgeGit/forgegit/tests/__init__.py
@@ -19,6 +19,7 @@
 
 
 # Make our own Git tool test decorator
+from __future__ import unicode_literals
 from allura.tests.decorators import with_tool
 
 with_git = with_tool('test', 'Git', 'src-git', 'Git', type='git')
diff --git a/ForgeGit/forgegit/tests/functional/test_auth.py b/ForgeGit/forgegit/tests/functional/test_auth.py
index 1a230d4..ec0f980 100644
--- a/ForgeGit/forgegit/tests/functional/test_auth.py
+++ b/ForgeGit/forgegit/tests/functional/test_auth.py
@@ -17,6 +17,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import json
 from datadiff.tools import assert_equal
 
diff --git a/ForgeGit/forgegit/tests/functional/test_controllers.py b/ForgeGit/forgegit/tests/functional/test_controllers.py
index afaaea3..3c18bcd 100644
--- a/ForgeGit/forgegit/tests/functional/test_controllers.py
+++ b/ForgeGit/forgegit/tests/functional/test_controllers.py
@@ -17,6 +17,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import json
 import re
 import os
@@ -151,11 +152,11 @@ class TestRootController(_TestCase):
         data = json.loads(resp.body)
         assert_equal(
             data['built_tree']['df30427c488aeab84b2352bdf88a3b19223f9d7a'],
-            {u'url': u'/p/test/src-git/ci/df30427c488aeab84b2352bdf88a3b19223f9d7a/',
-             u'oid': u'df30427c488aeab84b2352bdf88a3b19223f9d7a',
-             u'short_id': u'[df3042]',
-             u'parents': [u'6a45885ae7347f1cac5103b0050cc1be6a1496c8'],
-             u'message': u'Add README', u'row': 2})
+            {'url': '/p/test/src-git/ci/df30427c488aeab84b2352bdf88a3b19223f9d7a/',
+             'oid': 'df30427c488aeab84b2352bdf88a3b19223f9d7a',
+             'short_id': '[df3042]',
+             'parents': ['6a45885ae7347f1cac5103b0050cc1be6a1496c8'],
+             'message': 'Add README', 'row': 2})
 
     def test_commit_browser_basic_view(self):
         resp = self.app.get('/src-git/ci/1e146e67985dcd71c74de79613719bef7bddca4a/basic')
@@ -276,17 +277,17 @@ class TestRootController(_TestCase):
     def test_file_raw(self):
         self._setup_weird_chars_repo()
         ci = self._get_ci(repo='/p/test/weird-chars/')
-        url = ci + 'tree/' + h.urlquote(u'привіт.txt') + '?format=raw'
+        url = ci + 'tree/' + h.urlquote('привіт.txt') + '?format=raw'
         resp = self.app.get(url)
-        assert_in(u'Привіт!\nWhich means Hello!', resp.body.decode('utf-8'))
+        assert_in('Привіт!\nWhich means Hello!', resp.body.decode('utf-8'))
         assert_equal(resp.headers.get('Content-Disposition').decode('utf-8'),
-                     u'attachment;filename="привіт.txt"')
+                     'attachment;filename="привіт.txt"')
 
-        url = ci + 'tree/' + h.urlquote(u'with space.txt') + '?format=raw'
+        url = ci + 'tree/' + h.urlquote('with space.txt') + '?format=raw'
         resp = self.app.get(url)
-        assert_in(u'with space', resp.body.decode('utf-8'))
+        assert_in('with space', resp.body.decode('utf-8'))
         assert_equal(resp.headers.get('Content-Disposition').decode('utf-8'),
-                     u'attachment;filename="with space.txt"')
+                     'attachment;filename="with space.txt"')
 
     def test_invalid_file(self):
         ci = self._get_ci()
diff --git a/ForgeGit/forgegit/tests/model/test_repository.py b/ForgeGit/forgegit/tests/model/test_repository.py
index e277ce0..5799c72 100644
--- a/ForgeGit/forgegit/tests/model/test_repository.py
+++ b/ForgeGit/forgegit/tests/model/test_repository.py
@@ -17,6 +17,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import os
 import shutil
 import stat
@@ -268,49 +269,49 @@ class TestGitRepo(unittest.TestCase, RepoImplTestBase):
         entries = list(self.repo.log(id_only=False))
         assert_equal(entries, [
             {'authored': {'date': datetime.datetime(2010, 10, 7, 18, 44, 11),
-                          'email': u'rcopeland@geek.net',
-                          'name': u'Rick Copeland'},
+                          'email': 'rcopeland@geek.net',
+                          'name': 'Rick Copeland'},
              'committed': {'date': datetime.datetime(2010, 10, 7, 18, 44, 11),
-                           'email': u'rcopeland@geek.net',
-                           'name': u'Rick Copeland'},
+                           'email': 'rcopeland@geek.net',
+                           'name': 'Rick Copeland'},
              'id': '1e146e67985dcd71c74de79613719bef7bddca4a',
-             'message': u'Change README\n',
+             'message': 'Change README\n',
              'parents': ['df30427c488aeab84b2352bdf88a3b19223f9d7a'],
              'refs': ['HEAD', 'foo', 'master'],
              'size': None,
              'rename_details': {}},
             {'authored': {'date': datetime.datetime(2010, 10, 7, 18, 44, 1),
-                          'email': u'rcopeland@geek.net',
-                          'name': u'Rick Copeland'},
+                          'email': 'rcopeland@geek.net',
+                          'name': 'Rick Copeland'},
              'committed': {'date': datetime.datetime(2010, 10, 7, 18, 44, 1),
-                           'email': u'rcopeland@geek.net',
-                           'name': u'Rick Copeland'},
+                           'email': 'rcopeland@geek.net',
+                           'name': 'Rick Copeland'},
              'id': 'df30427c488aeab84b2352bdf88a3b19223f9d7a',
-             'message': u'Add README\n',
+             'message': 'Add README\n',
              'parents': ['6a45885ae7347f1cac5103b0050cc1be6a1496c8'],
              'refs': [],
              'size': None,
              'rename_details': {}},
             {'authored': {'date': datetime.datetime(2010, 10, 7, 18, 43, 26),
-                          'email': u'rcopeland@geek.net',
-                          'name': u'Rick Copeland'},
+                          'email': 'rcopeland@geek.net',
+                          'name': 'Rick Copeland'},
              'committed': {'date': datetime.datetime(2010, 10, 7, 18, 43, 26),
-                           'email': u'rcopeland@geek.net',
-                           'name': u'Rick Copeland'},
+                           'email': 'rcopeland@geek.net',
+                           'name': 'Rick Copeland'},
              'id': '6a45885ae7347f1cac5103b0050cc1be6a1496c8',
-             'message': u'Remove file\n',
+             'message': 'Remove file\n',
              'parents': ['9a7df788cf800241e3bb5a849c8870f2f8259d98'],
              'refs': [],
              'size': None,
              'rename_details': {}},
             {'authored': {'date': datetime.datetime(2010, 10, 7, 18, 42, 54),
-                          'email': u'rcopeland@geek.net',
-                          'name': u'Rick Copeland'},
+                          'email': 'rcopeland@geek.net',
+                          'name': 'Rick Copeland'},
              'committed': {'date': datetime.datetime(2010, 10, 7, 18, 42, 54),
-                           'email': u'rcopeland@geek.net',
-                           'name': u'Rick Copeland'},
+                           'email': 'rcopeland@geek.net',
+                           'name': 'Rick Copeland'},
              'id': '9a7df788cf800241e3bb5a849c8870f2f8259d98',
-             'message': u'Initial commit\n',
+             'message': 'Initial commit\n',
              'parents': [],
              'refs': [],
              'size': None,
@@ -318,32 +319,32 @@ class TestGitRepo(unittest.TestCase, RepoImplTestBase):
         ])
 
     def test_log_unicode(self):
-        entries = list(self.repo.log(path=u'völundr', id_only=False))
+        entries = list(self.repo.log(path='völundr', id_only=False))
         assert_equal(entries, [])
 
     def test_log_file(self):
         entries = list(self.repo.log(path='README', id_only=False))
         assert_equal(entries, [
             {'authored': {'date': datetime.datetime(2010, 10, 7, 18, 44, 11),
-                          'email': u'rcopeland@geek.net',
-                          'name': u'Rick Copeland'},
+                          'email': 'rcopeland@geek.net',
+                          'name': 'Rick Copeland'},
              'committed': {'date': datetime.datetime(2010, 10, 7, 18, 44, 11),
-                           'email': u'rcopeland@geek.net',
-                           'name': u'Rick Copeland'},
+                           'email': 'rcopeland@geek.net',
+                           'name': 'Rick Copeland'},
              'id': '1e146e67985dcd71c74de79613719bef7bddca4a',
-             'message': u'Change README\n',
+             'message': 'Change README\n',
              'parents': ['df30427c488aeab84b2352bdf88a3b19223f9d7a'],
              'refs': ['HEAD', 'foo', 'master'],
              'size': 28,
              'rename_details': {}},
             {'authored': {'date': datetime.datetime(2010, 10, 7, 18, 44, 1),
-                          'email': u'rcopeland@geek.net',
-                          'name': u'Rick Copeland'},
+                          'email': 'rcopeland@geek.net',
+                          'name': 'Rick Copeland'},
              'committed': {'date': datetime.datetime(2010, 10, 7, 18, 44, 1),
-                           'email': u'rcopeland@geek.net',
-                           'name': u'Rick Copeland'},
+                           'email': 'rcopeland@geek.net',
+                           'name': 'Rick Copeland'},
              'id': 'df30427c488aeab84b2352bdf88a3b19223f9d7a',
-             'message': u'Add README\n',
+             'message': 'Add README\n',
              'parents': ['6a45885ae7347f1cac5103b0050cc1be6a1496c8'],
              'refs': [],
              'size': 15,
@@ -393,7 +394,7 @@ class TestGitRepo(unittest.TestCase, RepoImplTestBase):
             self.repo, ['1e146e67985dcd71c74de79613719bef7bddca4a', ])
         ThreadLocalORMSession.flush_all()
 
-        n = M.Notification.query.find({'subject': u'[test:src-git] New commit [1e146e] by Rick Copeland'}).first()
+        n = M.Notification.query.find({'subject': '[test:src-git] New commit [1e146e] by Rick Copeland'}).first()
         assert n
         assert_in('Change README', n.text)
 
@@ -403,7 +404,7 @@ class TestGitRepo(unittest.TestCase, RepoImplTestBase):
                                        ])
         ThreadLocalORMSession.flush_all()
         n = M.Notification.query.find(
-            dict(subject=u'[test:src-git] 2 new commits to Git')).first()
+            dict(subject='[test:src-git] 2 new commits to Git')).first()
         assert n
         assert n.text.startswith('\n## Branch: master'), n.text
         assert n.text.find('Add README') < n.text.find('Change README'), n.text
@@ -415,7 +416,7 @@ class TestGitRepo(unittest.TestCase, RepoImplTestBase):
                                        ])
         ThreadLocalORMSession.flush_all()
         n = M.Notification.query.find(
-            dict(subject=u'[test:src-git] 3 new commits to Git')).first()
+            dict(subject='[test:src-git] 3 new commits to Git')).first()
         assert n
         assert n.text.startswith('\n## Branch: master'), n.text
         assert n.text.find('Add README') < n.text.find('Change README'), n.text
@@ -430,7 +431,7 @@ class TestGitRepo(unittest.TestCase, RepoImplTestBase):
                                        ])
         ThreadLocalORMSession.flush_all()
         n = M.Notification.query.find(
-            dict(subject=u'[test:src-git] 4 new commits to Git')).first()
+            dict(subject='[test:src-git] 4 new commits to Git')).first()
         assert n
         assert n.text.startswith('\n## Branch: master'), n.text
         assert n.text.endswith('And 1 more commits.\n'), n.text
@@ -513,17 +514,17 @@ By Dave Brondsema''', text_body)
         c.lcid_cache = {}  # else it'll be a mock
         lcd_map = self.repo.commit('HEAD').tree.ls()
         self.assertEqual(lcd_map, [{
-            'href': u'README',
+            'href': 'README',
             'kind': 'BLOB',
             'last_commit': {
-                    'author': u'Rick Copeland',
-                'author_email': u'rcopeland@geek.net',
+                    'author': 'Rick Copeland',
+                'author_email': 'rcopeland@geek.net',
                 'author_url': None,
                 'date': datetime.datetime(2010, 10, 7, 18, 44, 11),
-                'href': u'/p/test/src-git/ci/1e146e67985dcd71c74de79613719bef7bddca4a/',
-                'shortlink': u'[1e146e]',
-                'summary': u'Change README'},
-            'name': u'README'}])
+                'href': '/p/test/src-git/ci/1e146e67985dcd71c74de79613719bef7bddca4a/',
+                'shortlink': '[1e146e]',
+                'summary': 'Change README'},
+            'name': 'README'}])
 
     def test_ls_with_prev(self):
         c.lcid_cache = {}  # else it'll be a mock
@@ -654,46 +655,46 @@ By Dave Brondsema''', text_body)
         payload = sender.get_payload(commit_ids=cids, ref='refs/heads/zz')
         expected_payload = {
             'size': 2,
-            'ref': u'refs/heads/zz',
-            'after': u'5c47243c8e424136fd5cdd18cd94d34c66d1955c',
-            'before': u'df30427c488aeab84b2352bdf88a3b19223f9d7a',
+            'ref': 'refs/heads/zz',
+            'after': '5c47243c8e424136fd5cdd18cd94d34c66d1955c',
+            'before': 'df30427c488aeab84b2352bdf88a3b19223f9d7a',
             'commits': [{
-                'id': u'5c47243c8e424136fd5cdd18cd94d34c66d1955c',
-                'url': u'http://localhost/p/test/src-git/ci/5c47243c8e424136fd5cdd18cd94d34c66d1955c/',
+                'id': '5c47243c8e424136fd5cdd18cd94d34c66d1955c',
+                'url': 'http://localhost/p/test/src-git/ci/5c47243c8e424136fd5cdd18cd94d34c66d1955c/',
                 'timestamp': datetime.datetime(2013, 3, 28, 18, 54, 16),
-                'message': u'Not repo root',
-                'author': {'name': u'Cory Johns',
-                           'email': u'cjohns@slashdotmedia.com',
+                'message': 'Not repo root',
+                'author': {'name': 'Cory Johns',
+                           'email': 'cjohns@slashdotmedia.com',
                            'username': 'cory'},
-                'committer': {'name': u'Cory Johns',
-                              'email': u'cjohns@slashdotmedia.com',
+                'committer': {'name': 'Cory Johns',
+                              'email': 'cjohns@slashdotmedia.com',
                               'username': 'cory'},
-                'added': [u'bad'],
+                'added': ['bad'],
                 'removed': [],
                 'modified': [],
                 'copied': [],
                 'renamed': [],
             }, {
-                'id': u'1e146e67985dcd71c74de79613719bef7bddca4a',
-                'url': u'http://localhost/p/test/src-git/ci/1e146e67985dcd71c74de79613719bef7bddca4a/',
+                'id': '1e146e67985dcd71c74de79613719bef7bddca4a',
+                'url': 'http://localhost/p/test/src-git/ci/1e146e67985dcd71c74de79613719bef7bddca4a/',
                 'timestamp': datetime.datetime(2010, 10, 7, 18, 44, 11),
-                'message': u'Change README',
-                'author': {'name': u'Rick Copeland',
-                           'email': u'rcopeland@geek.net',
+                'message': 'Change README',
+                'author': {'name': 'Rick Copeland',
+                           'email': 'rcopeland@geek.net',
                            'username': 'rick'},
-                'committer': {'name': u'Rick Copeland',
-                              'email': u'rcopeland@geek.net',
+                'committer': {'name': 'Rick Copeland',
+                              'email': 'rcopeland@geek.net',
                               'username': 'rick'},
                 'added': [],
                 'removed': [],
-                'modified': [u'README'],
+                'modified': ['README'],
                 'copied': [],
                 'renamed': [],
             }],
             'repository': {
-                'name': u'Git',
-                'full_name': u'/p/test/src-git/',
-                'url': u'http://localhost/p/test/src-git/',
+                'name': 'Git',
+                'full_name': '/p/test/src-git/',
+                'url': 'http://localhost/p/test/src-git/',
             },
         }
         assert_equals(payload, expected_payload)
@@ -746,8 +747,8 @@ By Dave Brondsema''', text_body)
             tmp_repo.git.config.call_args_list,
             [mock.call('user.name', 'Test Admin'),
              mock.call('user.email', 'allura@localhost')])
-        msg = u'Merge downstream-repo-url branch source-branch into target-branch'
-        msg += u'\n\nhttp://localhost/merge-request/1/'
+        msg = 'Merge downstream-repo-url branch source-branch into target-branch'
+        msg += '\n\nhttp://localhost/merge-request/1/'
         tmp_repo.git.merge.assert_called_once_with('cid', '-m', msg)
         tmp_repo.git.push.assert_called_once_with('origin', 'target-branch')
         shutil.rmtree.assert_called_once_with(
@@ -787,7 +788,7 @@ By Dave Brondsema''', text_body)
             'removed': [],
             'changed': [],
             'renamed': [],
-            'added': [u'with space.txt', u'привіт.txt'],
+            'added': ['with space.txt', 'привіт.txt'],
             'copied': [],
             'total': 2,
         }
@@ -799,7 +800,7 @@ By Dave Brondsema''', text_body)
             'removed': [],
             'copied': [],
             'renamed': [],
-            'changed': [u'привіт.txt'],
+            'changed': ['привіт.txt'],
             'total': 1,
         }
         assert_equals(diffs, expected)
@@ -807,7 +808,7 @@ By Dave Brondsema''', text_body)
         # initial commit is special, but must work too
         diffs = repo.paged_diffs('afaa6d93eb5661fb04f8e10e9ba1039b7441a6c7')
         expected = {
-            'added': [u'README.md'],
+            'added': ['README.md'],
             'removed': [],
             'changed': [],
             'copied': [],
@@ -819,7 +820,7 @@ By Dave Brondsema''', text_body)
         # pagination
         diffs = repo.paged_diffs('407950e8fba4dbc108ffbce0128ed1085c52cfd7', start=0, end=1)
         expected = {
-            'added': [u'with space.txt'],
+            'added': ['with space.txt'],
             'removed': [],
             'copied': [],
             'renamed': [],
@@ -829,7 +830,7 @@ By Dave Brondsema''', text_body)
         assert_equals(diffs, expected)
         diffs = repo.paged_diffs('407950e8fba4dbc108ffbce0128ed1085c52cfd7', start=1, end=2)
         expected = {
-            'added': [u'привіт.txt'],
+            'added': ['привіт.txt'],
             'removed': [],
             'copied': [],
             'renamed': [],
@@ -839,18 +840,18 @@ By Dave Brondsema''', text_body)
         assert_equals(diffs, expected)
         diffs = repo.paged_diffs('346c52c1dddc729e2c2711f809336401f0ff925e')  # Test copy
         expected = {
-            'added': [u'README.copy'],
+            'added': ['README.copy'],
             'removed': [],
             'copied': [],
             'renamed': [],
-            'changed': [u'README'],
+            'changed': ['README'],
             'total': 2,
         }
         assert_equals(diffs, expected)
         diffs = repo.paged_diffs('3cb2bbcd7997f89060a14fe8b1a363f01883087f')  # Test rename
         expected = {
-            'added': [u'README'],
-            'removed': [u'README-copy.md'],
+            'added': ['README'],
+            'removed': ['README-copy.md'],
             'copied': [],
             'renamed': [],
             'changed': [],
@@ -863,7 +864,7 @@ By Dave Brondsema''', text_body)
             'removed': [],
             'copied': [],
             'renamed': [],
-            'changed': [u'README.copy'],
+            'changed': ['README.copy'],
             'total': 1,
         }
         assert_equals(diffs, expected)
@@ -889,9 +890,9 @@ By Dave Brondsema''', text_body)
         expected = {
             'added': [],
             'removed': [],
-            'copied': [{'new': u'README.copy', 'old': u'README', 'ratio': 1.0}],
+            'copied': [{'new': 'README.copy', 'old': 'README', 'ratio': 1.0}],
             'renamed': [],
-            'changed': [u'README'],
+            'changed': ['README'],
             'total': 2,
         }
         assert_equals(diffs, expected)
@@ -900,7 +901,7 @@ By Dave Brondsema''', text_body)
             'added': [],
             'removed': [],
             'copied': [],
-            'renamed': [{'new': u'README', 'old': u'README-copy.md', 'ratio': 1.0}],
+            'renamed': [{'new': 'README', 'old': 'README-copy.md', 'ratio': 1.0}],
             'changed': [],
             'total': 1,
         }
@@ -915,14 +916,14 @@ By Dave Brondsema''', text_body)
         expected = [
             {'authored': {
                 'date': datetime.datetime(2013, 3, 28, 18, 54, 16),
-                'email': u'cjohns@slashdotmedia.com',
-                'name': u'Cory Johns'},
+                'email': 'cjohns@slashdotmedia.com',
+                'name': 'Cory Johns'},
              'committed': {
                  'date': datetime.datetime(2013, 3, 28, 18, 54, 16),
-                 'email': u'cjohns@slashdotmedia.com',
-                 'name': u'Cory Johns'},
+                 'email': 'cjohns@slashdotmedia.com',
+                 'name': 'Cory Johns'},
              'id': '5c47243c8e424136fd5cdd18cd94d34c66d1955c',
-             'message': u'Not repo root\n',
+             'message': 'Not repo root\n',
              'parents': ['1e146e67985dcd71c74de79613719bef7bddca4a'],
              'refs': ['zz'],
              'rename_details': {},
diff --git a/ForgeGit/forgegit/tests/test_git_app.py b/ForgeGit/forgegit/tests/test_git_app.py
index c03e242..b43aac5 100644
--- a/ForgeGit/forgegit/tests/test_git_app.py
+++ b/ForgeGit/forgegit/tests/test_git_app.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import unittest
 from nose.tools import assert_equals
 
diff --git a/ForgeGit/forgegit/tests/test_tasks.py b/ForgeGit/forgegit/tests/test_tasks.py
index 4563457..37248f0 100644
--- a/ForgeGit/forgegit/tests/test_tasks.py
+++ b/ForgeGit/forgegit/tests/test_tasks.py
@@ -14,6 +14,7 @@
 #       KIND, either express or implied.  See the License for the
 #       specific language governing permissions and limitations
 #       under the License.
+from __future__ import unicode_literals
 import unittest
 import mock
 from testfixtures import LogCapture
diff --git a/ForgeGit/forgegit/version.py b/ForgeGit/forgegit/version.py
index 1b493f8..192acec 100644
--- a/ForgeGit/forgegit/version.py
+++ b/ForgeGit/forgegit/version.py
@@ -15,5 +15,6 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 __version_info__ = (0, 0)
 __version__ = '.'.join(map(str, __version_info__))
diff --git a/ForgeGit/setup.py b/ForgeGit/setup.py
index e91068b..8ceb54b 100644
--- a/ForgeGit/setup.py
+++ b/ForgeGit/setup.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 from setuptools import setup, find_packages
 
 from forgegit.version import __version__
diff --git a/ForgeImporters/docs/conf.py b/ForgeImporters/docs/conf.py
index ff9275c..2fb71f0 100644
--- a/ForgeImporters/docs/conf.py
+++ b/ForgeImporters/docs/conf.py
@@ -37,6 +37,7 @@
 
 # Add any Sphinx extension module names here, as strings. They can be extensions
 # coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
+from __future__ import unicode_literals
 extensions = ['sphinx.ext.autodoc', 'sphinx.ext.intersphinx',
               'sphinx.ext.todo', 'sphinx.ext.coverage', 'sphinx.ext.ifconfig']
 
@@ -187,8 +188,8 @@ htmlhelp_basename = 'alluradoc'
 # Grouping the document tree into LaTeX files. List of tuples
 # (source start file, target name, title, author, documentclass [howto/manual]).
 latex_documents = [
-    ('index', 'allura.tex', u'allura Documentation',
-     u'Cory Johns, Tim Van Steenburgh, Dave Brondsema', 'manual'),
+    ('index', 'allura.tex', 'allura Documentation',
+     'Cory Johns, Tim Van Steenburgh, Dave Brondsema', 'manual'),
 ]
 
 # The name of an image file (relative to this directory) to place at the top of
diff --git a/ForgeImporters/forgeimporters/base.py b/ForgeImporters/forgeimporters/base.py
index d7ea454..91d025e 100644
--- a/ForgeImporters/forgeimporters/base.py
+++ b/ForgeImporters/forgeimporters/base.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import os
 import errno
 import logging
diff --git a/ForgeImporters/forgeimporters/forge/tracker.py b/ForgeImporters/forgeimporters/forge/tracker.py
index 32ec0eb..e7024c1 100644
--- a/ForgeImporters/forgeimporters/forge/tracker.py
+++ b/ForgeImporters/forgeimporters/forge/tracker.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import os
 import json
 
diff --git a/ForgeImporters/forgeimporters/github/__init__.py b/ForgeImporters/forgeimporters/github/__init__.py
index e294ee1..ac538c5 100644
--- a/ForgeImporters/forgeimporters/github/__init__.py
+++ b/ForgeImporters/forgeimporters/github/__init__.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import re
 import logging
 import json
diff --git a/ForgeImporters/forgeimporters/github/code.py b/ForgeImporters/forgeimporters/github/code.py
index 9d6e5f5..ac53bce 100644
--- a/ForgeImporters/forgeimporters/github/code.py
+++ b/ForgeImporters/forgeimporters/github/code.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 from tg import tmpl_context as c
 from tg import app_globals as g
 from formencode import validators as fev
diff --git a/ForgeImporters/forgeimporters/github/project.py b/ForgeImporters/forgeimporters/github/project.py
index c4d8c61..7586971 100644
--- a/ForgeImporters/forgeimporters/github/project.py
+++ b/ForgeImporters/forgeimporters/github/project.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import logging
 
 from tg import expose, validate
diff --git a/ForgeImporters/forgeimporters/github/tasks.py b/ForgeImporters/forgeimporters/github/tasks.py
index db12c4d..a1cd9bb 100644
--- a/ForgeImporters/forgeimporters/github/tasks.py
+++ b/ForgeImporters/forgeimporters/github/tasks.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 from tg import tmpl_context as c
 from tg import app_globals as g
 
diff --git a/ForgeImporters/forgeimporters/github/tests/test_code.py b/ForgeImporters/forgeimporters/github/tests/test_code.py
index bc293ce..0f7bc09 100644
--- a/ForgeImporters/forgeimporters/github/tests/test_code.py
+++ b/ForgeImporters/forgeimporters/github/tests/test_code.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 from unittest import TestCase
 from mock import Mock, patch
 from ming.odm import ThreadLocalORMSession
@@ -96,12 +97,12 @@ class TestGitHubImportController(TestController, TestCase):
             r.location, 'http://localhost/p/{}/admin/'.format(
                 test_project_with_repo))
         self.assertEqual(
-            u'mymount', import_tool.post.call_args[1]['mount_point'])
+            'mymount', import_tool.post.call_args[1]['mount_point'])
         self.assertEqual(
-            u'mylabel', import_tool.post.call_args[1]['mount_label'])
+            'mylabel', import_tool.post.call_args[1]['mount_label'])
         self.assertEqual(
-            u'poop', import_tool.post.call_args[1]['project_name'])
-        self.assertEqual(u'spooky', import_tool.post.call_args[1]['user_name'])
+            'poop', import_tool.post.call_args[1]['project_name'])
+        self.assertEqual('spooky', import_tool.post.call_args[1]['user_name'])
         self.assertEqual(requests.head.call_count, 1)
 
     @with_git
diff --git a/ForgeImporters/forgeimporters/github/tests/test_oauth.py b/ForgeImporters/forgeimporters/github/tests/test_oauth.py
index a903623..ddbe89b 100644
--- a/ForgeImporters/forgeimporters/github/tests/test_oauth.py
+++ b/ForgeImporters/forgeimporters/github/tests/test_oauth.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 from unittest import TestCase
 
 from mock import Mock, patch, MagicMock
diff --git a/ForgeImporters/forgeimporters/github/tests/test_tracker.py b/ForgeImporters/forgeimporters/github/tests/test_tracker.py
index b2cda37..a25ccd5 100644
--- a/ForgeImporters/forgeimporters/github/tests/test_tracker.py
+++ b/ForgeImporters/forgeimporters/github/tests/test_tracker.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 from unittest import TestCase
 from mock import patch
 from ming.odm import ThreadLocalORMSession
@@ -58,12 +59,12 @@ class TestGitHubTrackerImportController(TestController, TestCase):
         self.assertEqual(r.location, 'http://localhost/p/%s/admin/' %
                          test_project_with_tracker)
         self.assertEqual(
-            u'Issues', import_tool.post.call_args[1]['mount_label'])
+            'Issues', import_tool.post.call_args[1]['mount_label'])
         self.assertEqual(
-            u'issues', import_tool.post.call_args[1]['mount_point'])
+            'issues', import_tool.post.call_args[1]['mount_point'])
         self.assertEqual(
-            u'mulder', import_tool.post.call_args[1]['project_name'])
-        self.assertEqual(u'spooky', import_tool.post.call_args[1]['user_name'])
+            'mulder', import_tool.post.call_args[1]['project_name'])
+        self.assertEqual('spooky', import_tool.post.call_args[1]['user_name'])
         self.assertEqual(requests.head.call_count, 1)
 
     @with_tracker
diff --git a/ForgeImporters/forgeimporters/github/tests/test_utils.py b/ForgeImporters/forgeimporters/github/tests/test_utils.py
index 6ce7821..0f482a2 100644
--- a/ForgeImporters/forgeimporters/github/tests/test_utils.py
+++ b/ForgeImporters/forgeimporters/github/tests/test_utils.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 from nose.tools import assert_equal
 
 from forgeimporters.github.utils import GitHubMarkdownConverter
@@ -86,12 +87,12 @@ class TestGitHubMarkdownConverter(object):
         assert_equal(self.conv.convert(text), '<s>mistake</s>')
 
     def test_inline_code_block(self):
-        text = u'This `~~some text~~` converts to this ~~strike out~~.'
-        result = u'This `~~some text~~` converts to this <s>strike out</s>.'
+        text = 'This `~~some text~~` converts to this ~~strike out~~.'
+        result = 'This `~~some text~~` converts to this <s>strike out</s>.'
         assert_equal(self.conv.convert(text).strip(), result)
 
     def test_convert_code_blocks(self):
-        text = u'''```python
+        text = '''```python
 print "Hello!"
 ```
 
@@ -102,7 +103,7 @@ for (var i = 0; i < a.length; i++) {
     console.log(i);
 }
 ```'''
-        result = u''':::python
+        result = ''':::python
     print "Hello!"
 
 Two code blocks here!
@@ -114,14 +115,14 @@ Two code blocks here!
         assert_equal(self.conv.convert(text).strip(), result)
 
     def test_code_blocks_without_newline_before(self):
-        text = u'''
+        text = '''
 There are some code snippet:
 ```
 print 'Hello'
 ```
 Pretty cool, ha?'''
 
-        result = u'''
+        result = '''
 There are some code snippet:
 
     print 'Hello'
@@ -130,14 +131,14 @@ Pretty cool, ha?'''
         text = text.replace('```', '~~~')
         assert_equal(self.conv.convert(text).strip(), result.strip())
 
-        text = u'''
+        text = '''
 There are some code snippet:
 ```python
 print 'Hello'
 ```
 Pretty cool, ha?'''
 
-        result = u'''
+        result = '''
 There are some code snippet:
 
     :::python
diff --git a/ForgeImporters/forgeimporters/github/tests/test_wiki.py b/ForgeImporters/forgeimporters/github/tests/test_wiki.py
index f99ecc4..8f21f55 100644
--- a/ForgeImporters/forgeimporters/github/tests/test_wiki.py
+++ b/ForgeImporters/forgeimporters/github/tests/test_wiki.py
@@ -17,6 +17,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 from unittest import TestCase
 from nose.tools import assert_equal
 from mock import Mock, patch, call
@@ -137,8 +138,8 @@ class TestGitHubWikiImporter(TestCase):
         assert_equal(upsert.call_args_list, [call('Home2'), call('Home3')])
 
         assert_equal(render.call_args_list, [
-            call('Home2.creole', u'**test message**'),
-            call('Home3.rest', u'test message')])
+            call('Home2.creole', '**test message**'),
+            call('Home3.rest', 'test message')])
 
     @patch('forgeimporters.github.wiki.git.Repo')
     @patch('forgeimporters.github.wiki.mkdtemp')
@@ -182,7 +183,7 @@ class TestGitHubWikiImporter(TestCase):
         importer._with_history(self.commit2)
         assert_equal(upsert.call_args_list, [call('Home')])
         assert_equal(render.call_args_list,
-                     [call('Home.rst', u'# test message')])
+                     [call('Home.rst', '# test message')])
 
     @skipif(module_not_available('html2text'))
     @patch('forgeimporters.github.wiki.WM.Page.upsert')
@@ -190,7 +191,7 @@ class TestGitHubWikiImporter(TestCase):
     def test_with_history_mediawiki(self, md2mkm, upsert):
         self.commit2.stats.files = {"Home.mediawiki": self.blob1}
         self.commit2.tree = {"Home.mediawiki": self.blob1}
-        md2mkm.return_value = u'# test message'
+        md2mkm.return_value = '# test message'
         importer = GitHubWikiImporter()
         importer._set_available_pages = Mock()
         importer.github_wiki_url = 'https://github.com/a/b/wiki'
@@ -198,28 +199,28 @@ class TestGitHubWikiImporter(TestCase):
         importer.app.config.options = {}
         importer.app.url = '/p/test/wiki/'
         importer.rewrite_links = Mock(return_value='')
-        importer.convert_gollum_tags = Mock(return_value=u'# test message')
+        importer.convert_gollum_tags = Mock(return_value='# test message')
         importer._with_history(self.commit2)
         assert_equal(upsert.call_args_list, [call('Home')])
-        assert_equal(md2mkm.call_args_list, [call(u'# test message')])
+        assert_equal(md2mkm.call_args_list, [call('# test message')])
 
     def test_set_available_pages(self):
         importer = GitHubWikiImporter()
         commit = Mock()
         blobs = [Mock() for i in range(3)]
-        blobs[0].name = u'Home-42.md'
-        blobs[1].name = u'image.png'
-        blobs[2].name = u'code & fun.textile'
+        blobs[0].name = 'Home-42.md'
+        blobs[1].name = 'image.png'
+        blobs[2].name = 'code & fun.textile'
         commit.tree.traverse.return_value = blobs
         importer._set_available_pages(commit)
-        assert_equal(importer.available_pages, [u'Home 42', u'code & fun'])
+        assert_equal(importer.available_pages, ['Home 42', 'code & fun'])
 
     def test_gollum_page_links_case_insensitive(self):
         i = GitHubWikiImporter()
-        i.available_pages = [u'Home 42', u'code & fun']
-        assert_equal(i.convert_gollum_tags(u'[[Code & Fun]]'), u'[code & fun]')
-        assert_equal(i.convert_gollum_tags(u'[[home-42]]'), u'[Home 42]')
-        assert_equal(i.convert_gollum_tags(u'[[Unknown]]'), u'[Unknown]')
+        i.available_pages = ['Home 42', 'code & fun']
+        assert_equal(i.convert_gollum_tags('[[Code & Fun]]'), '[code & fun]')
+        assert_equal(i.convert_gollum_tags('[[home-42]]'), '[Home 42]')
+        assert_equal(i.convert_gollum_tags('[[Unknown]]'), '[Unknown]')
 
     def test_convert_page_name(self):
         f = GitHubWikiImporter()._convert_page_name
@@ -229,57 +230,57 @@ class TestGitHubWikiImporter(TestCase):
 
     def test_convert_gollum_page_links(self):
         f = GitHubWikiImporter().convert_gollum_tags
-        assert_equal(f(u'[[Page]]'), u'[Page]')
-        assert_equal(f(u'[[Page Title|Page]]'), u'[Page Title](Page)')
-        assert_equal(f(u'[[Pagê Nâme]]'), u'[Pagê Nâme]')
+        assert_equal(f('[[Page]]'), '[Page]')
+        assert_equal(f('[[Page Title|Page]]'), '[Page Title](Page)')
+        assert_equal(f('[[Pagê Nâme]]'), '[Pagê Nâme]')
         # Github always converts spaces and slashes in links to hyphens,
         # to lookup page in the filesystem. During import we're converting
         # all hyphens in page name to spaces, but still supporting both link
         # formats.
-        assert_equal(f(u'[[Page With Spaces]]'), u'[Page With Spaces]')
-        assert_equal(f(u'[[Page-With-Spaces]]'), u'[Page With Spaces]')
-        assert_equal(f(u'[[Page / 1]]'), u'[Page   1]')
-        assert_equal(f(u'[[Title|Page With Spaces]]'),
-                     u'[Title](Page With Spaces)')
-        assert_equal(f(u'[[Title|Page-With-Spaces]]'),
-                     u'[Title](Page With Spaces)')
-        assert_equal(f(u'[[go here|Page / 1]]'), u'[go here](Page   1)')
+        assert_equal(f('[[Page With Spaces]]'), '[Page With Spaces]')
+        assert_equal(f('[[Page-With-Spaces]]'), '[Page With Spaces]')
+        assert_equal(f('[[Page / 1]]'), '[Page   1]')
+        assert_equal(f('[[Title|Page With Spaces]]'),
+                     '[Title](Page With Spaces)')
+        assert_equal(f('[[Title|Page-With-Spaces]]'),
+                     '[Title](Page With Spaces)')
+        assert_equal(f('[[go here|Page / 1]]'), '[go here](Page   1)')
 
     def test_convert_gollum_page_links_escaped(self):
         f = GitHubWikiImporter().convert_gollum_tags
-        assert_equal(f(u"'[[Page]]"), u'[[Page]]')
-        assert_equal(f(u"'[[Page Title|Page]]"), u'[[Page Title|Page]]')
-        assert_equal(f(u"'[[Page With Spaces]]"), u'[[Page With Spaces]]')
-        assert_equal(f(u"'[[Page-With-Spaces]]"), u'[[Page-With-Spaces]]')
-        assert_equal(f(u"'[[Page / 1]]"), u'[[Page / 1]]')
-        assert_equal(f(u"'[[Title|Page With Spaces]]"),
-                     u'[[Title|Page With Spaces]]')
-        assert_equal(f(u"'[[Title|Page-With-Spaces]]"),
-                     u'[[Title|Page-With-Spaces]]')
-        assert_equal(f(u"'[[go here|Page / 1]]"), u'[[go here|Page / 1]]')
+        assert_equal(f("'[[Page]]"), '[[Page]]')
+        assert_equal(f("'[[Page Title|Page]]"), '[[Page Title|Page]]')
+        assert_equal(f("'[[Page With Spaces]]"), '[[Page With Spaces]]')
+        assert_equal(f("'[[Page-With-Spaces]]"), '[[Page-With-Spaces]]')
+        assert_equal(f("'[[Page / 1]]"), '[[Page / 1]]')
+        assert_equal(f("'[[Title|Page With Spaces]]"),
+                     '[[Title|Page With Spaces]]')
+        assert_equal(f("'[[Title|Page-With-Spaces]]"),
+                     '[[Title|Page-With-Spaces]]')
+        assert_equal(f("'[[go here|Page / 1]]"), '[[go here|Page / 1]]')
 
     def test_convert_gollum_external_links(self):
         f = GitHubWikiImporter().convert_gollum_tags
-        assert_equal(f(u'[[http://domain.net]]'), u'<http://domain.net>')
-        assert_equal(f(u'[[https://domain.net]]'), u'<https://domain.net>')
-        assert_equal(f(u'[[Site|http://domain.net]]'),
-                     u'[Site](http://domain.net)')
+        assert_equal(f('[[http://domain.net]]'), '<http://domain.net>')
+        assert_equal(f('[[https://domain.net]]'), '<https://domain.net>')
+        assert_equal(f('[[Site|http://domain.net]]'),
+                     '[Site](http://domain.net)')
 
     def test_convert_gollum_external_links_escaped(self):
         f = GitHubWikiImporter().convert_gollum_tags
-        assert_equal(f(u"'[[http://domain.net]]"), u'[[http://domain.net]]')
-        assert_equal(f(u"'[[https://domain.net]]"), u'[[https://domain.net]]')
-        assert_equal(f(u"'[[Site|http://domain.net]]"),
-                     u'[[Site|http://domain.net]]')
+        assert_equal(f("'[[http://domain.net]]"), '[[http://domain.net]]')
+        assert_equal(f("'[[https://domain.net]]"), '[[https://domain.net]]')
+        assert_equal(f("'[[Site|http://domain.net]]"),
+                     '[[Site|http://domain.net]]')
 
     def test_convert_gollum_toc(self):
         f = GitHubWikiImporter().convert_gollum_tags
-        assert_equal(f(u'[[_TOC_]]'), u'[TOC]')
-        assert_equal(f(u"'[[_TOC_]]"), u'[[_TOC_]]')
+        assert_equal(f('[[_TOC_]]'), '[TOC]')
+        assert_equal(f("'[[_TOC_]]"), '[[_TOC_]]')
 
     def test_convert_gollum_tags(self):
         f = GitHubWikiImporter().convert_gollum_tags
-        source = u'''Look at [[this page|Some Page]]
+        source = '''Look at [[this page|Some Page]]
 
 More info at: [[MoreInfo]] [[Even More Info]]
 
@@ -287,7 +288,7 @@ Our website is [[http://domain.net]].
 
 '[[Escaped Tag]]'''
 
-        result = u'''Look at [this page](Some Page)
+        result = '''Look at [this page](Some Page)
 
 More info at: [MoreInfo] [Even More Info]
 
@@ -306,7 +307,7 @@ Our website is <http://domain.net>.
         importer.github_markdown_converter = GitHubMarkdownConverter(
             'user', 'proj')
         f = importer.convert_markup
-        source = u'''Look at [[this page|Some Page]]
+        source = '''Look at [[this page|Some Page]]
 
 More info at: [[MoreInfo]] [[Even More Info]]
 
@@ -323,7 +324,7 @@ ticket #1
 #1 header
 
 sha aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'''
-        result = u'''Look at [this page](Some Page)
+        result = '''Look at [this page](Some Page)
 
 More info at: [MoreInfo] [Even More Info]
 
@@ -341,7 +342,7 @@ ticket [#1]
 sha [aaaaaa]'''
         assert_equal(f(source, 'test.md').strip(), result)
 
-        assert_equal(f(u'h1. Hello', 't.textile').strip(), u'# Hello')
+        assert_equal(f('h1. Hello', 't.textile').strip(), '# Hello')
 
     @without_module('html2text')
     def test_convert_markup_without_html2text(self):
@@ -350,7 +351,7 @@ sha [aaaaaa]'''
         importer.app = Mock()
         importer.app.url = '/p/test/wiki/'
         f = importer.convert_markup
-        source = u'''Look at [[this page|Some Page]]
+        source = '''Look at [[this page|Some Page]]
 
 More info at: [[MoreInfo]] [[Even More Info]]
 
@@ -362,7 +363,7 @@ Our website is [[http://domain.net]].
 
 [External link](https://github.com/a/b/issues/1)'''
 
-        result = u'''<p>Look at [[this page|Some Page]]</p>
+        result = '''<p>Look at [[this page|Some Page]]</p>
 <p>More info at: [[MoreInfo]] [[Even More Info]]</p>
 <p>Our website is [[http://domain.net]].</p>
 <p>\u2018[[Escaped Tag]]</p>
@@ -376,29 +377,29 @@ Our website is [[http://domain.net]].
         prefix = 'https://github/a/b/wiki'
         new = '/p/test/wiki/'
         assert_equal(
-            f(u'<a href="https://github/a/b/wiki/Test Page">Test Page</a>',
+            f('<a href="https://github/a/b/wiki/Test Page">Test Page</a>',
               prefix, new),
-            u'<a href="/p/test/wiki/Test Page">Test Page</a>')
+            '<a href="/p/test/wiki/Test Page">Test Page</a>')
         assert_equal(
-            f(u'<a href="https://github/a/b/wiki/Test-Page">Test-Page</a>',
+            f('<a href="https://github/a/b/wiki/Test-Page">Test-Page</a>',
               prefix, new),
-            u'<a href="/p/test/wiki/Test Page">Test Page</a>')
+            '<a href="/p/test/wiki/Test Page">Test Page</a>')
         assert_equal(
-            f(u'<a href="https://github/a/b/issues/1" class="1"></a>',
+            f('<a href="https://github/a/b/issues/1" class="1"></a>',
               prefix, new),
-            u'<a class="1" href="https://github/a/b/issues/1"></a>')
+            '<a class="1" href="https://github/a/b/issues/1"></a>')
         assert_equal(
-            f(u'<a href="https://github/a/b/wiki/Test Page">https://github/a/b/wiki/Test Page</a>',
+            f('<a href="https://github/a/b/wiki/Test Page">https://github/a/b/wiki/Test Page</a>',
               prefix, new),
-            u'<a href="/p/test/wiki/Test Page">/p/test/wiki/Test Page</a>')
+            '<a href="/p/test/wiki/Test Page">/p/test/wiki/Test Page</a>')
         assert_equal(
-            f(u'<a href="https://github/a/b/wiki/Test Page">Test blah blah</a>',
+            f('<a href="https://github/a/b/wiki/Test Page">Test blah blah</a>',
               prefix, new),
-            u'<a href="/p/test/wiki/Test Page">Test blah blah</a>')
+            '<a href="/p/test/wiki/Test Page">Test blah blah</a>')
         assert_equal(
-            f(u'<a href="https://github/a/b/wiki/Test Page">Test <b>Page</b></a>',
+            f('<a href="https://github/a/b/wiki/Test Page">Test <b>Page</b></a>',
               prefix, new),
-            u'<a href="/p/test/wiki/Test Page">Test <b>Page</b></a>')
+            '<a href="/p/test/wiki/Test Page">Test <b>Page</b></a>')
 
     @skipif(module_not_available('html2text'))
     def test_convert_markup_with_mediawiki2markdown(self):
@@ -407,7 +408,7 @@ Our website is [[http://domain.net]].
         importer.app = Mock()
         importer.app.url = '/p/test/wiki/'
         f = importer.convert_markup
-        source = u'''
+        source = '''
 ''Al'fredas 235 BC''
 == See also ==
 * [https://github.com/a/b/wiki/AgentSpring-running-instructions-for-d13n-model Test1]
@@ -415,7 +416,7 @@ Our website is [[http://domain.net]].
 * [https://github.com/a/b/wiki/AgentSpring-Q&A Test3]
 * [https://github.com/a/b/wiki/Extensions Test4]'''
 
-        result = u'''_Al'fredas 235 BC_
+        result = '''_Al'fredas 235 BC_
 
 ## See also
 
@@ -434,7 +435,7 @@ Our website is [[http://domain.net]].
         importer.app = Mock()
         importer.app.url = '/p/test/wiki/'
         f = importer.convert_markup
-        source = u'''h1. Header 1
+        source = '''h1. Header 1
 
 Some text 1.
 
@@ -442,7 +443,7 @@ h2. Header 2
 
 See [[Page]]'''
 
-        result = u'''# Header 1
+        result = '''# Header 1
 
 Some text 1.
 
@@ -458,8 +459,8 @@ See [Page]'''
         importer.app = Mock()
         importer.app.url = '/p/test/wiki/'
         f = importer.convert_markup
-        source = u'[[Ticks & Leeches]]'
-        result = u'[Ticks & Leeches]'
+        source = '[[Ticks & Leeches]]'
+        result = '[Ticks & Leeches]'
         # markdown should be untouched
         assert_equal(f(source, 'test.rst').strip(), result)
 
@@ -472,13 +473,13 @@ See [Page]'''
         f = importer.convert_markup
 
         # check if lists converting works properly
-        source = u'''There are good reasons for this:
+        source = '''There are good reasons for this:
 
   # Duplicate libraries regularly break builds
   # Subtle bugs emerge with duplicate libraries, and to a lesser extent, duplicate tools
   # We want you to try harder to make your formula work with what OS X comes with
 '''
-        result = u'''There are good reasons for this:
+        result = '''There are good reasons for this:
 
   1. Duplicate libraries regularly break builds
   2. Subtle bugs emerge with duplicate libraries, and to a lesser extent, duplicate tools
@@ -493,13 +494,13 @@ See [Page]'''
         assert_equal(f(source, 'test2.textile'), result)
 
         # links with formatting converts normal in textile now
-        source = u'''*[[this checklist|Troubleshooting]]*
+        source = '''*[[this checklist|Troubleshooting]]*
 
 some text and *[[Tips n' Tricks]]*
 
 *[[link|http://otherlink.com]]*
 '''
-        result = u'''**[this checklist](Troubleshooting)**
+        result = '''**[this checklist](Troubleshooting)**
 
 some text and **[Tips n\u2019 Tricks]**
 
@@ -514,9 +515,9 @@ some text and **[Tips n\u2019 Tricks]**
         importer.app = Mock()
         importer.app.url = '/p/test/wiki/'
         f = importer.convert_markup
-        source = u'*[[this checklist|Troubleshooting]]*'
+        source = '*[[this checklist|Troubleshooting]]*'
         assert_equal(f(source, 't.textile').strip(),
-                     u'**[this checklist](Troubleshooting)**')
+                     '**[this checklist](Troubleshooting)**')
 
     @without_module('html2text')
     def test_convert_textile_special_tag_without_html2text(self):
@@ -525,8 +526,8 @@ some text and **[Tips n\u2019 Tricks]**
         importer.app = Mock()
         importer.app.url = '/p/test/wiki/'
         f = importer.convert_markup
-        source = u'*[[this checklist|Troubleshooting]]*'
-        result = u'<p><strong>[[this checklist|Troubleshooting]]</strong></p>'
+        source = '*[[this checklist|Troubleshooting]]*'
+        result = '<p><strong>[[this checklist|Troubleshooting]]</strong></p>'
         assert_equal(f(source, 't.textile').strip(), result)
 
     @patch('forgeimporters.github.wiki.mkdtemp', autospec=True)
@@ -575,11 +576,11 @@ class TestGitHubWikiImportController(TestController, TestCase):
         self.assertEqual(r.location, 'http://localhost/p/%s/admin/' %
                          test_project_with_wiki)
         args = import_tool.post.call_args[1]
-        self.assertEqual(u'GitHub Wiki', args['mount_label'])
-        self.assertEqual(u'gh-wiki', args['mount_point'])
-        self.assertEqual(u'mulder', args['project_name'])
-        self.assertEqual(u'spooky', args['user_name'])
-        self.assertEqual(u'import_history', args['tool_option'])
+        self.assertEqual('GitHub Wiki', args['mount_label'])
+        self.assertEqual('gh-wiki', args['mount_point'])
+        self.assertEqual('mulder', args['project_name'])
+        self.assertEqual('spooky', args['user_name'])
+        self.assertEqual('import_history', args['tool_option'])
         self.assertEqual(requests.head.call_count, 1)
 
     @with_wiki
@@ -597,11 +598,11 @@ class TestGitHubWikiImportController(TestController, TestCase):
         self.assertEqual(r.location, 'http://localhost/p/%s/admin/' %
                          test_project_with_wiki)
         args = import_tool.post.call_args[1]
-        self.assertEqual(u'GitHub Wiki', args['mount_label'])
-        self.assertEqual(u'gh-wiki', args['mount_point'])
-        self.assertEqual(u'mulder', args['project_name'])
-        self.assertEqual(u'spooky', args['user_name'])
-        self.assertEqual(u'', args['tool_option'])
+        self.assertEqual('GitHub Wiki', args['mount_label'])
+        self.assertEqual('gh-wiki', args['mount_point'])
+        self.assertEqual('mulder', args['project_name'])
+        self.assertEqual('spooky', args['user_name'])
+        self.assertEqual('', args['tool_option'])
         self.assertEqual(requests.head.call_count, 1)
 
     @with_wiki
diff --git a/ForgeImporters/forgeimporters/github/tracker.py b/ForgeImporters/forgeimporters/github/tracker.py
index 1a915bc..23c3fff 100644
--- a/ForgeImporters/forgeimporters/github/tracker.py
+++ b/ForgeImporters/forgeimporters/github/tracker.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import re
 import logging
 from datetime import datetime
@@ -157,7 +158,7 @@ class GitHubTrackerImporter(ToolImporter):
         return datetime.strptime(datetime_string, '%Y-%m-%dT%H:%M:%SZ')
 
     def get_user_link(self, user):
-        return u'[{0}](https://github.com/{0})'.format(user)
+        return '[{0}](https://github.com/{0})'.format(user)
 
     def process_fields(self, extractor, ticket, issue):
         ticket.summary = issue['title']
@@ -173,10 +174,10 @@ class GitHubTrackerImporter(ToolImporter):
         body, attachments = self._get_attachments(extractor, issue['body'])
         ticket.add_multiple_attachments(attachments)
         ticket.description = (
-            u'*Originally created by:* {creator}\n'
-            u'{owner}'
-            u'\n'
-            u'{body}').format(
+            '*Originally created by:* {creator}\n'
+            '{owner}'
+            '\n'
+            '{body}').format(
             creator=self.get_user_link(issue['user']['login']),
             owner=owner_line,
             body=self.github_markdown_converter.convert(body),
@@ -188,7 +189,7 @@ class GitHubTrackerImporter(ToolImporter):
             body, attachments = self._get_attachments(
                 extractor, comment['body'])
             if comment['user']:
-                posted_by = u'*Originally posted by:* {}\n\n'.format(
+                posted_by = '*Originally posted by:* {}\n\n'.format(
                     self.get_user_link(comment['user']['login']))
                 body = posted_by + body
             p = ticket.discussion_thread.add_post(
diff --git a/ForgeImporters/forgeimporters/github/utils.py b/ForgeImporters/forgeimporters/github/utils.py
index 3f00310..edecb1b 100644
--- a/ForgeImporters/forgeimporters/github/utils.py
+++ b/ForgeImporters/forgeimporters/github/utils.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import re
 
 
@@ -22,7 +23,7 @@ class GitHubMarkdownConverter(object):
 
     def __init__(self, gh_user, gh_project):
         self.gh_project = '%s/%s' % (gh_user, gh_project)
-        self.gh_base_url = u'https://github.com/'
+        self.gh_base_url = 'https://github.com/'
         self.code_patterns = ['```', '~~~']
 
     def convert(self, text):
@@ -110,10 +111,10 @@ class GitHubMarkdownConverter(object):
         return text
 
     def _gh_commit_url(self, project, sha, title):
-        return u'[%s](%s)' % (title, self.gh_base_url + project + '/commit/' + sha)
+        return '[%s](%s)' % (title, self.gh_base_url + project + '/commit/' + sha)
 
     def _gh_ticket_url(self, project, tid, title):
-        return u'[%s](%s)' % (title, self.gh_base_url + project + '/issues/' + str(tid))
+        return '[%s](%s)' % (title, self.gh_base_url + project + '/issues/' + str(tid))
 
     def _convert_sha(self, m):
         return '%s[%s]%s' % (m.group(1), m.group(2)[:6], m.group(3))
diff --git a/ForgeImporters/forgeimporters/github/wiki.py b/ForgeImporters/forgeimporters/github/wiki.py
index 220eef3..b1f7a38 100644
--- a/ForgeImporters/forgeimporters/github/wiki.py
+++ b/ForgeImporters/forgeimporters/github/wiki.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import os
 import re
 from datetime import datetime
@@ -69,7 +70,7 @@ log = logging.getLogger(__name__)
 class GitHubWikiImportForm(ToolImportForm):
     gh_project_name = GitHubProjectNameValidator()
     gh_user_name = fev.UnicodeString(not_empty=True)
-    tool_option = fev.UnicodeString(if_missing=u'')
+    tool_option = fev.UnicodeString(if_missing='')
 
 
 class GitHubWikiImportController(ToolImportController, GitHubOAuthMixin):
@@ -390,12 +391,12 @@ class GitHubWikiImporter(ToolImporter):
 
     def _gollum_external_link(self, link, title, options):
         if title:
-            return u'[{}]({})'.format(title, link)
-        return u'<{}>'.format(link)
+            return '[{}]({})'.format(title, link)
+        return '<{}>'.format(link)
 
     def _gollum_page_link(self, link, title, options):
         page = self._convert_page_name(link)
-        page = page.replace(u'&amp;', u'&')  # allow & in page links
+        page = page.replace('&amp;', '&')  # allow & in page links
         # gollum page lookups are case-insensitive, you'll always get link to
         # whatever comes first in the file system, no matter how you refer to a page.
         # E.g. if you have two pages: a.md and A.md both [[a]] and [[A]] will refer a.md.
@@ -409,8 +410,8 @@ class GitHubWikiImporter(ToolImporter):
             page = self.available_pages[idx]
 
         if title:
-            return u'[{}]({})'.format(title, page)
-        return u'[{}]'.format(page)
+            return '[{}]({})'.format(title, page)
+        return '[{}]'.format(page)
 
     def rewrite_links(self, html, prefix, new_prefix):
         if not prefix.endswith('/'):
diff --git a/ForgeImporters/forgeimporters/tests/forge/test_tracker.py b/ForgeImporters/forgeimporters/tests/forge/test_tracker.py
index 9be7743..99a098c 100644
--- a/ForgeImporters/forgeimporters/tests/forge/test_tracker.py
+++ b/ForgeImporters/forgeimporters/tests/forge/test_tracker.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 from datetime import datetime
 from unittest import TestCase
 
@@ -365,9 +366,9 @@ class TestForgeTrackerImportController(TestController, TestCase):
         self.assertEqual(r.location, 'http://localhost/p/test/admin/')
         sui.assert_called_once_with(project, 'tickets.json', '{"key": "val"}')
         self.assertEqual(
-            u'mymount', import_tool.post.call_args[1]['mount_point'])
+            'mymount', import_tool.post.call_args[1]['mount_point'])
         self.assertEqual(
-            u'mylabel', import_tool.post.call_args[1]['mount_label'])
+            'mylabel', import_tool.post.call_args[1]['mount_label'])
 
     @with_tracker
     @mock.patch('forgeimporters.forge.tracker.save_importer_upload')
diff --git a/ForgeImporters/forgeimporters/tests/github/functional/test_github.py b/ForgeImporters/forgeimporters/tests/github/functional/test_github.py
index c4d2bce..bfada9e 100644
--- a/ForgeImporters/forgeimporters/tests/github/functional/test_github.py
+++ b/ForgeImporters/forgeimporters/tests/github/functional/test_github.py
@@ -14,6 +14,7 @@
 #       KIND, either express or implied.  See the License for the
 #       specific language governing permissions and limitations
 #       under the License.
+from __future__ import unicode_literals
 import requests
 import tg
 from mock import patch, call, Mock
diff --git a/ForgeImporters/forgeimporters/tests/github/test_extractor.py b/ForgeImporters/forgeimporters/tests/github/test_extractor.py
index d91c25b..f4106aa 100644
--- a/ForgeImporters/forgeimporters/tests/github/test_extractor.py
+++ b/ForgeImporters/forgeimporters/tests/github/test_extractor.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import json
 from unittest import TestCase
 import urllib2
@@ -35,28 +36,28 @@ class TestGitHubProjectExtractor(TestCase):
         'has_wiki': True,
     }
     CLOSED_ISSUES_LIST = [
-        {u'number': 1},
-        {u'number': 2},
+        {'number': 1},
+        {'number': 2},
     ]
     OPENED_ISSUES_LIST = [
-        {u'number': 3},
-        {u'number': 4},
-        {u'number': 5},
+        {'number': 3},
+        {'number': 4},
+        {'number': 5},
     ]
     OPENED_ISSUES_LIST_PAGE2 = [
-        {u'number': 6},
-        {u'number': 7},
-        {u'number': 8},
+        {'number': 6},
+        {'number': 7},
+        {'number': 8},
     ]
-    ISSUE_COMMENTS = [u'hello', u'mocked_comment']
-    ISSUE_COMMENTS_PAGE2 = [u'hello2', u'mocked_comment2']
+    ISSUE_COMMENTS = ['hello', 'mocked_comment']
+    ISSUE_COMMENTS_PAGE2 = ['hello2', 'mocked_comment2']
     ISSUE_EVENTS = [
-        {u'event': u'closed'},
-        {u'event': u'reopened'},
+        {'event': 'closed'},
+        {'event': 'reopened'},
     ]
     ISSUE_EVENTS_PAGE2 = [
-        {u'event': u'assigned'},
-        {u'event': u'not-supported-event'},
+        {'event': 'assigned'},
+        {'event': 'not-supported-event'},
     ]
 
     def mocked_urlopen(self, url):
diff --git a/ForgeImporters/forgeimporters/tests/github/test_tasks.py b/ForgeImporters/forgeimporters/tests/github/test_tasks.py
index b048407..98effbd 100644
--- a/ForgeImporters/forgeimporters/tests/github/test_tasks.py
+++ b/ForgeImporters/forgeimporters/tests/github/test_tasks.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import mock
 
 from ...github import tasks
diff --git a/ForgeImporters/forgeimporters/tests/github/test_tracker.py b/ForgeImporters/forgeimporters/tests/github/test_tracker.py
index da6460b..47af12d 100644
--- a/ForgeImporters/forgeimporters/tests/github/test_tracker.py
+++ b/ForgeImporters/forgeimporters/tests/github/test_tracker.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 from datetime import datetime
 from unittest import TestCase
 from urllib2 import HTTPError
@@ -124,9 +125,9 @@ class TestTrackerImporter(TestCase):
                 'label': 'Milestone',
                 'milestones': [
                         {'name': 'first', 'due_date':
-                            u'2015-04-23', 'complete': False},
+                            '2015-04-23', 'complete': False},
                     {'name': 'second', 'due_date':
-                     u'2015-04-25', 'complete': False},
+                     '2015-04-25', 'complete': False},
                 ],
             },
         ])
diff --git a/ForgeImporters/forgeimporters/tests/test_base.py b/ForgeImporters/forgeimporters/tests/test_base.py
index cb96ed2..14a38b8 100644
--- a/ForgeImporters/forgeimporters/tests/test_base.py
+++ b/ForgeImporters/forgeimporters/tests/test_base.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 from unittest import TestCase
 import errno
 
diff --git a/ForgeImporters/forgeimporters/trac/__init__.py b/ForgeImporters/forgeimporters/trac/__init__.py
index fa733a6..dafc543 100644
--- a/ForgeImporters/forgeimporters/trac/__init__.py
+++ b/ForgeImporters/forgeimporters/trac/__init__.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 from formencode import validators as fev
 import requests
 
diff --git a/ForgeImporters/forgeimporters/trac/project.py b/ForgeImporters/forgeimporters/trac/project.py
index 0f44259..25c3f43 100644
--- a/ForgeImporters/forgeimporters/trac/project.py
+++ b/ForgeImporters/forgeimporters/trac/project.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import logging
 
 from tg import expose, validate
diff --git a/ForgeImporters/forgeimporters/trac/tests/functional/test_trac.py b/ForgeImporters/forgeimporters/trac/tests/functional/test_trac.py
index b6a640c..c8237cd 100644
--- a/ForgeImporters/forgeimporters/trac/tests/functional/test_trac.py
+++ b/ForgeImporters/forgeimporters/trac/tests/functional/test_trac.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 from mock import patch, Mock
 from nose.tools import assert_equal
 from tg import config
diff --git a/ForgeImporters/forgeimporters/trac/tests/test_tickets.py b/ForgeImporters/forgeimporters/trac/tests/test_tickets.py
index 1e98584..005f9f2 100644
--- a/ForgeImporters/forgeimporters/trac/tests/test_tickets.py
+++ b/ForgeImporters/forgeimporters/trac/tests/test_tickets.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import json
 import os
 
@@ -140,12 +141,12 @@ class TestTracTicketImportController(TestController, TestCase):
                           status=302)
         self.assertEqual(r.location, 'http://localhost/p/test/admin/')
         self.assertEqual(
-            u'mymount', import_tool.post.call_args[1]['mount_point'])
+            'mymount', import_tool.post.call_args[1]['mount_point'])
         self.assertEqual(
-            u'mylabel', import_tool.post.call_args[1]['mount_label'])
+            'mylabel', import_tool.post.call_args[1]['mount_label'])
         self.assertEqual('{"orig_user": "new_user"}',
                          import_tool.post.call_args[1]['user_map'])
-        self.assertEqual(u'http://example.com/trac/url/',
+        self.assertEqual('http://example.com/trac/url/',
                          import_tool.post.call_args[1]['trac_url'])
 
     @with_tracker
diff --git a/ForgeImporters/forgeimporters/trac/tickets.py b/ForgeImporters/forgeimporters/trac/tickets.py
index 50c867a..ebdc60a 100644
--- a/ForgeImporters/forgeimporters/trac/tickets.py
+++ b/ForgeImporters/forgeimporters/trac/tickets.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import json
 import re
 
diff --git a/ForgeImporters/setup.py b/ForgeImporters/setup.py
index 2ad2726..21c9d91 100644
--- a/ForgeImporters/setup.py
+++ b/ForgeImporters/setup.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 from setuptools import setup, find_packages
 
 
diff --git a/ForgeLink/forgelink/link_main.py b/ForgeLink/forgelink/link_main.py
index dbe6858..5e4af9d 100644
--- a/ForgeLink/forgelink/link_main.py
+++ b/ForgeLink/forgelink/link_main.py
@@ -16,6 +16,7 @@
 #       under the License.
 
 # -*- python -*-
+from __future__ import unicode_literals
 import logging
 import json
 
diff --git a/ForgeLink/forgelink/tests/functional/test_rest.py b/ForgeLink/forgelink/tests/functional/test_rest.py
index b2ac3b3..9678042 100644
--- a/ForgeLink/forgelink/tests/functional/test_rest.py
+++ b/ForgeLink/forgelink/tests/functional/test_rest.py
@@ -17,6 +17,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 from nose.tools import assert_equal
 from allura.tests import decorators as td
 from alluratest.controller import TestRestApiBase
@@ -35,20 +36,20 @@ class TestLinkApi(TestRestApiBase):
         h.set_context('test', 'link', neighborhood='Projects')
 
     def test_rest_link(self):
-        r = self.api_get(u'/rest/p/test/link'.encode('utf-8'))
+        r = self.api_get('/rest/p/test/link'.encode('utf-8'))
         assert_equal(r.json['url'], None)
 
-        r = self.api_post(u'/rest/p/test/link'.encode('utf-8'),
+        r = self.api_post('/rest/p/test/link'.encode('utf-8'),
                           url='http://google.com')
         assert_equal(r.json['url'], 'http://google.com')
 
-        self.api_post(u'/rest/p/test/link'.encode('utf-8'),
+        self.api_post('/rest/p/test/link'.encode('utf-8'),
                       url='http://yahoo.com')
-        r = self.api_get(u'/rest/p/test/link'.encode('utf-8'))
+        r = self.api_get('/rest/p/test/link'.encode('utf-8'))
         assert_equal(r.json['url'], 'http://yahoo.com')
 
-        self.api_post(u'/rest/p/test/link'.encode('utf-8'))
-        r = self.api_get(u'/rest/p/test/link'.encode('utf-8'))
+        self.api_post('/rest/p/test/link'.encode('utf-8'))
+        r = self.api_get('/rest/p/test/link'.encode('utf-8'))
         assert_equal(r.json['url'], 'http://yahoo.com')
 
     def test_rest_link_get_permissions(self):
@@ -76,7 +77,7 @@ class TestLinkApi(TestRestApiBase):
                       params={'url': 'http://yahoo.com'},
                       extra_environ={'username': '*anonymous'},
                       status=200)
-        r = self.api_get(u'/rest/p/test/link'.encode('utf-8'))
+        r = self.api_get('/rest/p/test/link'.encode('utf-8'))
         assert_equal(r.json['url'], 'http://yahoo.com')
 
 
diff --git a/ForgeLink/forgelink/tests/functional/test_root.py b/ForgeLink/forgelink/tests/functional/test_root.py
index 0167fda..ad82051 100644
--- a/ForgeLink/forgelink/tests/functional/test_root.py
+++ b/ForgeLink/forgelink/tests/functional/test_root.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import json
 
 from nose.tools import assert_equal, assert_in
@@ -136,7 +137,7 @@ class TestConfigOptions(TestController):
                           menu_item['admin_options'])
                 break
         else:
-            raise AssertionError(u"Didn't find 'link' tool in {}".format(admin_nav_data['menu']))
+            raise AssertionError("Didn't find 'link' tool in {}".format(admin_nav_data['menu']))
 
     @td.with_link
     def test_menu_configurable(self):
diff --git a/ForgeLink/forgelink/tests/test_app.py b/ForgeLink/forgelink/tests/test_app.py
index 2976341..4950f20 100644
--- a/ForgeLink/forgelink/tests/test_app.py
+++ b/ForgeLink/forgelink/tests/test_app.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import tempfile
 import json
 
diff --git a/ForgeLink/forgelink/version.py b/ForgeLink/forgelink/version.py
index 1b493f8..192acec 100644
--- a/ForgeLink/forgelink/version.py
+++ b/ForgeLink/forgelink/version.py
@@ -15,5 +15,6 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 __version_info__ = (0, 0)
 __version__ = '.'.join(map(str, __version_info__))
diff --git a/ForgeLink/setup.py b/ForgeLink/setup.py
index fc91792..7e10d92 100644
--- a/ForgeLink/setup.py
+++ b/ForgeLink/setup.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 from setuptools import setup, find_packages
 
 from forgelink.version import __version__
diff --git a/ForgeSVN/forgesvn/controllers.py b/ForgeSVN/forgesvn/controllers.py
index 8a13c7a..b8cdbd6 100644
--- a/ForgeSVN/forgesvn/controllers.py
+++ b/ForgeSVN/forgesvn/controllers.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 from tg import expose, redirect
 from tg.decorators import with_trailing_slash
 from tg import tmpl_context as c
diff --git a/ForgeSVN/forgesvn/model/svn.py b/ForgeSVN/forgesvn/model/svn.py
index 0e8a2d0..fdf7bf1 100644
--- a/ForgeSVN/forgesvn/model/svn.py
+++ b/ForgeSVN/forgesvn/model/svn.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import re
 import os
 import shutil
diff --git a/ForgeSVN/forgesvn/svn_main.py b/ForgeSVN/forgesvn/svn_main.py
index 8c97630..24ee714 100644
--- a/ForgeSVN/forgesvn/svn_main.py
+++ b/ForgeSVN/forgesvn/svn_main.py
@@ -16,6 +16,7 @@
 #       under the License.
 
 #-*- python -*-
+from __future__ import unicode_literals
 import logging
 from tg import tmpl_context as c, request
 
diff --git a/ForgeSVN/forgesvn/tests/__init__.py b/ForgeSVN/forgesvn/tests/__init__.py
index d152a9e..172a607 100644
--- a/ForgeSVN/forgesvn/tests/__init__.py
+++ b/ForgeSVN/forgesvn/tests/__init__.py
@@ -19,6 +19,7 @@
 
 
 # Make our own SVN tool test decorator
+from __future__ import unicode_literals
 from allura.tests.decorators import with_tool
 
 with_svn = with_tool('test', 'SVN', 'src', 'SVN')
diff --git a/ForgeSVN/forgesvn/tests/functional/test_auth.py b/ForgeSVN/forgesvn/tests/functional/test_auth.py
index 7394c1b..b7b350a 100644
--- a/ForgeSVN/forgesvn/tests/functional/test_auth.py
+++ b/ForgeSVN/forgesvn/tests/functional/test_auth.py
@@ -17,6 +17,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import json
 from datadiff.tools import assert_equal
 
diff --git a/ForgeSVN/forgesvn/tests/functional/test_controllers.py b/ForgeSVN/forgesvn/tests/functional/test_controllers.py
index 3824e71..91291b4 100644
--- a/ForgeSVN/forgesvn/tests/functional/test_controllers.py
+++ b/ForgeSVN/forgesvn/tests/functional/test_controllers.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import json
 import shutil
 import os
@@ -229,12 +230,12 @@ class TestRootController(SVNTestController):
         shutil.rmtree(c.app.repo.tarball_path, ignore_errors=True)
         r = self.app.get('/p/test/svn-tags/19/tree/')
         form = r.html.find('form', 'tarball')
-        assert_equal(form.button.text, u'\xa0Download Snapshot')
+        assert_equal(form.button.text, '\xa0Download Snapshot')
         assert_equal(form.get('action'), '/p/test/svn-tags/19/tarball')
 
         r = self.app.get('/p/test/svn-tags/19/tree/tags/tag-1.0/')
         form = r.html.find('form', 'tarball')
-        assert_equal(form.button.text, u'\xa0Download Snapshot')
+        assert_equal(form.button.text, '\xa0Download Snapshot')
         assert_equal(form.get('action'), '/p/test/svn-tags/19/tarball')
         assert_equal(form.find('input', attrs=dict(name='path')).get('value'), '/tags/tag-1.0')
 
diff --git a/ForgeSVN/forgesvn/tests/model/test_repository.py b/ForgeSVN/forgesvn/tests/model/test_repository.py
index d4323f0..3135697 100644
--- a/ForgeSVN/forgesvn/tests/model/test_repository.py
+++ b/ForgeSVN/forgesvn/tests/model/test_repository.py
@@ -16,6 +16,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import os
 import shutil
 import unittest
@@ -241,83 +242,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}])
 
@@ -326,24 +327,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,
@@ -404,7 +405,7 @@ class TestSVNRepo(unittest.TestCase, RepoImplTestBase):
     def test_diff_copy(self):
         entry = self.repo.commit(self.repo.log(5, id_only=True, limit=1).next())
         assert_equals(dict(entry.diffs), dict(
-                copied=[{'new': u'/b', 'old': u'/a', 'ratio': 1}],  renamed=[],
+                copied=[{'new': '/b', 'old': '/a', 'ratio': 1}],  renamed=[],
                 changed=[], removed=[], added=[], total=1))
 
     def test_commit(self):
@@ -522,44 +523,44 @@ 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': [],
                 'renamed': [],
             }, {
-                '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', 'ratio': 1},
+                    {'new': '/b', 'old': '/a', 'ratio': 1},
                 ],
                 'renamed': [],
             }],
             '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)
@@ -635,11 +636,11 @@ 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({u'subject': u'[test:src] New commit [r1] by rick446'}).first()
+        n = M.Notification.query.find({'subject': '[test:src] New commit [r1] by rick446'}).first()
 
         assert n
-        assert_in(u'By rick446', n.text)
-        assert_in(u'Create readme', n.text)
+        assert_in('By rick446', n.text)
+        assert_in('Create readme', n.text)
 
 
 class _Test(unittest.TestCase):
@@ -816,8 +817,8 @@ class TestRepo(_TestWithRepo):
         notifications = M.Notification.query.find().all()
         for n in notifications:
             if '100 new commits' in n.subject:
-                assert_in(u'By Test Committer on 10/08/2010 15:32', n.text)
-                assert_in(u'http://localhost/ci/foo99/', n.text)
+                assert_in('By Test Committer on 10/08/2010 15:32', n.text)
+                assert_in('http://localhost/ci/foo99/', n.text)
                 break
         else:
             assert False, 'Did not find notification'
@@ -921,13 +922,13 @@ class TestCommit(_TestWithRepo):
     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
             return StringIO(blobs.get(blob.path(), ''))
@@ -981,19 +982,19 @@ class TestCommit(_TestWithRepo):
         ci.parent_ids = ['bar']
         self._make_log(ci)
         self.repo._impl.paged_diffs.return_value = {
-            'added': [u'b/c', u'b/a/z'],
-            'removed': [u'/b/a/b', u'b/b'],
+            'added': ['b/c', 'b/a/z'],
+            'removed': ['/b/a/b', 'b/b'],
             'changed': [],
             'copied': [
                 {
-                    'new': u'b/c',
-                    'old': u'b/a/b',
+                    'new': 'b/c',
+                    'old': 'b/a/b',
                     'ratio': 1,
                     'diff': '',
                 },
                 {
-                    'new': u'b/a/z',
-                    'old': u'b/b',
+                    'new': 'b/a/z',
+                    'old': 'b/b',
                     'ratio': 1,
                     'diff': '',
                 },
@@ -1001,9 +1002,9 @@ class TestCommit(_TestWithRepo):
             'renamed': [],
             'total': 2
         }
-        assert_equal(ci.diffs.added, [u'b/a/z', u'b/c'])
+        assert_equal(ci.diffs.added, ['b/a/z', 'b/c'])
         assert_equal(ci.diffs.changed, [])
-        assert_equal(ci.diffs.removed, [u'/b/a/b', u'b/b'])
+        assert_equal(ci.diffs.removed, ['/b/a/b', 'b/b'])
         # see mock for open_blob
         assert_equal(len(ci.diffs.copied), 2)
         assert_equal(ci.diffs.copied[1]['old'], 'b/a/b')
@@ -1077,7 +1078,7 @@ class TestDirectRepoAccess(object):
     def test_paged_diffs(self):
         diffs = self.rev.diffs
         expected = {
-            'added': [u'/ЗРЯЧИЙ_ТА_ПОБАЧИТЬ'],
+            'added': ['/ЗРЯЧИЙ_ТА_ПОБАЧИТЬ'],
             'removed': [],
             'changed': [],
             'copied': [],
@@ -1089,7 +1090,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': [],
             'renamed': [],
@@ -1104,7 +1105,7 @@ class TestDirectRepoAccess(object):
             'added': [],
             'removed': [],
             'renamed': [],
-            'changed': [u'/README'],
+            'changed': ['/README'],
             'copied': [],
             'total': 1,
         }
diff --git a/ForgeSVN/forgesvn/tests/model/test_svnimplementation.py b/ForgeSVN/forgesvn/tests/model/test_svnimplementation.py
index 99fed05..9efaee7 100644
--- a/ForgeSVN/forgesvn/tests/model/test_svnimplementation.py
+++ b/ForgeSVN/forgesvn/tests/model/test_svnimplementation.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 from mock import Mock, patch
 from nose.tools import assert_equal
 from tg import app_globals as g
diff --git a/ForgeSVN/forgesvn/tests/test_svn_app.py b/ForgeSVN/forgesvn/tests/test_svn_app.py
index a962424..a8d382b 100644
--- a/ForgeSVN/forgesvn/tests/test_svn_app.py
+++ b/ForgeSVN/forgesvn/tests/test_svn_app.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import unittest
 from nose.tools import assert_equals
 
diff --git a/ForgeSVN/forgesvn/tests/test_tasks.py b/ForgeSVN/forgesvn/tests/test_tasks.py
index 2e0e99b..c041027 100644
--- a/ForgeSVN/forgesvn/tests/test_tasks.py
+++ b/ForgeSVN/forgesvn/tests/test_tasks.py
@@ -17,6 +17,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import shutil
 import unittest
 import os
diff --git a/ForgeSVN/forgesvn/version.py b/ForgeSVN/forgesvn/version.py
index 1b493f8..192acec 100644
--- a/ForgeSVN/forgesvn/version.py
+++ b/ForgeSVN/forgesvn/version.py
@@ -15,5 +15,6 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 __version_info__ = (0, 0)
 __version__ = '.'.join(map(str, __version_info__))
diff --git a/ForgeSVN/forgesvn/widgets.py b/ForgeSVN/forgesvn/widgets.py
index c0c880f..591174c 100644
--- a/ForgeSVN/forgesvn/widgets.py
+++ b/ForgeSVN/forgesvn/widgets.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import re
 
 import ew as ew_core
diff --git a/ForgeSVN/setup.py b/ForgeSVN/setup.py
index 40dcf73..0a0314a 100644
--- a/ForgeSVN/setup.py
+++ b/ForgeSVN/setup.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 from setuptools import setup, find_packages
 
 from forgesvn.version import __version__
diff --git a/ForgeShortUrl/forgeshorturl/main.py b/ForgeShortUrl/forgeshorturl/main.py
index 66a5075..db01bf5 100644
--- a/ForgeShortUrl/forgeshorturl/main.py
+++ b/ForgeShortUrl/forgeshorturl/main.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 from tg import expose, validate, redirect, flash, request
 from tg.decorators import without_trailing_slash
 
diff --git a/ForgeShortUrl/forgeshorturl/model/shorturl.py b/ForgeShortUrl/forgeshorturl/model/shorturl.py
index e8b0c29..b1c2559 100644
--- a/ForgeShortUrl/forgeshorturl/model/shorturl.py
+++ b/ForgeShortUrl/forgeshorturl/model/shorturl.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import pymongo
 from tg import config
 from tg import tmpl_context as c
diff --git a/ForgeShortUrl/forgeshorturl/tests/functional/test.py b/ForgeShortUrl/forgeshorturl/tests/functional/test.py
index d4aa712..059a348 100644
--- a/ForgeShortUrl/forgeshorturl/tests/functional/test.py
+++ b/ForgeShortUrl/forgeshorturl/tests/functional/test.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 from tg import tmpl_context as c
 from tg import config
 from nose.tools import assert_equal
diff --git a/ForgeShortUrl/forgeshorturl/widgets/short_url.py b/ForgeShortUrl/forgeshorturl/widgets/short_url.py
index 0094402..a17086e 100644
--- a/ForgeShortUrl/forgeshorturl/widgets/short_url.py
+++ b/ForgeShortUrl/forgeshorturl/widgets/short_url.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 from allura.lib.widgets import form_fields as ffw
 
 
diff --git a/ForgeShortUrl/setup.py b/ForgeShortUrl/setup.py
index 7450912..61a618b 100644
--- a/ForgeShortUrl/setup.py
+++ b/ForgeShortUrl/setup.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 from setuptools import setup, find_packages
 
 
diff --git a/ForgeTracker/forgetracker/command/fix_discussion.py b/ForgeTracker/forgetracker/command/fix_discussion.py
index a0d090d..a85add1 100644
--- a/ForgeTracker/forgetracker/command/fix_discussion.py
+++ b/ForgeTracker/forgetracker/command/fix_discussion.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 from bson import ObjectId
 from bson.errors import InvalidId
 from ming.orm import ThreadLocalORMSession
diff --git a/ForgeTracker/forgetracker/config/resources.py b/ForgeTracker/forgetracker/config/resources.py
index c62d4c4..95b4eaf 100644
--- a/ForgeTracker/forgetracker/config/resources.py
+++ b/ForgeTracker/forgetracker/config/resources.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import pkg_resources
 
 
diff --git a/ForgeTracker/forgetracker/import_support.py b/ForgeTracker/forgetracker/import_support.py
index bc2aa73..c0f5f89 100644
--- a/ForgeTracker/forgetracker/import_support.py
+++ b/ForgeTracker/forgetracker/import_support.py
@@ -16,6 +16,7 @@
 #       under the License.
 
 #-*- python -*-
+from __future__ import unicode_literals
 import logging
 import json
 from datetime import datetime
@@ -215,12 +216,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 +259,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
diff --git a/ForgeTracker/forgetracker/model/ticket.py b/ForgeTracker/forgetracker/model/ticket.py
index a3b5f46..52563d7 100644
--- a/ForgeTracker/forgetracker/model/ticket.py
+++ b/ForgeTracker/forgetracker/model/ticket.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import logging
 import urllib
 import json
@@ -84,7 +85,7 @@ SOLR_TYPE_DEFAULTS = dict(_b=False, _d=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',
diff --git a/ForgeTracker/forgetracker/search.py b/ForgeTracker/forgetracker/search.py
index 86e46b4..088d9bf 100644
--- a/ForgeTracker/forgetracker/search.py
+++ b/ForgeTracker/forgetracker/search.py
@@ -14,6 +14,7 @@
 #       KIND, either express or implied.  See the License for the
 #       specific language governing permissions and limitations
 #       under the License.
+from __future__ import unicode_literals
 from tg import tmpl_context as c
 
 from allura.lib.search import search
diff --git a/ForgeTracker/forgetracker/site_stats.py b/ForgeTracker/forgetracker/site_stats.py
index f9f36ce..69f552a 100644
--- a/ForgeTracker/forgetracker/site_stats.py
+++ b/ForgeTracker/forgetracker/site_stats.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 from datetime import datetime, timedelta
 
 from bson import ObjectId
diff --git a/ForgeTracker/forgetracker/tasks.py b/ForgeTracker/forgetracker/tasks.py
index 5ad4214..d3217a0 100644
--- a/ForgeTracker/forgetracker/tasks.py
+++ b/ForgeTracker/forgetracker/tasks.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import logging
 from datetime import datetime
 
diff --git a/ForgeTracker/forgetracker/tests/command/test_fix_discussion.py b/ForgeTracker/forgetracker/tests/command/test_fix_discussion.py
index 8560556..161852e 100644
--- a/ForgeTracker/forgetracker/tests/command/test_fix_discussion.py
+++ b/ForgeTracker/forgetracker/tests/command/test_fix_discussion.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 from ming.orm import session
 from nose.tools import assert_equal, assert_not_equal
 import pkg_resources
diff --git a/ForgeTracker/forgetracker/tests/functional/test_rest.py b/ForgeTracker/forgetracker/tests/functional/test_rest.py
index b44a881..8dcd154 100644
--- a/ForgeTracker/forgetracker/tests/functional/test_rest.py
+++ b/ForgeTracker/forgetracker/tests/functional/test_rest.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 from tg import tmpl_context as c
 
 from datadiff.tools import assert_equal
diff --git a/ForgeTracker/forgetracker/tests/functional/test_root.py b/ForgeTracker/forgetracker/tests/functional/test_root.py
index 4456465..8a62959 100644
--- a/ForgeTracker/forgetracker/tests/functional/test_root.py
+++ b/ForgeTracker/forgetracker/tests/functional/test_root.py
@@ -16,6 +16,7 @@
 #       KIND, either express or implied.  See the License for the
 #       specific language governing permissions and limitations
 #       under the License.
+from __future__ import unicode_literals
 from datetime import datetime
 import urllib
 import os
@@ -577,12 +578,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}
@@ -781,7 +782,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'], ['icon', 'sidebar-disabled'])
 
     def test_render_markdown_syntax(self):
@@ -794,8 +795,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)
 
@@ -842,12 +843,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',
@@ -1136,9 +1137,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(
@@ -1153,9 +1154,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(
@@ -1661,7 +1662,7 @@ class TestFunctionalController(TrackerTestController):
         assert_false('test third ticket' in str(ticket_rows))
 
     def test_new_ticket_notification_contains_attachments(self):
-        file_name = u'tést_root.py'.encode('utf-8')
+        file_name = 'tést_root.py'.encode('utf-8')
         file_data = file(__file__).read()
         upload = ('ticket_form.attachment', file_name, file_data)
         r = self.app.post('/bugs/save_ticket', {
@@ -1675,9 +1676,9 @@ class TestFunctionalController(TrackerTestController):
             dict(task_name='allura.tasks.mail_tasks.sendmail')
         ).first()
         expected_text = (
-            u'**Attachments:**\n\n'
-            u'- [tést_root.py]'
-            u'(http://localhost/p/test/bugs/1/attachment/t%C3%A9st_root.py)')
+            '**Attachments:**\n\n'
+            '- [tést_root.py]'
+            '(http://localhost/p/test/bugs/1/attachment/t%C3%A9st_root.py)')
         assert_in(expected_text, email.kwargs['text'])
 
     def test_ticket_notification_contains_milestones(self):
@@ -1788,9 +1789,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)
@@ -2029,7 +2030,7 @@ class TestFunctionalController(TrackerTestController):
         ticket_url = r.headers['Location']
         r = self.app.get(ticket_url, extra_environ=dict(username='*anonymous'))
         a = r.html.find('a', {'class': 'icon edit_ticket'})
-        assert_equal(a.text, u'\xa0Edit')
+        assert_equal(a.text, '\xa0Edit')
 
     def test_ticket_creator_cant_edit_private_ticket_without_update_perm(self):
         p = M.Project.query.get(shortname='test')
@@ -3357,9 +3358,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)
diff --git a/ForgeTracker/forgetracker/tests/test_app.py b/ForgeTracker/forgetracker/tests/test_app.py
index 9128d30..b349988 100644
--- a/ForgeTracker/forgetracker/tests/test_app.py
+++ b/ForgeTracker/forgetracker/tests/test_app.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import tempfile
 import json
 import operator
diff --git a/ForgeTracker/forgetracker/tests/test_tracker_roles.py b/ForgeTracker/forgetracker/tests/test_tracker_roles.py
index 242646c..2cc33cd 100644
--- a/ForgeTracker/forgetracker/tests/test_tracker_roles.py
+++ b/ForgeTracker/forgetracker/tests/test_tracker_roles.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 from tg import tmpl_context as c, app_globals as g
 
 from alluratest.controller import setup_basic_test, setup_global_objects
diff --git a/ForgeTracker/forgetracker/tests/unit/__init__.py b/ForgeTracker/forgetracker/tests/unit/__init__.py
index 1ef9b92..7065226 100644
--- a/ForgeTracker/forgetracker/tests/unit/__init__.py
+++ b/ForgeTracker/forgetracker/tests/unit/__init__.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 from tg import tmpl_context as c
 import tg
 from ming.orm.ormsession import ThreadLocalORMSession
diff --git a/ForgeTracker/forgetracker/tests/unit/test_globals_model.py b/ForgeTracker/forgetracker/tests/unit/test_globals_model.py
index d1daad3..93ecf32 100644
--- a/ForgeTracker/forgetracker/tests/unit/test_globals_model.py
+++ b/ForgeTracker/forgetracker/tests/unit/test_globals_model.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 from datetime import datetime, timedelta
 
 import mock
diff --git a/ForgeTracker/forgetracker/tests/unit/test_milestone_controller.py b/ForgeTracker/forgetracker/tests/unit/test_milestone_controller.py
index e7c36c5..579b805 100644
--- a/ForgeTracker/forgetracker/tests/unit/test_milestone_controller.py
+++ b/ForgeTracker/forgetracker/tests/unit/test_milestone_controller.py
@@ -18,6 +18,7 @@
 #       under the License.
 
 
+from __future__ import unicode_literals
 from mock import Mock
 from nose.tools import assert_equal
 
@@ -29,7 +30,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 +44,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, 'Перспектива')
diff --git a/ForgeTracker/forgetracker/tests/unit/test_root_controller.py b/ForgeTracker/forgetracker/tests/unit/test_root_controller.py
index c7bef18..fe8c304 100644
--- a/ForgeTracker/forgetracker/tests/unit/test_root_controller.py
+++ b/ForgeTracker/forgetracker/tests/unit/test_root_controller.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import unittest
 
 from mock import Mock, patch
diff --git a/ForgeTracker/forgetracker/tests/unit/test_search.py b/ForgeTracker/forgetracker/tests/unit/test_search.py
index e991c28..421a780 100644
--- a/ForgeTracker/forgetracker/tests/unit/test_search.py
+++ b/ForgeTracker/forgetracker/tests/unit/test_search.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import mock
 from nose.tools import assert_equal
 from forgetracker.search import get_facets, query_filter_choices
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 60576d1..b97a4c8 100644
--- a/ForgeTracker/forgetracker/tests/unit/test_ticket_custom_fields_form.py
+++ b/ForgeTracker/forgetracker/tests/unit/test_ticket_custom_fields_form.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 from ming.orm.ormsession import ThreadLocalORMSession
 from ming.base import Object
 
diff --git a/ForgeTracker/forgetracker/tests/unit/test_ticket_form.py b/ForgeTracker/forgetracker/tests/unit/test_ticket_form.py
index a11a088..982e801 100644
--- a/ForgeTracker/forgetracker/tests/unit/test_ticket_form.py
+++ b/ForgeTracker/forgetracker/tests/unit/test_ticket_form.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 from ming.orm.ormsession import ThreadLocalORMSession
 
 from tg import tmpl_context as c
diff --git a/ForgeTracker/forgetracker/tests/unit/test_ticket_model.py b/ForgeTracker/forgetracker/tests/unit/test_ticket_model.py
index 99ba7de..55b5f18 100644
--- a/ForgeTracker/forgetracker/tests/unit/test_ticket_model.py
+++ b/ForgeTracker/forgetracker/tests/unit/test_ticket_model.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 from tg import tmpl_context as c
 from datetime import datetime
 import urllib2
diff --git a/ForgeTracker/forgetracker/tracker_main.py b/ForgeTracker/forgetracker/tracker_main.py
index f7f6c63..02cfbaf 100644
--- a/ForgeTracker/forgetracker/tracker_main.py
+++ b/ForgeTracker/forgetracker/tracker_main.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import logging
 import re
 from datetime import datetime, timedelta
@@ -157,11 +158,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):
@@ -857,7 +858,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:
@@ -939,7 +940,7 @@ class RootController(BaseController, FeedController):
             self.rate_limit(TM.Ticket, 'Ticket creation', redir='.')
             ticket = TM.Ticket.new(form_fields=ticket_form)
         ticket.update_fields_finish(ticket_form)
-        g.spam_checker.check(ticket_form['summary'] + u'\n' + ticket_form.get('description', ''), artifact=ticket,
+        g.spam_checker.check(ticket_form['summary'] + '\n' + ticket_form.get('description', ''), artifact=ticket,
                              user=c.user, content_type='ticket')
         c.app.globals.invalidate_bin_counts()
         notification_tasks.send_usermentions_notification.post(ticket.index_id(), ticket_form.get('description', ''))
@@ -1448,7 +1449,7 @@ class TicketController(BaseController, FeedController):
         require_access(self.ticket, 'update')
         old_text = self.ticket.description
         new_text = post_data.get('description', '')
-        g.spam_checker.check(post_data.get('summary', '') + u'\n' + post_data.get('description', ''),
+        g.spam_checker.check(post_data.get('summary', '') + '\n' + post_data.get('description', ''),
                              artifact=self.ticket, user=c.user, content_type='ticket')
         changes = changelog()
         comment = post_data.pop('comment', None)
diff --git a/ForgeTracker/forgetracker/version.py b/ForgeTracker/forgetracker/version.py
index 1b493f8..192acec 100644
--- a/ForgeTracker/forgetracker/version.py
+++ b/ForgeTracker/forgetracker/version.py
@@ -15,5 +15,6 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 __version_info__ = (0, 0)
 __version__ = '.'.join(map(str, __version_info__))
diff --git a/ForgeTracker/forgetracker/widgets/admin.py b/ForgeTracker/forgetracker/widgets/admin.py
index 586ce6f..25ab534 100644
--- a/ForgeTracker/forgetracker/widgets/admin.py
+++ b/ForgeTracker/forgetracker/widgets/admin.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import ew.jinja2_ew as ew
 
 from formencode import validators as fev
diff --git a/ForgeTracker/forgetracker/widgets/admin_custom_fields.py b/ForgeTracker/forgetracker/widgets/admin_custom_fields.py
index 19b55ec..04d1308 100644
--- a/ForgeTracker/forgetracker/widgets/admin_custom_fields.py
+++ b/ForgeTracker/forgetracker/widgets/admin_custom_fields.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import ew as ew_core
 import ew.jinja2_ew as ew
 
diff --git a/ForgeTracker/forgetracker/widgets/bin_form.py b/ForgeTracker/forgetracker/widgets/bin_form.py
index a62026e..bda0d78 100644
--- a/ForgeTracker/forgetracker/widgets/bin_form.py
+++ b/ForgeTracker/forgetracker/widgets/bin_form.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import ew
 from ew import jinja2_ew
 from allura.lib import validators as V
diff --git a/ForgeTracker/forgetracker/widgets/ticket_form.py b/ForgeTracker/forgetracker/widgets/ticket_form.py
index 64c0863..297bb33 100644
--- a/ForgeTracker/forgetracker/widgets/ticket_form.py
+++ b/ForgeTracker/forgetracker/widgets/ticket_form.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 from tg import tmpl_context as c
 from formencode import validators as fev
 from webhelpers.html.builder import literal
diff --git a/ForgeTracker/forgetracker/widgets/ticket_search.py b/ForgeTracker/forgetracker/widgets/ticket_search.py
index cb6d989..cd6ebf1 100644
--- a/ForgeTracker/forgetracker/widgets/ticket_search.py
+++ b/ForgeTracker/forgetracker/widgets/ticket_search.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import ew as ew_core
 import ew.jinja2_ew as ew
 
diff --git a/ForgeTracker/setup.py b/ForgeTracker/setup.py
index d3e00cc..7837afb 100644
--- a/ForgeTracker/setup.py
+++ b/ForgeTracker/setup.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 from setuptools import setup, find_packages
 
 from forgetracker.version import __version__
diff --git a/ForgeUserStats/forgeuserstats/controllers/userstats.py b/ForgeUserStats/forgeuserstats/controllers/userstats.py
index 8ab2db2..c885386 100644
--- a/ForgeUserStats/forgeuserstats/controllers/userstats.py
+++ b/ForgeUserStats/forgeuserstats/controllers/userstats.py
@@ -14,6 +14,7 @@
 #       KIND, either express or implied.  See the License for the
 #       specific language governing permissions and limitations
 #       under the License.
+from __future__ import unicode_literals
 from datetime import datetime
 import re
 
diff --git a/ForgeUserStats/forgeuserstats/main.py b/ForgeUserStats/forgeuserstats/main.py
index 580b3a6..0629d8b 100644
--- a/ForgeUserStats/forgeuserstats/main.py
+++ b/ForgeUserStats/forgeuserstats/main.py
@@ -16,6 +16,7 @@
 #       under the License.
 
 #-*- python -*-
+from __future__ import unicode_literals
 import logging
 from tg import tmpl_context as c
 from datetime import datetime
diff --git a/ForgeUserStats/forgeuserstats/model/stats.py b/ForgeUserStats/forgeuserstats/model/stats.py
index 340f857..4ac9bd6 100644
--- a/ForgeUserStats/forgeuserstats/model/stats.py
+++ b/ForgeUserStats/forgeuserstats/model/stats.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 from ming.orm import FieldProperty
 from ming import schema as S
 from datetime import datetime, timedelta
diff --git a/ForgeUserStats/forgeuserstats/tests/test_model.py b/ForgeUserStats/forgeuserstats/tests/test_model.py
index b9eff67..da5a5dd 100644
--- a/ForgeUserStats/forgeuserstats/tests/test_model.py
+++ b/ForgeUserStats/forgeuserstats/tests/test_model.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import pkg_resources
 import unittest
 from datetime import datetime, timedelta
diff --git a/ForgeUserStats/forgeuserstats/tests/test_stats.py b/ForgeUserStats/forgeuserstats/tests/test_stats.py
index aa08385..c858d78 100644
--- a/ForgeUserStats/forgeuserstats/tests/test_stats.py
+++ b/ForgeUserStats/forgeuserstats/tests/test_stats.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import pkg_resources
 import unittest
 
diff --git a/ForgeUserStats/forgeuserstats/version.py b/ForgeUserStats/forgeuserstats/version.py
index 1b493f8..192acec 100644
--- a/ForgeUserStats/forgeuserstats/version.py
+++ b/ForgeUserStats/forgeuserstats/version.py
@@ -15,5 +15,6 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 __version_info__ = (0, 0)
 __version__ = '.'.join(map(str, __version_info__))
diff --git a/ForgeUserStats/forgeuserstats/widgets/forms.py b/ForgeUserStats/forgeuserstats/widgets/forms.py
index a807e8c..7b73715 100644
--- a/ForgeUserStats/forgeuserstats/widgets/forms.py
+++ b/ForgeUserStats/forgeuserstats/widgets/forms.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 from allura.lib.widgets.forms import ForgeForm
 
 import ew as ew_core
diff --git a/ForgeUserStats/setup.py b/ForgeUserStats/setup.py
index 1ad5c3a..df68a2b 100644
--- a/ForgeUserStats/setup.py
+++ b/ForgeUserStats/setup.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 from setuptools import setup, find_packages
 
 from forgeuserstats.version import __version__
diff --git a/ForgeWiki/forgewiki/converters.py b/ForgeWiki/forgewiki/converters.py
index c4fea2f..c4dd2f3 100644
--- a/ForgeWiki/forgewiki/converters.py
+++ b/ForgeWiki/forgewiki/converters.py
@@ -16,6 +16,7 @@
 #       under the License.
 
 #-*- python -*-
+from __future__ import unicode_literals
 import re
 from bs4 import BeautifulSoup
 import six
diff --git a/ForgeWiki/forgewiki/model/wiki.py b/ForgeWiki/forgewiki/model/wiki.py
index a9240c3..2d1b71c 100644
--- a/ForgeWiki/forgewiki/model/wiki.py
+++ b/ForgeWiki/forgewiki/model/wiki.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 from datetime import datetime
 import difflib
 import os
diff --git a/ForgeWiki/forgewiki/tests/functional/test_rest.py b/ForgeWiki/forgewiki/tests/functional/test_rest.py
index b06aa3d..9e804a7 100644
--- a/ForgeWiki/forgewiki/tests/functional/test_rest.py
+++ b/ForgeWiki/forgewiki/tests/functional/test_rest.py
@@ -17,6 +17,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import json
 
 from nose.tools import assert_equal, assert_in, assert_not_equal
@@ -82,9 +83,9 @@ class TestWikiApi(TestRestApiBase):
             'text': 'Embrace the Dark Side',
             'labels': 'head hunting,dark side'
         }
-        r = self.api_post(u'/rest/p/test/wiki/tést/'.encode('utf-8'), **data)
+        r = self.api_post('/rest/p/test/wiki/tést/'.encode('utf-8'), **data)
         assert_equal(r.status_int, 200)
-        r = self.api_get(u'/rest/p/test/wiki/tést/'.encode('utf-8'))
+        r = self.api_get('/rest/p/test/wiki/tést/'.encode('utf-8'))
         assert_equal(r.json['text'], data['text'])
         assert_equal(r.json['labels'], data['labels'].split(','))
 
@@ -95,12 +96,12 @@ class TestWikiApi(TestRestApiBase):
         }
         # Set rate limit to unlimit
         with h.push_config(tg.config, **{'forgewiki.rate_limits': '{}'}):
-            r = self.api_post(u'/rest/p/test/wiki/page1/', status=200, **data)
+            r = self.api_post('/rest/p/test/wiki/page1/', status=200, **data)
             p = Page.query.get(title='page1')
             assert_not_equal(p, None)
         # Set rate limit to 1 in first hour of project
         with h.push_config(tg.config, **{'forgewiki.rate_limits': '{"3600": 1}'}):
-            r = self.api_post(u'/rest/p/test/wiki/page2/', status=429, **data)
+            r = self.api_post('/rest/p/test/wiki/page2/', status=429, **data)
             p = Page.query.get(title='page2')
             assert_equal(p, None)
 
@@ -116,7 +117,7 @@ class TestWikiApi(TestRestApiBase):
 
     def test_json_encoding_directly(self):
         # used in @expose('json')
-        assert_equal(tg.jsonify.encode('<'), '"\u003C"')
+        assert_equal(tg.jsonify.encode('<'), '"\\u003C"')
         # make sure these are unchanged
         assert_equal(json.dumps('<'), '"<"')
 
diff --git a/ForgeWiki/forgewiki/tests/functional/test_root.py b/ForgeWiki/forgewiki/tests/functional/test_root.py
index 29be374..f2fd42c 100644
--- a/ForgeWiki/forgewiki/tests/functional/test_root.py
+++ b/ForgeWiki/forgewiki/tests/functional/test_root.py
@@ -17,6 +17,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import os
 import StringIO
 import allura
@@ -52,9 +53,9 @@ class TestRootController(TestController):
         return self.find_form(resp, cond)
 
     def test_root_index(self):
-        page_url = h.urlquote(u'/wiki/tést/')
+        page_url = h.urlquote('/wiki/tést/')
         r = self.app.get(page_url).follow()
-        assert u'tést' in r
+        assert 'tést' in r
         assert 'Create Page' in r
         # No 'Create Page' button if user doesn't have 'create' perm
         r = self.app.get('/wiki/Home',
@@ -62,10 +63,10 @@ class TestRootController(TestController):
         assert 'Create Page' not in r, r
 
     def test_create_wiki_page(self):
-        url = u"/p/test/wiki/create_wiki_page/"
+        url = "/p/test/wiki/create_wiki_page/"
         r = self.app.get(url)
-        assert u'test' in r
-        assert u'Create page' in r.body
+        assert 'test' in r
+        assert 'Create page' in r.body
 
     def test_root_markdown_syntax(self):
         response = self.app.get('/wiki/markdown_syntax/')
@@ -80,13 +81,13 @@ class TestRootController(TestController):
         assert 'Browse Pages' in response
 
     def test_root_new_page(self):
-        response = self.app.get('/wiki/new_page?title=' + h.urlquote(u'tést'))
-        assert u'tést' in response
+        response = self.app.get('/wiki/new_page?title=' + h.urlquote('tést'))
+        assert 'tést' in response
 
     def test_root_new_search(self):
-        self.app.get(h.urlquote(u'/wiki/tést/'))
-        response = self.app.get('/wiki/search/?q=' + h.urlquote(u'tést'))
-        assert u'Search wiki: tést' in response
+        self.app.get(h.urlquote('/wiki/tést/'))
+        response = self.app.get('/wiki/search/?q=' + h.urlquote('tést'))
+        assert 'Search wiki: tést' in response
 
     def test_feed(self):
         for ext in ['', '.rss', '.atom']:
@@ -151,7 +152,7 @@ class TestRootController(TestController):
 
     def test_nonexistent_page_edit(self):
         resp = self.app.get('/wiki/tést/')
-        assert resp.location.endswith(h.urlquote(u'/wiki/tést/edit')), resp.location
+        assert resp.location.endswith(h.urlquote('/wiki/tést/edit')), resp.location
         resp = resp.follow()
         assert 'tést' in resp
 
@@ -405,7 +406,7 @@ class TestRootController(TestController):
                 'text': 'sometext',
                 'labels': '',
                 })
-        assert_equal(spam_checker.check.call_args[0][0], u'tést\nsometext')
+        assert_equal(spam_checker.check.call_args[0][0], 'tést\nsometext')
         assert 'tést' in response
 
     def test_page_get_markdown(self):
diff --git a/ForgeWiki/forgewiki/tests/test_app.py b/ForgeWiki/forgewiki/tests/test_app.py
index 5abf101..4bdba8e 100644
--- a/ForgeWiki/forgewiki/tests/test_app.py
+++ b/ForgeWiki/forgewiki/tests/test_app.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import datetime
 import tempfile
 import json
diff --git a/ForgeWiki/forgewiki/tests/test_converters.py b/ForgeWiki/forgewiki/tests/test_converters.py
index f30bf1d..5e2bdfa 100644
--- a/ForgeWiki/forgewiki/tests/test_converters.py
+++ b/ForgeWiki/forgewiki/tests/test_converters.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 from IPython.testing.decorators import module_not_available, skipif
 
 from forgewiki import converters
diff --git a/ForgeWiki/forgewiki/tests/test_models.py b/ForgeWiki/forgewiki/tests/test_models.py
index 3c6c7ef..e078743 100644
--- a/ForgeWiki/forgewiki/tests/test_models.py
+++ b/ForgeWiki/forgewiki/tests/test_models.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 from tg import tmpl_context as c
 from ming.orm import session
 
diff --git a/ForgeWiki/forgewiki/tests/test_wiki_roles.py b/ForgeWiki/forgewiki/tests/test_wiki_roles.py
index 9750f0d..21918a0 100644
--- a/ForgeWiki/forgewiki/tests/test_wiki_roles.py
+++ b/ForgeWiki/forgewiki/tests/test_wiki_roles.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 from tg import tmpl_context as c, app_globals as g
 
 from nose.tools import assert_equal
diff --git a/ForgeWiki/forgewiki/version.py b/ForgeWiki/forgewiki/version.py
index 1b493f8..192acec 100644
--- a/ForgeWiki/forgewiki/version.py
+++ b/ForgeWiki/forgewiki/version.py
@@ -15,5 +15,6 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 __version_info__ = (0, 0)
 __version__ = '.'.join(map(str, __version_info__))
diff --git a/ForgeWiki/forgewiki/wiki_main.py b/ForgeWiki/forgewiki/wiki_main.py
index 55704e4..d089974 100644
--- a/ForgeWiki/forgewiki/wiki_main.py
+++ b/ForgeWiki/forgewiki/wiki_main.py
@@ -16,6 +16,7 @@
 #       under the License.
 
 #-*- python -*-
+from __future__ import unicode_literals
 import json
 import logging
 import os
@@ -113,7 +114,7 @@ class ForgeWikiApp(Application):
     default_mount_label = 'Wiki'
     default_mount_point = 'wiki'
     ordinal = 5
-    default_root_page_name = u'Home'
+    default_root_page_name = 'Home'
     icons = {
         24: 'images/wiki_24.png',
         32: 'images/wiki_32.png',
@@ -747,7 +748,7 @@ class PageController(BaseController, FeedController):
         else:
             self.page.labels = []
         self.page.commit(subscribe=subscribe)
-        g.spam_checker.check(title + u'\n' + text, artifact=self.page,
+        g.spam_checker.check(title + '\n' + text, artifact=self.page,
                              user=c.user, content_type='wiki')
         if activity_verb == 'created':
             notification_tasks.send_usermentions_notification.post(self.page.index_id(), text)
diff --git a/ForgeWiki/setup.py b/ForgeWiki/setup.py
index 76db941..52c332f 100644
--- a/ForgeWiki/setup.py
+++ b/ForgeWiki/setup.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 from setuptools import setup, find_packages
 
 from forgewiki.version import __version__
diff --git a/fuse/accessfs.py b/fuse/accessfs.py
index 56808f8..62808ea 100644
--- a/fuse/accessfs.py
+++ b/fuse/accessfs.py
@@ -17,6 +17,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import os
 import json
 import urllib
diff --git a/scripts/ApacheAccessHandler.py b/scripts/ApacheAccessHandler.py
index 70055d1..c9e8900 100644
--- a/scripts/ApacheAccessHandler.py
+++ b/scripts/ApacheAccessHandler.py
@@ -29,6 +29,7 @@ this authorization code without Allura set up and configured on the git host.
 """
 
 
+from __future__ import unicode_literals
 from mod_python import apache
 import os
 import json
diff --git a/scripts/add_user_to_group.py b/scripts/add_user_to_group.py
index aa2fe1e..96835b5 100644
--- a/scripts/add_user_to_group.py
+++ b/scripts/add_user_to_group.py
@@ -36,6 +36,7 @@ Example:
 
 """
 
+from __future__ import unicode_literals
 from allura import model as M
 from ming.orm import ThreadLocalORMSession
 
diff --git a/scripts/changelog.py b/scripts/changelog.py
index 61aafdd..13300fd 100755
--- a/scripts/changelog.py
+++ b/scripts/changelog.py
@@ -17,6 +17,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import sys
 import re
 import git
diff --git a/scripts/create-allura-sitemap.py b/scripts/create-allura-sitemap.py
index 7f76f6b..7e5b31b 100644
--- a/scripts/create-allura-sitemap.py
+++ b/scripts/create-allura-sitemap.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 from allura.scripts.create_sitemap_files import CreateSitemapFiles
 
 # this file exists for backwards compatibility
diff --git a/scripts/migrations/013-update-ordinals.py b/scripts/migrations/013-update-ordinals.py
index 0568fa0..a6dcea0 100644
--- a/scripts/migrations/013-update-ordinals.py
+++ b/scripts/migrations/013-update-ordinals.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import sys
 import logging
 
diff --git a/scripts/migrations/015-add-neighborhood_id-to-blog-posts.py b/scripts/migrations/015-add-neighborhood_id-to-blog-posts.py
index 1fa8542..02bfb91 100644
--- a/scripts/migrations/015-add-neighborhood_id-to-blog-posts.py
+++ b/scripts/migrations/015-add-neighborhood_id-to-blog-posts.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import logging
 
 from tg import tmpl_context as c
diff --git a/scripts/migrations/018-add-svn-checkout-url.py b/scripts/migrations/018-add-svn-checkout-url.py
index 2a5469c..372c6fb 100644
--- a/scripts/migrations/018-add-svn-checkout-url.py
+++ b/scripts/migrations/018-add-svn-checkout-url.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 from allura import model as M
 from ming.orm import ThreadLocalORMSession
 
diff --git a/scripts/migrations/020-remove-wiki-title-slashes.py b/scripts/migrations/020-remove-wiki-title-slashes.py
index 0df73f7..00c314a 100644
--- a/scripts/migrations/020-remove-wiki-title-slashes.py
+++ b/scripts/migrations/020-remove-wiki-title-slashes.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import logging
 
 from tg import tmpl_context as c
diff --git a/scripts/migrations/022-change-anon-display-name.py b/scripts/migrations/022-change-anon-display-name.py
index dbe9911..8722d8e 100644
--- a/scripts/migrations/022-change-anon-display-name.py
+++ b/scripts/migrations/022-change-anon-display-name.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 from ming.orm.ormsession import ThreadLocalORMSession
 from allura import model as M
 
diff --git a/scripts/migrations/024-migrate-custom-profile-text.py b/scripts/migrations/024-migrate-custom-profile-text.py
index 060b70e..bf851b5 100644
--- a/scripts/migrations/024-migrate-custom-profile-text.py
+++ b/scripts/migrations/024-migrate-custom-profile-text.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import logging
 import re
 
diff --git a/scripts/migrations/025-add-is-nbhd-project.py b/scripts/migrations/025-add-is-nbhd-project.py
index ca3fc77..e2e7fdf 100644
--- a/scripts/migrations/025-add-is-nbhd-project.py
+++ b/scripts/migrations/025-add-is-nbhd-project.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import logging
 
 from allura import model as M
diff --git a/scripts/migrations/026-install-activity-tool.py b/scripts/migrations/026-install-activity-tool.py
index c7eb39f..620a614 100644
--- a/scripts/migrations/026-install-activity-tool.py
+++ b/scripts/migrations/026-install-activity-tool.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import logging
 
 from ming.orm import ThreadLocalORMSession
diff --git a/scripts/migrations/027-change-ticket-write-permissions.py b/scripts/migrations/027-change-ticket-write-permissions.py
index 3c6877a..4f51e20 100644
--- a/scripts/migrations/027-change-ticket-write-permissions.py
+++ b/scripts/migrations/027-change-ticket-write-permissions.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import logging
 from ming.orm import ThreadLocalORMSession
 
diff --git a/scripts/migrations/028-remove-svn-trees.py b/scripts/migrations/028-remove-svn-trees.py
index 79e82aa..4023106 100644
--- a/scripts/migrations/028-remove-svn-trees.py
+++ b/scripts/migrations/028-remove-svn-trees.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import logging
 
 from ming.orm import ThreadLocalORMSession
diff --git a/scripts/migrations/029-set-mailbox-queue_empty.py b/scripts/migrations/029-set-mailbox-queue_empty.py
index c2075b7..0f936fe 100644
--- a/scripts/migrations/029-set-mailbox-queue_empty.py
+++ b/scripts/migrations/029-set-mailbox-queue_empty.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import logging
 
 from allura import model as M
diff --git a/scripts/migrations/031-set-user-pending-to-false.py b/scripts/migrations/031-set-user-pending-to-false.py
index 02d35d9..d243e8b 100644
--- a/scripts/migrations/031-set-user-pending-to-false.py
+++ b/scripts/migrations/031-set-user-pending-to-false.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import logging
 
 from ming.odm import ThreadLocalORMSession, state
diff --git a/scripts/migrations/032-subscribe-merge-request-submitters.py b/scripts/migrations/032-subscribe-merge-request-submitters.py
index 3791dd7..57aa778 100644
--- a/scripts/migrations/032-subscribe-merge-request-submitters.py
+++ b/scripts/migrations/032-subscribe-merge-request-submitters.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import logging
 
 from ming.odm import ThreadLocalORMSession, state
diff --git a/scripts/migrations/033-change-comment-anon-permissions.py b/scripts/migrations/033-change-comment-anon-permissions.py
index f0b7da8..2dad964 100644
--- a/scripts/migrations/033-change-comment-anon-permissions.py
+++ b/scripts/migrations/033-change-comment-anon-permissions.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import sys
 import logging
 from ming.orm import ThreadLocalORMSession, session
diff --git a/scripts/migrations/034-update_subscriptions_ticket_and_mr_titles.py b/scripts/migrations/034-update_subscriptions_ticket_and_mr_titles.py
index 94904c8..44093de 100644
--- a/scripts/migrations/034-update_subscriptions_ticket_and_mr_titles.py
+++ b/scripts/migrations/034-update_subscriptions_ticket_and_mr_titles.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import logging
 import re
 import sys
diff --git a/scripts/new_ticket.py b/scripts/new_ticket.py
index 924f9d2..86c6ca8 100755
--- a/scripts/new_ticket.py
+++ b/scripts/new_ticket.py
@@ -16,6 +16,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import sys
 import argparse
 import requests
diff --git a/scripts/perf/benchmark-scm.py b/scripts/perf/benchmark-scm.py
index be9b93a..3dc70b4 100755
--- a/scripts/perf/benchmark-scm.py
+++ b/scripts/perf/benchmark-scm.py
@@ -18,6 +18,7 @@
 #       under the License.
 
 
+from __future__ import unicode_literals
 import os
 import sys
 import argparse
diff --git a/scripts/perf/call_count.py b/scripts/perf/call_count.py
index e94c238..669e95e 100755
--- a/scripts/perf/call_count.py
+++ b/scripts/perf/call_count.py
@@ -17,6 +17,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import argparse
 import json
 import logging
diff --git a/scripts/perf/generate-projects.py b/scripts/perf/generate-projects.py
index 0374969..74d83ad 100644
--- a/scripts/perf/generate-projects.py
+++ b/scripts/perf/generate-projects.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import re
 from ming.odm import ThreadLocalORMSession
 from allura import model as M
diff --git a/scripts/perf/load-up-forum.py b/scripts/perf/load-up-forum.py
index ee8f93d..38d134d 100644
--- a/scripts/perf/load-up-forum.py
+++ b/scripts/perf/load-up-forum.py
@@ -16,6 +16,7 @@
 #       under the License.
 
 
+from __future__ import unicode_literals
 import logging
 import uuid
 from ming.orm import ThreadLocalORMSession, session
diff --git a/scripts/perf/md_perf.py b/scripts/perf/md_perf.py
index d4511da..baee3f5 100644
--- a/scripts/perf/md_perf.py
+++ b/scripts/perf/md_perf.py
@@ -45,6 +45,7 @@ sys     0m1.112s
 
 """
 
+from __future__ import unicode_literals
 import argparse
 import cProfile
 import time
diff --git a/scripts/perf/parse_timings.py b/scripts/perf/parse_timings.py
index f16052c..9bda703 100644
--- a/scripts/perf/parse_timings.py
+++ b/scripts/perf/parse_timings.py
@@ -17,6 +17,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import json
 from datetime import datetime
 import argparse
diff --git a/scripts/perf/sstress.py b/scripts/perf/sstress.py
index 3f1a1b1..475a975 100644
--- a/scripts/perf/sstress.py
+++ b/scripts/perf/sstress.py
@@ -21,6 +21,7 @@
 sstress - an SMTP stress testing tool
 '''
 
+from __future__ import unicode_literals
 import smtplib
 import threading
 import time
diff --git a/scripts/perf/test_git_lcd.py b/scripts/perf/test_git_lcd.py
index 8d75e54..c992aa8 100644
--- a/scripts/perf/test_git_lcd.py
+++ b/scripts/perf/test_git_lcd.py
@@ -17,6 +17,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import sys
 import os
 from glob import glob
diff --git a/scripts/project-import.py b/scripts/project-import.py
index 9833bc2..eedbf1d 100644
--- a/scripts/project-import.py
+++ b/scripts/project-import.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import bson
 import datetime
 import json
diff --git a/scripts/publicize-neighborhood.py b/scripts/publicize-neighborhood.py
index b2d208c..804d0e4 100644
--- a/scripts/publicize-neighborhood.py
+++ b/scripts/publicize-neighborhood.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import logging
 import sys
 
diff --git a/scripts/rethumb.py b/scripts/rethumb.py
index 4dbfc04..8d84e5d 100644
--- a/scripts/rethumb.py
+++ b/scripts/rethumb.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import sys
 
 import PIL
diff --git a/scripts/scrub-allura-data.py b/scripts/scrub-allura-data.py
index 226ea13..8902426 100644
--- a/scripts/scrub-allura-data.py
+++ b/scripts/scrub-allura-data.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import logging
 import sys
 
diff --git a/scripts/teamforge-import.py b/scripts/teamforge-import.py
index 9bd88a6..ed920ad 100644
--- a/scripts/teamforge-import.py
+++ b/scripts/teamforge-import.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import logging
 from getpass import getpass
 from optparse import OptionParser
diff --git a/scripts/trac_export.py b/scripts/trac_export.py
index ac90b17..6fe9655 100755
--- a/scripts/trac_export.py
+++ b/scripts/trac_export.py
@@ -17,6 +17,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 if __name__ == '__main__':
     from allura.scripts.trac_export import main
     main()
diff --git a/scripts/trac_export_wiki.py b/scripts/trac_export_wiki.py
index 025d3ca..ef62ddf 100755
--- a/scripts/trac_export_wiki.py
+++ b/scripts/trac_export_wiki.py
@@ -17,6 +17,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import sys
 from optparse import OptionParser
 
diff --git a/scripts/trac_import.py b/scripts/trac_import.py
index d4e51fd..55938ac 100644
--- a/scripts/trac_import.py
+++ b/scripts/trac_import.py
@@ -15,6 +15,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import json
 from optparse import OptionParser
 
diff --git a/scripts/wiki-copy.py b/scripts/wiki-copy.py
index 26304cb..bfceec3 100644
--- a/scripts/wiki-copy.py
+++ b/scripts/wiki-copy.py
@@ -17,6 +17,7 @@
 #       specific language governing permissions and limitations
 #       under the License.
 
+from __future__ import unicode_literals
 import os
 import sys
 import urllib