You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@allura.apache.org by di...@apache.org on 2022/03/02 18:01:36 UTC

[allura] 02/04: [#8415] py2 removal - bare pyupgrade run

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

dill0wn pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/allura.git

commit 65dfb2c1c88c62c5375b76c4bad237c7926ada43
Author: Kenton Taylor <kt...@slashdotmedia.com>
AuthorDate: Wed Feb 23 20:00:50 2022 +0000

    [#8415] py2 removal - bare pyupgrade run
---
 Allura/allura/app.py                               | 16 +++----
 Allura/allura/command/base.py                      |  2 +-
 Allura/allura/command/reclone_repo.py              |  4 +-
 Allura/allura/command/script.py                    |  2 +-
 Allura/allura/command/show_models.py               | 16 +++----
 Allura/allura/command/taskd.py                     | 12 ++---
 Allura/allura/command/taskd_cleanup.py             |  4 +-
 Allura/allura/controllers/auth.py                  | 24 +++++-----
 Allura/allura/controllers/basetest_project_root.py |  2 +-
 Allura/allura/controllers/feed.py                  |  2 +-
 Allura/allura/controllers/project.py               | 14 +++---
 Allura/allura/controllers/repository.py            | 10 ++---
 Allura/allura/controllers/rest.py                  |  2 +-
 Allura/allura/controllers/search.py                |  2 +-
 Allura/allura/controllers/site_admin.py            |  4 +-
 Allura/allura/controllers/trovecategories.py       |  4 +-
 Allura/allura/ext/admin/admin_main.py              | 18 ++++----
 Allura/allura/lib/app_globals.py                   |  6 +--
 Allura/allura/lib/custom_middleware.py             |  6 +--
 Allura/allura/lib/exceptions.py                    |  2 +-
 Allura/allura/lib/gravatar.py                      |  2 +-
 Allura/allura/lib/helpers.py                       | 38 ++++++++--------
 Allura/allura/lib/import_api.py                    |  2 +-
 Allura/allura/lib/macro.py                         | 12 ++---
 Allura/allura/lib/markdown_extensions.py           |  2 +-
 Allura/allura/lib/package_path_loader.py           |  2 +-
 Allura/allura/lib/plugin.py                        | 12 ++---
 Allura/allura/lib/project_create_helpers.py        |  2 +-
 Allura/allura/lib/search.py                        | 10 ++---
 Allura/allura/lib/security.py                      | 10 ++---
 Allura/allura/lib/spam/__init__.py                 |  2 +-
 Allura/allura/lib/spam/stopforumspamfilter.py      |  2 +-
 Allura/allura/lib/utils.py                         | 14 +++---
 Allura/allura/lib/widgets/form_fields.py           |  6 +--
 Allura/allura/lib/widgets/forms.py                 |  6 +--
 Allura/allura/model/artifact.py                    | 10 ++---
 Allura/allura/model/auth.py                        | 12 ++---
 Allura/allura/model/discuss.py                     | 12 ++---
 Allura/allura/model/filesystem.py                  |  4 +-
 Allura/allura/model/index.py                       | 12 ++---
 Allura/allura/model/monq_model.py                  |  2 +-
 Allura/allura/model/notification.py                | 14 +++---
 Allura/allura/model/project.py                     | 10 ++---
 Allura/allura/model/repo_refresh.py                | 10 ++---
 Allura/allura/model/repository.py                  | 52 +++++++++++-----------
 Allura/allura/model/session.py                     |  4 +-
 Allura/allura/model/stats.py                       |  6 +--
 Allura/allura/model/timeline.py                    |  6 +--
 Allura/allura/scripts/refresh_last_commits.py      |  2 +-
 Allura/allura/tasks/index_tasks.py                 |  4 +-
 Allura/allura/tasks/repo_tasks.py                  |  4 +-
 Allura/allura/tests/decorators.py                  |  2 +-
 Allura/allura/tests/functional/test_admin.py       |  4 +-
 Allura/allura/tests/functional/test_auth.py        |  6 +--
 Allura/allura/tests/functional/test_gravatar.py    |  2 +-
 Allura/allura/tests/functional/test_nav.py         |  2 +-
 .../tests/functional/test_personal_dashboard.py    |  2 +-
 .../allura/tests/functional/test_user_profile.py   |  2 +-
 Allura/allura/tests/model/test_auth.py             | 30 ++++++-------
 Allura/allura/tests/model/test_repo.py             |  6 +--
 Allura/allura/tests/test_app.py                    |  2 +-
 Allura/allura/tests/test_commands.py               |  6 +--
 Allura/allura/tests/test_diff.py                   | 40 ++++++++---------
 Allura/allura/tests/test_globals.py                | 10 ++---
 Allura/allura/tests/test_middlewares.py            |  4 +-
 Allura/allura/tests/test_security.py               | 28 ++++++------
 Allura/allura/tests/test_tasks.py                  |  4 +-
 Allura/allura/tests/test_utils.py                  |  6 +--
 Allura/allura/tests/test_webhooks.py               |  8 ++--
 Allura/allura/tests/unit/factories.py              |  2 +-
 .../allura/tests/unit/test_ldap_auth_provider.py   |  4 +-
 Allura/allura/tests/unit/test_repo.py              | 12 ++---
 Allura/allura/tests/unit/test_session.py           | 12 ++---
 Allura/ldap-setup.py                               |  4 +-
 AlluraTest/alluratest/controller.py                |  2 +-
 AlluraTest/alluratest/validation.py                |  2 +-
 ForgeActivity/forgeactivity/main.py                |  2 +-
 ForgeBlog/forgeblog/model/blog.py                  | 16 +++----
 ForgeChat/forgechat/command.py                     | 10 ++---
 ForgeChat/forgechat/model/chat.py                  |  4 +-
 ForgeDiscussion/forgediscussion/model/forum.py     |  4 +-
 .../forgediscussion/tests/functional/test_forum.py |  6 +--
 ForgeFiles/forgefiles/files_main.py                |  2 +-
 ForgeGit/forgegit/model/git_repo.py                |  4 +-
 ForgeGit/forgegit/tests/model/test_repository.py   |  2 +-
 ForgeImporters/forgeimporters/base.py              |  4 +-
 .../forgeimporters/forge/alluraImporter.py         |  2 +-
 ForgeImporters/forgeimporters/forge/discussion.py  |  2 +-
 ForgeImporters/forgeimporters/forge/tracker.py     |  2 +-
 ForgeImporters/forgeimporters/github/__init__.py   |  2 +-
 ForgeImporters/forgeimporters/github/code.py       |  4 +-
 ForgeImporters/forgeimporters/github/project.py    |  2 +-
 ForgeImporters/forgeimporters/github/tracker.py    |  6 +--
 ForgeImporters/forgeimporters/github/utils.py      | 20 ++++-----
 ForgeImporters/forgeimporters/github/wiki.py       |  4 +-
 .../forgeimporters/tests/github/test_tracker.py    |  4 +-
 ForgeImporters/forgeimporters/tests/test_base.py   |  4 +-
 ForgeImporters/forgeimporters/trac/tickets.py      |  4 +-
 ForgeSVN/forgesvn/model/svn.py                     | 22 ++++-----
 ForgeSVN/forgesvn/tests/model/test_repository.py   |  2 +-
 ForgeShortUrl/forgeshorturl/main.py                |  4 +-
 ForgeShortUrl/forgeshorturl/model/shorturl.py      |  2 +-
 ForgeTracker/forgetracker/import_support.py        | 10 ++---
 ForgeTracker/forgetracker/model/ticket.py          | 50 ++++++++++-----------
 .../forgetracker/tests/functional/test_rest.py     |  2 +-
 .../forgetracker/tests/functional/test_root.py     | 30 ++++++-------
 ForgeTracker/forgetracker/tracker_main.py          | 34 +++++++-------
 ForgeTracker/forgetracker/widgets/ticket_form.py   |  2 +-
 ForgeTracker/forgetracker/widgets/ticket_search.py |  2 +-
 ForgeWiki/forgewiki/converters.py                  |  2 +-
 ForgeWiki/forgewiki/model/wiki.py                  | 12 ++---
 ForgeWiki/forgewiki/tests/test_models.py           |  2 +-
 fuse/accessfs.py                                   |  4 +-
 scripts/ApacheAccessHandler.py                     |  8 ++--
 scripts/add_user_to_group.py                       |  2 +-
 scripts/changelog.py                               |  4 +-
 .../migrations/020-remove-wiki-title-slashes.py    |  2 +-
 .../migrations/024-migrate-custom-profile-text.py  |  2 +-
 scripts/migrations/028-remove-svn-trees.py         |  6 +--
 .../migrations/031-set-user-pending-to-false.py    |  2 +-
 .../032-subscribe-merge-request-submitters.py      |  2 +-
 scripts/new_ticket.py                              |  2 +-
 scripts/perf/test_git_lcd.py                       |  2 +-
 scripts/project-import.py                          |  2 +-
 scripts/scrub-allura-data.py                       |  4 +-
 scripts/teamforge-import.py                        | 20 ++++-----
 scripts/wiki-copy.py                               |  8 ++--
 127 files changed, 498 insertions(+), 498 deletions(-)

diff --git a/Allura/allura/app.py b/Allura/allura/app.py
index 924d7f3..831d9fa 100644
--- a/Allura/allura/app.py
+++ b/Allura/allura/app.py
@@ -187,8 +187,8 @@ class SitemapEntry(object):
         children or our copy with the children of the new copy.
 
         """
-        child_index = dict(
-            (ch.label, ch) for ch in self.children)
+        child_index = {
+            ch.label: ch for ch in self.children}
         for e in sitemap_entries:
             lbl = e.label
             match = child_index.get(e.label)
@@ -361,7 +361,7 @@ class Application(object):
         """
         if self.config.options.get('AllowEmailPosting', True):
             parts = list(reversed(self.url[1:-1].split('/')))
-            return '%s@%s%s' % (parts[0], '.'.join(parts[1:]), config.common_suffix)
+            return '{}@{}{}'.format(parts[0], '.'.join(parts[1:]), config.common_suffix)
         else:
             return tg_config.get('forgemail.return_path')
 
@@ -528,9 +528,9 @@ class Application(object):
         :rtype: dict
 
         """
-        return dict(
-            (co.name, co.default)
-            for co in cls.config_options)
+        return {
+            co.name: co.default
+            for co in cls.config_options}
 
     @classmethod
     def options_on_install(cls):
@@ -896,7 +896,7 @@ class DefaultAdminController(BaseController, AdminControllerMixin):
         c.card = PermissionCard()
         c.block_user = BlockUser()
         c.block_list = BlockList()
-        permissions = dict((p, []) for p in self.app.permissions)
+        permissions = {p: [] for p in self.app.permissions}
         block_list = defaultdict(list)
         for ace in self.app.config.acl:
             if ace.access == model.ACE.ALLOW:
@@ -1026,7 +1026,7 @@ class DefaultAdminController(BaseController, AdminControllerMixin):
                 return ', '.join((role.name or '<Unnamed>') for role in groups if role)
 
             if new_groups or del_groups:
-                model.AuditLog.log('updated "%s" permission: "%s" => "%s" for %s' % (
+                model.AuditLog.log('updated "{}" permission: "{}" => "{}" for {}'.format(
                     perm,
                     group_names(groups + del_groups),
                     group_names(groups + new_groups),
diff --git a/Allura/allura/command/base.py b/Allura/allura/command/base.py
index f8b78fc..8e38b57 100644
--- a/Allura/allura/command/base.py
+++ b/Allura/allura/command/base.py
@@ -69,7 +69,7 @@ class Command(six.with_metaclass(MetaParserDocstring, command.Command)):
 
     @classmethod
     def post(cls, *args, **kw):
-        cmd = '%s.%s' % (cls.__module__, cls.__name__)
+        cmd = '{}.{}'.format(cls.__module__, cls.__name__)
         return run_command.post(cmd, *args, **kw)
 
     @ming.utils.LazyProperty
diff --git a/Allura/allura/command/reclone_repo.py b/Allura/allura/command/reclone_repo.py
index 51e5ca6..d03d691 100644
--- a/Allura/allura/command/reclone_repo.py
+++ b/Allura/allura/command/reclone_repo.py
@@ -56,10 +56,10 @@ class RecloneRepoCommand(base.Command):
         assert nbhd, 'Neighborhood with prefix %s not found' % self.options.nbhd
         c.project = M.Project.query.get(
             shortname=self.args[1], neighborhood_id=nbhd._id)
-        assert c.project, 'Project with shortname %s not found in neighborhood %s' % (
+        assert c.project, 'Project with shortname {} not found in neighborhood {}'.format(
             self.args[1], nbhd.name)
         c.app = c.project.app_instance(self.args[2])
-        assert c.app, 'Mount point %s not found on project %s' % (
+        assert c.app, 'Mount point {} not found on project {}'.format(
             self.args[2], c.project.shortname)
 
     def _clone_repo(self):
diff --git a/Allura/allura/command/script.py b/Allura/allura/command/script.py
index 17d0988..31e9674 100644
--- a/Allura/allura/command/script.py
+++ b/Allura/allura/command/script.py
@@ -90,7 +90,7 @@ class SetToolAccessCommand(base.Command):
                 print('Unknown tool status %s' % s)
                 sys.exit(1)
             extra_status.append(s)
-        print('Setting project "%s" tool access to production + %r' % (
+        print('Setting project "{}" tool access to production + {!r}'.format(
             self.args[1], extra_status))
         c.project._extra_tool_status = extra_status
         session(c.project).flush()
diff --git a/Allura/allura/command/show_models.py b/Allura/allura/command/show_models.py
index 8120021..4e39448 100644
--- a/Allura/allura/command/show_models.py
+++ b/Allura/allura/command/show_models.py
@@ -247,15 +247,15 @@ class EnsureIndexCommand(base.Command):
         base.log.info('Done updating indexes')
 
     def _update_indexes(self, collection, indexes):
-        uindexes = dict(
+        uindexes = {
             # convert list to tuple so it's hashable for 'set'
-            (tuple(i.index_spec), i)
+            tuple(i.index_spec): i
             for i in indexes
-            if i.unique)
-        indexes = dict(
-            (tuple(i.index_spec), i)
+            if i.unique}
+        indexes = {
+            tuple(i.index_spec): i
             for i in indexes
-            if not i.unique)
+            if not i.unique}
         prev_indexes = {}
         prev_uindexes = {}
         unique_flag_drop = {}
@@ -356,7 +356,7 @@ class EnsureIndexCommand(base.Command):
 
 
 def build_model_inheritance_graph():
-    graph = dict((m.mapped_class, ([], [])) for m in Mapper.all_mappers())
+    graph = {m.mapped_class: ([], []) for m in Mapper.all_mappers()}
     for cls, (parents, children) in six.iteritems(graph):
         for b in cls.__bases__:
             if b not in graph:
@@ -368,7 +368,7 @@ def build_model_inheritance_graph():
 
 def dump_cls(depth, cls):
     indent = ' ' * 4 * depth
-    yield indent + '%s.%s' % (cls.__module__, cls.__name__)
+    yield indent + '{}.{}'.format(cls.__module__, cls.__name__)
     m = mapper(cls)
     for p in m.properties:
         s = indent * 2 + ' - ' + str(p)
diff --git a/Allura/allura/command/taskd.py b/Allura/allura/command/taskd.py
index b1a38fa..ae684e6 100644
--- a/Allura/allura/command/taskd.py
+++ b/Allura/allura/command/taskd.py
@@ -94,14 +94,14 @@ class TaskdCommand(base.Command):
         self.keep_running = False
 
     def log_current_task(self, signum, frame):
-        entry = 'taskd pid %s is currently handling task %s' % (
+        entry = 'taskd pid {} is currently handling task {}'.format(
             os.getpid(), getattr(self, 'task', None))
         status_log.info(entry)
         base.log.info(entry)
 
     def worker(self):
         from allura import model as M
-        name = '%s pid %s' % (os.uname()[1], os.getpid())
+        name = '{} pid {}'.format(os.uname()[1], os.getpid())
         wsgi_app = loadapp('config:%s#task' %
                            self.args[0], relative_to=os.getcwd())
         poll_interval = asint(tg.config.get('monq.poll_interval', 10))
@@ -136,10 +136,10 @@ class TaskdCommand(base.Command):
                         waitfunc=waitfunc,
                         only=only)
                     if self.task:
-                        with(proctitle("taskd:{0}:{1}".format(
+                        with(proctitle("taskd:{}:{}".format(
                                 self.task.task_name, self.task._id))):
                             # Build the (fake) request
-                            request_path = '/--%s--/%s/' % (self.task.task_name,
+                            request_path = '/--{}--/{}/'.format(self.task.task_name,
                                                             self.task._id)
                             r = Request.blank(request_path,
                                               base_url=tg.config['base_url'].rstrip(
@@ -175,7 +175,7 @@ class TaskCommand(base.Command):
     parser.add_option('-s', '--state', dest='state', default=None,
                       help='state of processes for "list", "count", or "purge" subcommands.  * means all. '
                            '(Defaults per command: %s)' %
-                           ", ".join(['%s="%s"' % (k, v) for k, v in cmd_default_states.items()]))
+                           ", ".join(['{}="{}"'.format(k, v) for k, v in cmd_default_states.items()]))
     parser.add_option('-t', '--timeout', dest='timeout', type=int, default=60,
                       help='timeout (in seconds) for busy tasks (only applies to "timeout" command)')
     parser.add_option('--filter-name-prefix', dest='filter_name_prefix', default=None,
@@ -236,7 +236,7 @@ class TaskCommand(base.Command):
         return q
 
     def _print_query(self, cmd, *args):
-        print('running mongod cmd: %s, %s' % (cmd, args))
+        print('running mongod cmd: {}, {}'.format(cmd, args))
 
     def _list(self):
         '''List tasks'''
diff --git a/Allura/allura/command/taskd_cleanup.py b/Allura/allura/command/taskd_cleanup.py
index d42c9ba..7bf95b5 100644
--- a/Allura/allura/command/taskd_cleanup.py
+++ b/Allura/allura/command/taskd_cleanup.py
@@ -123,7 +123,7 @@ class TaskdCleanupCommand(base.Command):
     def _busy_tasks(self, pid=None):
         regex = '^%s ' % self.hostname
         if pid is not None:
-            regex = '^%s pid %s' % (self.hostname, pid)
+            regex = '^{} pid {}'.format(self.hostname, pid)
         return M.MonQTask.query.find({
             'state': 'busy',
             'process': {'$regex': regex}
@@ -167,7 +167,7 @@ class TaskdCleanupCommand(base.Command):
         for i in range(self.options.num_retry):
             retry = False if i == 0 else True
             status = self._taskd_status(taskd_pid, retry)
-            line = 'taskd pid %s is currently handling task %s' % (
+            line = 'taskd pid {} is currently handling task {}'.format(
                 taskd_pid, task)
             if line in status:
                 return 'OK'
diff --git a/Allura/allura/controllers/auth.py b/Allura/allura/controllers/auth.py
index cc71a38..72bbed8 100644
--- a/Allura/allura/controllers/auth.py
+++ b/Allura/allura/controllers/auth.py
@@ -119,7 +119,7 @@ class AuthController(BaseController):
             raise AttributeError("TG decoratedcontroller calls this during import time, can't do anything complex")
         urls = plugin.UserPreferencesProvider.get().additional_urls()
         if name not in urls:
-            raise AttributeError("'%s' object has no attribute '%s'" % (type(self).__name__, name))
+            raise AttributeError("'{}' object has no attribute '{}'".format(type(self).__name__, name))
         return urls[name]
 
     @expose()
@@ -458,7 +458,7 @@ class AuthController(BaseController):
                         continue
                     if not has_access(app, 'write', user, p):
                         continue
-                    repos.append('/%s/%s/%s' % (
+                    repos.append('/{}/{}/{}'.format(
                         app.tool_name.lower(),
                         _unix_group_name(p.neighborhood, p.shortname),
                         app.options['mount_point']))
@@ -1174,12 +1174,12 @@ class SubscriptionsController(BaseController):
         subscriptions = []
         mailboxes = list(M.Mailbox.query.find(
             dict(user_id=c.user._id, is_flash=False)))
-        projects = dict(
-            (p._id, p) for p in M.Project.query.find(dict(
-                _id={'$in': [mb.project_id for mb in mailboxes]})))
-        app_index = dict(
-            (ac._id, ac) for ac in M.AppConfig.query.find(dict(
-                _id={'$in': [mb.app_config_id for mb in mailboxes]})))
+        projects = {
+            p._id: p for p in M.Project.query.find(dict(
+                _id={'$in': [mb.project_id for mb in mailboxes]}))}
+        app_index = {
+            ac._id: ac for ac in M.AppConfig.query.find(dict(
+                _id={'$in': [mb.app_config_id for mb in mailboxes]}))}
 
         # Add the tools that are already subscribed to by the user.
         for mb in mailboxes:
@@ -1210,19 +1210,19 @@ class SubscriptionsController(BaseController):
                 subscribed=True))
 
         # Dictionary of all projects projects accessible based on a users credentials (user_roles).
-        my_projects = dict((p._id, p) for p in c.user.my_projects())
+        my_projects = {p._id: p for p in c.user.my_projects()}
 
         # Dictionary containing all tools (subscribed and un-subscribed).
         my_tools = M.AppConfig.query.find(dict(
             project_id={'$in': list(my_projects.keys())}))
 
         # Dictionary containing all the currently subscribed tools for a given user.
-        my_tools_subscriptions = dict(
-            (mb.app_config_id, mb) for mb in M.Mailbox.query.find(dict(
+        my_tools_subscriptions = {
+            mb.app_config_id: mb for mb in M.Mailbox.query.find(dict(
                 user_id=c.user._id,
                 project_id={'$in': list(projects.keys())},
                 app_config_id={'$in': list(app_index.keys())},
-                artifact_index_id=None)))
+                artifact_index_id=None))}
 
         # Add the remaining tools that are eligible for subscription.
         for tool in my_tools:
diff --git a/Allura/allura/controllers/basetest_project_root.py b/Allura/allura/controllers/basetest_project_root.py
index f192b7c..b2db6d9 100644
--- a/Allura/allura/controllers/basetest_project_root.py
+++ b/Allura/allura/controllers/basetest_project_root.py
@@ -151,7 +151,7 @@ class NamedController(object):
 
     @expose()
     def _default(self, *args):
-        return 'default(%s)(%r)' % (self.name, args)
+        return 'default({})({!r})'.format(self.name, args)
 
 
 class SecurityTests(object):
diff --git a/Allura/allura/controllers/feed.py b/Allura/allura/controllers/feed.py
index bab4db7..72cfad2 100644
--- a/Allura/allura/controllers/feed.py
+++ b/Allura/allura/controllers/feed.py
@@ -66,7 +66,7 @@ class FeedController(object):
 
     """
     FEED_TYPES = [str('.atom'), str('.rss')]
-    FEED_NAMES = [str('feed{0}'.format(typ)) for typ in FEED_TYPES]
+    FEED_NAMES = [str('feed{}'.format(typ)) for typ in FEED_TYPES]
 
     def __getattr__(self, name):
         if name in self.FEED_NAMES:
diff --git a/Allura/allura/controllers/project.py b/Allura/allura/controllers/project.py
index 80ae7a3..a55b296 100644
--- a/Allura/allura/controllers/project.py
+++ b/Allura/allura/controllers/project.py
@@ -267,7 +267,7 @@ class NeighborhoodController(object):
         return {
             'options': [{
                 'value': u.username,
-                'label': '%s (%s)' % (u.display_name, u.username)
+                'label': '{} ({})'.format(u.display_name, u.username)
             } for u in p.users()]
         }
 
@@ -373,7 +373,7 @@ class ProjectController(FeedController):
         for user in c.project.users():
             roles = M.ProjectRole.query.find(
                 {'_id': {'$in': M.ProjectRole.by_user(user).roles}})
-            roles = set([r.name for r in roles])
+            roles = {r.name for r in roles}
             u = dict(
                 display_name=user.display_name,
                 username=user.username,
@@ -456,7 +456,7 @@ class ProjectController(FeedController):
         return dict(
             users=[
                 dict(
-                    label='%s (%s)' % (u.get_pref('display_name'), u.username),
+                    label='{} ({})'.format(u.get_pref('display_name'), u.username),
                     value=u.username,
                     id=u.username)
                 for u in users])
@@ -471,7 +471,7 @@ class ProjectController(FeedController):
         return {
             'options': [{
                 'value': u.username,
-                'label': '%s (%s)' % (u.display_name, u.username)
+                'label': '{} ({})'.format(u.display_name, u.username)
             } for u in users]
         }
 
@@ -657,9 +657,9 @@ class NeighborhoodAdminController(object):
         result = True
         if anchored_tools.strip() != '':
             try:
-                validate_tools = dict(
-                    (tool.split(':')[0].lower(), tool.split(':')[1])
-                    for tool in anchored_tools.replace(' ', '').split(','))
+                validate_tools = {
+                    tool.split(':')[0].lower(): tool.split(':')[1]
+                    for tool in anchored_tools.replace(' ', '').split(',')}
             except Exception:
                 flash('Anchored tools "%s" is invalid' %
                       anchored_tools, 'error')
diff --git a/Allura/allura/controllers/repository.py b/Allura/allura/controllers/repository.py
index 1fb72b1..134dd23 100644
--- a/Allura/allura/controllers/repository.py
+++ b/Allura/allura/controllers/repository.py
@@ -76,8 +76,8 @@ class RepoRootController(BaseController, FeedController):
     def get_feed(self, project, app, user):
         query = dict(project_id=project._id, app_config_id=app.config._id)
         pname, repo = (project.shortname, app.config.options.mount_label)
-        title = '%s %s changes' % (pname, repo)
-        description = 'Recent changes to %s repository in %s project' % (
+        title = '{} {} changes'.format(pname, repo)
+        description = 'Recent changes to {} repository in {} project'.format(
             repo, pname)
         return FeedArgs(query, title, app.url, description=description)
 
@@ -101,7 +101,7 @@ class RepoRootController(BaseController, FeedController):
                 repo_path_parts = f.url().strip('/').split('/')
                 links.append(dict(
                     repo_url=f.url(),
-                    repo='%s / %s' % (repo_path_parts[1],
+                    repo='{} / {}'.format(repo_path_parts[1],
                                       repo_path_parts[-1]),
                 ))
         return dict(links=links)
@@ -127,7 +127,7 @@ class RepoRootController(BaseController, FeedController):
         ThreadLocalORMSession.close_all()
         from_project = c.project
         to_project = M.Project.query.get(_id=ObjectId(project_id))
-        mount_label = mount_label or '%s - %s' % (c.project.name,
+        mount_label = mount_label or '{} - {}'.format(c.project.name,
                                                   c.app.config.options.mount_label)
         mount_point = (mount_point or from_project.shortname)
         if request.method != 'POST' or not mount_point:
@@ -718,7 +718,7 @@ class CommitBrowser(BaseController):
         status = c.app.repo.get_tarball_status(rev, path)
         if not status and request.method == 'POST':
             allura.tasks.repo_tasks.tarball.post(rev, path)
-            redirect('tarball?path={0}'.format(h.urlquote(path) if path else ''))
+            redirect('tarball?path={}'.format(h.urlquote(path) if path else ''))
         return dict(commit=self._commit, revision=rev, status=status)
 
     @expose('json:')
diff --git a/Allura/allura/controllers/rest.py b/Allura/allura/controllers/rest.py
index dda7e07..ee7689f 100644
--- a/Allura/allura/controllers/rest.py
+++ b/Allura/allura/controllers/rest.py
@@ -237,7 +237,7 @@ class OAuthNegotiator(object):
             url = rtok.callback + '&'
         else:
             url = rtok.callback + '?'
-        url += 'oauth_token=%s&oauth_verifier=%s' % (
+        url += 'oauth_token={}&oauth_verifier={}'.format(
             rtok.api_key, rtok.validation_pin)
         redirect(url)
 
diff --git a/Allura/allura/controllers/search.py b/Allura/allura/controllers/search.py
index 63c4edb..c5c454c 100644
--- a/Allura/allura/controllers/search.py
+++ b/Allura/allura/controllers/search.py
@@ -75,7 +75,7 @@ class ProjectBrowseController(BaseController):
         if self.category:
             title = self.category.label
             if self.parent_category:
-                title = "%s: %s" % (self.parent_category.label, title)
+                title = "{}: {}".format(self.parent_category.label, title)
         return title
 
     def _build_nav(self):
diff --git a/Allura/allura/controllers/site_admin.py b/Allura/allura/controllers/site_admin.py
index d9aab9e..2bcf8b0 100644
--- a/Allura/allura/controllers/site_admin.py
+++ b/Allura/allura/controllers/site_admin.py
@@ -226,7 +226,7 @@ class SiteAdminController(object):
                 for msg in list(c.form_errors):
                     names = {'prefix': 'Neighborhood prefix', 'shortname':
                              'Project shortname', 'mount_point': 'Repository mount point'}
-                    error_msg += '%s: %s ' % (names[msg], c.form_errors[msg])
+                    error_msg += '{}: {} '.format(names[msg], c.form_errors[msg])
                     flash(error_msg, 'error')
                 return dict(prefix=prefix, shortname=shortname, mount_point=mount_point)
             nbhd = M.Neighborhood.query.get(url_prefix='/%s/' % prefix)
@@ -697,7 +697,7 @@ class AdminUserDetailsController(object):
             M.AuditLog.comment_user(c.user, comment, user=user)
             flash('Comment added', 'ok')
         else:
-            flash('Can not add comment "%s" for user %s' % (comment, user))
+            flash('Can not add comment "{}" for user {}'.format(comment, user))
         redirect(six.ensure_text(request.referer or '/'))
 
     @expose()
diff --git a/Allura/allura/controllers/trovecategories.py b/Allura/allura/controllers/trovecategories.py
index 0232587..0e2e41f 100644
--- a/Allura/allura/controllers/trovecategories.py
+++ b/Allura/allura/controllers/trovecategories.py
@@ -126,7 +126,7 @@ class TroveCategoryController(BaseController):
             show_as_skill = upper.show_as_skill
 
         newid = max(
-            [el.trove_cat_id for el in M.TroveCategory.query.find()]) + 1
+            el.trove_cat_id for el in M.TroveCategory.query.find()) + 1
         shortname = h.slugify(shortname or name, True)[1]
 
         if upper:
@@ -139,7 +139,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"),
+                ('A category with shortname "{}" already exists ({}).  Try a different, unique shortname'.format(shortname, oldcat.fullpath), "error"),
                 '?categoryname={}&shortname={}'.format(name, shortname),
                 upper
             )
diff --git a/Allura/allura/ext/admin/admin_main.py b/Allura/allura/ext/admin/admin_main.py
index e254967..314051f 100644
--- a/Allura/allura/ext/admin/admin_main.py
+++ b/Allura/allura/ext/admin/admin_main.py
@@ -648,7 +648,7 @@ class ProjectAdminController(BaseController):
                         new_url = new_url()
                     redirect(new_url)
         except forge_exc.ForgeError as exc:
-            flash('%s: %s' % (exc.__class__.__name__, exc.args[0]),
+            flash('{}: {}'.format(exc.__class__.__name__, exc.args[0]),
                   'error')
         if request.referer is not None and tool is not None and 'delete' in tool[0] and \
             re.search(c.project.url() + r'(admin\/|)' + tool[0]['mount_point']+ r'\/*',
@@ -804,7 +804,7 @@ class ProjectAdminRestController(BaseController):
                 'Must give at least one tool mount point to export')
         tools = aslist(tools, ',')
         exportable_tools = AdminApp.exportable_tools_for(c.project)
-        allowed = set(t.options.mount_point for t in exportable_tools)
+        allowed = {t.options.mount_point for t in exportable_tools}
         if not set(tools).issubset(allowed):
             raise exc.HTTPBadRequest('Invalid tool')
         if c.project.bulk_export_status() == 'busy':
@@ -1030,8 +1030,8 @@ class PermissionsController(BaseController):
         redirect('.')
 
     def _index_permissions(self):
-        permissions = dict(
-            (p, []) for p in c.project.permissions)
+        permissions = {
+            p: [] for p in c.project.permissions}
         for ace in c.project.acl:
             if ace.access == M.ACE.ALLOW:
                 permissions[ace.permission].append(ace.role_id)
@@ -1043,8 +1043,8 @@ class GroupsController(BaseController):
         require_access(c.project, 'admin')
 
     def _index_permissions(self):
-        permissions = dict(
-            (p, []) for p in c.project.permissions)
+        permissions = {
+            p: [] for p in c.project.permissions}
         for ace in c.project.acl:
             if ace.access == M.ACE.ALLOW:
                 permissions[ace.permission].append(ace.role_id)
@@ -1068,7 +1068,7 @@ class GroupsController(BaseController):
                 else:
                     for r in role.child_roles():
                         if r._id in role_ids:
-                            perm_info['text'] = "Inherited permission %s from %s" % (
+                            perm_info['text'] = "Inherited permission {} from {}".format(
                                 perm, r.name)
                             perm_info['has'] = "inherit"
                             break
@@ -1146,7 +1146,7 @@ class GroupsController(BaseController):
             return dict(error='User %s not found' % username)
         user_role = M.ProjectRole.by_user(user, upsert=True)
         if group._id in user_role.roles:
-            return dict(error='%s (%s) is already in the group %s.' % (user.display_name, username, group.name))
+            return dict(error='{} ({}) is already in the group {}.'.format(user.display_name, username, group.name))
         M.AuditLog.log('add user %s to %s', username, group.name)
         user_role.roles.append(group._id)
         if group.name == 'Admin':
@@ -1170,7 +1170,7 @@ class GroupsController(BaseController):
             return dict(error='User %s not found' % username)
         user_role = M.ProjectRole.by_user(user)
         if not user_role or group._id not in user_role.roles:
-            return dict(error='%s (%s) is not in the group %s.' % (user.display_name, username, group.name))
+            return dict(error='{} ({}) is not in the group {}.'.format(user.display_name, username, group.name))
         M.AuditLog.log('remove user %s from %s', username, group.name)
         user_role.roles.remove(group._id)
         if len(user_role.roles) == 0:
diff --git a/Allura/allura/lib/app_globals.py b/Allura/allura/lib/app_globals.py
index 3af1e89..e1d397e 100644
--- a/Allura/allura/lib/app_globals.py
+++ b/Allura/allura/lib/app_globals.py
@@ -543,12 +543,12 @@ class Globals(object):
     def register_app_css(self, href, **kw):
         app = kw.pop('app', c.app)
         self.resource_manager.register(
-            ew.CSSLink('tool/%s/%s' % (app.config.tool_name.lower(), href), **kw))
+            ew.CSSLink('tool/{}/{}'.format(app.config.tool_name.lower(), href), **kw))
 
     def register_app_js(self, href, **kw):
         app = kw.pop('app', c.app)
         self.resource_manager.register(
-            ew.JSLink('tool/%s/%s' % (app.config.tool_name.lower(), href), **kw))
+            ew.JSLink('tool/{}/{}'.format(app.config.tool_name.lower(), href), **kw))
 
     def register_theme_css(self, href, **kw):
         self.resource_manager.register(ew.CSSLink(self.theme_href(href), **kw))
@@ -625,7 +625,7 @@ class Globals(object):
             return False
 
         allura_path = os.path.dirname(os.path.dirname(__file__))
-        image_full_path = '%s/public/nf/images/%s' % (
+        image_full_path = '{}/public/nf/images/{}'.format(
             allura_path, logo['image_path'])
 
         if not os.path.isfile(image_full_path):
diff --git a/Allura/allura/lib/custom_middleware.py b/Allura/allura/lib/custom_middleware.py
index 85496a4..f121f49 100644
--- a/Allura/allura/lib/custom_middleware.py
+++ b/Allura/allura/lib/custom_middleware.py
@@ -92,7 +92,7 @@ class CORSMiddleware(object):
     def __init__(self, app, allowed_methods, allowed_headers, cache=None):
         self.app = app
         self.allowed_methods = [m.upper() for m in allowed_methods]
-        self.allowed_headers = set(h.lower() for h in allowed_headers)
+        self.allowed_headers = {h.lower() for h in allowed_headers}
         self.cache_preflight = cache or None
 
     def __call__(self, environ, start_response):
@@ -141,7 +141,7 @@ class CORSMiddleware(object):
 
     def get_access_control_request_headers(self, environ):
         headers = environ.get('HTTP_ACCESS_CONTROL_REQUEST_HEADERS', '')
-        return set(h.strip().lower() for h in headers.split(',') if h.strip())
+        return {h.strip().lower() for h in headers.split(',') if h.strip()}
 
 
 class LoginRedirectMiddleware(object):
@@ -218,7 +218,7 @@ class CSRFMiddleware(object):
                 use_secure = 'secure; ' if environ['beaker.session'].secure else ''
                 headers.append(
                     (str('Set-cookie'),
-                     str('%s=%s; %sPath=/' % (self._cookie_name, cookie, use_secure))))
+                     str('{}={}; {}Path=/'.format(self._cookie_name, cookie, use_secure))))
             return start_response(status, headers, exc_info)
 
         return self._app(environ, session_start_response)
diff --git a/Allura/allura/lib/exceptions.py b/Allura/allura/lib/exceptions.py
index 0e4ba22..c271348 100644
--- a/Allura/allura/lib/exceptions.py
+++ b/Allura/allura/lib/exceptions.py
@@ -96,7 +96,7 @@ class InvalidRecoveryCode(ForgeError):
 class CompoundError(ForgeError):
 
     def __repr__(self):
-        return '<%s>\n%s\n</%s>' % (
+        return '<{}>\n{}\n</{}>'.format(
             self.__class__.__name__,
             '\n'.join(map(repr, self.args)),
             self.__class__.__name__)
diff --git a/Allura/allura/lib/gravatar.py b/Allura/allura/lib/gravatar.py
index 92d879c..bde5305 100644
--- a/Allura/allura/lib/gravatar.py
+++ b/Allura/allura/lib/gravatar.py
@@ -88,7 +88,7 @@ def url(email=None, gravatar_id=None, **kw):
         kw['r'] = 'pg'
     if 'd' not in kw and config.get('default_avatar_image'):
         kw['d'] = h.absurl(config['default_avatar_image'])
-    return ('https://secure.gravatar.com/avatar/%s?%s' % (gravatar_id, six.moves.urllib.parse.urlencode(kw)))
+    return ('https://secure.gravatar.com/avatar/{}?{}'.format(gravatar_id, six.moves.urllib.parse.urlencode(kw)))
 
 
 def for_user(user):
diff --git a/Allura/allura/lib/helpers.py b/Allura/allura/lib/helpers.py
index 0a614dd..64331b3 100644
--- a/Allura/allura/lib/helpers.py
+++ b/Allura/allura/lib/helpers.py
@@ -275,9 +275,9 @@ def _make_xs(X, ids):
     from allura import model as M
     X = getattr(M, X)
     ids = list(ids)
-    results = dict(
-        (r._id, r)
-        for r in X.query.find(dict(_id={'$in': ids})))
+    results = {
+        r._id: r
+        for r in X.query.find(dict(_id={'$in': ids}))}
     result = (results.get(i) for i in ids)
     return (r for r in result if r is not None)
 
@@ -403,16 +403,16 @@ def push_context(project_id, mount_point=None, app_config_id=None, neighborhood=
 def encode_keys(d):
     '''Encodes the unicode keys of d, making the result
     a valid kwargs argument'''
-    return dict(
-        (six.ensure_str(k), v)
-        for k, v in six.iteritems(d))
+    return {
+        six.ensure_str(k): v
+        for k, v in six.iteritems(d)}
 
 
 def vardec(fun):
     def vardec_hook(remainder, params):
-        new_params = variable_decode(dict(
-            (k, v) for k, v in params.items()
-            if re_clean_vardec_key.match(k)))
+        new_params = variable_decode({
+            k: v for k, v in params.items()
+            if re_clean_vardec_key.match(k)})
         params.update(new_params)
     before_validate(vardec_hook)(fun)
     return fun
@@ -563,10 +563,10 @@ def gen_message_id(_id=None):
     else:
         parts = ['mail']
     if getattr(c, 'app', None):
-        addr = '%s.%s' % (_id, c.app.config.options['mount_point'])
+        addr = '{}.{}'.format(_id, c.app.config.options['mount_point'])
     else:
         addr = _id
-    return '%s@%s.%s' % (
+    return '{}@{}.{}'.format(
         addr, '.'.join(reversed(parts)), tg.config['domain'])
 
 
@@ -585,7 +585,7 @@ class attrproxy(object):
         self.attrs = attrs
 
     def __repr__(self):
-        return '<attrproxy on %s for %s>' % (
+        return '<attrproxy on {} for {}>'.format(
             self.cls, self.attrs)
 
     def __get__(self, obj, klass=None):
@@ -610,7 +610,7 @@ class promised_attrproxy(attrproxy):
         self._promise = promise
 
     def __repr__(self):
-        return '<promised_attrproxy for %s>' % (self.attrs,)
+        return '<promised_attrproxy for {}>'.format(self.attrs)
 
     def __getattr__(self, name):
         cls = self._promise()
@@ -673,8 +673,8 @@ def config_with_prefix(d, prefix):
     with the prefix stripped
     '''
     plen = len(prefix)
-    return dict((k[plen:], v) for k, v in six.iteritems(d)
-                if k.startswith(prefix))
+    return {k[plen:]: v for k, v in six.iteritems(d)
+                if k.startswith(prefix)}
 
 
 @contextmanager
@@ -729,7 +729,7 @@ def _add_inline_line_numbers_to_text(txt):
     markup_text = '<div class="codehilite"><pre>'
     for line_num, line in enumerate(txt.splitlines(), 1):
         markup_text = markup_text + \
-            '<span id="l%s" class="code_block"><span class="lineno">%s</span> %s</span>' % (
+            '<span id="l{}" class="code_block"><span class="lineno">{}</span> {}</span>'.format(
                 line_num, line_num, line)
     markup_text = markup_text + '</pre></div>'
     return markup_text
@@ -752,7 +752,7 @@ def _add_table_line_numbers_to_text(txt):
         linenumbers + '<td class="code"><div class="codehilite"><pre>'
     for line_num, line in enumerate(lines, 1):
         markup_text = markup_text + \
-            '<span id="l%s" class="code_block">%s</span>' % (line_num, line)
+            '<span id="l{}" class="code_block">{}</span>'.format(line_num, line)
     markup_text = markup_text + '</pre></div></td></tr></tbody></table>'
     return markup_text
 
@@ -926,7 +926,7 @@ def topological_sort(items, partial_order):
 
 @contextmanager
 def ming_config(**conf):
-    """Temporarily swap in a new ming configuration, restoring the previous
+    r"""Temporarily swap in a new ming configuration, restoring the previous
     one when the contextmanager exits.
 
     :param \*\*conf: keyword arguments defining the new ming configuration
@@ -1129,7 +1129,7 @@ def iter_entry_points(group, *a, **kw):
                 yield subclass(eps)
 
     def subclass(entry_points):
-        loaded = dict((ep, ep.load()) for ep in entry_points)
+        loaded = {ep: ep.load() for ep in entry_points}
         for ep, cls in six.iteritems(loaded):
             others = list(loaded.values())[:]
             others.remove(cls)
diff --git a/Allura/allura/lib/import_api.py b/Allura/allura/lib/import_api.py
index 32f35c4..768ac75 100644
--- a/Allura/allura/lib/import_api.py
+++ b/Allura/allura/lib/import_api.py
@@ -51,7 +51,7 @@ class AlluraImportApiClient(object):
                 resp = result.read()
                 return json.loads(resp)
             except six.moves.urllib.error.HTTPError as e:
-                e.msg += ' ({0})'.format(url)
+                e.msg += ' ({})'.format(url)
                 if self.verbose:
                     error_content = e.read()
                     e.msg += '. Error response:\n' + six.ensure_text(error_content)
diff --git a/Allura/allura/lib/macro.py b/Allura/allura/lib/macro.py
index 82a135c..1fec43d 100644
--- a/Allura/allura/lib/macro.py
+++ b/Allura/allura/lib/macro.py
@@ -80,11 +80,11 @@ class parse(object):
                 log.warn('macro error.  Upwards stack is %s',
                          ''.join(traceback.format_stack()),
                          exc_info=True)
-                msg = cgi.escape('[[%s]] (%s)' % (s, repr(ex)))
+                msg = cgi.escape('[[{}]] ({})'.format(s, repr(ex)))
                 return '\n<div class="error"><pre><code>%s</code></pre></div>' % msg
         except Exception as ex:
             raise
-            return '[[Error parsing %s: %s]]' % (s, ex)
+            return '[[Error parsing {}: {}]]'.format(s, ex)
 
     def _lookup_macro(self, s):
         macro, context = _macros.get(s, (None, None))
@@ -360,7 +360,7 @@ def include_file(repo, path=None, rev=None, **kw):
     try:
         file = app.repo.commit(rev).get_path(path)
     except Exception:
-        return "[[include can't find file %s in revision %s]]" % (path, rev)
+        return "[[include can't find file {} in revision {}]]".format(path, rev)
 
     text = ''
     if file.has_pypeline_view:
@@ -368,7 +368,7 @@ def include_file(repo, path=None, rev=None, **kw):
     elif file.has_html_view:
         text = g.highlight(file.text, filename=file.name)
     else:
-        return "[[include can't display file %s in revision %s]]" % (path, rev)
+        return "[[include can't display file {} in revision {}]]".format(path, rev)
 
     from allura.lib.widgets.macros import Include
     sb = Include()
@@ -409,9 +409,9 @@ def img(src=None, **kw):
     included = request.environ.setdefault('allura.macro.att_embedded', set())
     included.add(src)
     if '://' in src:
-        return '<img src="%s" %s/>' % (src, ' '.join(attrs))
+        return '<img src="{}" {}/>'.format(src, ' '.join(attrs))
     else:
-        return '<img src="./attachment/%s" %s/>' % (src, ' '.join(attrs))
+        return '<img src="./attachment/{}" {}/>'.format(src, ' '.join(attrs))
 
 
 @macro()
diff --git a/Allura/allura/lib/markdown_extensions.py b/Allura/allura/lib/markdown_extensions.py
index 452b161..ab11ca9 100644
--- a/Allura/allura/lib/markdown_extensions.py
+++ b/Allura/allura/lib/markdown_extensions.py
@@ -534,7 +534,7 @@ class AutolinkPattern(markdown.inlinepatterns.Pattern):
         super().__init__(pattern, markdown_instance)
         # override the complete regex, requiring the preceding text (.*?) to end
         # with whitespace or beginning of line "\s|^"
-        self.compiled_re = re.compile("^(.*?\s|^)%s(.*?)$" % pattern,
+        self.compiled_re = re.compile(r"^(.*?\s|^)%s(.*?)$" % pattern,
                                       re.DOTALL | re.UNICODE)
 
     def handleMatch(self, mo):
diff --git a/Allura/allura/lib/package_path_loader.py b/Allura/allura/lib/package_path_loader.py
index be28fed..9be2d94 100644
--- a/Allura/allura/lib/package_path_loader.py
+++ b/Allura/allura/lib/package_path_loader.py
@@ -202,7 +202,7 @@ class PackagePathLoader(jinja2.BaseLoader):
                     order_rules.append((rule[1], ep.name))
                 else:
                     raise jinja2.TemplateError(
-                        'Unknown template path rule in %s: %s' % (
+                        'Unknown template path rule in {}: {}'.format(
                             ep.name, ' '.join(rule)))
         return order_rules, replacement_rules
 
diff --git a/Allura/allura/lib/plugin.py b/Allura/allura/lib/plugin.py
index 6331387..b3dcb15 100644
--- a/Allura/allura/lib/plugin.py
+++ b/Allura/allura/lib/plugin.py
@@ -645,7 +645,7 @@ def ldap_user_dn(username):
     'return a Distinguished Name for a given username'
     if not username:
         raise ValueError('Empty username')
-    return 'uid=%s,%s' % (
+    return 'uid={},{}'.format(
         ldap.dn.escape_dn_chars(username),
         config['auth.ldap.suffix'])
 
@@ -690,7 +690,7 @@ class LdapAuthenticationProvider(AuthenticationProvider):
         con.unbind_s()
 
         if asbool(config.get('auth.ldap.use_schroot', True)):
-            argv = ('schroot -d / -c %s -u root /ldap-userconfig.py init %s' % (
+            argv = ('schroot -d / -c {} -u root /ldap-userconfig.py init {}'.format(
                 config['auth.ldap.schroot_name'], user_doc['username'])).split()
             p = subprocess.Popen(
                 argv, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
@@ -704,7 +704,7 @@ class LdapAuthenticationProvider(AuthenticationProvider):
         if not asbool(config.get('auth.ldap.use_schroot', True)):
             raise NotImplementedError('SSH keys are not supported')
 
-        argv = ('schroot -d / -c %s -u root /ldap-userconfig.py upload %s' % (
+        argv = ('schroot -d / -c {} -u root /ldap-userconfig.py upload {}'.format(
             config['auth.ldap.schroot_name'], username)).split() + [pubkey]
         p = subprocess.Popen(
             argv, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
@@ -728,7 +728,7 @@ class LdapAuthenticationProvider(AuthenticationProvider):
         salt = self._get_salt(salt_len) if salt is None else salt
         encrypted = crypt.crypt(
             six.ensure_str(password),
-            '$%s$rounds=%s$%s' % (algorithm, rounds, salt))
+            '${}$rounds={}${}'.format(algorithm, rounds, salt))
         return b'{CRYPT}%s' % encrypted.encode('utf-8')
 
     def by_username(self, username):
@@ -1019,7 +1019,7 @@ class ProjectRegistrationProvider(object):
         p = M.Project.query.get(shortname=shortname, neighborhood_id=neighborhood._id)
         if p:
             raise forge_exc.ProjectConflict(
-                '%s already exists in nbhd %s' % (shortname, neighborhood._id))
+                '{} already exists in nbhd {}'.format(shortname, neighborhood._id))
 
     def index_project(self, project):
         """
@@ -1328,7 +1328,7 @@ class ThemeProvider(object):
         '''
         if theme_name is None:
             theme_name = config.get('theme', 'allura')
-        return g.resource_manager.absurl('theme/%s/%s' % (theme_name, href))
+        return g.resource_manager.absurl('theme/{}/{}'.format(theme_name, href))
 
     @LazyProperty
     def personal_data_form(self):
diff --git a/Allura/allura/lib/project_create_helpers.py b/Allura/allura/lib/project_create_helpers.py
index 8256e71..fdee7c5 100644
--- a/Allura/allura/lib/project_create_helpers.py
+++ b/Allura/allura/lib/project_create_helpers.py
@@ -188,7 +188,7 @@ class NewProjectSchema(col.MappingSchema):
 def trove_ids(orig, new_):
     if new_ is None:
         return orig
-    return list(set(t._id for t in list(new_)))
+    return list({t._id for t in list(new_)})
 
 
 def make_newproject_schema(nbhd, update=False):
diff --git a/Allura/allura/lib/search.py b/Allura/allura/lib/search.py
index e343090..d936f00 100644
--- a/Allura/allura/lib/search.py
+++ b/Allura/allura/lib/search.py
@@ -51,7 +51,7 @@ class SearchIndexable(object):
 
         Used for SOLR ID, shortlinks, and possibly elsewhere.
         """
-        id = '%s.%s#%s' % (
+        id = '{}.{}#{}'.format(
             self.__class__.__module__,
             self.__class__.__name__,
             self._id)
@@ -188,15 +188,15 @@ def search_artifact(atype, q, history=False, rows=10, short_timeout=False, filte
     fq += kw.pop('fq', [])
     if isinstance(filter, six.string_types):  # may be stringified after a ticket filter, then bulk edit
         filter = ast.literal_eval(filter)
-    for name, values in six.iteritems((filter or {})):
+    for name, values in six.iteritems(filter or {}):
         field_name = name + '_s'
         parts = []
         for v in values:
             # Specific solr syntax for empty fields
             if v == '' or v is None:
-                part = '(-%s:[* TO *] AND *:*)' % (field_name,)
+                part = '(-{}:[* TO *] AND *:*)'.format(field_name)
             else:
-                part = '%s:%s' % (field_name, escape_solr_arg(v))
+                part = '{}:{}'.format(field_name, escape_solr_arg(v))
             parts.append(part)
         fq.append(' OR '.join(parts))
     if not history:
@@ -223,7 +223,7 @@ 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('%s:(%s)' % (field, q), fields)
+        q = obj.translate_query('{}:({})'.format(field, q), fields)
         kw['q.op'] = 'AND'  # so that all terms within the () are required
     fq = ['type_s:%s' % model.type_s]
     return search(q, fq=fq, ignore_errors=False, **kw)
diff --git a/Allura/allura/lib/security.py b/Allura/allura/lib/security.py
index 52ba513..84ad562 100644
--- a/Allura/allura/lib/security.py
+++ b/Allura/allura/lib/security.py
@@ -101,7 +101,7 @@ class Credentials(object):
                 'project_id': {'$in': project_ids},
                 'name': None})
             q = chain(q0, q1)
-        roles_by_project = dict((pid, []) for pid in project_ids)
+        roles_by_project = {pid: [] for pid in project_ids}
         for role in q:
             roles_by_project[role['project_id']].append(role)
         for pid, roles in six.iteritems(roles_by_project):
@@ -116,7 +116,7 @@ class Credentials(object):
             return
         q = self.project_role.find({
             'project_id': {'$in': project_ids}})
-        roles_by_project = dict((pid, []) for pid in project_ids)
+        roles_by_project = {pid: [] for pid in project_ids}
         for role in q:
             roles_by_project[role['project_id']].append(role)
         for pid, roles in six.iteritems(roles_by_project):
@@ -206,7 +206,7 @@ class RoleCache(object):
 
     @LazyProperty
     def index(self):
-        return dict((r['_id'], r) for r in self.q)
+        return {r['_id']: r for r in self.q}
 
     @LazyProperty
     def named(self):
@@ -252,7 +252,7 @@ class RoleCache(object):
     def reaching_roles(self):
         def _iter():
             to_visit = list(self.index.items())
-            project_ids = set([r['project_id'] for _id, r in to_visit])
+            project_ids = {r['project_id'] for _id, r in to_visit}
             pr_index = {r['_id']: r for r in self.cred.project_role.find({
                 'project_id': {'$in': list(project_ids)},
                 'user_id': None,
@@ -443,7 +443,7 @@ def all_allowed(obj, user_or_role=None, project=None):
                         denied[role_id].add(ace.permission)
         obj = obj.parent_security_context()
     if M.ALL_PERMISSIONS in perms:
-        return set([M.ALL_PERMISSIONS])
+        return {M.ALL_PERMISSIONS}
     return perms
 
 
diff --git a/Allura/allura/lib/spam/__init__.py b/Allura/allura/lib/spam/__init__.py
index 2e2d5a3..28065b8 100644
--- a/Allura/allura/lib/spam/__init__.py
+++ b/Allura/allura/lib/spam/__init__.py
@@ -49,7 +49,7 @@ class SpamFilter(object):
         log.info("No submit_ham available for %s", self.filter_name)
 
     def record_result(self, result, artifact, user):
-        log.info("spam=%s (%s): %s" % (str(result), self.filter_name, artifact.url() if artifact else ''))
+        log.info("spam={} ({}): {}".format(str(result), self.filter_name, artifact.url() if artifact else ''))
         r = SpamCheckResult(
             ref=artifact.ref if artifact else None,
             project_id=artifact.project_id if artifact else None,
diff --git a/Allura/allura/lib/spam/stopforumspamfilter.py b/Allura/allura/lib/spam/stopforumspamfilter.py
index 7abcdff..f84fc4d 100644
--- a/Allura/allura/lib/spam/stopforumspamfilter.py
+++ b/Allura/allura/lib/spam/stopforumspamfilter.py
@@ -47,7 +47,7 @@ class StopForumSpamSpamFilter(SpamFilter):
     def __init__(self, config):
         self.packed_ips = set()
         threshold = int(config.get('spam.stopforumspam.threshold', 20))
-        threshold_strs = set(str(i) for i in range(threshold+1))  # make strs, so that in the loop no int cast needed
+        threshold_strs = {str(i) for i in range(threshold+1)}  # make strs, so that in the loop no int cast needed
         with open(config['spam.stopforumspam.ip_addr_file']) as f:
             csv_file = csv.reader(f)
             for record in csv_file:
diff --git a/Allura/allura/lib/utils.py b/Allura/allura/lib/utils.py
index e2aefc5..9391e82 100644
--- a/Allura/allura/lib/utils.py
+++ b/Allura/allura/lib/utils.py
@@ -154,7 +154,7 @@ class CustomWatchedFileHandler(logging.handlers.WatchedFileHandler):
         """
         title = getproctitle()
         if title.startswith('taskd:'):
-            record.name = "{0}:{1}".format(title, record.name)
+            record.name = "{}:{}".format(title, record.name)
         return super(CustomWatchedFileHandler, self).format(record)
 
 
@@ -472,7 +472,7 @@ class LineAnchorCodeHtmlFormatter(HtmlFormatter):
         num = self.linenostart
         yield 0, ('<pre' + (style and ' style="%s"' % style) + '>')
         for tup in inner:
-            yield (tup[0], '<div id="l%s" class="code_block">%s</div>' % (num, tup[1]))
+            yield (tup[0], '<div id="l{}" class="code_block">{}</div>'.format(num, tup[1]))
             num += 1
         yield 0, '</pre>'
 
@@ -488,7 +488,7 @@ def generate_code_stats(blob):
     stats['code_size'] = blob.size
     stats['line_count'] = len(lines)
     spaces = re.compile(r'^\s*$')
-    stats['data_line_count'] = sum([1 for l in lines if not spaces.match(l)])
+    stats['data_line_count'] = sum(1 for l in lines if not spaces.match(l))
     return stats
 
 
@@ -510,7 +510,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 = '{0}?{1}'.format(filename, last_modified).encode('utf-8')
+        etag = '{}?{}'.format(filename, last_modified).encode('utf-8')
     if etag:
         etag_cache(etag)
     tg.response.headers['Content-Type'] = str('')
@@ -560,9 +560,9 @@ class ForgeHTMLSanitizerFilter(html5lib.filters.sanitizer.Filter):
                           (ns_html, 'select'),
                           (ns_html, 'textarea'),
                           }
-        _extra_allowed_elements = set([
+        _extra_allowed_elements = {
             (ns_html, 'summary'),
-        ])
+        }
         self.allowed_elements = (set(html5lib.filters.sanitizer.allowed_elements) | _extra_allowed_elements) - _form_elements
 
         # srcset is used in our own project_list/project_summary widgets
@@ -657,7 +657,7 @@ class DateJSONEncoder(json.JSONEncoder):
 
 
 def clean_phone_number(number):
-    pattern = re.compile('\W+')
+    pattern = re.compile(r'\W+')
     number = pattern.sub('', number)
     return number
 
diff --git a/Allura/allura/lib/widgets/form_fields.py b/Allura/allura/lib/widgets/form_fields.py
index 69e982a..3560ad7 100644
--- a/Allura/allura/lib/widgets/form_fields.py
+++ b/Allura/allura/lib/widgets/form_fields.py
@@ -381,9 +381,9 @@ class JQueryMixin(object):
             yield r
         if self.js_plugin_file is not None:
             yield self.js_plugin_file
-        opts = dict(
-            (k, getattr(self, k))
-            for k in self.js_params)
+        opts = {
+            k: getattr(self, k)
+            for k in self.js_params}
         yield onready('''
 $(document).bind('clone', function () {
     $('.%s').%s(%s); });
diff --git a/Allura/allura/lib/widgets/forms.py b/Allura/allura/lib/widgets/forms.py
index 7340b72..14dc669 100644
--- a/Allura/allura/lib/widgets/forms.py
+++ b/Allura/allura/lib/widgets/forms.py
@@ -105,7 +105,7 @@ class ForgeForm(ew.SimpleForm):
             or ctx.get('label')
             or getattr(field, 'label', None)
             or ctx['name'])
-        html = '<label for="%s">%s</label>' % (
+        html = '<label for="{}">{}</label>'.format(
             ctx['id'], label_text)
         return Markup(html)
 
@@ -119,7 +119,7 @@ class ForgeForm(ew.SimpleForm):
         ctx = self.context_for(field)
         display = field.display(**ctx)
         if ctx['errors'] and field.show_errors and not ignore_errors:
-            display = "%s<div class='error'>%s</div>" % (display,
+            display = "{}<div class='error'>{}</div>".format(display,
                                                          ctx['errors'])
         return Markup(display)
 
@@ -879,7 +879,7 @@ class NeighborhoodOverviewForm(ForgeForm):
             display += '</table>'
 
             if ctx['errors'] and field.show_errors and not ignore_errors:
-                display = "%s<div class='error'>%s</div>" % (display,
+                display = "{}<div class='error'>{}</div>".format(display,
                                                              ctx['errors'])
 
             return Markup(display)
diff --git a/Allura/allura/model/artifact.py b/Allura/allura/model/artifact.py
index 44bae1c..90fed8b 100644
--- a/Allura/allura/model/artifact.py
+++ b/Allura/allura/model/artifact.py
@@ -279,7 +279,7 @@ class Artifact(MappedClass, SearchIndexable):
 
         """
         if subject:
-            return 'mailto:%s?subject=[%s:%s:%s] Re: %s' % (
+            return 'mailto:{}?subject=[{}:{}:{}] Re: {}'.format(
                 self.email_address,
                 self.app_config.project.shortname,
                 self.app_config.options.mount_point,
@@ -540,7 +540,7 @@ class Snapshot(Artifact):
         raise NotImplementedError('original')  # pragma no cover
 
     def shorthand_id(self):
-        return '%s#%s' % (self.original().shorthand_id(), self.version)
+        return '{}#{}'.format(self.original().shorthand_id(), self.version)
 
     def clear_user_data(self):
         """ Redact author data for a given user """
@@ -591,7 +591,7 @@ class VersionedArtifact(Artifact):
             ip_address = '0.0.0.0'
         data = dict(
             artifact_id=self._id,
-            artifact_class='%s.%s' % (
+            artifact_class='{}.{}'.format(
                 self.__class__.__module__,
                 self.__class__.__name__),
             author=dict(
@@ -630,7 +630,7 @@ class VersionedArtifact(Artifact):
             n = self.version + n + 1
         ss = self.__mongometa__.history_class.query.get(
             artifact_id=self._id,
-            artifact_class='%s.%s' % (
+            artifact_class='{}.{}'.format(
                 self.__class__.__module__,
                 self.__class__.__name__),
             version=n)
@@ -954,7 +954,7 @@ class Feed(MappedClass):
         if author_name is None:
             author_name = author.get_pref('display_name')
         if title is None:
-            title = '%s modified by %s' % (
+            title = '{} modified by {}'.format(
                 h.get_first(idx, 'title'), author_name)
         if description is None:
             description = title
diff --git a/Allura/allura/model/auth.py b/Allura/allura/model/auth.py
index 9b5f995..8fe2ad6 100644
--- a/Allura/allura/model/auth.py
+++ b/Allura/allura/model/auth.py
@@ -140,7 +140,7 @@ class EmailAddress(MappedClass):
         if '@' in addr:
             try:
                 user, domain = addr.strip().split('@')
-                return '%s@%s' % (user, domain.lower())
+                return '{}@{}'.format(user, domain.lower())
             except ValueError:
                 return addr.strip()
         else:
@@ -325,10 +325,10 @@ class User(MappedClass, ActivityNode, ActivityObject, SearchIndexable):
 
     def index(self):
         provider = plugin.AuthenticationProvider.get(None)  # no need in request here
-        localization = '%s/%s' % (
+        localization = '{}/{}'.format(
             self.get_pref('localization')['country'],
             self.get_pref('localization')['city'])
-        socialnetworks = ' '.join(['%s: %s' % (n['socialnetwork'], n['accounturl'])
+        socialnetworks = ' '.join(['{}: {}'.format(n['socialnetwork'], n['accounturl'])
                                    for n in self.get_pref('socialnetworks')])
         fields = dict(
             id=self.index_id(),
@@ -487,7 +487,7 @@ class User(MappedClass, ActivityNode, ActivityObject, SearchIndexable):
 
         """
         tmpl = g.jinja2_env.get_template('allura:templates/mail/usermentions_email.md')
-        subject = '[%s:%s] Your name was mentioned' % (
+        subject = '[{}:{}] Your name was mentioned'.format(
             c.project.shortname, c.app.config.options.mount_point)
         item_url = artifact.url()
         if artifact.type_s == 'Post':
@@ -835,7 +835,7 @@ class User(MappedClass, ActivityNode, ActivityObject, SearchIndexable):
 
     def email_address_header(self):
         h = header.Header()
-        h.append('"%s"%s' % (self.get_pref('display_name'),
+        h.append('"{}"{}'.format(self.get_pref('display_name'),
                              ' ' if six.PY2 else ''))  # py2 needs explicit space for unicode/text_type cast of Header
         h.append('<%s>' % self.get_pref('email_address'))
         return h
@@ -1081,7 +1081,7 @@ class AuditLog(MappedClass):
 
     @classmethod
     def comment_user(cls, by, message, *args, **kwargs):
-        message = 'Comment by %s: %s' % (by.username, message)
+        message = 'Comment by {}: {}'.format(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 8a55985..3392be3 100644
--- a/Allura/allura/model/discuss.py
+++ b/Allura/allura/model/discuss.py
@@ -362,7 +362,7 @@ class Thread(Artifact, ActivityObject):
     def notify_moderators(self, post):
         ''' Notify moderators that a post needs approval [#2963] '''
         artifact = self.artifact or self
-        subject = '[%s:%s] Moderation action required' % (
+        subject = '[{}:{}] Moderation action required'.format(
             c.project.shortname, c.app.config.options.mount_point)
         author = post.author()
         url = self.discussion_class().query.get(_id=self.discussion_id).url()
@@ -489,7 +489,7 @@ class PostHistory(Snapshot):
     def shorthand_id(self):
         original = self.original()
         if original:
-            return '%s#%s' % (original.shorthand_id(), self.version)
+            return '{}#{}'.format(original.shorthand_id(), self.version)
         else:
             return None
 
@@ -601,7 +601,7 @@ class Post(Message, VersionedArtifact, ActivityObject, ReactableArtifact):
     def index(self):
         result = super(Post, self).index()
         result.update(
-            title='Post by %s on %s' % (
+            title='Post by {} on {}'.format(
                 self.author().username, self.subject),
             name_s=self.subject,
             type_s='Post',
@@ -728,12 +728,12 @@ class Post(Message, VersionedArtifact, ActivityObject, ReactableArtifact):
         slug = h.urlquote(self.slug)
         url = self.main_url()
         if page == 0:
-            return '%s?limit=%s#%s' % (url, limit, slug)
-        return '%s?limit=%s&page=%s#%s' % (url, limit, page, slug)
+            return '{}?limit={}#{}'.format(url, limit, slug)
+        return '{}?limit={}&page={}#{}'.format(url, limit, page, slug)
 
     def shorthand_id(self):
         if self.thread:
-            return '%s#%s' % (self.thread.shorthand_id(), self.slug)
+            return '{}#{}'.format(self.thread.shorthand_id(), self.slug)
         else:  # pragma no cover
             return None
 
diff --git a/Allura/allura/model/filesystem.py b/Allura/allura/model/filesystem.py
index d33af38..798c0a1 100644
--- a/Allura/allura/model/filesystem.py
+++ b/Allura/allura/model/filesystem.py
@@ -38,14 +38,14 @@ if typing.TYPE_CHECKING:
 
 log = logging.getLogger(__name__)
 
-SUPPORTED_BY_PIL = set([
+SUPPORTED_BY_PIL = {
     'image/jpg',
     'image/jpeg',
     'image/pjpeg',
     'image/png',
     'image/x-png',
     'image/gif',
-    'image/bmp'])
+    'image/bmp'}
 
 
 class File(MappedClass):
diff --git a/Allura/allura/model/index.py b/Allura/allura/model/index.py
index bf38717..cc4747c 100644
--- a/Allura/allura/model/index.py
+++ b/Allura/allura/model/index.py
@@ -133,7 +133,7 @@ class Shortlink(MappedClass):
     re_link_2 = re.compile(r'^' + _core_re, re.VERBOSE)
 
     def __repr__(self):
-        return '<Shortlink %s %s %s -> %s>' % (
+        return '<Shortlink {} {} {} -> {}>'.format(
             self.project_id,
             self.app_config_id,
             self.link,
@@ -169,8 +169,8 @@ class Shortlink(MappedClass):
         if len(links):
             result = {}
             # Parse all the links
-            parsed_links = dict((link, cls._parse_link(link))
-                                for link in links)
+            parsed_links = {link: cls._parse_link(link)
+                                for link in links}
             links_by_artifact = defaultdict(list)
             project_ids = set()
             for link, d in list(parsed_links.items()):
@@ -187,9 +187,9 @@ class Shortlink(MappedClass):
                 validate=False,
                 sort=[('_id', pymongo.DESCENDING)],  # if happen to be multiple (ticket move?) have newest first
             )
-            matches_by_artifact = dict(
-                (link, list(matches))
-                for link, matches in groupby(q, key=lambda s: unquote(s.link)))
+            matches_by_artifact = {
+                link: list(matches)
+                for link, matches in groupby(q, key=lambda s: unquote(s.link))}
             for link, d in six.iteritems(parsed_links):
                 matches = matches_by_artifact.get(unquote(d['artifact']), [])
                 matches = (
diff --git a/Allura/allura/model/monq_model.py b/Allura/allura/model/monq_model.py
index 556bd5f..bc1b66f 100644
--- a/Allura/allura/model/monq_model.py
+++ b/Allura/allura/model/monq_model.py
@@ -155,7 +155,7 @@ class MonQTask(MappedClass):
             args = ()
         if kwargs is None:
             kwargs = {}
-        task_name = '%s.%s' % (
+        task_name = '{}.{}'.format(
             function.__module__,
             function.__name__)
         context = dict(
diff --git a/Allura/allura/model/notification.py b/Allura/allura/model/notification.py
index 3773054..5519682 100644
--- a/Allura/allura/model/notification.py
+++ b/Allura/allura/model/notification.py
@@ -155,7 +155,7 @@ class Notification(MappedClass):
 
         from allura.model import Project
         idx = artifact.index() if artifact else None
-        subject_prefix = '[%s:%s] ' % (
+        subject_prefix = '[{}:{}] '.format(
             c.project.shortname, c.app.config.options.mount_point)
         post = ''
         if topic == 'message':
@@ -172,7 +172,7 @@ class Notification(MappedClass):
                     attach.file.seek(0)
                     url = h.absurl('{}attachment/{}'.format(
                         post.url(), h.urlquote(attach.filename)))
-                    text = "%s\n- [%s](%s) (%s; %s)" % (
+                    text = "{}\n- [{}]({}) ({}; {})".format(
                         text, attach.filename, url,
                         h.do_filesizeformat(bytecount), attach.type)
 
@@ -187,7 +187,7 @@ class Notification(MappedClass):
                 _id=msg_id,
                 from_address=str(
                     author._id) if author != User.anonymous() else None,
-                reply_to_address='"%s" <%s>' % (
+                reply_to_address='"{}" <{}>'.format(
                     subject_prefix, getattr(
                         artifact, 'email_address', g.noreply)),
                 subject=subject_prefix + subject,
@@ -202,9 +202,9 @@ class Notification(MappedClass):
                     subject=kwargs.pop('subject', ''))
             return n
         else:
-            subject = kwargs.pop('subject', '%s modified by %s' % (
+            subject = kwargs.pop('subject', '{} modified by {}'.format(
                 h.get_first(idx, 'title'), c.user.get_pref('display_name')))
-            reply_to = '"%s" <%s>' % (
+            reply_to = '"{}" <{}>'.format(
                 h.get_first(idx, 'title'),
                 getattr(artifact, 'email_address', g.noreply))
             d = dict(
@@ -217,11 +217,11 @@ class Notification(MappedClass):
             if kwargs.get('message_id'):
                 d['_id'] = kwargs['message_id']
             if c.user.get_pref('email_address'):
-                d['from_address'] = '"%s" <%s>' % (
+                d['from_address'] = '"{}" <{}>'.format(
                     c.user.get_pref('display_name'),
                     c.user.get_pref('email_address'))
             elif c.user.email_addresses:
-                d['from_address'] = '"%s" <%s>' % (
+                d['from_address'] = '"{}" <{}>'.format(
                     c.user.get_pref('display_name'),
                     c.user.email_addresses[0])
         if not d.get('text'):
diff --git a/Allura/allura/model/project.py b/Allura/allura/model/project.py
index 609a551..8b6cdd1 100644
--- a/Allura/allura/model/project.py
+++ b/Allura/allura/model/project.py
@@ -522,8 +522,8 @@ class Project(SearchIndexable, MappedClass, ActivityNode, ActivityObject):
     @classmethod
     def icon_urls(cls, projects):
         '''Return a dict[project_id] = icon_url, efficiently'''
-        project_index = dict((p._id, p) for p in projects)
-        result = dict((p._id, None) for p in projects)
+        project_index = {p._id: p for p in projects}
+        result = {p._id: None for p in projects}
         for icon in ProjectFile.query.find(dict(
                 project_id={'$in': list(result.keys())},
                 category='icon')):
@@ -534,7 +534,7 @@ class Project(SearchIndexable, MappedClass, ActivityNode, ActivityObject):
     def accolades_index(cls, projects):
         '''Return a dict[project_id] = list of accolades, efficiently'''
         from .artifact import AwardGrant
-        result = dict((p._id, []) for p in projects)
+        result = {p._id: [] for p in projects}
         for award in AwardGrant.query.find(dict(
                 granted_to_project_id={'$in': list(result.keys())})):
             result[award.granted_to_project_id].append(award)
@@ -1117,8 +1117,8 @@ class Project(SearchIndexable, MappedClass, ActivityNode, ActivityObject):
 
     def social_account(self, socialnetwork):
         try:
-            account = next((
-                sn for sn in self.socialnetworks if sn.socialnetwork == socialnetwork))
+            account = next(
+                sn for sn in self.socialnetworks if sn.socialnetwork == socialnetwork)
         except StopIteration:
             return None
         else:
diff --git a/Allura/allura/model/repo_refresh.py b/Allura/allura/model/repo_refresh.py
index 7c6f077..b497105 100644
--- a/Allura/allura/model/repo_refresh.py
+++ b/Allura/allura/model/repo_refresh.py
@@ -184,7 +184,7 @@ def unknown_commit_ids(all_commit_ids):
     for chunk in utils.chunked_iter(all_commit_ids, QSIZE):
         chunk = list(chunk)
         q = CommitDoc.m.find(dict(_id={'$in': chunk}))
-        known_commit_ids = set(ci._id for ci in q)
+        known_commit_ids = {ci._id for ci in q}
         result += [oid for oid in chunk if oid not in known_commit_ids]
     return result
 
@@ -203,9 +203,9 @@ def send_notifications(repo, commit_ids):
     base_url = tg.config['base_url']
     for oids in utils.chunked_iter(commit_ids, QSIZE):
         chunk = list(oids)
-        index = dict(
-            (doc._id, doc)
-            for doc in Commit.query.find(dict(_id={'$in': chunk})))
+        index = {
+            doc._id: doc
+            for doc in Commit.query.find(dict(_id={'$in': chunk}))}
         for oid in chunk:
             ci = index[oid]
             href = repo.url_for_commit(oid)
@@ -213,7 +213,7 @@ def send_notifications(repo, commit_ids):
             summary = _summarize(ci.message)
             Feed.post(
                 repo, title=title,
-                description='%s<br><a href="%s">View Changes</a>' % (
+                description='{}<br><a href="{}">View Changes</a>'.format(
                     summary, href),
                 author_link=ci.author_url,
                 author_name=ci.authored.name,
diff --git a/Allura/allura/model/repository.py b/Allura/allura/model/repository.py
index bbd17db..34013a7 100644
--- a/Allura/allura/model/repository.py
+++ b/Allura/allura/model/repository.py
@@ -72,7 +72,7 @@ config = utils.ConfigProxy(
     common_suffix='forgemail.domain',
 )
 
-README_RE = re.compile('^README(\.[^.]*)?$', re.IGNORECASE)
+README_RE = re.compile(r'^README(\.[^.]*)?$', re.IGNORECASE)
 VIEWABLE_EXTENSIONS = frozenset([
     '.php', '.py', '.js', '.java', '.html', '.htm', '.yaml', '.sh',
     '.rb', '.phtml', '.txt', '.bat', '.ps1', '.xhtml', '.css', '.cfm', '.jsp', '.jspx',
@@ -422,11 +422,11 @@ class Repository(Artifact, ActivityObject):
     def tarball_filename(self, revision, path=None):
         shortname = c.project.shortname.replace('/', '-')
         mount_point = c.app.config.options.mount_point
-        filename = '%s-%s-%s' % (shortname, mount_point, revision)
+        filename = '{}-{}-{}'.format(shortname, mount_point, revision)
         return filename
 
     def tarball_url(self, revision, path=None):
-        filename = '%s%s' % (self.tarball_filename(revision, path), '.zip')
+        filename = '{}{}'.format(self.tarball_filename(revision, path), '.zip')
         r = os.path.join(self.tool,
                          self.project.shortname[:1],
                          self.project.shortname[:2],
@@ -438,7 +438,7 @@ class Repository(Artifact, ActivityObject):
     def get_tarball_status(self, revision, path=None):
         pathname = os.path.join(
             self.tarball_path, self.tarball_filename(revision, path))
-        filename = '%s%s' % (pathname, '.zip')
+        filename = '{}{}'.format(pathname, '.zip')
         if os.path.isfile(filename.encode('utf-8')):
             return 'complete'
 
@@ -452,7 +452,7 @@ class Repository(Artifact, ActivityObject):
         return task.state if task else None
 
     def __repr__(self):  # pragma no cover
-        return '<%s %s>' % (
+        return '<{} {}>'.format(
             self.__class__.__name__,
             self.full_fs_path)
 
@@ -603,7 +603,7 @@ class Repository(Artifact, ActivityObject):
 
     @property
     def email_address(self):
-        return 'noreply@%s%s' % (self.email_domain, config.common_suffix)
+        return 'noreply@{}{}'.format(self.email_domain, config.common_suffix)
 
     def index(self):
         result = Artifact.index(self)
@@ -623,7 +623,7 @@ class Repository(Artifact, ActivityObject):
             projname = owning_user.username
         else:
             projname = c.project.shortname.replace('/', '-')
-        return '%s-%s' % (projname, self.name)
+        return '{}-{}'.format(projname, self.name)
 
     def clone_url(self, category, username=''):
         '''Return a URL string suitable for copy/paste that describes _this_ repo,
@@ -632,7 +632,7 @@ class Repository(Artifact, ActivityObject):
         if self.app.config.options.get('external_checkout_url', None):
             tpl = string.Template(self.app.config.options.external_checkout_url)
         else:
-            tpl = string.Template(tg.config.get('scm.host.%s.%s' % (category, self.tool)))
+            tpl = string.Template(tg.config.get('scm.host.{}.{}'.format(category, self.tool)))
         url = tpl.substitute(dict(username=username, path=self.url_path + self.name))
         # this is an svn option, but keeps clone_*() code from diverging
         url += self.app.config.options.get('checkout_url', '')
@@ -653,7 +653,7 @@ class Repository(Artifact, ActivityObject):
         '''
         if not username and c.user not in (None, User.anonymous()):
             username = c.user.username
-        tpl = string.Template(tg.config.get('scm.clone.%s.%s' % (category, self.tool)) or
+        tpl = string.Template(tg.config.get('scm.clone.{}.{}'.format(category, self.tool)) or
                               tg.config.get('scm.clone.%s' % self.tool))
         return tpl.substitute(dict(username=username,
                                    source_url=self.clone_url(category, username),
@@ -999,7 +999,7 @@ class MergeRequest(VersionedArtifact, ActivityObject):
 class RepoObject(object):
 
     def __repr__(self):  # pragma no cover
-        return '<%s %s>' % (
+        return '<{} {}>'.format(
             self.__class__.__name__, self._id)
 
     def primary(self):
@@ -1009,7 +1009,7 @@ class RepoObject(object):
         '''Globally unique artifact identifier.  Used for
         SOLR ID, shortlinks, and maybe elsewhere
         '''
-        id = '%s.%s#%s' % (
+        id = '{}.{}#{}'.format(
             'allura.model.repo',  # preserve index_id after module consolidation
             self.__class__.__name__,
             self._id)
@@ -1421,9 +1421,9 @@ class Tree(MappedClass, RepoObject):
         for commit in commits:
             commit.set_context(self.repo)
         commit_infos = {c._id: c.info for c in commits}
-        tree_names = sorted([n.name for n in self.tree_ids])
+        tree_names = sorted(n.name for n in self.tree_ids)
         blob_names = sorted(
-            [n.name for n in chain(self.blob_ids, self.other_ids)])
+            n.name for n in chain(self.blob_ids, self.other_ids))
 
         results = []
         for type, names in (('DIR', tree_names), ('BLOB', blob_names)):
@@ -1606,7 +1606,7 @@ class LastCommit(MappedClass, RepoObject):
     )])
 
     def __repr__(self):
-        return '<LastCommit /%r %s>' % (self.path, self.commit_id)
+        return '<LastCommit /{!r} {}>'.format(self.path, self.commit_id)
 
     @classmethod
     def _last_commit_id(cls, commit, path):
@@ -1664,10 +1664,10 @@ class LastCommit(MappedClass, RepoObject):
             prev_lcd = model_cache.get(
                 cls, {'path': path, 'commit_id': prev_lcd_cid})
         entries = {}
-        nodes = set(
-            [node.name for node in chain(tree.tree_ids, tree.blob_ids, tree.other_ids)])
-        changed = set(
-            [node for node in nodes if os.path.join(path, node) in tree.commit.changed_paths])
+        nodes = {
+            node.name for node in chain(tree.tree_ids, tree.blob_ids, tree.other_ids)}
+        changed = {
+            node for node in nodes if os.path.join(path, node) in tree.commit.changed_paths}
         unchanged = [os.path.join(path, node) for node in nodes - changed]
         if prev_lcd:
             # get unchanged entries from previously computed LCD
@@ -1834,13 +1834,13 @@ class ModelCache(object):
 
     def num_queries(self, cls=None):
         if cls is None:
-            return sum([len(c) for c in self._query_cache.values()])
+            return sum(len(c) for c in self._query_cache.values())
         else:
             return len(self._query_cache[cls])
 
     def num_instances(self, cls=None):
         if cls is None:
-            return sum([len(c) for c in self._instance_cache.values()])
+            return sum(len(c) for c in self._instance_cache.values())
         else:
             return len(self._instance_cache[cls])
 
@@ -1919,9 +1919,9 @@ class GitLikeTree(object):
 
     def __repr__(self):
         # this can't change, is used in hex() above
-        lines = ['t %s %s' % (t.hex(), h.really_unicode(name))
+        lines = ['t {} {}'.format(t.hex(), h.really_unicode(name))
                  for name, t in six.iteritems(self.trees)]
-        lines += ['b %s %s' % (oid, h.really_unicode(name))
+        lines += ['b {} {}'.format(oid, h.really_unicode(name))
                   for name, oid in six.iteritems(self.blobs)]
         return six.ensure_str('\n'.join(sorted(lines)))
 
@@ -1934,7 +1934,7 @@ class GitLikeTree(object):
                  (name, '\n' + t.unicode_full_tree(indent + 2, show_id=show_id)
                   if recurse else t.hex())
                  for name, t in sorted(six.iteritems(self.trees))]
-        lines += [' ' * indent + 'b %s %s' % (name, oid if show_id else '')
+        lines += [' ' * indent + 'b {} {}'.format(name, oid if show_id else '')
                   for name, oid in sorted(six.iteritems(self.blobs))]
         output = h.really_unicode('\n'.join(lines)).encode('utf-8')
         return output
@@ -2002,9 +2002,9 @@ def zipdir(source, zipfile, exclude=None):
     stdout, stderr = p.communicate()
     if p.returncode != 0:
         raise Exception(
-            "Command: {0} returned non-zero exit code {1}\n"
-            "STDOUT: {2}\n"
-            "STDERR: {3}".format(command, p.returncode, stdout, stderr))
+            "Command: {} returned non-zero exit code {}\n"
+            "STDOUT: {}\n"
+            "STDERR: {}".format(command, p.returncode, stdout, stderr))
 
 
 Mapper.compile_all()
diff --git a/Allura/allura/model/session.py b/Allura/allura/model/session.py
index b1288dd..51a79ec 100644
--- a/Allura/allura/model/session.py
+++ b/Allura/allura/model/session.py
@@ -78,7 +78,7 @@ class IndexerSessionExtension(ManagedSessionExtension):
     def _objects_by_types(self, obj_list):
         result = defaultdict(list)
         for obj in obj_list:
-            class_path = '%s.%s' % (type(obj).__module__, type(obj).__name__)
+            class_path = '{}.{}'.format(type(obj).__module__, type(obj).__name__)
             result[class_path].append(obj)
         return result
 
@@ -176,7 +176,7 @@ class BatchIndexer(ArtifactSessionExtension):
         cls = self.__class__
         cls.to_add -= set(deleted_aref_ids)
         cls.to_delete |= set(del_index_ids)
-        cls.to_add |= set([aref._id for aref in arefs_added])
+        cls.to_add |= {aref._id for aref in arefs_added}
 
     @classmethod
     def flush(cls):
diff --git a/Allura/allura/model/stats.py b/Allura/allura/model/stats.py
index b3c02a0..731ed59 100644
--- a/Allura/allura/model/stats.py
+++ b/Allura/allura/model/stats.py
@@ -179,9 +179,9 @@ class Stats(MappedClass):
         if i is None:
             return {}
         entry = self.general[i].messages
-        by_type = dict([(el.messagetype, dict(created=el.created,
-                                              modified=el.modified))
-                        for el in entry])
+        by_type = {el.messagetype: dict(created=el.created,
+                                              modified=el.modified)
+                        for el in entry}
         return by_type
 
     def getTicketsByCategory(self):
diff --git a/Allura/allura/model/timeline.py b/Allura/allura/model/timeline.py
index ee50462..b1d7ed3 100644
--- a/Allura/allura/model/timeline.py
+++ b/Allura/allura/model/timeline.py
@@ -65,7 +65,7 @@ class ActivityNode(NodeBase):
 
     @property
     def node_id(self):
-        return "%s:%s" % (self.__class__.__name__, self._id)
+        return "{}:{}".format(self.__class__.__name__, self._id)
 
 
 class ActivityObject(ActivityObjectBase):
@@ -76,7 +76,7 @@ class ActivityObject(ActivityObjectBase):
     @property
     def activity_name(self):
         """Override this for each Artifact type."""
-        return "%s %s" % (self.__mongometa__.name.capitalize(), self._id)
+        return "{} {}".format(self.__mongometa__.name.capitalize(), self._id)
 
     @property
     def activity_url(self):
@@ -94,7 +94,7 @@ class ActivityObject(ActivityObjectBase):
         """Return a string which uniquely identifies this object and which can
         be used to retrieve the object from mongo.
         """
-        return "%s:%s" % (self.__class__.__name__, self._id)
+        return "{}:{}".format(self.__class__.__name__, self._id)
 
     def has_activity_access(self, perm, user, activity):
         """Return True if user has perm access to this object, otherwise
diff --git a/Allura/allura/scripts/refresh_last_commits.py b/Allura/allura/scripts/refresh_last_commits.py
index 980b44d..75487b0 100644
--- a/Allura/allura/scripts/refresh_last_commits.py
+++ b/Allura/allura/scripts/refresh_last_commits.py
@@ -42,7 +42,7 @@ class RefreshLastCommits(ScriptTask):
                 repo_type = repo_type.strip()
                 if repo_type not in ['git', 'hg']:
                     raise argparse.ArgumentTypeError(
-                        '{0} is not a valid repo type.'.format(repo_type))
+                        '{} is not a valid repo type.'.format(repo_type))
                 repo_types.append(repo_type)
             return repo_types
         parser = argparse.ArgumentParser(description='Using existing commit data, '
diff --git a/Allura/allura/tasks/index_tasks.py b/Allura/allura/tasks/index_tasks.py
index 867746f..8a834f6 100644
--- a/Allura/allura/tasks/index_tasks.py
+++ b/Allura/allura/tasks/index_tasks.py
@@ -43,7 +43,7 @@ def __add_objects(objects, solr_hosts=None):
 
 def __del_objects(object_solr_ids):
     solr_instance = __get_solr()
-    solr_query = 'id:({0})'.format(' || '.join(object_solr_ids))
+    solr_query = 'id:({})'.format(' || '.join(object_solr_ids))
     solr_instance.delete(q=solr_query)
 
 
@@ -153,7 +153,7 @@ def commit():
 
 @task
 def solr_del_tool(project_id, mount_point_s):
-    g.solr.delete(q='project_id_s:"%s" AND mount_point_s:"%s"' % (project_id, mount_point_s))
+    g.solr.delete(q='project_id_s:"{}" AND mount_point_s:"{}"'.format(project_id, mount_point_s))
 
 @contextmanager
 def _indexing_disabled(session):
diff --git a/Allura/allura/tasks/repo_tasks.py b/Allura/allura/tasks/repo_tasks.py
index eefb792..7292a08 100644
--- a/Allura/allura/tasks/repo_tasks.py
+++ b/Allura/allura/tasks/repo_tasks.py
@@ -42,7 +42,7 @@ def clone(cloned_from_path, cloned_from_name, cloned_from_url):
             cloned_from_url)
         M.Notification.post_user(
             c.user, c.app.repo, 'created',
-            text='Repository %s/%s created' % (
+            text='Repository {}/{} created'.format(
                 c.project.shortname, c.app.config.options.mount_point))
     except Exception:
         g.post_event('repo_clone_task_failed', cloned_from_url,
@@ -120,7 +120,7 @@ def reclone_repo(*args, **kwargs):
         c.app.repo.init_as_clone(source_path, None, source_url)
         M.Notification.post_user(
             c.user, c.app.repo, 'created',
-            text='Repository %s/%s created' % (
+            text='Repository {}/{} created'.format(
                 c.project.shortname, c.app.config.options.mount_point))
     except Exception:
         g.post_event('repo_clone_task_failed', source_url,
diff --git a/Allura/allura/tests/decorators.py b/Allura/allura/tests/decorators.py
index 236a860..1a28721 100644
--- a/Allura/allura/tests/decorators.py
+++ b/Allura/allura/tests/decorators.py
@@ -182,7 +182,7 @@ def audits(*messages, **kwargs):
                 hints += '\nin these AuditLog messages:\n\t' + '\n\t'.join(a.message for a in all)
             if message != re.escape(message):
                 hints += '\nYou may need to escape the regex chars in the text you are matching'
-            raise AssertionError('Could not find "%s"%s' % (message, hints))
+            raise AssertionError('Could not find "{}"{}'.format(message, hints))
 
 
 @contextlib.contextmanager
diff --git a/Allura/allura/tests/functional/test_admin.py b/Allura/allura/tests/functional/test_admin.py
index 039af98..37ce2d5 100644
--- a/Allura/allura/tests/functional/test_admin.py
+++ b/Allura/allura/tests/functional/test_admin.py
@@ -450,8 +450,8 @@ class TestProjectAdmin(TestController):
         screenshots = project.get_screenshots()
         assert_equals(screenshots[0].filename, 'admin_24.png')
         # reverse order
-        params = dict((str(ss._id), str(len(screenshots) - 1 - i))
-                      for i, ss in enumerate(screenshots))
+        params = {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_auth.py b/Allura/allura/tests/functional/test_auth.py
index b949311..72ece15 100644
--- a/Allura/allura/tests/functional/test_auth.py
+++ b/Allura/allura/tests/functional/test_auth.py
@@ -609,7 +609,7 @@ class TestAuth(TestController):
                       params=change_params)
 
         u = M.User.by_username('test-admin')
-        print((u.get_tool_data('AuthPasswordReset', 'hash')))
+        print(u.get_tool_data('AuthPasswordReset', 'hash'))
         assert_equal(u.get_tool_data('AuthPasswordReset', 'hash'), '')
         assert_equal(u.get_tool_data('AuthPasswordReset', 'hash_expiry'), '')
 
@@ -1649,7 +1649,7 @@ To update your password on %s, please visit the following URL:
         assert_in('New Password (again):', r)
         form = r.forms[0]
         form['pw'] = form['pw2'] = new_password = '154321'
-        with td.audits('Password changed \(through recovery process\)', user=True):
+        with td.audits(r'Password changed \(through recovery process\)', user=True):
             # escape parentheses, so they would not be treated as regex group
             r = form.submit()
 
@@ -2472,7 +2472,7 @@ class TestTwoFactor(TestController):
 
         first_key_shown = r.session['totp_new_key']
 
-        with audits('Failed to set up multifactor TOTP \(wrong code\)', user=True):
+        with audits(r'Failed to set up multifactor TOTP \(wrong code\)', user=True):
             form = r.forms['totp_set']
             form['code'] = ''
             r = form.submit()
diff --git a/Allura/allura/tests/functional/test_gravatar.py b/Allura/allura/tests/functional/test_gravatar.py
index f106692..c0155c0 100644
--- a/Allura/allura/tests/functional/test_gravatar.py
+++ b/Allura/allura/tests/functional/test_gravatar.py
@@ -37,7 +37,7 @@ class TestGravatar(TestController):
         email = 'Vin\u00EDcius@example.com'
         expected_id = 'e00968255d68523b034a6a39c522efdb'
         actual_id = gravatar.id(email)
-        assert expected_id == actual_id, 'Expected gravitar ID %s, got %s' % (
+        assert expected_id == actual_id, 'Expected gravitar ID {}, got {}'.format(
             repr(expected_id), repr(actual_id))
 
     def test_url(self):
diff --git a/Allura/allura/tests/functional/test_nav.py b/Allura/allura/tests/functional/test_nav.py
index 8fb2f78..cde61cd 100644
--- a/Allura/allura/tests/functional/test_nav.py
+++ b/Allura/allura/tests/functional/test_nav.py
@@ -102,7 +102,7 @@ class TestNavigation(TestController):
         width = self.width % self.logo_data["width"]
         height = self.height % self.logo_data["height"]
         assert nav_logo.find(
-            'img', style='%s %s' % (width, height)) is not None
+            'img', style='{} {}'.format(width, height)) is not None
 
     def test_missing_logo_width(self):
         self.logo_data = {
diff --git a/Allura/allura/tests/functional/test_personal_dashboard.py b/Allura/allura/tests/functional/test_personal_dashboard.py
index dc09830..6db35c5 100644
--- a/Allura/allura/tests/functional/test_personal_dashboard.py
+++ b/Allura/allura/tests/functional/test_personal_dashboard.py
@@ -38,7 +38,7 @@ class TestPersonalDashboard(TestController):
     def test_dashboard(self):
         r = self.app.get('/dashboard')
         assert_equal('Test Admin / Dashboard', r.html.find('h1', 'project_title').text.strip())
-        sections = set([c for s in r.html.findAll(None, 'profile-section') for c in s['class']])
+        sections = {c for s in r.html.findAll(None, 'profile-section') for c in s['class']}
         assert_in('tickets', sections)
         assert_in('projects', sections)
         assert_in('merge_requests', sections)
diff --git a/Allura/allura/tests/functional/test_user_profile.py b/Allura/allura/tests/functional/test_user_profile.py
index 51bf174..566c2dd 100644
--- a/Allura/allura/tests/functional/test_user_profile.py
+++ b/Allura/allura/tests/functional/test_user_profile.py
@@ -33,7 +33,7 @@ class TestUserProfile(TestController):
         r = self.app.get('/u/test-admin/profile/')
         assert_equal('Test Admin',
                      r.html.find('h1', 'project_title').find('a').text)
-        sections = set([c for s in r.html.findAll(None, 'profile-section') for c in s['class']])
+        sections = {c for s in r.html.findAll(None, 'profile-section') for c in s['class']}
         assert_in('personal-data', sections)
         assert_in('Username:\ntest-admin', r.html.find(None, 'personal-data').getText().replace(' ', ''))
         assert_in('projects', sections)
diff --git a/Allura/allura/tests/model/test_auth.py b/Allura/allura/tests/model/test_auth.py
index 5129e46..fd3c3ad 100644
--- a/Allura/allura/tests/model/test_auth.py
+++ b/Allura/allura/tests/model/test_auth.py
@@ -134,14 +134,14 @@ def test_email_address_send_verification_link():
 def test_user():
     assert c.user.url() .endswith('/u/test-admin/')
     assert c.user.script_name .endswith('/u/test-admin/')
-    assert_equal(set(p.shortname for p in c.user.my_projects()),
-                 set(['test', 'test2', 'u/test-admin', 'adobe-1', '--init--']))
+    assert_equal({p.shortname for p in c.user.my_projects()},
+                 {'test', 'test2', 'u/test-admin', 'adobe-1', '--init--'})
     # delete one of the projects and make sure it won't appear in my_projects()
     p = M.Project.query.get(shortname='test2')
     p.deleted = True
     ThreadLocalORMSession.flush_all()
-    assert_equal(set(p.shortname for p in c.user.my_projects()),
-                 set(['test', 'u/test-admin', 'adobe-1', '--init--']))
+    assert_equal({p.shortname for p in c.user.my_projects()},
+                 {'test', 'u/test-admin', 'adobe-1', '--init--'})
     u = M.User.register(dict(
         username='nosetest-user'))
     ThreadLocalORMSession.flush_all()
@@ -273,11 +273,11 @@ def test_project_role():
 
 @with_setup(setUp)
 def test_default_project_roles():
-    roles = dict(
-        (pr.name, pr)
+    roles = {
+        pr.name: pr
         for pr in M.ProjectRole.query.find(dict(
             project_id=c.project._id)).all()
-        if pr.name)
+        if pr.name}
     assert 'Admin' in list(roles.keys()), list(roles.keys())
     assert 'Developer' in list(roles.keys()), list(roles.keys())
     assert 'Member' in list(roles.keys()), list(roles.keys())
@@ -302,10 +302,10 @@ def test_email_address_claimed_by_user():
 @with_setup(setUp)
 @td.with_user_project('test-admin')
 def test_user_projects_by_role():
-    assert_equal(set(p.shortname for p in c.user.my_projects()),
-                 set(['test', 'test2', 'u/test-admin', 'adobe-1', '--init--']))
-    assert_equal(set(p.shortname for p in c.user.my_projects_by_role_name('Admin')),
-                 set(['test', 'test2', 'u/test-admin', 'adobe-1', '--init--']))
+    assert_equal({p.shortname for p in c.user.my_projects()},
+                 {'test', 'test2', 'u/test-admin', 'adobe-1', '--init--'})
+    assert_equal({p.shortname for p in c.user.my_projects_by_role_name('Admin')},
+                 {'test', 'test2', 'u/test-admin', 'adobe-1', '--init--'})
     # Remove admin access from c.user to test2 project
     project = M.Project.query.get(shortname='test2')
     admin_role = M.ProjectRole.by_name('Admin', project)
@@ -315,10 +315,10 @@ def test_user_projects_by_role():
     user_role.roles.append(developer_role._id)
     ThreadLocalORMSession.flush_all()
     g.credentials.clear()
-    assert_equal(set(p.shortname for p in c.user.my_projects()),
-                 set(['test', 'test2', 'u/test-admin', 'adobe-1', '--init--']))
-    assert_equal(set(p.shortname for p in c.user.my_projects_by_role_name('Admin')),
-                 set(['test', 'u/test-admin', 'adobe-1', '--init--']))
+    assert_equal({p.shortname for p in c.user.my_projects()},
+                 {'test', 'test2', 'u/test-admin', 'adobe-1', '--init--'})
+    assert_equal({p.shortname for p in c.user.my_projects_by_role_name('Admin')},
+                 {'test', 'u/test-admin', 'adobe-1', '--init--'})
 
 
 @td.with_user_project('test-admin')
diff --git a/Allura/allura/tests/model/test_repo.py b/Allura/allura/tests/model/test_repo.py
index 9d1f079..2a31145 100644
--- a/Allura/allura/tests/model/test_repo.py
+++ b/Allura/allura/tests/model/test_repo.py
@@ -413,8 +413,8 @@ class TestModelCache(unittest.TestCase):
         q = mock.Mock(spec_set=['query'], query='foo')
         m = mock.Mock(spec_set=['m'], m='bar')
         n = mock.Mock(spec_set=['foo'], foo='qux')
-        self.assertEquals(self.cache._model_query(q), 'foo')
-        self.assertEquals(self.cache._model_query(m), 'bar')
+        self.assertEqual(self.cache._model_query(q), 'foo')
+        self.assertEqual(self.cache._model_query(m), 'bar')
         self.assertRaises(AttributeError, self.cache._model_query, [n])
 
     @mock.patch.object(M.repository.Tree.query, 'get')
@@ -503,7 +503,7 @@ class TestModelCache(unittest.TestCase):
         self.cache.set(M.repository.Tree, {'val': 'tree1'}, tree1)
         self.cache.set(M.repository.Tree, {'val': 'tree2'}, tree2)
         self.assertEqual(set(self.cache.instance_ids(M.repository.Tree)),
-                         set(['id1', 'id2']))
+                         {'id1', 'id2'})
         self.assertEqual(self.cache.instance_ids(M.repository.LastCommit), [])
 
     @mock.patch.object(M.repository.Tree.query, 'find')
diff --git a/Allura/allura/tests/test_app.py b/Allura/allura/tests/test_app.py
index 9d09bca..a7fe5ca 100644
--- a/Allura/allura/tests/test_app.py
+++ b/Allura/allura/tests/test_app.py
@@ -142,7 +142,7 @@ def test_handle_artifact_unicode(qg):
     a.handle_artifact_message(ticket, msg)
     assert_equal(post.attach.call_args[0][1].getvalue(), 'foo ƒ†©¥˙¨ˆ'.encode('utf-8'))
 
-    msg = dict(payload='foo'.encode('utf-8'), message_id=1, headers={})
+    msg = dict(payload=b'foo', message_id=1, headers={})
     a.handle_artifact_message(ticket, msg)
     assert_equal(post.attach.call_args[0][1].getvalue(), b'foo')
 
diff --git a/Allura/allura/tests/test_commands.py b/Allura/allura/tests/test_commands.py
index 9de376d..4f89c2e 100644
--- a/Allura/allura/tests/test_commands.py
+++ b/Allura/allura/tests/test_commands.py
@@ -528,9 +528,9 @@ class TestReindexCommand(object):
         cmd._chunked_add_artifacts(list(range(10)))
         # check constructors of first and second Solr() instantiations
         assert_equal(
-            set([Solr.call_args_list[0][0][0], Solr.call_args_list[1][0][0]]),
-            set(['http://blah.com/solr/forge',
-                 'https://other.net/solr/forge'])
+            {Solr.call_args_list[0][0][0], Solr.call_args_list[1][0][0]},
+            {'http://blah.com/solr/forge',
+                 'https://other.net/solr/forge'}
         )
 
     @patch('allura.command.show_models.utils')
diff --git a/Allura/allura/tests/test_diff.py b/Allura/allura/tests/test_diff.py
index cb699b6..926c7b1 100644
--- a/Allura/allura/tests/test_diff.py
+++ b/Allura/allura/tests/test_diff.py
@@ -52,32 +52,32 @@ class TestHtmlSideBySideDiff(unittest.TestCase):
 
     def test_preprocess(self):
         d = self.diff
-        self.assertEquals(d._preprocess(None), None)
-        self.assertEquals(d._preprocess('<br>&nbsp;'), '&lt;br&gt;&amp;nbsp;')
-        self.assertEquals(d._preprocess('\ttabbed'), '    tabbed')
+        self.assertEqual(d._preprocess(None), None)
+        self.assertEqual(d._preprocess('<br>&nbsp;'), '&lt;br&gt;&amp;nbsp;')
+        self.assertEqual(d._preprocess('\ttabbed'), '    tabbed')
         # test non default tab size
         d = HtmlSideBySideDiff(2)
-        self.assertEquals(d._preprocess('\ttabbed'), '  tabbed')
+        self.assertEqual(d._preprocess('\ttabbed'), '  tabbed')
 
     def test_replace_marks(self):
         line, flag = self.diff._replace_marks('\0+line added\1')
-        self.assertEquals(line, 'line added')
-        self.assertEquals(flag, 'diff-add')
+        self.assertEqual(line, 'line added')
+        self.assertEqual(flag, 'diff-add')
         line, flag = self.diff._replace_marks('\0-line removed\1')
-        self.assertEquals(line, 'line removed')
-        self.assertEquals(flag, 'diff-rem')
+        self.assertEqual(line, 'line removed')
+        self.assertEqual(flag, 'diff-rem')
         line, flag = self.diff._replace_marks('\0^line changed\1')
-        self.assertEquals(line, '<span class="diff-chg">line changed</span>')
-        self.assertEquals(flag, 'diff-chg')
+        self.assertEqual(line, '<span class="diff-chg">line changed</span>')
+        self.assertEqual(flag, 'diff-chg')
         line, flag = self.diff._replace_marks('chunk \0+add\1ed')
-        self.assertEquals(line, 'chunk <span class="diff-add">add</span>ed')
-        self.assertEquals(flag, 'diff-chg')
+        self.assertEqual(line, 'chunk <span class="diff-add">add</span>ed')
+        self.assertEqual(flag, 'diff-chg')
         line, flag = self.diff._replace_marks('chunk \0-remov\1ed')
-        self.assertEquals(line, 'chunk <span class="diff-rem">remov</span>ed')
-        self.assertEquals(flag, 'diff-chg')
+        self.assertEqual(line, 'chunk <span class="diff-rem">remov</span>ed')
+        self.assertEqual(flag, 'diff-chg')
         line, flag = self.diff._replace_marks('chunk \0^chang\1ed')
-        self.assertEquals(line, 'chunk <span class="diff-chg">chang</span>ed')
-        self.assertEquals(flag, 'diff-chg')
+        self.assertEqual(line, 'chunk <span class="diff-chg">chang</span>ed')
+        self.assertEqual(flag, 'diff-chg')
 
     def test_make_line(self):
         # context separation
@@ -89,7 +89,7 @@ class TestHtmlSideBySideDiff(unittest.TestCase):
   <td class="lineno"></td>
   <td class="diff-gap"><pre>...</pre></td>
 </tr>'''.strip()
-        self.assertEquals(self.diff._make_line(d), expected)
+        self.assertEqual(self.diff._make_line(d), expected)
         # no change
         d = ((1, 'aline'), (1, 'aline'), False)
         expected = '''
@@ -99,7 +99,7 @@ class TestHtmlSideBySideDiff(unittest.TestCase):
   <td class="lineno">1</td>
   <td><pre>aline</pre></td>
 </tr>'''.strip()
-        self.assertEquals(self.diff._make_line(d), expected)
+        self.assertEqual(self.diff._make_line(d), expected)
         # has change
         d = ((1, '\0^a\1line'), (1, '\0^b\1line'), True)
         expected = '''
@@ -109,7 +109,7 @@ class TestHtmlSideBySideDiff(unittest.TestCase):
   <td class="lineno">1</td>
   <td class="diff-chg"><pre><span class="diff-chg">b</span>line</pre></td>
 </tr>'''.strip()
-        self.assertEquals(self.diff._make_line(d), expected)
+        self.assertEqual(self.diff._make_line(d), expected)
 
     def test_make_table(self):
         a = 'line 1\nline 2'.split('\n')
@@ -137,7 +137,7 @@ class TestHtmlSideBySideDiff(unittest.TestCase):
 </table>
 '''.strip()
         html = self.diff.make_table(a, b, 'file a', 'file b')
-        self.assertEquals(html, expected)
+        self.assertEqual(html, expected)
 
     def test_unicode_make_table(self):
         a = ['строка']
diff --git a/Allura/allura/tests/test_globals.py b/Allura/allura/tests/test_globals.py
index 986297e..8ca1c00 100644
--- a/Allura/allura/tests/test_globals.py
+++ b/Allura/allura/tests/test_globals.py
@@ -553,7 +553,7 @@ def test_markdown_autolink():
     tgt = 'http://everything2.com/?node=nate+oostendorp'
     s = g.markdown.convert('This is %s' % tgt)
     assert_equal(
-        s, '<div class="markdown_content"><p>This is <a href="%s" rel="nofollow">%s</a></p></div>' % (tgt, tgt))
+        s, '<div class="markdown_content"><p>This is <a href="{}" rel="nofollow">{}</a></p></div>'.format(tgt, tgt))
     assert '<a href=' in g.markdown.convert('This is http://domain.net')
     # beginning of doc
     assert_in('<a href=', g.markdown.convert('http://domain.net abc'))
@@ -573,7 +573,7 @@ def test_markdown_autolink():
 def test_markdown_autolink_with_escape():
     # \_ is unnecessary but valid markdown escaping and should be considered as a regular underscore
     # (it occurs during html2text conversion during project migrations)
-    r = g.markdown.convert('a http://www.phpmyadmin.net/home\_page/security/\#target b')
+    r = g.markdown.convert(r'a http://www.phpmyadmin.net/home\_page/security/\#target b')
     assert 'href="http://www.phpmyadmin.net/home_page/security/#target"' in r, r
 
 
@@ -745,7 +745,7 @@ def test_myprojects_macro():
     for p in c.user.my_projects():
         if p.deleted or p.is_nbhd_project:
             continue
-        proj_title = '<h2><a href="%s">%s</a></h2>' % (p.url(), p.name)
+        proj_title = '<h2><a href="{}">{}</a></h2>'.format(p.url(), p.name)
         assert_in(proj_title, r)
 
     h.set_context('u/test-user-1', 'wiki', neighborhood='Users')
@@ -754,7 +754,7 @@ def test_myprojects_macro():
     for p in user.my_projects():
         if p.deleted or p.is_nbhd_project:
             continue
-        proj_title = '<h2><a href="%s">%s</a></h2>' % (p.url(), p.name)
+        proj_title = '<h2><a href="{}">{}</a></h2>'.format(p.url(), p.name)
         assert_in(proj_title, r)
 
 
@@ -842,7 +842,7 @@ def get_projects_property_in_the_same_order(names, prop):
     It is required because results of the query are not in the same order as names.
     """
     projects = M.Project.query.find(dict(name={'$in': names})).all()
-    projects_dict = dict([(p['name'], p[prop]) for p in projects])
+    projects_dict = {p['name']: p[prop] for p in projects}
     return [projects_dict[name] for name in names]
 
 
diff --git a/Allura/allura/tests/test_middlewares.py b/Allura/allura/tests/test_middlewares.py
index e76c32f..7d7ae3e 100644
--- a/Allura/allura/tests/test_middlewares.py
+++ b/Allura/allura/tests/test_middlewares.py
@@ -36,7 +36,7 @@ class TestCORSMiddleware(object):
         cors = CORSMiddleware(self.app, ['get', 'post'], ['Some-Header'])
         assert_equal(cors.app, self.app)
         assert_equal(cors.allowed_methods, ['GET', 'POST'])
-        assert_equal(cors.allowed_headers, set(['some-header']))
+        assert_equal(cors.allowed_headers, {'some-header'})
 
     def test_call_not_api_request(self):
         callback = MagicMock()
@@ -104,4 +104,4 @@ class TestCORSMiddleware(object):
         assert_equal(f({}), set())
         assert_equal(f({key: ''}), set())
         assert_equal(f({key: 'Authorization, Accept'}),
-                     set(['authorization', 'accept']))
+                     {'authorization', 'accept'})
diff --git a/Allura/allura/tests/test_security.py b/Allura/allura/tests/test_security.py
index 6d7a13e..052e04d 100644
--- a/Allura/allura/tests/test_security.py
+++ b/Allura/allura/tests/test_security.py
@@ -94,29 +94,29 @@ class TestSecurity(TestController):
         anon_role = M.ProjectRole.by_name('*anonymous')
         test_user = M.User.by_username('test-user')
 
-        assert_equal(all_allowed(wiki, admin_role), set(
-            ['configure', 'read', 'create', 'edit', 'unmoderated_post', 'post', 'moderate', 'admin', 'delete']))
-        assert_equal(all_allowed(wiki, dev_role), set(
-            ['read', 'create', 'edit', 'unmoderated_post', 'post', 'moderate', 'delete']))
+        assert_equal(all_allowed(wiki, admin_role), {
+            'configure', 'read', 'create', 'edit', 'unmoderated_post', 'post', 'moderate', 'admin', 'delete'})
+        assert_equal(all_allowed(wiki, dev_role), {
+            'read', 'create', 'edit', 'unmoderated_post', 'post', 'moderate', 'delete'})
         assert_equal(all_allowed(wiki, member_role),
-                     set(['read', 'create', 'edit', 'unmoderated_post', 'post']))
+                     {'read', 'create', 'edit', 'unmoderated_post', 'post'})
         assert_equal(all_allowed(wiki, auth_role),
-                     set(['read', 'post', 'unmoderated_post']))
-        assert_equal(all_allowed(wiki, anon_role), set(['read']))
+                     {'read', 'post', 'unmoderated_post'})
+        assert_equal(all_allowed(wiki, anon_role), {'read'})
         assert_equal(all_allowed(wiki, test_user),
-                     set(['read', 'post', 'unmoderated_post']))
+                     {'read', 'post', 'unmoderated_post'})
 
         _add_to_group(test_user, member_role)
 
         assert_equal(all_allowed(wiki, test_user),
-                     set(['read', 'create', 'edit', 'unmoderated_post', 'post']))
+                     {'read', 'create', 'edit', 'unmoderated_post', 'post'})
 
         _deny(wiki, auth_role, 'unmoderated_post')
 
         assert_equal(all_allowed(wiki, member_role),
-                     set(['read', 'create', 'edit', 'post']))
+                     {'read', 'create', 'edit', 'post'})
         assert_equal(all_allowed(wiki, test_user),
-                     set(['read', 'create', 'edit', 'post']))
+                     {'read', 'create', 'edit', 'post'})
 
     @td.with_wiki
     def test_deny_vs_allow(self):
@@ -133,13 +133,13 @@ class TestSecurity(TestController):
         assert has_access(page, 'read', anon_role)()
         assert has_access(page, 'post', anon_role)()
         assert has_access(page, 'unmoderated_post', anon_role)()
-        assert_equal(all_allowed(page, anon_role), set(['read']))
+        assert_equal(all_allowed(page, anon_role), {'read'})
         # as well as an authenticated user
         assert has_access(page, 'read', test_user)()
         assert has_access(page, 'post', test_user)()
         assert has_access(page, 'unmoderated_post', test_user)()
         assert_equal(all_allowed(page, test_user),
-                     set(['read', 'post', 'unmoderated_post']))
+                     {'read', 'post', 'unmoderated_post'})
 
         _deny(page, auth_role, 'read')
 
@@ -155,7 +155,7 @@ class TestSecurity(TestController):
         assert has_access(wiki, 'post', test_user)()
         assert has_access(wiki, 'unmoderated_post', test_user)()
         assert_equal(all_allowed(wiki, test_user),
-                     set(['read', 'post', 'unmoderated_post']))
+                     {'read', 'post', 'unmoderated_post'})
 
         _deny(wiki, anon_role, 'read')
         _allow(wiki, auth_role, 'read')
diff --git a/Allura/allura/tests/test_tasks.py b/Allura/allura/tests/test_tasks.py
index 1edd59b..ca3e3f2 100644
--- a/Allura/allura/tests/test_tasks.py
+++ b/Allura/allura/tests/test_tasks.py
@@ -233,14 +233,14 @@ class TestIndexTasks(unittest.TestCase):
         sort_key = operator.itemgetter('id')
         assert_equal(
             sorted(solr.add.call_args[0][0], key=sort_key),
-            sorted([ref.artifact.solarize() for ref in arefs],
+            sorted((ref.artifact.solarize() for ref in arefs),
                    key=sort_key))
         index_tasks.del_artifacts(ref_ids)
         M.main_orm_session.flush()
         M.main_orm_session.clear()
         new_shortlinks = M.Shortlink.query.find().count()
         assert old_shortlinks == new_shortlinks, 'Shortlinks not deleted'
-        solr_query = 'id:({0})'.format(' || '.join(ref_ids))
+        solr_query = 'id:({})'.format(' || '.join(ref_ids))
         solr.delete.assert_called_once_with(q=solr_query)
 
 
diff --git a/Allura/allura/tests/test_utils.py b/Allura/allura/tests/test_utils.py
index 6bfe62d..2e48858 100644
--- a/Allura/allura/tests/test_utils.py
+++ b/Allura/allura/tests/test_utils.py
@@ -169,8 +169,8 @@ class TestAntispam(unittest.TestCase):
         self.assertRaises(ValueError, utils.AntiSpam.validate_request, r)
 
     def _encrypt_form(self, **kwargs):
-        encrypted_form = dict(
-            (self.a.enc(k), v) for k, v in kwargs.items())
+        encrypted_form = {
+            self.a.enc(k): v for k, v in kwargs.items()}
         encrypted_form.setdefault(self.a.enc('honey0'), '')
         encrypted_form.setdefault(self.a.enc('honey1'), '')
         encrypted_form['spinner'] = self.a.spinner_text
@@ -390,7 +390,7 @@ class FakeAttachment(object):
         self._id = ObjectId()
         self.filename = filename
     def __repr__(self):
-        return '%s %s' % (self._id, self.filename)
+        return '{} {}'.format(self._id, self.filename)
 
 
 def unique_attachments():
diff --git a/Allura/allura/tests/test_webhooks.py b/Allura/allura/tests/test_webhooks.py
index daab1d7..ff82089 100644
--- a/Allura/allura/tests/test_webhooks.py
+++ b/Allura/allura/tests/test_webhooks.py
@@ -383,7 +383,7 @@ class TestWebhookController(TestController):
         assert_in('<h1>repo-push</h1>', r)
         rows = r.html.find('table').findAll('tr')
         assert_equal(len(rows), 2)
-        rows = sorted([self._format_row(row) for row in rows], key=lambda rows: rows[0]['text'])
+        rows = sorted((self._format_row(row) for row in rows), key=lambda rows: rows[0]['text'])
         expected_rows = sorted([
             [{'text': wh1.hook_url},
              {'text': wh1.secret},
@@ -477,7 +477,7 @@ class TestSendWebhookHelper(TestWebhookBase):
             headers=headers,
             timeout=30)
         log.info.assert_called_once_with(
-            'Webhook successfully sent: %s %s %s' % (
+            'Webhook successfully sent: {} {} {}'.format(
                 self.wh.type, self.wh.hook_url, self.wh.app_config.url()))
 
     @patch('allura.webhooks.time', autospec=True)
@@ -496,7 +496,7 @@ class TestSendWebhookHelper(TestWebhookBase):
             call('Retrying webhook in %s seconds', 240)])
         assert_equal(log.error.call_count, 4)
         log.error.assert_called_with(
-            'Webhook send error: %s %s %s %s %s %s' % (
+            'Webhook send error: {} {} {} {} {} {}'.format(
                 self.wh.type, self.wh.hook_url,
                 self.wh.app_config.url(),
                 requests.post.return_value.status_code,
@@ -515,7 +515,7 @@ class TestSendWebhookHelper(TestWebhookBase):
             log.info.assert_called_once_with('Retrying webhook in: %s', [])
             assert_equal(log.error.call_count, 1)
             log.error.assert_called_with(
-                'Webhook send error: %s %s %s %s %s %s' % (
+                'Webhook send error: {} {} {} {} {} {}'.format(
                     self.wh.type, self.wh.hook_url,
                     self.wh.app_config.url(),
                     requests.post.return_value.status_code,
diff --git a/Allura/allura/tests/unit/factories.py b/Allura/allura/tests/unit/factories.py
index 6acb54f..2cf6ee8 100644
--- a/Allura/allura/tests/unit/factories.py
+++ b/Allura/allura/tests/unit/factories.py
@@ -66,7 +66,7 @@ def create_post(slug):
     author = create_user(username='someguy')
     return Post(slug=slug,
                 thread_id=thread._id,
-                full_slug='%s:%s' % (thread._id, slug),
+                full_slug='{}:{}'.format(thread._id, slug),
                 discussion_id=discussion._id,
                 author_id=author._id)
 
diff --git a/Allura/allura/tests/unit/test_ldap_auth_provider.py b/Allura/allura/tests/unit/test_ldap_auth_provider.py
index 642e76d..90dfb08 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 six.iteritems(params)]).encode('utf-8')
+        self.provider.request.body = '&'.join(['{}={}'.format(k,v) for k,v in six.iteritems(params)]).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 six.iteritems(params)]).encode('utf-8')
+        self.provider.request.body = '&'.join(['{}={}'.format(k,v) for k,v in six.iteritems(params)]).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/tests/unit/test_repo.py b/Allura/allura/tests/unit/test_repo.py
index f926a2f..165dae2 100644
--- a/Allura/allura/tests/unit/test_repo.py
+++ b/Allura/allura/tests/unit/test_repo.py
@@ -289,18 +289,18 @@ class TestZipDir(unittest.TestCase):
 class TestPrefixPathsUnion(unittest.TestCase):
 
     def test_disjoint(self):
-        a = set(['a1', 'a2', 'a3'])
-        b = set(['b1', 'b1/foo', 'b2'])
+        a = {'a1', 'a2', 'a3'}
+        b = {'b1', 'b1/foo', 'b2'}
         self.assertEqual(prefix_paths_union(a, b), set())
 
     def test_exact(self):
-        a = set(['a1', 'a2', 'a3'])
-        b = set(['b1', 'a2', 'a3'])
+        a = {'a1', 'a2', 'a3'}
+        b = {'b1', 'a2', 'a3'}
         self.assertEqual(prefix_paths_union(a, b), {'a2', 'a3'})
 
     def test_prefix(self):
-        a = set(['a1', 'a2', 'a3'])
-        b = set(['b1', 'a2/foo', 'b3/foo'])
+        a = {'a1', 'a2', 'a3'}
+        b = {'b1', 'a2/foo', 'b3/foo'}
         self.assertEqual(prefix_paths_union(a, b), {'a2'})
 
 
diff --git a/Allura/allura/tests/unit/test_session.py b/Allura/allura/tests/unit/test_session.py
index 36aa287..fe1868e 100644
--- a/Allura/allura/tests/unit/test_session.py
+++ b/Allura/allura/tests/unit/test_session.py
@@ -172,23 +172,23 @@ class TestBatchIndexer(TestCase):
         find.return_value = [m(_id=i) for i in (7, 8, 9)]
         self.ext.update_index(objs_deleted, arefs)
         self.assertEqual(self.ext.to_delete,
-                         set([o.index_id() for o in objs_deleted]))
-        self.assertEqual(self.ext.to_add, set([4, 5, 6]))
+                         {o.index_id() for o in objs_deleted})
+        self.assertEqual(self.ext.to_add, {4, 5, 6})
 
         # test deleting something that was previously added
         objs_deleted += [m(_id=4)]
         find.return_value = [m(_id=4)]
         self.ext.update_index(objs_deleted, [])
         self.assertEqual(self.ext.to_delete,
-                         set([o.index_id() for o in objs_deleted]))
-        self.assertEqual(self.ext.to_add, set([5, 6]))
+                         {o.index_id() for o in objs_deleted})
+        self.assertEqual(self.ext.to_add, {5, 6})
 
     @mock.patch('allura.model.session.index_tasks')
     def test_flush(self, index_tasks):
         objs_deleted = [self._mock_indexable(_id=i) for i in (1, 2, 3)]
-        del_index_ids = set([o.index_id() for o in objs_deleted])
+        del_index_ids = {o.index_id() for o in objs_deleted}
         self.extcls.to_delete = del_index_ids
-        self.extcls.to_add = set([4, 5, 6])
+        self.extcls.to_add = {4, 5, 6}
         self.ext.flush()
         index_tasks.del_artifacts.post.assert_called_once_with(
             list(del_index_ids))
diff --git a/Allura/ldap-setup.py b/Allura/ldap-setup.py
index a2d0fcb..d160b76 100644
--- a/Allura/ldap-setup.py
+++ b/Allura/ldap-setup.py
@@ -87,7 +87,7 @@ def get_value(key, default):
         default = config.get('scm', key)
     except NoOptionError:
         pass
-    value = input('%s? [%s]' % (key, default))
+    value = input('{}? [{}]'.format(key, default))
     if not value:
         value = default
     config.set('scm', key, value)
@@ -253,7 +253,7 @@ MIDSTART=10000
 ''')
 
 
-ldapscripts_debian = '''
+ldapscripts_debian = r'''
 ### Allura-customized
 ### This file predefine some ldapscripts variables for Debian boxes.
 #
diff --git a/AlluraTest/alluratest/controller.py b/AlluraTest/alluratest/controller.py
index 43ff9be..cfcffe7 100644
--- a/AlluraTest/alluratest/controller.py
+++ b/AlluraTest/alluratest/controller.py
@@ -112,7 +112,7 @@ def setup_functional_test(config=None, app_name=DFL_APP_NAME, current_pkg=None):
     config = get_config_file(config, current_pkg=current_pkg)
     setup_basic_test(config, app_name)
     conf_dir = tg.config.here
-    wsgiapp = loadapp('config:%s#%s' % (config, app_name),
+    wsgiapp = loadapp('config:{}#{}'.format(config, app_name),
                       relative_to=conf_dir)
     return wsgiapp
 # sometimes __test__ above isn't sufficient
diff --git a/AlluraTest/alluratest/validation.py b/AlluraTest/alluratest/validation.py
index 228dca8..f1b6ab0 100644
--- a/AlluraTest/alluratest/validation.py
+++ b/AlluraTest/alluratest/validation.py
@@ -84,7 +84,7 @@ class Config(object):
 
 
 def report_validation_error(val_name, filename, message):
-    message = '%s Validation errors (%s):\n%s\n' % (val_name, filename, message)
+    message = '{} Validation errors ({}):\n{}\n'.format(val_name, filename, message)
     raise AssertionError(message)
 
 
diff --git a/ForgeActivity/forgeactivity/main.py b/ForgeActivity/forgeactivity/main.py
index 8de2b7f..ae04ba7 100644
--- a/ForgeActivity/forgeactivity/main.py
+++ b/ForgeActivity/forgeactivity/main.py
@@ -180,7 +180,7 @@ class ForgeActivityController(BaseController):
         for t in data['timeline']:
             url_id = h.absurl(t.obj.activity_url)  # try to keep this consistent over time (not url-quoted)
             url = h.absurl(h.urlquote_path_only(t.obj.activity_url))
-            feed.add_item(title='%s %s %s%s' % (
+            feed.add_item(title='{} {} {}{}'.format(
                                 t.actor.activity_name,
                 t.verb,
                 t.obj.activity_name,
diff --git a/ForgeBlog/forgeblog/model/blog.py b/ForgeBlog/forgeblog/model/blog.py
index 73cf8f2..bb0c792 100644
--- a/ForgeBlog/forgeblog/model/blog.py
+++ b/ForgeBlog/forgeblog/model/blog.py
@@ -77,7 +77,7 @@ class BlogPostSnapshot(M.Snapshot):
         orig = self.original()
         if not orig:
             return None
-        return '%s#%s' % (orig.shorthand_id(), self.version)
+        return '{}#{}'.format(orig.shorthand_id(), self.version)
 
     def url(self):
         orig = self.original()
@@ -203,7 +203,7 @@ class BlogPost(M.VersionedArtifact, ActivityObject):
     def email_address(self):
         if self.config.options.get('AllowEmailPosting', True):
             domain = self.email_domain
-            return '%s@%s%s' % (self.title.replace('/', '.'), domain, config.common_suffix)
+            return '{}@{}{}'.format(self.title.replace('/', '.'), domain, config.common_suffix)
         else:
             return tg_config.get('forgemail.return_path')
 
@@ -213,7 +213,7 @@ class BlogPost(M.VersionedArtifact, ActivityObject):
             ch.lower()
             for ch in title.replace(' ', '-')
             if ch.isalnum() or ch == '-')
-        return '%s/%s' % (
+        return '{}/{}'.format(
             timestamp.strftime('%Y/%m'),
             slugsafe)
 
@@ -239,7 +239,7 @@ class BlogPost(M.VersionedArtifact, ActivityObject):
             title=self.title,
             type_s=self.type_s,
             state_s=self.state,
-            snippet_s='%s: %s' % (self.title, h.text.truncate(self.text, 200)),
+            snippet_s='{}: {}'.format(self.title, h.text.truncate(self.text, 200)),
             text=self.text)
         return result
 
@@ -268,7 +268,7 @@ class BlogPost(M.VersionedArtifact, ActivityObject):
                 M.Feed.post(self, self.title, self.text, author=self.author(),
                             pubdate=self.get_version(1).timestamp)
                 description = self.text
-                subject = '%s created post %s' % (
+                subject = '{} created post {}'.format(
                     c.user.username, self.title)
             elif v2.state == 'published':
                 feed_item = self.feed_item()
@@ -280,11 +280,11 @@ class BlogPost(M.VersionedArtifact, ActivityObject):
                                 pubdate=self.get_version(1).timestamp)
                 if v1.title != v2.title:
                     activity('renamed', self)
-                    subject = '%s renamed post %s to %s' % (
+                    subject = '{} renamed post {} to {}'.format(
                         c.user.username, v1.title, v2.title)
                 else:
                     activity('modified', self)
-                    subject = '%s modified post %s' % (
+                    subject = '{} modified post {}'.format(
                         c.user.username, self.title)
             elif v1.state == 'published' and v2.state == 'draft':
                 feed_item = self.feed_item()
@@ -292,7 +292,7 @@ class BlogPost(M.VersionedArtifact, ActivityObject):
                     feed_item.delete()
         else:
             description = self.text
-            subject = '%s created post %s' % (
+            subject = '{} created post {}'.format(
                 c.user.username, self.title)
             if self.state == 'published':
                 activity('created', self)
diff --git a/ForgeChat/forgechat/command.py b/ForgeChat/forgechat/command.py
index 8dbbe6a..c7ceda6 100644
--- a/ForgeChat/forgechat/command.py
+++ b/ForgeChat/forgechat/command.py
@@ -91,7 +91,7 @@ class IRCBot(asynchat.async_chat):
     def set_nick(self, suffix=None):
         if suffix is None:
             suffix = '%.3d' % random.randint(0, 999)
-        nick = '%s-%s' % (self.nick, suffix)
+        nick = '{}-{}'.format(self.nick, suffix)
         self.say('NICK ' + nick)
 
     def collect_incoming_data(self, data):
@@ -110,8 +110,8 @@ class IRCBot(asynchat.async_chat):
         self.handle_command(sender, cmd, rest)
 
     def configure(self):
-        new_channels = dict(
-            (ch.channel, ch) for ch in CM.ChatChannel.query.find())
+        new_channels = {
+            ch.channel: ch for ch in CM.ChatChannel.query.find()}
         for channel in new_channels:
             if channel not in self.channels and channel:
                 self.say('JOIN %s' % channel)
@@ -132,7 +132,7 @@ class IRCBot(asynchat.async_chat):
         self.push(s + b'\r\n')
 
     def notice(self, out, message):
-        self.say('NOTICE %s :%s' % (out, message))
+        self.say('NOTICE {} :{}'.format(out, message))
         CM.ChatMessage(
             sender=self.nick,
             channel=out,
@@ -181,7 +181,7 @@ class IRCBot(asynchat.async_chat):
             text = index['snippet_s'] or h.get_first(index, 'title')
             url = urljoin(
                 tg.config['base_url'], index['url_s'])
-            self.notice(rcpt, '[%s] - [%s](%s)' % (lnk.link, text, url))
+            self.notice(rcpt, '[{}] - [{}]({})'.format(lnk.link, text, url))
 
     def log_channel(self, sender, cmd, rcpt, rest):
         if cmd not in ('NOTICE', 'PRIVMSG'):
diff --git a/ForgeChat/forgechat/model/chat.py b/ForgeChat/forgechat/model/chat.py
index 1504326..11a1ccb 100644
--- a/ForgeChat/forgechat/model/chat.py
+++ b/ForgeChat/forgechat/model/chat.py
@@ -62,7 +62,7 @@ class ChatMessage(M.Artifact):
     text_cache = FieldProperty(MarkdownCache)
 
     def index_id(self):
-        id = 'Chat-%s:%s:%s.%s' % (
+        id = 'Chat-{}:{}:{}.{}'.format(
             self.channel,
             self.sender,
             self.timestamp.isoformat(),
@@ -72,7 +72,7 @@ class ChatMessage(M.Artifact):
     def index(self):
         result = super(ChatMessage, self).index()
         result.update(
-            snippet_s='%s > %s' % (self.sender, self.text),
+            snippet_s='{} > {}'.format(self.sender, self.text),
             sender_t=self.sender,
             text=self.text)
         return result
diff --git a/ForgeDiscussion/forgediscussion/model/forum.py b/ForgeDiscussion/forgediscussion/model/forum.py
index c0b0b96..7d74f56 100644
--- a/ForgeDiscussion/forgediscussion/model/forum.py
+++ b/ForgeDiscussion/forgediscussion/model/forum.py
@@ -80,7 +80,7 @@ class Forum(M.Discussion):
         if c.app.config.options.get('AllowEmailPosting', True):
             domain = self.email_domain
             local_part = self.shortname.replace('/', '.')
-            return '%s@%s%s' % (local_part, domain, config.common_suffix)
+            return '{}@{}{}'.format(local_part, domain, config.common_suffix)
         else:
             return tg_config.get('forgemail.return_path')
 
@@ -136,7 +136,7 @@ class Forum(M.Discussion):
             return MailFooter.monitored(
                 toaddr,
                 h.absurl(self.url()),
-                h.absurl('{0}admin/{1}/forums'.format(
+                h.absurl('{}admin/{}/forums'.format(
                     self.project.url(),
                     self.app.config.options.mount_point)))
         return super(Forum, self).get_mail_footer(notification, toaddr)
diff --git a/ForgeDiscussion/forgediscussion/tests/functional/test_forum.py b/ForgeDiscussion/forgediscussion/tests/functional/test_forum.py
index 541edeb..e64c69b 100644
--- a/ForgeDiscussion/forgediscussion/tests/functional/test_forum.py
+++ b/ForgeDiscussion/forgediscussion/tests/functional/test_forum.py
@@ -239,7 +239,7 @@ class TestForumMessageHandling(TestController):
         thd_url = str('/discussion/testforum/thread/%s/' % thd._id)
         r = self.app.get(thd_url)
         p = FM.ForumPost.query.find().first()
-        url = str('/discussion/testforum/thread/%s/%s/' % (thd._id, p.slug))
+        url = str('/discussion/testforum/thread/{}/{}/'.format(thd._id, p.slug))
         r = self.app.get(url)
         f = r.html.find('form', {'action': '/p/test' + url})
         params = dict()
@@ -592,9 +592,9 @@ class TestForum(TestController):
         r = self.app.get('/discussion/testforum/moderate/')
         post = FM.ForumPost.query.get(text='Post content')
         post2 = FM.ForumPost.query.get(text='anon reply to anon post content')
-        link = '<a href="%s">[%s]</a>' % (post.thread.url() + '?limit=25#' + post.slug, post.shorthand_id())
+        link = '<a href="{}">[{}]</a>'.format(post.thread.url() + '?limit=25#' + post.slug, post.shorthand_id())
         assert link in r, link
-        link = '<a href="%s">[%s]</a>' % (post2.thread.url() + '?limit=25#' + post2.slug, post2.shorthand_id())
+        link = '<a href="{}">[{}]</a>'.format(post2.thread.url() + '?limit=25#' + post2.slug, post2.shorthand_id())
         assert link in r, link
 
         # approve posts
diff --git a/ForgeFiles/forgefiles/files_main.py b/ForgeFiles/forgefiles/files_main.py
index d276ec1..7ed30fe 100755
--- a/ForgeFiles/forgefiles/files_main.py
+++ b/ForgeFiles/forgefiles/files_main.py
@@ -395,7 +395,7 @@ class FilesController(BaseController):
                             fromaddr=g.noreply,
                             reply_to=g.noreply,
                             toaddr=email_addr,
-                            subject='%s - %s Release Update' % (config.get('site_name'), c.project.name),
+                            subject='{} - {} Release Update'.format(config.get('site_name'), c.project.name),
                             message_id=h.gen_message_id(),
                             text=text)
                 if folder_object.parent_folder:
diff --git a/ForgeGit/forgegit/model/git_repo.py b/ForgeGit/forgegit/model/git_repo.py
index cc8b14f..5fab39a 100644
--- a/ForgeGit/forgegit/model/git_repo.py
+++ b/ForgeGit/forgegit/model/git_repo.py
@@ -103,7 +103,7 @@ class Repository(M.Repository):
             ).format(
                 merge_request.downstream_repo.clone_url_first(anon=False, username=c.user.username),
             )
-        return 'git checkout %s\n%s\ngit merge %s' % (
+        return 'git checkout {}\n{}\ngit merge {}'.format(
             merge_request.target_branch,
             fetch_command,
             merge_request.downstream.commit_id,
@@ -276,7 +276,7 @@ class GitImplementation(M.RepositoryImplementation):
                 if M.repository.Commit.query.find(dict(_id=obj.hexsha)).count():
                     graph[obj.hexsha] = set()  # mark as parentless
                     continue
-            graph[obj.hexsha] = set(p.hexsha for p in obj.parents)
+            graph[obj.hexsha] = {p.hexsha for p in obj.parents}
             to_visit += obj.parents
         return list(topological_sort(graph))
 
diff --git a/ForgeGit/forgegit/tests/model/test_repository.py b/ForgeGit/forgegit/tests/model/test_repository.py
index 6243f35..b594e1b 100644
--- a/ForgeGit/forgegit/tests/model/test_repository.py
+++ b/ForgeGit/forgegit/tests/model/test_repository.py
@@ -380,7 +380,7 @@ class TestGitRepo(unittest.TestCase, RepoImplTestBase):
         assert notification
         domain = '.'.join(reversed(c.app.url[1:-1].split('/'))).replace('_', '-')
         common_suffix = tg.config['forgemail.domain']
-        email = 'noreply@%s%s' % (domain, common_suffix)
+        email = 'noreply@{}{}'.format(domain, common_suffix)
         assert_in(email, notification['reply_to_address'])
 
         commit1_loc = notification.text.find('Initial commit')
diff --git a/ForgeImporters/forgeimporters/base.py b/ForgeImporters/forgeimporters/base.py
index 1df43b9..bffdc7d 100644
--- a/ForgeImporters/forgeimporters/base.py
+++ b/ForgeImporters/forgeimporters/base.py
@@ -378,7 +378,7 @@ class ToolImporterMeta(type):
     def __init__(cls, name, bases, attrs):
         if not (hasattr(cls, 'target_app_ep_names')
                 or hasattr(cls, 'target_app')):
-            raise AttributeError("{0} must define either `target_app` or `target_app_ep_names`".format(name))
+            raise AttributeError("{} must define either `target_app` or `target_app_ep_names`".format(name))
         return type.__init__(cls, name, bases, attrs)
 
     def __call__(cls, *args, **kw):
@@ -538,7 +538,7 @@ class ToolImporter(six.with_metaclass(ToolImporterMeta, object)):
 
         """
         klass = self.__class__
-        importer_path = '{0}.{1}'.format(klass.__module__, klass.__name__)
+        importer_path = '{}.{}'.format(klass.__module__, klass.__name__)
         import_tool.post(importer_path, **kw)
 
 
diff --git a/ForgeImporters/forgeimporters/forge/alluraImporter.py b/ForgeImporters/forgeimporters/forge/alluraImporter.py
index 1cfb7de..ee194bc 100644
--- a/ForgeImporters/forgeimporters/forge/alluraImporter.py
+++ b/ForgeImporters/forgeimporters/forge/alluraImporter.py
@@ -43,7 +43,7 @@ class AlluraImporter(ToolImporter):
            and username != "" \
            and username != 'nobody' \
            and username != '*anonymous':
-            return '*Originally%s by:* %s\n\n%s' % (label, username, text)
+            return '*Originally{} by:* {}\n\n{}'.format(label, username, text)
 
         if text is None:
             text = ""
diff --git a/ForgeImporters/forgeimporters/forge/discussion.py b/ForgeImporters/forgeimporters/forge/discussion.py
index 0ba9ad1..098411a 100644
--- a/ForgeImporters/forgeimporters/forge/discussion.py
+++ b/ForgeImporters/forgeimporters/forge/discussion.py
@@ -141,7 +141,7 @@ class ForgeDiscussionImporter(AlluraImporter):
                     session(forum).expunge(forum)
 
                 M.AuditLog.log(
-                    "import tool %s from exported Allura JSON" % (
+                    "import tool {} from exported Allura JSON".format(
                         app.config.options.mount_point,
                     ),
                     project=project,
diff --git a/ForgeImporters/forgeimporters/forge/tracker.py b/ForgeImporters/forgeimporters/forge/tracker.py
index 518f509..a00e933 100644
--- a/ForgeImporters/forgeimporters/forge/tracker.py
+++ b/ForgeImporters/forgeimporters/forge/tracker.py
@@ -161,7 +161,7 @@ class ForgeTrackerImporter(AlluraImporter):
             self.process_bins(app, tracker_json['saved_bins'])
             app.globals.last_ticket_num = self.max_ticket_num
             M.AuditLog.log(
-                'import tool %s from exported Allura JSON' % (
+                'import tool {} from exported Allura JSON'.format(
                     app.config.options.mount_point,
                 ),
                 project=project,
diff --git a/ForgeImporters/forgeimporters/github/__init__.py b/ForgeImporters/forgeimporters/github/__init__.py
index e53e090..9467719 100644
--- a/ForgeImporters/forgeimporters/github/__init__.py
+++ b/ForgeImporters/forgeimporters/github/__init__.py
@@ -59,7 +59,7 @@ class GitHubProjectNameValidator(fev.FancyValidator):
         user_name = state.full_dict.get('user_name', '')
         user_name = state.full_dict.get('gh_user_name', user_name).strip()
         project_name = value.strip()
-        full_project_name = '%s/%s' % (user_name, project_name)
+        full_project_name = '{}/{}'.format(user_name, project_name)
         if not re.match(r'^[a-zA-Z0-9-_.]+$', project_name):
             raise fev.Invalid(self.message('invalid', state), value, state)
 
diff --git a/ForgeImporters/forgeimporters/github/code.py b/ForgeImporters/forgeimporters/github/code.py
index 19c0e51..e3335d3 100644
--- a/ForgeImporters/forgeimporters/github/code.py
+++ b/ForgeImporters/forgeimporters/github/code.py
@@ -88,7 +88,7 @@ class GitHubRepoImporter(ToolImporter):
         """ Import a GitHub repo into a new Git Allura tool.
 
         """
-        project_name = "%s/%s" % (user_name, project_name)
+        project_name = "{}/{}".format(user_name, project_name)
         extractor = GitHubProjectExtractor(project_name, user=user)
         repo_url = extractor.get_repo_url()
         app = project.install_app(
@@ -102,7 +102,7 @@ class GitHubRepoImporter(ToolImporter):
             }
         )
         M.AuditLog.log(
-            'import tool %s from %s on %s' % (
+            'import tool {} from {} on {}'.format(
                 app.config.options.mount_point,
                 project_name, self.source,
             ), project=project, user=user, url=app.url)
diff --git a/ForgeImporters/forgeimporters/github/project.py b/ForgeImporters/forgeimporters/github/project.py
index 60157a0..facff8e 100644
--- a/ForgeImporters/forgeimporters/github/project.py
+++ b/ForgeImporters/forgeimporters/github/project.py
@@ -47,7 +47,7 @@ class GitHubProjectImporter(base.ProjectImporter, GitHubOAuthMixin):
 
     def after_project_create(self, project, **kw):
         project.set_tool_data('github', project_name=project.name)
-        project_name = '%s/%s' % (kw['user_name'], kw['project_name'])
+        project_name = '{}/{}'.format(kw['user_name'], kw['project_name'])
         tasks.import_project_info.post(project_name)
 
     @with_trailing_slash
diff --git a/ForgeImporters/forgeimporters/github/tracker.py b/ForgeImporters/forgeimporters/github/tracker.py
index 51c83fd..59444d1 100644
--- a/ForgeImporters/forgeimporters/github/tracker.py
+++ b/ForgeImporters/forgeimporters/github/tracker.py
@@ -102,7 +102,7 @@ class GitHubTrackerImporter(ToolImporter):
     def import_tool(self, project, user, project_name, mount_point=None,
                     mount_label=None, **kw):
         import_id_converter = ImportIdConverter.get()
-        project_name = '%s/%s' % (kw['user_name'], project_name)
+        project_name = '{}/{}'.format(kw['user_name'], project_name)
         extractor = GitHubProjectExtractor(project_name, user=user)
         if not extractor.has_tracker():
             return
@@ -139,7 +139,7 @@ class GitHubTrackerImporter(ToolImporter):
                 app.globals.last_ticket_num = self.max_ticket_num
                 ThreadLocalORMSession.flush_all()
             M.AuditLog.log(
-                'import tool %s from %s on %s' % (
+                'import tool {} from {} on {}'.format(
                     app.config.options.mount_point,
                     project_name, self.source),
                 project=project, user=user, url=app.url)
@@ -249,7 +249,7 @@ class GitHubTrackerImporter(ToolImporter):
         # usual syntax is
         # ![cdbpzjc5ex4](https://f.cloud.github.com/assets/979771/1027411/a393ab5e-0e70-11e3-8a38-b93a3df904cf.jpg)\r\n
         REGEXP = r'!\[[\w0-9]+?\]\(((?:https?:\/\/)?[\da-z\.-]+\.[a-z\.]{2,6}'\
-            '[\/%\w\.-]*.(jpg|jpeg|png|gif))\)[\r\n]*'
+            '[\\/%\\w\\.-]*.(jpg|jpeg|png|gif))\\)[\r\n]*'
         attachments = []
 
         try:
diff --git a/ForgeImporters/forgeimporters/github/utils.py b/ForgeImporters/forgeimporters/github/utils.py
index 2084159..becd18c 100644
--- a/ForgeImporters/forgeimporters/github/utils.py
+++ b/ForgeImporters/forgeimporters/github/utils.py
@@ -21,7 +21,7 @@ import re
 class GitHubMarkdownConverter(object):
 
     def __init__(self, gh_user, gh_project):
-        self.gh_project = '%s/%s' % (gh_user, gh_project)
+        self.gh_project = '{}/{}'.format(gh_user, gh_project)
         self.gh_base_url = 'https://github.com/'
         self.code_patterns = ['```', '~~~']
 
@@ -110,13 +110,13 @@ class GitHubMarkdownConverter(object):
         return text
 
     def _gh_commit_url(self, project, sha, title):
-        return '[%s](%s)' % (title, self.gh_base_url + project + '/commit/' + sha)
+        return '[{}]({})'.format(title, self.gh_base_url + project + '/commit/' + sha)
 
     def _gh_ticket_url(self, project, tid, title):
-        return '[%s](%s)' % (title, self.gh_base_url + project + '/issues/' + str(tid))
+        return '[{}]({})'.format(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))
+        return '{}[{}]{}'.format(m.group(1), m.group(2)[:6], m.group(3))
 
     def _convert_ticket(self, m):
         return '%s[%s]' % m.groups()
@@ -125,14 +125,14 @@ class GitHubMarkdownConverter(object):
         user = m.group(2)
         tid = m.group(3)
         if self.gh_project.startswith(user + '/'):
-            return '%s[%s]%s' % (m.group(1), '#' + tid, m.group(4))
+            return '{}[{}]{}'.format(m.group(1), '#' + tid, m.group(4))
         return m.group(0)
 
     def _convert_user_repo_ticket(self, m):
-        project = '%s/%s' % (m.group(2), m.group(3))
+        project = '{}/{}'.format(m.group(2), m.group(3))
         tid = m.group(4)
         if project == self.gh_project:
-            return '%s[%s]%s' % (m.group(1), '#' + tid, m.group(5))
+            return '{}[{}]{}'.format(m.group(1), '#' + tid, m.group(5))
         title = project + '#' + tid
         return ''.join([m.group(1),
                         self._gh_ticket_url(project, tid, title),
@@ -142,14 +142,14 @@ class GitHubMarkdownConverter(object):
         user = m.group(2)
         sha = m.group(3)
         if self.gh_project.startswith(user + '/'):
-            return '%s[%s]%s' % (m.group(1), sha[:6], m.group(4))
+            return '{}[{}]{}'.format(m.group(1), sha[:6], m.group(4))
         return m.group(0)
 
     def _convert_user_repo_sha(self, m):
-        project = '%s/%s' % (m.group(2), m.group(3))
+        project = '{}/{}'.format(m.group(2), m.group(3))
         sha = m.group(4)
         if project == self.gh_project:
-            return '%s[%s]%s' % (m.group(1), sha[:6], m.group(5))
+            return '{}[{}]{}'.format(m.group(1), sha[:6], m.group(5))
         title = project + '@' + sha[:6]
         return ''.join([m.group(1),
                         self._gh_commit_url(project, sha, title),
diff --git a/ForgeImporters/forgeimporters/github/wiki.py b/ForgeImporters/forgeimporters/github/wiki.py
index 60604df..c28e872 100644
--- a/ForgeImporters/forgeimporters/github/wiki.py
+++ b/ForgeImporters/forgeimporters/github/wiki.py
@@ -134,7 +134,7 @@ class GitHubWikiImporter(ToolImporter):
         """ Import a GitHub wiki into a new Wiki Allura tool.
 
         """
-        project_name = "%s/%s" % (user_name, project_name)
+        project_name = "{}/{}".format(user_name, project_name)
         extractor = GitHubProjectExtractor(project_name, user=user)
         wiki_avail = extractor.has_wiki()
         # has_wiki only indicates that wiki is enabled, but it does not mean
@@ -176,7 +176,7 @@ class GitHubWikiImporter(ToolImporter):
                     raise
             ThreadLocalORMSession.flush_all()
             M.AuditLog.log(
-                'import tool %s from %s on %s' % (
+                'import tool {} from {} on {}'.format(
                     self.app.config.options.mount_point,
                     project_name,
                     self.source),
diff --git a/ForgeImporters/forgeimporters/tests/github/test_tracker.py b/ForgeImporters/forgeimporters/tests/github/test_tracker.py
index e9bd59f..c54fbbb 100644
--- a/ForgeImporters/forgeimporters/tests/github/test_tracker.py
+++ b/ForgeImporters/forgeimporters/tests/github/test_tracker.py
@@ -113,10 +113,10 @@ class TestTrackerImporter(TestCase):
     @mock.patch.object(tracker, 'c')
     def test_postprocess_milestones(self, c):
         importer = tracker.GitHubTrackerImporter()
-        importer.open_milestones = set([
+        importer.open_milestones = {
             ('first', datetime(day=23, month=4, year=2015)),
             ('second', datetime(day=25, month=4, year=2015))
-        ])
+        }
         milestones = importer.postprocess_milestones()
         # for stable order
         milestones[0]['milestones'] = sorted(milestones[0]['milestones'], key=itemgetter('name'))
diff --git a/ForgeImporters/forgeimporters/tests/test_base.py b/ForgeImporters/forgeimporters/tests/test_base.py
index 7042393..386215b 100644
--- a/ForgeImporters/forgeimporters/tests/test_base.py
+++ b/ForgeImporters/forgeimporters/tests/test_base.py
@@ -228,10 +228,10 @@ class TestToolImporter(TestCase):
             ep('importer3', importer=TI3),
         ]
         importers = base.ToolImporter.by_app(TA2)
-        self.assertEqual(set(importers.keys()), set([
+        self.assertEqual(set(importers.keys()), {
             'importer2',
             'importer3',
-        ]))
+        })
         self.assertIsInstance(importers['importer2'], TI2)
         self.assertIsInstance(importers['importer3'], TI3)
 
diff --git a/ForgeImporters/forgeimporters/trac/tickets.py b/ForgeImporters/forgeimporters/trac/tickets.py
index a71578c..602354a 100644
--- a/ForgeImporters/forgeimporters/trac/tickets.py
+++ b/ForgeImporters/forgeimporters/trac/tickets.py
@@ -120,7 +120,7 @@ class TracTicketImporter(ToolImporter):
                     }),
                 )
             AuditLog.log(
-                'import tool %s from %s' % (
+                'import tool {} from {}'.format(
                     app.config.options.mount_point,
                     trac_url,
                 ),
@@ -179,7 +179,7 @@ class TracImportSupport(ImportSupport):
         text = text.replace('\n', ' ')
         slug = self.get_slug_by_id(ticket, comment)
         if slug:
-            return '[%s](%s/#%s)' % (text, ticket, slug)
+            return '[{}]({}/#{})'.format(text, ticket, slug)
         else:
             return text
 
diff --git a/ForgeSVN/forgesvn/model/svn.py b/ForgeSVN/forgesvn/model/svn.py
index 36c5337..95ee33d 100644
--- a/ForgeSVN/forgesvn/model/svn.py
+++ b/ForgeSVN/forgesvn/model/svn.py
@@ -169,7 +169,7 @@ class SVNImplementation(M.RepositoryImplementation):
 
     @LazyProperty
     def _url(self):
-        return 'file://%s%s' % (self._repo.fs_path, self._repo.name)
+        return 'file://{}{}'.format(self._repo.fs_path, self._repo.name)
 
     def shorthand_for_commit(self, oid):
         return '[r%d]' % self._revno(self.rev_parse(oid))
@@ -306,13 +306,13 @@ class SVNImplementation(M.RepositoryImplementation):
 
         """
         opts = self._repo.app.config.options
-        if not svn_path_exists('file://{0}{1}/{2}'.format(self._repo.fs_path,
+        if not svn_path_exists('file://{}{}/{}'.format(self._repo.fs_path,
                                                           self._repo.name, opts['checkout_url'])):
             opts['checkout_url'] = ''
 
         if (not opts['checkout_url'] and
                 svn_path_exists(
-                    'file://{0}{1}/trunk'.format(self._repo.fs_path,
+                    'file://{}{}/trunk'.format(self._repo.fs_path,
                                                  self._repo.name))):
             opts['checkout_url'] = 'trunk'
 
@@ -459,11 +459,11 @@ class SVNImplementation(M.RepositoryImplementation):
         return tree_id
 
     def _tree_oid(self, commit_id, path):
-        data = 'tree\n%s\n%s' % (commit_id, h.really_unicode(path))
+        data = 'tree\n{}\n{}'.format(commit_id, h.really_unicode(path))
         return sha1(data.encode('utf-8')).hexdigest()
 
     def _blob_oid(self, commit_id, path):
-        data = 'blob\n%s\n%s' % (commit_id, h.really_unicode(path))
+        data = 'blob\n{}\n{}'.format(commit_id, h.really_unicode(path))
         return sha1(data.encode('utf-8')).hexdigest()
 
     def _obj_oid(self, commit_id, info):
@@ -500,11 +500,11 @@ class SVNImplementation(M.RepositoryImplementation):
         if revs is None:
             revno = self.head
         else:
-            revno = max([self._revno(self.rev_parse(r)) for r in revs])
+            revno = max(self._revno(self.rev_parse(r)) for r in revs)
         if exclude is None:
             exclude = 0
         else:
-            exclude = max([self._revno(self.rev_parse(r)) for r in exclude])
+            exclude = max(self._revno(self.rev_parse(r)) for r in exclude)
         if path is None:
             url = self._url
         else:
@@ -631,7 +631,7 @@ class SVNImplementation(M.RepositoryImplementation):
             self._revno(oid))
 
     def _oid(self, revno):
-        return '%s:%s' % (self._repo._id, revno)
+        return '{}:{}'.format(self._repo._id, revno)
 
     def last_commit_ids(self, commit, paths):
         '''
@@ -687,7 +687,7 @@ class SVNImplementation(M.RepositoryImplementation):
         if path:
             return path.strip('/')
         else:
-            trunk_exists = svn_path_exists('file://%s%s/%s' % (self._repo.fs_path, self._repo.name, 'trunk'), rev)
+            trunk_exists = svn_path_exists('file://{}{}/{}'.format(self._repo.fs_path, self._repo.name, 'trunk'), rev)
             if trunk_exists:
                 return 'trunk'
             return ''
@@ -706,8 +706,8 @@ class SVNImplementation(M.RepositoryImplementation):
         archive_name = self._repo.tarball_filename(commit, path)
         dest = os.path.join(self._repo.tarball_path, archive_name)
         tmpdest = os.path.join(self._repo.tarball_tmpdir, archive_name)
-        filename = os.path.join(self._repo.tarball_path, '%s%s' % (archive_name, '.zip')).encode('utf-8')
-        tmpfilename = os.path.join(self._repo.tarball_path, '%s%s' % (archive_name, '.tmp')).encode('utf-8')
+        filename = os.path.join(self._repo.tarball_path, '{}{}'.format(archive_name, '.zip')).encode('utf-8')
+        tmpfilename = os.path.join(self._repo.tarball_path, '{}{}'.format(archive_name, '.tmp')).encode('utf-8')
         rmtree(dest.encode('utf8'), ignore_errors=True)  # must encode into bytes or it'll fail on non-ascii filenames
         rmtree(tmpdest.encode('utf8'), ignore_errors=True)
         path = os.path.join(self._url, path)
diff --git a/ForgeSVN/forgesvn/tests/model/test_repository.py b/ForgeSVN/forgesvn/tests/model/test_repository.py
index 5feab1a..f86b189 100644
--- a/ForgeSVN/forgesvn/tests/model/test_repository.py
+++ b/ForgeSVN/forgesvn/tests/model/test_repository.py
@@ -612,7 +612,7 @@ class TestSVNRev(unittest.TestCase):
             print(d)
 
     def _oid(self, rev_id):
-        return '%s:%s' % (self.repo._id, rev_id)
+        return '{}:{}'.format(self.repo._id, rev_id)
 
     def test_log(self):
         # path only
diff --git a/ForgeShortUrl/forgeshorturl/main.py b/ForgeShortUrl/forgeshorturl/main.py
index 5101a86..3e52afa 100644
--- a/ForgeShortUrl/forgeshorturl/main.py
+++ b/ForgeShortUrl/forgeshorturl/main.py
@@ -229,7 +229,7 @@ class ShortURLAdminController(DefaultAdminController):
                 error_msg = 'Error: '
                 for msg in list(c.form_errors):
                     names = {'short_url': 'Short url', 'full_url': 'Full URL'}
-                    error_msg += '%s: %s ' % (names[msg], c.form_errors[msg])
+                    error_msg += '{}: {} '.format(names[msg], c.form_errors[msg])
                     flash(error_msg, 'error')
                 redirect(six.ensure_text(request.referer or '/'))
 
@@ -250,7 +250,7 @@ class ShortURLAdminController(DefaultAdminController):
                 shorturl = ShortUrl()
                 shorturl.created = datetime.utcnow()
                 shorturl.app_config_id = self.app.config._id
-                msg = 'create short url %s for %s' % (short_url, full_url)
+                msg = 'create short url {} for {}'.format(short_url, full_url)
                 flash("Short url created")
 
             shorturl.short_name = short_url
diff --git a/ForgeShortUrl/forgeshorturl/model/shorturl.py b/ForgeShortUrl/forgeshorturl/model/shorturl.py
index 7bb6f4b..403b8ee 100644
--- a/ForgeShortUrl/forgeshorturl/model/shorturl.py
+++ b/ForgeShortUrl/forgeshorturl/model/shorturl.py
@@ -69,7 +69,7 @@ class ShortUrl(M.Artifact):
             full_url_s=self.full_url,
             short_name_s=self.short_name,
             description_s=self.description,
-            title='%s => %s' % (self.url(), self.full_url),
+            title='{} => {}'.format(self.url(), self.full_url),
             private_b=self.private,
             type_s=self.type_s)
         return result
diff --git a/ForgeTracker/forgetracker/import_support.py b/ForgeTracker/forgetracker/import_support.py
index 498b773..a36c526 100644
--- a/ForgeTracker/forgetracker/import_support.py
+++ b/ForgeTracker/forgetracker/import_support.py
@@ -215,12 +215,12 @@ class ImportSupport(object):
             self.description_processing(remapped['description']))
         creator = owner = ''
         if ticket_dict.get('submitter') and not remapped.get('reported_by_id'):
-            creator = '*Originally created by:* {0}\n'.format(
+            creator = '*Originally created by:* {}\n'.format(
                 h.really_unicode(ticket_dict['submitter']))
         if ticket_dict.get('assigned_to') and not remapped.get('assigned_to_id'):
-            owner = '*Originally owned by:* {0}\n'.format(
+            owner = '*Originally owned by:* {}\n'.format(
                     h.really_unicode(ticket_dict['assigned_to']))
-        remapped['description'] = '{0}{1}{2}{3}'.format(creator, owner,
+        remapped['description'] = '{}{}{}{}'.format(creator, owner,
                                                          '\n' if creator or owner else '', description)
 
         ticket_num = ticket_dict['id']
@@ -258,7 +258,7 @@ class ImportSupport(object):
         text = h.really_unicode(
             self.comment_processing(comment_dict['comment']))
         if not author_id and comment_dict['submitter']:
-            text = '*Originally posted by:* {0}\n\n{1}'.format(
+            text = '*Originally posted by:* {}\n\n{}'.format(
                 h.really_unicode(comment_dict['submitter']), text)
         comment = thread.post(text=text, timestamp=ts)
         comment.author_id = author_id
@@ -311,7 +311,7 @@ class ImportSupport(object):
             u = M.User.by_username(allura_user)
             if not u:
                 raise ImportException(
-                    'User mapping %s:%s - target user does not exist' % (foreign_user, allura_user))
+                    'User mapping {}:{} - target user does not exist'.format(foreign_user, allura_user))
 
     #
     # Main methods
diff --git a/ForgeTracker/forgetracker/model/ticket.py b/ForgeTracker/forgetracker/model/ticket.py
index e0e3106..86515f1 100644
--- a/ForgeTracker/forgetracker/model/ticket.py
+++ b/ForgeTracker/forgetracker/model/ticket.py
@@ -151,15 +151,15 @@ class Globals(MappedClass):
 
     @property
     def set_of_all_status_names(self):
-        return set([name for name in self.all_status_names.split(' ') if name])
+        return {name for name in self.all_status_names.split(' ') if name}
 
     @property
     def set_of_open_status_names(self):
-        return set([name for name in self.open_status_names.split(' ') if name])
+        return {name for name in self.open_status_names.split(' ') if name}
 
     @property
     def set_of_closed_status_names(self):
-        return set([name for name in self.closed_status_names.split(' ') if name])
+        return {name for name in self.closed_status_names.split(' ') if name}
 
     @property
     def not_closed_query(self):
@@ -272,7 +272,7 @@ class Globals(MappedClass):
             return self.get_custom_field_solr_type(field_name) or '_s'
 
         return [dict(
-            sortable_name='{0}{1}'.format(field['name'],
+            sortable_name='{}{}'.format(field['name'],
                 solr_type(field['name'])),
             name=field['name'],
             label=field['label'])
@@ -299,16 +299,16 @@ class Globals(MappedClass):
             sender=c.project.app_instance(self.app_config).email_address,
             fromaddr=str(c.user.email_address_header()),
             reply_to=str(c.user.email_address_header()),
-            subject='[%s:%s] Mass ticket moving by %s' % (c.project.shortname,
+            subject='[{}:{}] Mass ticket moving by {}'.format(c.project.shortname,
                                                           self.app_config.options.mount_point,
                                                           c.user.display_name))
         tmpl = g.jinja2_env.get_template(
             'forgetracker:data/mass_move_report.html')
 
         tmpl_context = {
-            'original_tracker': '%s:%s' % (c.project.shortname,
+            'original_tracker': '{}:{}'.format(c.project.shortname,
                                            self.app_config.options.mount_point),
-            'destination_tracker': '%s:%s' % (tracker.project.shortname,
+            'destination_tracker': '{}:{}'.format(tracker.project.shortname,
                                               tracker.options.mount_point),
             'tickets': [],
         }
@@ -343,11 +343,11 @@ class Globals(MappedClass):
                     destinations=[monitoring_email]))
                 mail_tasks.sendmail.post(**mail)
 
-        moved_from = '%s/%s' % (c.project.shortname,
+        moved_from = '{}/{}'.format(c.project.shortname,
                                 self.app_config.options.mount_point)
-        moved_to = '%s/%s' % (tracker.project.shortname,
+        moved_to = '{}/{}'.format(tracker.project.shortname,
                               tracker.options.mount_point)
-        text = 'Tickets moved from %s to %s' % (moved_from, moved_to)
+        text = 'Tickets moved from {} to {}'.format(moved_from, moved_to)
         Notification.post_user(c.user, None, 'flash', text=text)
 
     def update_tickets(self, **post_data):
@@ -358,7 +358,7 @@ class Globals(MappedClass):
                              post_data['__ticket_ids'])]},
             app_config_id=self.app_config_id)).all()
 
-        fields = set(['status', 'private'])
+        fields = {'status', 'private'}
         values = {}
         labels = post_data.get('labels', [])
 
@@ -464,7 +464,7 @@ class Globals(MappedClass):
             sender=c.project.app_instance(self.app_config).email_address,
             fromaddr=str(c.user._id),
             reply_to=tg_config['forgemail.return_path'],
-            subject='[%s:%s] Mass edit changes by %s' % (c.project.shortname,
+            subject='[{}:{}] Mass edit changes by {}'.format(c.project.shortname,
                                                          self.app_config.options.mount_point,
                                                          c.user.display_name),
         )
@@ -474,13 +474,13 @@ class Globals(MappedClass):
             if f == 'assigned_to_id':
                 user = User.query.get(_id=v)
                 v = user.display_name if user else v
-            head.append('- **%s**: %s' % (get_label(f), v))
+            head.append('- **{}**: {}'.format(get_label(f), v))
         for f, v in sorted(six.iteritems(custom_values)):
             cf = custom_fields[f]
             if cf.type == 'user':
                 user = User.by_username(v)
                 v = user.display_name if user else v
-            head.append('- **%s**: %s' % (cf.label, v))
+            head.append('- **{}**: {}'.format(cf.label, v))
         tmpl_context = {'context': c, 'data':
                         {'header': jinja2.Markup('\n'.join(['Mass edit changing:', ''] + head))}}
         for user in users:
@@ -512,7 +512,7 @@ class Globals(MappedClass):
 
         self.invalidate_bin_counts()
         ThreadLocalORMSession.flush_all()
-        app = '%s/%s' % (c.project.shortname,
+        app = '{}/{}'.format(c.project.shortname,
                          self.app_config.options.mount_point)
         count = len(tickets)
         text = 'Updated {} ticket{} in {}'.format(
@@ -563,7 +563,7 @@ class TicketHistory(Snapshot):
         orig = self.original()
         if not orig:
             return None
-        return '%s#%s' % (orig.shorthand_id(), self.version)
+        return '{}#{}'.format(orig.shorthand_id(), self.version)
 
     def url(self):
         orig = self.original()
@@ -810,13 +810,13 @@ class Ticket(VersionedArtifact, ActivityObject, VotableArtifact):
             return tg_config.get('forgemail.return_path')
         if c.app.config.options.get('AllowEmailPosting', True):
             domain = self.email_domain
-            return '%s@%s%s' % (self.ticket_num, domain, config.common_suffix)
+            return '{}@{}{}'.format(self.ticket_num, domain, config.common_suffix)
         else:
             return tg_config.get('forgemail.return_path')
 
     @property
     def email_subject(self):
-        return '#%s %s' % (self.ticket_num, self.summary)
+        return '#{} {}'.format(self.ticket_num, self.summary)
 
     @LazyProperty
     def globals(self):
@@ -850,7 +850,7 @@ class Ticket(VersionedArtifact, ActivityObject, VotableArtifact):
         if not fld:
             raise KeyError('Custom field "%s" does not exist.' % custom_user_field_name)
         if fld.type != 'user':
-            raise TypeError('Custom field "%s" is of type "%s"; expected type "user".' % (
+            raise TypeError('Custom field "{}" is of type "{}"; expected type "user".'.format(
                 custom_user_field_name, fld.type))
         username = self.custom_fields.get(custom_user_field_name)
         if not username:
@@ -907,9 +907,9 @@ class Ticket(VersionedArtifact, ActivityObject, VotableArtifact):
             hist = TicketHistory.query.get(
                 artifact_id=self._id, version=self.version - 1)
             old = hist.data
-            changes = ['Ticket %s has been modified: %s' % (
+            changes = ['Ticket {} has been modified: {}'.format(
                 self.ticket_num, self.summary),
-                'Edited By: %s (%s)' % (c.user.get_pref('display_name'), c.user.username)]
+                'Edited By: {} ({})'.format(c.user.get_pref('display_name'), c.user.username)]
             fields = [
                 ('Summary', old.summary, self.summary),
                 ('Status', old.status, self.status)]
@@ -921,12 +921,12 @@ class Ticket(VersionedArtifact, ActivityObject, VotableArtifact):
                     (key, old.custom_fields.get(key, ''), self.custom_fields[key]))
             for title, o, n in fields:
                 if o != n:
-                    changes.append('%s updated: %r => %r' % (
+                    changes.append('{} updated: {!r} => {!r}'.format(
                         title, o, n))
             o = hist.assigned_to
             n = self.assigned_to
             if o != n:
-                changes.append('Owner updated: %r => %r' % (
+                changes.append('Owner updated: {!r} => {!r}'.format(
                     o and o.username, n and n.username))
                 self.subscribe(user=n)
                 g.statsUpdater.ticketEvent("assigned", self, self.project, n)
@@ -1345,7 +1345,7 @@ class Ticket(VersionedArtifact, ActivityObject, VotableArtifact):
             from forgetracker.tracker_main import _mongo_col_to_solr_col
             sort_split = sort.split(' ')
             solr_col = _mongo_col_to_solr_col(sort_split[0])
-            solr_sort = '%s %s' % (solr_col, sort_split[1])
+            solr_sort = '{} {}'.format(solr_col, sort_split[1])
         if not filter:
             result = cls.paged_query(app_config, user, query, sort=sort, limit=limit, page=page, **kw)
             t = cls.query.find().first()
@@ -1366,7 +1366,7 @@ class Ticket(VersionedArtifact, ActivityObject, VotableArtifact):
             return MailFooter.monitored(
                 toaddr,
                 h.absurl(self.app.url),
-                h.absurl('{0}admin/{1}/options'.format(
+                h.absurl('{}admin/{}/options'.format(
                     self.project.url(),
                     self.app.config.options.mount_point)))
         return MailFooter.standard(
diff --git a/ForgeTracker/forgetracker/tests/functional/test_rest.py b/ForgeTracker/forgetracker/tests/functional/test_rest.py
index c6d3e77..164d914 100644
--- a/ForgeTracker/forgetracker/tests/functional/test_rest.py
+++ b/ForgeTracker/forgetracker/tests/functional/test_rest.py
@@ -184,7 +184,7 @@ class TestRestDiscussion(TestTrackerApiBase):
         assert len(thread.json['thread']['posts']) == 1, thread.json
         assert post.json['post']['text'] == 'This is a comment', post.json
         reply = self.api_post(
-            '/rest/p/test/bugs/_discuss/thread/%s/%s/reply' % (thread.json['thread']
+            '/rest/p/test/bugs/_discuss/thread/{}/{}/reply'.format(thread.json['thread']
                                                                ['_id'], post.json['post']['slug']),
             text='This is a reply', wrap_args=None)
         assert reply.json['post']['text'] == 'This is a reply', reply.json
diff --git a/ForgeTracker/forgetracker/tests/functional/test_root.py b/ForgeTracker/forgetracker/tests/functional/test_root.py
index 6df39a9..f907653 100644
--- a/ForgeTracker/forgetracker/tests/functional/test_root.py
+++ b/ForgeTracker/forgetracker/tests/functional/test_root.py
@@ -930,7 +930,7 @@ class TestFunctionalController(TrackerTestController):
                 params[field['name']] = field.get('value') or ''
         params[f.find('textarea')['name']] = 'test comment'
         self.app.post(f['action'], params=params,
-                      headers={'Referer': '/bugs/1/'.encode("utf-8")})
+                      headers={'Referer': b'/bugs/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',
@@ -1526,7 +1526,7 @@ class TestFunctionalController(TrackerTestController):
                 params[field['name']] = field.get('value') or ''
         params[f.find('textarea')['name']] = post_content
         r = self.app.post(f['action'], params=params,
-                          headers={'Referer': '/bugs/1/'.encode("utf-8")})
+                          headers={'Referer': b'/bugs/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)
@@ -1542,7 +1542,7 @@ class TestFunctionalController(TrackerTestController):
                 params[field['name']] = field.get('value') or ''
         params['ticket_form.summary'] = new_summary
         r = self.app.post(f['action'], params=params,
-                          headers={'Referer': '/bugs/1/'.encode("utf-8")})
+                          headers={'Referer': b'/bugs/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)
@@ -1561,7 +1561,7 @@ class TestFunctionalController(TrackerTestController):
                 params[field['name']] = field.get('value') or ''
         params[f.find('textarea')['name']] = post_content
         r = self.app.post(f['action'], params=params,
-                          headers={'Referer': '/bugs/1/'.encode("utf-8")})
+                          headers={'Referer': b'/bugs/1/'})
         r = self.app.get('/bugs/1/', dict(page='-1'))
         assert_true(summary in r)
         r = self.app.get('/bugs/1/', dict(page='1'))
@@ -1571,7 +1571,7 @@ class TestFunctionalController(TrackerTestController):
         # add some more posts and check for pager
         for i in range(2):
             r = self.app.post(f['action'], params=params,
-                              headers={'Referer': '/bugs/1/'.encode("utf-8")})
+                              headers={'Referer': b'/bugs/1/'})
         r = self.app.get('/bugs/1/', dict(page='1', limit='2'))
         assert_true('Page 2 of 2' in r)
 
@@ -1589,7 +1589,7 @@ class TestFunctionalController(TrackerTestController):
                 params[field['name']] = field.get('value') or ''
         params[f.find('textarea')['name']] = post_content
         self.app.post(f['action'], params=params,
-                      headers={'Referer': '/bugs/1/'.encode("utf-8")})
+                      headers={'Referer': b'/bugs/1/'})
         r = self.app.get('/bugs/feed.rss')
         post = M.Post.query.find().first()
         assert '/p/test/bugs/1/?limit=25#' + post.slug in r
@@ -1970,7 +1970,7 @@ class TestFunctionalController(TrackerTestController):
         filtered_changes = c.app.globals.filtered_by_subscription(changes)
         filtered_users = [uid for uid, data in six.iteritems(filtered_changes)]
         assert_equal(sorted(filtered_users),
-                     sorted([u._id for u in users[:-1] + [admin]]))
+                     sorted(u._id for u in users[:-1] + [admin]))
         ticket_ids = [t._id for t in tickets]
         assert_equal(filtered_changes[users[0]._id], set(ticket_ids[0:1]))
         assert_equal(filtered_changes[users[1]._id], set(ticket_ids[:-1]))
@@ -2154,8 +2154,8 @@ class TestFunctionalController(TrackerTestController):
         self.new_ticket(summary='test')
         r = self.app.get('/p/test/bugs/1/move')
         trackers = r.html.find('select', {'name': 'tracker'}).findAll('option')
-        trackers = set([t.text for t in trackers])
-        expected = set(['test/bugs', 'test/bugs2', 'test2/bugs', 'test2/bugs2'])
+        trackers = {t.text for t in trackers}
+        expected = {'test/bugs', 'test/bugs2', 'test2/bugs', 'test2/bugs2'}
         assert trackers == expected, trackers
 
         p = M.Project.query.get(shortname='test2')
@@ -2212,7 +2212,7 @@ class TestFunctionalController(TrackerTestController):
                 params[field['name']] = field.get('value') or ''
         params[f.find('textarea')['name']] = post_content
         r = self.app.post(f['action'], params=params,
-                          headers={'Referer': '/p/test2/bugs2/1/'.encode("utf-8")})
+                          headers={'Referer': b'/p/test2/bugs2/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'}))
@@ -2462,7 +2462,7 @@ class TestFunctionalController(TrackerTestController):
                 params[field['name']] = field.get('value') or ''
         params[f.find('textarea')['name']] = 'test comment'
         self.app.post(f['action'], params=params,
-                      headers={'Referer': '/bugs/1/'.encode("utf-8")})
+                      headers={'Referer': b'/bugs/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',
@@ -2475,7 +2475,7 @@ class TestFunctionalController(TrackerTestController):
                      'http://localhost/rest%s' % discussion_url)
         slug = r['ticket']['discussion_thread']['posts'][0]['slug']
         assert_equal(r['ticket']['discussion_thread']['posts'][0]['attachments'][0]['url'],
-                     'http://localhost%s%s/attachment/test.txt' % (discussion_url, slug))
+                     'http://localhost{}{}/attachment/test.txt'.format(discussion_url, slug))
         assert_equal(r['ticket']['discussion_thread']['posts'][0]['attachments'][0]['bytes'],
                      11)
 
@@ -2635,7 +2635,7 @@ class TestMilestoneAdmin(TrackerTestController):
                  show_in_search='on',
                  type='milestone',
                  milestones=[
-                     dict((k, v) for k, v in six.iteritems(d)) for d in mf['milestones']])
+                     {k: v for k, v in six.iteritems(d)} for d in mf['milestones']])
             for mf in milestones]}
         return self._post(params)
 
@@ -3029,8 +3029,8 @@ class TestBulkMove(TrackerTestController):
     def test_controls_present(self):
         r = self.app.get('/bugs/move/')
         trackers = r.html.find('select', {'name': 'tracker'}).findAll('option')
-        trackers = set([t.text for t in trackers])
-        expected = set(['test/bugs', 'test/bugs2', 'test2/bugs', 'test2/bugs2'])
+        trackers = {t.text for t in trackers}
+        expected = {'test/bugs', 'test/bugs2', 'test2/bugs', 'test2/bugs2'}
         assert_equal(trackers, expected)
         move_btn = r.html.find('input', attrs={'type': 'submit', 'value': 'Move'})
         assert move_btn is not None
diff --git a/ForgeTracker/forgetracker/tracker_main.py b/ForgeTracker/forgetracker/tracker_main.py
index ab6a513..b3ad3b1 100644
--- a/ForgeTracker/forgetracker/tracker_main.py
+++ b/ForgeTracker/forgetracker/tracker_main.py
@@ -191,7 +191,7 @@ def _my_trackers(user, current_tracker_app_config):
         for ac in p.app_configs:
             if ac.tool_name.lower() == 'tickets':
                 trac = (str(ac._id),
-                        '%s/%s' % (p.shortname, ac.options['mount_point']),
+                        '{}/{}'.format(p.shortname, ac.options['mount_point']),
                         bool(current_tracker_app_config == ac))
                 trackers.append(trac)
     return trackers
@@ -535,7 +535,7 @@ class ForgeTrackerApp(Application):
             if fld.name == '_milestone':
                 for m in fld.milestones:
                     d = self.globals.milestone_count(
-                        '%s:%s' % (fld.name, m.name))
+                        '{}:{}'.format(fld.name, m.name))
                     milestones.append(dict(
                         name=m.name,
                         due_date=m.get('due_date'),
@@ -674,7 +674,7 @@ class RootController(BaseController, FeedController):
                 if m.complete:
                     continue
                 count = c.app.globals.milestone_count(
-                    '%s:%s' % (fld.name, m.name))['hits']
+                    '{}:{}'.format(fld.name, m.name))['hits']
                 name = h.text.truncate(m.name, 72)
                 milestone_counts.append({'name': name, 'count': count})
         return {'milestone_counts': milestone_counts}
@@ -782,7 +782,7 @@ class RootController(BaseController, FeedController):
                                 m.complete = new['complete'] == 'Closed'
                                 m.default = new.get('default', False)
                                 if new['old_name'] != m.name:
-                                    q = '%s:"%s"' % (fld.name, new['old_name'])
+                                    q = '{}:"{}"'.format(fld.name, new['old_name'])
                                     # search_artifact() limits results to 10
                                     # rows by default, so give it a high upper
                                     # bound to make sure we get all tickets
@@ -1092,7 +1092,7 @@ class RootController(BaseController, FeedController):
         c.user_select = ffw.ProjectUserCombo()
         if dates is None:
             today = datetime.utcnow()
-            dates = "%s to %s" % ((today - timedelta(days=61))
+            dates = "{} to {}".format((today - timedelta(days=61))
                                   .strftime('%Y-%m-%d'), today.strftime('%Y-%m-%d'))
         return dict(
             now=str(now),
@@ -1667,8 +1667,8 @@ class TrackerAdminController(DefaultAdminController):
     @expose('jinja:forgetracker:templates/tracker/admin_fields.html')
     def fields(self, **kw):
         c.form = W.field_admin
-        columns = dict((column, get_label(column))
-                       for column in self.app.globals['show_in_search'].keys())
+        columns = {column: get_label(column)
+                       for column in self.app.globals['show_in_search'].keys()}
         return dict(app=self.app, globals=self.app.globals, columns=columns)
 
     @expose('jinja:forgetracker:templates/tracker/admin_options.html')
@@ -1724,10 +1724,10 @@ class TrackerAdminController(DefaultAdminController):
             if field['type'] == 'milestone':
                 field.setdefault('milestones', [])
 
-        existing_milestone_fld_names = set(
-            mf.name for mf in self.app.globals.milestone_fields)
-        posted_milestone_fld_names = set(
-            cf['name'] for cf in custom_fields if cf['type'] == 'milestone')
+        existing_milestone_fld_names = {
+            mf.name for mf in self.app.globals.milestone_fields}
+        posted_milestone_fld_names = {
+            cf['name'] for cf in custom_fields if cf['type'] == 'milestone'}
         deleted_milestone_fld_names = existing_milestone_fld_names -\
             posted_milestone_fld_names
         added_milestone_fld_names = posted_milestone_fld_names -\
@@ -1762,13 +1762,13 @@ class TrackerAdminController(DefaultAdminController):
                     cf for cf in custom_fields
                     if cf['type'] == 'milestone'
                     and cf['name'] == milestone_fld_name][0]
-                existing_milestone_names = set(
+                existing_milestone_names = {
                     m.name for m in
-                    existing_milestone_fld.get('milestones', []))
-                old_posted_milestone_names = set(
+                    existing_milestone_fld.get('milestones', [])}
+                old_posted_milestone_names = {
                     m['old_name']
                     for m in posted_milestone_fld.get('milestones', [])
-                    if m.get('old_name', None))
+                    if m.get('old_name', None)}
                 deleted_milestone_names = existing_milestone_names -\
                     old_posted_milestone_names
 
@@ -1919,9 +1919,9 @@ class MilestoneController(BaseController):
         self.field = fld
         self.milestone = m
         escaped_name = escape_solr_arg(m.name)
-        self.progress_key = '%s:%s' % (fld.name, escaped_name)
+        self.progress_key = '{}:{}'.format(fld.name, escaped_name)
         self.mongo_query = {'custom_fields.%s' % fld.name: m.name}
-        self.solr_query = '%s:%s' % (_mongo_col_to_solr_col(fld.name), escaped_name)
+        self.solr_query = '{}:{}'.format(_mongo_col_to_solr_col(fld.name), escaped_name)
 
     @with_trailing_slash
     @h.vardec
diff --git a/ForgeTracker/forgetracker/widgets/ticket_form.py b/ForgeTracker/forgetracker/widgets/ticket_form.py
index 0ebdc23..11dce5d 100644
--- a/ForgeTracker/forgetracker/widgets/ticket_form.py
+++ b/ForgeTracker/forgetracker/widgets/ticket_form.py
@@ -95,7 +95,7 @@ class GenericTicketForm(ew.SimpleForm):
             field.options = [
                 ew.Option(
                     py_value=user.username,
-                    label='%s (%s)' % (user.display_name, user.username))
+                    label='{} ({})'.format(user.display_name, user.username))
             ]
 
     @property
diff --git a/ForgeTracker/forgetracker/widgets/ticket_search.py b/ForgeTracker/forgetracker/widgets/ticket_search.py
index bc65ccd..096cfca 100644
--- a/ForgeTracker/forgetracker/widgets/ticket_search.py
+++ b/ForgeTracker/forgetracker/widgets/ticket_search.py
@@ -49,7 +49,7 @@ class TicketSearchResults(ew_core.SimpleForm):
         for name, field in six.iteritems(filters):
             self.filters[name] = options = [{
                 'value': val,
-                'label': '%s (%s)' % (val, count),
+                'label': '{} ({})'.format(val, count),
                 'selected': False
             } for val, count in field]
             options.append({'value': '', 'label': 'Not set', 'selected': False})
diff --git a/ForgeWiki/forgewiki/converters.py b/ForgeWiki/forgewiki/converters.py
index 8373d8c..fc21775 100644
--- a/ForgeWiki/forgewiki/converters.py
+++ b/ForgeWiki/forgewiki/converters.py
@@ -44,7 +44,7 @@ def _internal_link_markdown(match):
     page_name = page_name.replace(' ', '_')
     page_name = page_name[:1].upper() + page_name[1:]
     if match.group(2):
-        return r'[%s](%s)' % (match.group(2), page_name)
+        return r'[{}]({})'.format(match.group(2), page_name)
     return r'[%s]' % page_name
 
 
diff --git a/ForgeWiki/forgewiki/model/wiki.py b/ForgeWiki/forgewiki/model/wiki.py
index ca7f47d..3efad27 100644
--- a/ForgeWiki/forgewiki/model/wiki.py
+++ b/ForgeWiki/forgewiki/model/wiki.py
@@ -87,7 +87,7 @@ class PageHistory(Snapshot):
         return self.original().authors()
 
     def shorthand_id(self):
-        return '%s#%s' % (self.original().shorthand_id(), self.version)
+        return '{}#{}'.format(self.original().shorthand_id(), self.version)
 
     def url(self):
         return self.original().url() + '?version=%d' % self.version
@@ -175,14 +175,14 @@ class Page(VersionedArtifact, ActivityObject):
                 'v%d' % v2.version))
             description = '<pre>' + diff + '</pre>'
             if v1.title != v2.title:
-                subject = '%s renamed page %s to %s' % (
+                subject = '{} renamed page {} to {}'.format(
                     context.user.username, v1.title, v2.title)
             else:
-                subject = '%s modified page %s' % (
+                subject = '{} modified page {}'.format(
                     context.user.username, self.title)
         else:
             description = self.text
-            subject = '%s created page %s' % (
+            subject = '{} created page {}'.format(
                 context.user.username, self.title)
         Feed.post(self, title=None, description=description)
         Notification.post(
@@ -194,7 +194,7 @@ class Page(VersionedArtifact, ActivityObject):
         if context.app.config.options.get('AllowEmailPosting', True):
             domain = self.email_domain
             title = self.title.replace(' ', '_')
-            return '%s@%s%s' % (title.replace('/', '.'), domain, config.common_suffix)
+            return '{}@{}{}'.format(title.replace('/', '.'), domain, config.common_suffix)
         else:
             return tg_config.get('forgemail.return_path')
 
@@ -276,7 +276,7 @@ class Page(VersionedArtifact, ActivityObject):
         }).all()
 
     def delete(self):
-        subject = '%s removed page %s' % (
+        subject = '{} removed page {}'.format(
             context.user.username, self.title)
         description = self.text
         Notification.post(
diff --git a/ForgeWiki/forgewiki/tests/test_models.py b/ForgeWiki/forgewiki/tests/test_models.py
index f18fd9c..97e2a15 100644
--- a/ForgeWiki/forgewiki/tests/test_models.py
+++ b/ForgeWiki/forgewiki/tests/test_models.py
@@ -47,7 +47,7 @@ class TestPageSnapshots(TestController):
             setup_global_objects()
             for i in range(10):
                 page = Page.query.get(title='test-page')
-                page.text = 'Test Page %s.%s' % (n, i)
+                page.text = 'Test Page {}.{}'.format(n, i)
                 time.sleep(random.random())
                 # tests use mim (mongo-in-memory), which isn't thread-safe
                 lock.acquire()
diff --git a/fuse/accessfs.py b/fuse/accessfs.py
index 5e66e3c..b4299c4 100644
--- a/fuse/accessfs.py
+++ b/fuse/accessfs.py
@@ -342,7 +342,7 @@ class PermissionCache(object):
             + six.moves.urllib.parse.urlencode(dict(
                 repo_path=path,
                 username=uname)))
-        print('Checking access for %s at %s (%s)' % (uname, url, path))
+        print('Checking access for {} at {} ({})'.format(uname, url, path))
         fp = six.moves.urllib.request.urlopen(url)
         result = json.load(fp)
         print(result)
@@ -379,7 +379,7 @@ class PermissionCache(object):
         '''
         parts = [p for p in path.split(os.path.sep) if p]
         scm, nbhd, proj, rest = parts[0], parts[1], parts[2], parts[3:]
-        parts = ['/SCM/%s.%s' % (proj, nbhd)] + rest
+        parts = ['/SCM/{}.{}'.format(proj, nbhd)] + rest
         return '/'.join(parts)
 
 
diff --git a/scripts/ApacheAccessHandler.py b/scripts/ApacheAccessHandler.py
index 28782c1..7ac8e4d 100644
--- a/scripts/ApacheAccessHandler.py
+++ b/scripts/ApacheAccessHandler.py
@@ -49,7 +49,7 @@ def load_requests_lib(req):
         try:
             exec(compile(open(activate_this, "rb").read(), activate_this, 'exec'), {'__file__': activate_this})
         except Exception as e:
-            log(req, "Couldn't activate venv via %s: %s" % (activate_this, repr(e)))
+            log(req, "Couldn't activate venv via {}: {}".format(activate_this, repr(e)))
     global requests
     import requests as requests_lib
     requests = requests_lib
@@ -64,7 +64,7 @@ def mangle(path):
     if len(parts) < 4:
         return None
     scm, nbhd, proj, rest = parts[0], parts[1], parts[2], parts[3:]
-    parts = ['/SCM/%s.%s' % (proj, nbhd)] + rest
+    parts = ['/SCM/{}.{}'.format(proj, nbhd)] + rest
     return '/'.join(parts)
 
 
@@ -185,13 +185,13 @@ def check_permissions(req):
     try:
         cred = json.loads(r.content)
     except Exception as ex:
-        log(req, "error decoding JSON %s %s" % (r.headers['content-type'], ex))
+        log(req, "error decoding JSON {} {}".format(r.headers['content-type'], ex))
         return False
 
     permission = get_permission_name(req_path, req_query, req.method)
     authorized = cred.get(permission, False)
 
-    log(req, "%s -> %s -> %s -> authorized:%s" % (r.url, cred, permission, authorized))
+    log(req, "{} -> {} -> {} -> authorized:{}".format(r.url, cred, permission, authorized))
     return authorized
 
 
diff --git a/scripts/add_user_to_group.py b/scripts/add_user_to_group.py
index f4601dc..19da8fd 100644
--- a/scripts/add_user_to_group.py
+++ b/scripts/add_user_to_group.py
@@ -88,7 +88,7 @@ def main(options):
 def update_project(options, user, project, replace_users=None):
     project_role = M.ProjectRole.by_name(options.group, project=project)
     if not project_role:
-        return "Couldn't find group (ProjectRole) with name '%s' in %s" % (options.group, project.url())
+        return "Couldn't find group (ProjectRole) with name '{}' in {}".format(options.group, project.url())
 
     found_any_replace_users = False
     for replace_user in replace_users:
diff --git a/scripts/changelog.py b/scripts/changelog.py
index 76ef2fe..4b32289 100755
--- a/scripts/changelog.py
+++ b/scripts/changelog.py
@@ -58,7 +58,7 @@ def get_ticket_summaries(tickets):
     summaries = {}
     r = requests.get(API_URL.format(' '.join(tickets)))
     if r.status_code != 200:
-        raise ValueError('Unexpected response code: {0}'.format(r.status_code))
+        raise ValueError('Unexpected response code: {}'.format(r.status_code))
     for ticket in r.json()['tickets']:
         summaries[ticket['ticket_num']] = ticket['summary']
     return summaries
@@ -70,7 +70,7 @@ def print_changelog(version, summaries, changes_without_tickets):
         'date': datetime.utcnow().strftime('%B %Y'),
     }))
     for ticket in sorted(summaries.keys()):
-        print(" * [#{0}] {1}".format(ticket, six.ensure_text(summaries[ticket])))
+        print(" * [#{}] {}".format(ticket, six.ensure_text(summaries[ticket])))
     for change in changes_without_tickets:
         print(" * {}".format(six.ensure_text(change)))
 
diff --git a/scripts/migrations/020-remove-wiki-title-slashes.py b/scripts/migrations/020-remove-wiki-title-slashes.py
index 41cf3d4..036e2a0 100644
--- a/scripts/migrations/020-remove-wiki-title-slashes.py
+++ b/scripts/migrations/020-remove-wiki-title-slashes.py
@@ -28,7 +28,7 @@ log = logging.getLogger(__name__)
 
 def main():
     c.project = None
-    pages = WM.Page.query.find({'title': {'$regex': '\/'}}).all()
+    pages = WM.Page.query.find({'title': {'$regex': r'\/'}}).all()
     print('Found %s wiki titles containing "/"...' % len(pages))
     for page in pages:
         page.title = page.title.replace('/', '-')
diff --git a/scripts/migrations/024-migrate-custom-profile-text.py b/scripts/migrations/024-migrate-custom-profile-text.py
index 060b70e..894e7c5 100644
--- a/scripts/migrations/024-migrate-custom-profile-text.py
+++ b/scripts/migrations/024-migrate-custom-profile-text.py
@@ -68,7 +68,7 @@ def main():
 
             if "This is the personal project of" in page.text:
                 if description not in page.text:
-                    page.text = "%s\n\n%s" % (page.text, description)
+                    page.text = "{}\n\n{}".format(page.text, description)
                     log.info("Update wiki home page text for %s" %
                              user.username)
             elif "This is the default page" in page.text:
diff --git a/scripts/migrations/028-remove-svn-trees.py b/scripts/migrations/028-remove-svn-trees.py
index c733d82..5990e23 100644
--- a/scripts/migrations/028-remove-svn-trees.py
+++ b/scripts/migrations/028-remove-svn-trees.py
@@ -33,16 +33,16 @@ def kill_tree(repo, commit_id, path, tree):
         tid = repo._tree_oid(commit_id, path + '/' + tree_rec.name)
         child_tree = M.repository.Tree.query.get(_id=tid)
         if child_tree:
-            print('  Found {0}'.format((path + '/' + tree_rec.name).encode('utf8')))
+            print('  Found {}'.format((path + '/' + tree_rec.name).encode('utf8')))
             kill_tree(repo, commit_id, path + '/' + tree_rec.name, child_tree)
         else:
-            print('  Missing {0}'.format((path + '/' + tree_rec.name).encode('utf8')))
+            print('  Missing {}'.format((path + '/' + tree_rec.name).encode('utf8')))
 
 
 def main():
     for chunk in utils.chunked_find(SM.Repository):
         for r in chunk:
-            print('Processing {0}'.format(r))
+            print('Processing {}'.format(r))
             all_commit_ids = r._impl.all_commit_ids()
             if all_commit_ids:
                 for commit in M.repository.Commit.query.find({'_id': {'$in': all_commit_ids}}):
diff --git a/scripts/migrations/031-set-user-pending-to-false.py b/scripts/migrations/031-set-user-pending-to-false.py
index 8ea5161..13c3751 100644
--- a/scripts/migrations/031-set-user-pending-to-false.py
+++ b/scripts/migrations/031-set-user-pending-to-false.py
@@ -28,7 +28,7 @@ log = logging.getLogger(__name__)
 def main():
     for chunk in utils.chunked_find(M.User):
         for user in chunk:
-            print('Processing {0}'.format(user.username))
+            print('Processing {}'.format(user.username))
             user.pending = False
             # Ming doesn't mark document for update, since pending is False
             # by default, even if field is missing from mongo
diff --git a/scripts/migrations/032-subscribe-merge-request-submitters.py b/scripts/migrations/032-subscribe-merge-request-submitters.py
index bb75846..741a4e4 100644
--- a/scripts/migrations/032-subscribe-merge-request-submitters.py
+++ b/scripts/migrations/032-subscribe-merge-request-submitters.py
@@ -29,7 +29,7 @@ def main():
     for chunk in utils.chunked_find(M.MergeRequest):
         for mr in chunk:
             try:
-                print('Processing {0}'.format(mr.url()))
+                print('Processing {}'.format(mr.url()))
                 mr.subscribe(user=mr.creator)
                 ThreadLocalORMSession.flush_all()
             except Exception:
diff --git a/scripts/new_ticket.py b/scripts/new_ticket.py
index e87cf6e..3424b9b 100755
--- a/scripts/new_ticket.py
+++ b/scripts/new_ticket.py
@@ -57,4 +57,4 @@ if __name__ == '__main__':
         print('Ticket created at: %s' % r.url)
         pprint(r.json())
     else:
-        print('Error [%s]:\n%s' % (r.status_code, r.text))
+        print('Error [{}]:\n{}'.format(r.status_code, r.text))
diff --git a/scripts/perf/test_git_lcd.py b/scripts/perf/test_git_lcd.py
index 5a49c3d..38602ca 100644
--- a/scripts/perf/test_git_lcd.py
+++ b/scripts/perf/test_git_lcd.py
@@ -42,7 +42,7 @@ def main(repo_dir, sub_dir='', commit=None):
     commit = Mock(_id=commit or git.head)
     paths = glob(os.path.join(repo_dir, sub_dir, '*'))
     paths = [path.replace(repo_dir + '/', '', 1) for path in paths]
-    print("Timing LCDs for %s at %s" % (paths, commit._id))
+    print("Timing LCDs for {} at {}".format(paths, commit._id))
     with benchmark() as timer:
         result = git.last_commit_ids(commit, paths)
     pprint(result)
diff --git a/scripts/project-import.py b/scripts/project-import.py
index d67c0dc..86ef3c0 100644
--- a/scripts/project-import.py
+++ b/scripts/project-import.py
@@ -63,7 +63,7 @@ def main(options):
         return
 
     for p in projects:
-        log.info('Creating%s project "%s".' % ('/updating' if options.update else '', p.shortname))
+        log.info('Creating{} project "{}".'.format('/updating' if options.update else '', p.shortname))
         try:
             project = create_project_with_attrs(p, nbhd, update=options.update, ensure_tools=options.ensure_tools)
         except Exception as e:
diff --git a/scripts/scrub-allura-data.py b/scripts/scrub-allura-data.py
index 226ea13..f7214f3 100644
--- a/scripts/scrub-allura-data.py
+++ b/scripts/scrub-allura-data.py
@@ -47,7 +47,7 @@ def scrub_project(p, options):
     log.info('Scrubbing project "%s"' % p.shortname)
     preamble = options.preamble
     if not public(p):
-        log.info('%s project "%s"' % (preamble, p.shortname))
+        log.info('{} project "{}"'.format(preamble, p.shortname))
         if not options.dry_run:
             p.delete()
         return
@@ -59,7 +59,7 @@ def scrub_project(p, options):
         if tool_name in ('admin', 'search', 'profile'):
             continue
         if not public(ac, project=p):
-            log.info('%s tool %s/%s on project "%s"' % (
+            log.info('{} tool {}/{} on project "{}"'.format(
                 preamble, tool_name, mount_point, p.shortname))
             if not options.dry_run:
                 p.uninstall_app(mount_point)
diff --git a/scripts/teamforge-import.py b/scripts/teamforge-import.py
index 424c5d2..4cc37f4 100644
--- a/scripts/teamforge-import.py
+++ b/scripts/teamforge-import.py
@@ -242,7 +242,7 @@ def get_user(orig_username):
         load_users()
         user = users[orig_username]
         if user.status != 'Active':
-            log.warn('Inactive user %s %s' % (orig_username, user.status))
+            log.warn('Inactive user {} {}'.format(orig_username, user.status))
 
         if not 3 <= len(user.fullName) <= 32:
             raise Exception('invalid fullName length: %s' % user.fullName)
@@ -309,23 +309,23 @@ def convert_project_shortname(teamforge_path):
 
 
 # FIXME hardcoded
-skip_perms_usernames = set([
+skip_perms_usernames = {
     'username1', 'username2', 'username3'
-])
+}
 
 
 def create_project(pid, nbhd):
     M.session.artifact_orm_session._get().skip_mod_date = True
     data = loadjson(pid, pid + '.json')
     # pprint(data)
-    log.info('Loading: %s %s %s' % (pid, data.data.title, data.data.path))
+    log.info('Loading: {} {} {}'.format(pid, data.data.title, data.data.path))
     shortname = convert_project_shortname(data.data.path)
 
     project = M.Project.query.get(
         shortname=shortname, neighborhood_id=nbhd._id)
     if not project:
         private = (data.access_level == 'private')
-        log.debug('Creating %s private=%s' % (shortname, private))
+        log.debug('Creating {} private={}'.format(shortname, private))
         one_admin = [
             u.userName for u in data.admins if u.status == 'Active'][0]
         project = nbhd.register_project(shortname,
@@ -547,7 +547,7 @@ def import_discussion(project, pid, frs_mapping, sf_project_shortname, nbhd):
                             post_data = loadjson(pid, 'forum',
                                                  forum_name, topic_name, post)
                             p = DM.ForumPost.query.get(
-                                _id='%s%s@import' % (
+                                _id='{}{}@import'.format(
                                     post_name, str(discuss_app.config._id)),
                                 thread_id=to._id,
                                 discussion_id=fo._id,
@@ -555,7 +555,7 @@ def import_discussion(project, pid, frs_mapping, sf_project_shortname, nbhd):
 
                             if not p:
                                 p = DM.ForumPost(
-                                    _id='%s%s@import' % (
+                                    _id='{}{}@import'.format(
                                         post_name, str(
                                             discuss_app.config._id)),
                                     thread_id=to._id,
@@ -570,7 +570,7 @@ def import_discussion(project, pid, frs_mapping, sf_project_shortname, nbhd):
                                 frs_mapping, sf_project_shortname, post_data.content, nbhd)
                             p.status = 'ok'
                             if post_data.replyToId:
-                                p.parent_id = '%s%s@import' % (
+                                p.parent_id = '{}{}@import'.format(
                                     post_data.replyToId, str(discuss_app.config._id))
                             slug, full_slug = p.make_slugs(
                                 parent=p.parent, timestamp=create_date)
@@ -745,7 +745,7 @@ def wiki2markdown(markup):
             return '[[img src=%s]]' % filename
         elif '|' in snippet:
             text, link = snippet.split('|', 1)
-            return '[%s](%s)' % (text, link)
+            return '[{}]({})'.format(text, link)
         else:
             # regular link
             return '<%s>' % snippet
@@ -765,7 +765,7 @@ def convert_post_content(frs_mapping, sf_project_shortname, text, nbhd):
         relno = matchobj.group(1)
         path = frs_mapping.get(relno)
         if path:
-            return '<a href="/projects/%s.%s/files/%s">%s</a>' % (
+            return '<a href="/projects/{}.{}/files/{}">{}</a>'.format(
                 sf_project_shortname, nbhd.url_prefix.strip('/'), path, path)
         else:
             return relno
diff --git a/scripts/wiki-copy.py b/scripts/wiki-copy.py
index 612bd8c..e4a5e53 100644
--- a/scripts/wiki-copy.py
+++ b/scripts/wiki-copy.py
@@ -61,9 +61,9 @@ def main():
             resp = oauth_client.request(
                 to_url, 'POST', body=six.moves.urllib.parse.urlencode(dict(text=page_json['text'].encode('utf-8'))))
             if resp[0]['status'] == '200':
-                print("Posted {0} to {1}".format(page_json['title'], to_url))
+                print("Posted {} to {}".format(page_json['title'], to_url))
             else:
-                print("Error posting {0} to {1}: {2} (project may not exist)".format(page_json['title'], to_url, resp[0]['status']))
+                print("Error posting {} to {}: {} (project may not exist)".format(page_json['title'], to_url, resp[0]['status']))
                 break
         except Exception:
             print("Error processing " + p)
@@ -96,11 +96,11 @@ def make_oauth_client(base_url):
         assert resp['status'] == '200', resp
 
         request_token = dict(six.moves.urllib.parse.parse_qsl(content))
-        pin_url = "%s?oauth_token=%s" % (
+        pin_url = "{}?oauth_token={}".format(
             AUTHORIZE_URL, request_token['oauth_token'])
         if getattr(webbrowser.get(), 'name', '') == 'links':
             # sandboxes
-            print(("Go to %s" % pin_url))
+            print("Go to %s" % pin_url)
         else:
             webbrowser.open(pin_url)
         oauth_verifier = input('What is the PIN? ')